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