xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/main/java/org/unicode/cldr/util/MapComparator.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
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 com.ibm.icu.text.Collator;
12 import com.ibm.icu.text.RuleBasedCollator;
13 import com.ibm.icu.text.UnicodeSet;
14 import com.ibm.icu.util.Freezable;
15 import com.ibm.icu.util.ULocale;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.Comparator;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.TreeMap;
24 import java.util.function.Function;
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          *
32          * @return
33          */
getUCA()34         private static Collator getUCA() {
35             final RuleBasedCollator newUca = (RuleBasedCollator) Collator.getInstance(ULocale.ROOT);
36             newUca.setNumericCollation(true);
37             return newUca.freeze();
38         }
39     }
40     // initialize this once
41     private Map<K, Integer> ordering = new TreeMap<>(); // maps from name to rank
42     private List<K> rankToName = new ArrayList<>();
43     private boolean errorOnMissing = true;
44     private volatile boolean locked = false;
45     private int before = 1;
46     private boolean fallback = true;
47 
48     /**
49      * @return Returns the errorOnMissing.
50      */
isErrorOnMissing()51     public boolean isErrorOnMissing() {
52         return errorOnMissing;
53     }
54 
55     /**
56      * @param errorOnMissing 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 
MapComparator(K[] data)93     public MapComparator(K[] data) {
94         add(data);
95     }
96 
MapComparator(Collection<K> c)97     public MapComparator(Collection<K> c) {
98         add(c);
99     }
100 
add(K newObject)101     public MapComparator<K> add(K newObject) {
102         Integer already = ordering.get(newObject);
103         if (already == null) {
104             if (locked) throw new UnsupportedOperationException("Attempt to modify locked object");
105             ordering.put(newObject, rankToName.size());
106             rankToName.add(newObject);
107         }
108         return this;
109     }
110 
getNumericOrder(K object)111     public Integer getNumericOrder(K object) {
112         return ordering.get(object);
113     }
114 
add(Collection<K> c)115     public MapComparator<K> add(Collection<K> c) {
116         for (Iterator<K> it = c.iterator(); it.hasNext(); ) {
117             add(it.next());
118         }
119         return this;
120     }
121 
add(Collection<S> c, Function<S, K> mapper)122     public <S extends Object> MapComparator<K> add(Collection<S> c, Function<S, K> mapper) {
123         c.stream().map(mapper).forEach(x -> add(x));
124         return this;
125     }
126 
127     @SuppressWarnings("unchecked")
add(K... data)128     public MapComparator<K> add(K... data) {
129         for (int i = 0; i < data.length; ++i) {
130             add(data[i]);
131         }
132         return this;
133     }
134 
135     private static final UnicodeSet numbers = new UnicodeSet("[\\-0-9.]").freeze();
136 
137     @Override
138     @SuppressWarnings({"unchecked", "rawtypes"})
compare(K a, K b)139     public int compare(K a, K b) {
140         if (false && (a.equals("lines") || b.equals("lines"))) {
141             System.out.println();
142         }
143         Integer aa = ordering.get(a);
144         Integer bb = ordering.get(b);
145         if (aa != null && bb != null) {
146             return aa.compareTo(bb);
147         }
148         if (errorOnMissing) {
149             throw new IllegalArgumentException(
150                     "Missing Map Comparator value(s): "
151                             + a.toString()
152                             + "("
153                             + aa
154                             + "),\t"
155                             + b.toString()
156                             + "("
157                             + bb
158                             + "),\t");
159         }
160         // must handle halfway case, otherwise we are not transitive!!!
161         if (aa == null && bb != null) {
162             return before;
163         }
164         if (aa != null && bb == null) {
165             return -before;
166         }
167         if (!fallback) {
168             return 0;
169         }
170         // do numeric
171         // first we do a quick check, then parse.
172         // for transitivity, we have to check both.
173         boolean anumeric = numbers.containsAll((String) a);
174         double an = Double.NaN, bn = Double.NaN;
175         if (anumeric) {
176             try {
177                 an = Double.parseDouble((String) a);
178             } catch (NumberFormatException e) {
179                 anumeric = false;
180             }
181         }
182         boolean bnumeric = numbers.containsAll((String) b);
183         if (bnumeric) {
184             try {
185                 bn = Double.parseDouble((String) b);
186             } catch (NumberFormatException e) {
187                 bnumeric = false;
188             }
189         }
190         if (anumeric && bnumeric) {
191             if (an < bn) return -1;
192             if (an > bn) return 1;
193             return 0;
194         }
195         // must handle halfway case, otherwise we are not transitive!!!
196         if (!anumeric && bnumeric) return 1;
197         if (anumeric && !bnumeric) return -1;
198 
199         if (a instanceof CharSequence) {
200             if (b instanceof CharSequence) {
201                 int result = CollatorHelper.UCA.compare(a.toString(), b.toString());
202                 if (result != 0) {
203                     return result;
204                 }
205             } else {
206                 return 1; // handle for transitivity
207             }
208         } else {
209             return -1; // handle for transitivity
210         }
211 
212         // do fallback
213         return ((Comparable) a).compareTo(b);
214     }
215 
216     @Override
toString()217     public String toString() {
218         StringBuffer buffer = new StringBuffer();
219         boolean isFirst = true;
220         for (Iterator<K> it = rankToName.iterator(); it.hasNext(); ) {
221             K key = it.next();
222             if (isFirst) isFirst = false;
223             else buffer.append(" ");
224             buffer.append("<").append(key).append(">");
225         }
226         return buffer.toString();
227     }
228 
229     /*
230      * (non-Javadoc)
231      *
232      * @see com.ibm.icu.dev.test.util.Freezeble
233      */
234     @Override
isFrozen()235     public boolean isFrozen() {
236         return locked;
237     }
238 
239     /*
240      * (non-Javadoc)
241      *
242      * @see com.ibm.icu.dev.test.util.Freezeble
243      */
244     @Override
freeze()245     public MapComparator<K> freeze() {
246         locked = true;
247         return this;
248     }
249 
250     /*
251      * (non-Javadoc)
252      *
253      * @see com.ibm.icu.dev.test.util.Freezeble
254      */
255     @Override
256     @SuppressWarnings("unchecked")
cloneAsThawed()257     public MapComparator<K> cloneAsThawed() {
258         try {
259             MapComparator<K> result = (MapComparator<K>) super.clone();
260             result.locked = false;
261             result.ordering = (Map<K, Integer>) ((TreeMap<K, Integer>) ordering).clone();
262             result.rankToName = (List<K>) ((ArrayList<K>) rankToName).clone();
263             return result;
264         } catch (CloneNotSupportedException e) {
265             throw new InternalError("should never happen");
266         }
267     }
268 
getOrdering(K item)269     public int getOrdering(K item) {
270         Integer result = ordering.get(item);
271         return result == null ? -1 : result;
272     }
273 }
274