• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  **********************************************************************
3  * Copyright (c) 2002-2004, International Business Machines
4  * Corporation and others.  All Rights Reserved.
5  **********************************************************************
6  * Author: Mark Davis
7  **********************************************************************
8  */
9 package org.unicode.cldr.util;
10 
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.Comparator;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.TreeMap;
19 
20 import com.ibm.icu.text.Collator;
21 import com.ibm.icu.text.RuleBasedCollator;
22 import com.ibm.icu.text.UnicodeSet;
23 import com.ibm.icu.util.Freezable;
24 import com.ibm.icu.util.ULocale;
25 
26 public class MapComparator<K> implements Comparator<K>, Freezable<MapComparator<K>> {
27     private static final class CollatorHelper {
28         public static final Collator UCA = getUCA();
29         /**
30          * This does not change, so we can create one and freeze it.
31          * @return
32          */
getUCA()33         private static Collator getUCA() {
34             final RuleBasedCollator newUca = (RuleBasedCollator) Collator.getInstance(ULocale.ROOT);
35             newUca.setNumericCollation(true);
36             return newUca.freeze();
37         }
38     }
39     // initialize this once
40     private Map<K, Integer> ordering = new TreeMap<>(); // maps from name to rank
41     private List<K> rankToName = new ArrayList<>();
42     private boolean errorOnMissing = true;
43     private volatile boolean locked = false;
44     private int before = 1;
45     private boolean fallback = true;
46 
47     /**
48      * @return Returns the errorOnMissing.
49      */
isErrorOnMissing()50     public boolean isErrorOnMissing() {
51         return errorOnMissing;
52     }
53 
54     /**
55      * @param errorOnMissing
56      *            The errorOnMissing to set.
57      */
setErrorOnMissing(boolean errorOnMissing)58     public MapComparator<K> setErrorOnMissing(boolean errorOnMissing) {
59         if (locked) throw new UnsupportedOperationException("Attempt to modify locked object");
60         this.errorOnMissing = errorOnMissing;
61         return this;
62     }
63 
isSortBeforeOthers()64     public boolean isSortBeforeOthers() {
65         return before == 1;
66     }
67 
setSortBeforeOthers(boolean sortBeforeOthers)68     public MapComparator<K> setSortBeforeOthers(boolean sortBeforeOthers) {
69         if (locked) throw new UnsupportedOperationException("Attempt to modify locked object");
70         this.before = sortBeforeOthers ? 1 : -1;
71         return this;
72     }
73 
isDoFallback()74     public boolean isDoFallback() {
75         return fallback;
76     }
77 
setDoFallback(boolean doNumeric)78     public MapComparator<K> setDoFallback(boolean doNumeric) {
79         if (locked) throw new UnsupportedOperationException("Attempt to modify locked object");
80         this.fallback = doNumeric;
81         return this;
82     }
83 
84     /**
85      * @return Returns the rankToName.
86      */
getOrder()87     public List<K> getOrder() {
88         return Collections.unmodifiableList(rankToName);
89     }
90 
MapComparator()91     public MapComparator() {
92     }
93 
MapComparator(K[] data)94     public MapComparator(K[] data) {
95         add(data);
96     }
97 
MapComparator(Collection<K> c)98     public MapComparator(Collection<K> c) {
99         add(c);
100     }
101 
add(K newObject)102     public MapComparator<K> add(K newObject) {
103         Integer already = ordering.get(newObject);
104         if (already == null) {
105             if (locked) throw new UnsupportedOperationException("Attempt to modify locked object");
106             ordering.put(newObject, new Integer(rankToName.size()));
107             rankToName.add(newObject);
108         }
109         return this;
110     }
111 
getNumericOrder(K object)112     public Integer getNumericOrder(K object) {
113         return ordering.get(object);
114     }
115 
add(Collection<K> c)116     public MapComparator<K> add(Collection<K> c) {
117         for (Iterator<K> it = c.iterator(); it.hasNext();) {
118             add(it.next());
119         }
120         return this;
121     }
122 
123     @SuppressWarnings("unchecked")
add(K... data)124     public MapComparator<K> add(K... data) {
125         for (int i = 0; i < data.length; ++i) {
126             add(data[i]);
127         }
128         return this;
129     }
130 
131     private static final UnicodeSet numbers = new UnicodeSet("[\\-0-9.]").freeze();
132 
133     @Override
134     @SuppressWarnings({ "unchecked", "rawtypes" })
compare(K a, K b)135     public int compare(K a, K b) {
136         if (false && (a.equals("lines") || b.equals("lines"))) {
137             System.out.println();
138         }
139         Integer aa = ordering.get(a);
140         Integer bb = ordering.get(b);
141         if (aa != null && bb != null) {
142             return aa.compareTo(bb);
143         }
144         if (errorOnMissing) {
145             throw new IllegalArgumentException("Missing Map Comparator value(s): "
146                 + a.toString() + "(" + aa + "),\t"
147                 + b.toString() + "(" + bb + "),\t");
148         }
149         // must handle halfway case, otherwise we are not transitive!!!
150         if (aa == null && bb != null) {
151             return before;
152         }
153         if (aa != null && bb == null) {
154             return -before;
155         }
156         if (!fallback) {
157             return 0;
158         }
159         // do numeric
160         // first we do a quick check, then parse.
161         // for transitivity, we have to check both.
162         boolean anumeric = numbers.containsAll((String) a);
163         double an = Double.NaN, bn = Double.NaN;
164         if (anumeric) {
165             try {
166                 an = Double.parseDouble((String) a);
167             } catch (NumberFormatException e) {
168                 anumeric = false;
169             }
170         }
171         boolean bnumeric = numbers.containsAll((String) b);
172         if (bnumeric) {
173             try {
174                 bn = Double.parseDouble((String) b);
175             } catch (NumberFormatException e) {
176                 bnumeric = false;
177             }
178         }
179         if (anumeric && bnumeric) {
180             if (an < bn) return -1;
181             if (an > bn) return 1;
182             return 0;
183         }
184         // must handle halfway case, otherwise we are not transitive!!!
185         if (!anumeric && bnumeric) return 1;
186         if (anumeric && !bnumeric) return -1;
187 
188         if (a instanceof CharSequence) {
189             if (b instanceof CharSequence) {
190                 int result = CollatorHelper.UCA.compare(a.toString(), b.toString());
191                 if (result != 0) {
192                     return result;
193                 }
194             } else {
195                 return 1; // handle for transitivity
196             }
197         } else {
198             return -1; // handle for transitivity
199         }
200 
201         // do fallback
202         return ((Comparable) a).compareTo(b);
203     }
204 
205     @Override
toString()206     public String toString() {
207         StringBuffer buffer = new StringBuffer();
208         boolean isFirst = true;
209         for (Iterator<K> it = rankToName.iterator(); it.hasNext();) {
210             K key = it.next();
211             if (isFirst)
212                 isFirst = false;
213             else
214                 buffer.append(" ");
215             buffer.append("<").append(key).append(">");
216         }
217         return buffer.toString();
218     }
219 
220     /*
221      * (non-Javadoc)
222      *
223      * @see com.ibm.icu.dev.test.util.Freezeble
224      */
225     @Override
isFrozen()226     public boolean isFrozen() {
227         return locked;
228     }
229 
230     /*
231      * (non-Javadoc)
232      *
233      * @see com.ibm.icu.dev.test.util.Freezeble
234      */
235     @Override
freeze()236     public MapComparator<K> freeze() {
237         locked = true;
238         return this;
239     }
240 
241     /*
242      * (non-Javadoc)
243      *
244      * @see com.ibm.icu.dev.test.util.Freezeble
245      */
246     @Override
247     @SuppressWarnings("unchecked")
cloneAsThawed()248     public MapComparator<K> cloneAsThawed() {
249         try {
250             MapComparator<K> result = (MapComparator<K>) super.clone();
251             result.locked = false;
252             result.ordering = (Map<K, Integer>) ((TreeMap<K, Integer>) ordering).clone();
253             result.rankToName = (List<K>) ((ArrayList<K>) rankToName).clone();
254             return result;
255         } catch (CloneNotSupportedException e) {
256             throw new InternalError("should never happen");
257         }
258     }
259 
getOrdering(K item)260     public int getOrdering(K item) {
261         Integer result = ordering.get(item);
262         return result == null ? -1 : result;
263     }
264 }