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 }