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 }