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