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<K, Integer>(); // maps from name to rank 32 private List<K> rankToName = new ArrayList<K>(); 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 @SuppressWarnings({ "unchecked", "rawtypes" }) compare(K a, K b)125 public int compare(K a, K b) { 126 if (false && (a.equals("lines") || b.equals("lines"))) { 127 System.out.println(); 128 } 129 Integer aa = ordering.get(a); 130 Integer bb = ordering.get(b); 131 if (aa != null && bb != null) { 132 return aa.compareTo(bb); 133 } 134 if (errorOnMissing) { 135 throw new IllegalArgumentException("Missing Map Comparator value(s): " 136 + a.toString() + "(" + aa + "),\t" 137 + b.toString() + "(" + bb + "),\t"); 138 } 139 // must handle halfway case, otherwise we are not transitive!!! 140 if (aa == null && bb != null) { 141 return before; 142 } 143 if (aa != null && bb == null) { 144 return -before; 145 } 146 if (!fallback) { 147 return 0; 148 } 149 // do numeric 150 // first we do a quick check, then parse. 151 // for transitivity, we have to check both. 152 boolean anumeric = numbers.containsAll((String) a); 153 double an = Double.NaN, bn = Double.NaN; 154 if (anumeric) { 155 try { 156 an = Double.parseDouble((String) a); 157 } catch (NumberFormatException e) { 158 anumeric = false; 159 } 160 } 161 boolean bnumeric = numbers.containsAll((String) b); 162 if (bnumeric) { 163 try { 164 bn = Double.parseDouble((String) b); 165 } catch (NumberFormatException e) { 166 bnumeric = false; 167 } 168 } 169 if (anumeric && bnumeric) { 170 if (an < bn) return -1; 171 if (an > bn) return 1; 172 return 0; 173 } 174 // must handle halfway case, otherwise we are not transitive!!! 175 if (!anumeric && bnumeric) return 1; 176 if (anumeric && !bnumeric) return -1; 177 178 if (a instanceof CharSequence) { 179 if (b instanceof CharSequence) { 180 int result = uca.compare(a.toString(), b.toString()); 181 if (result != 0) { 182 return result; 183 } 184 } else { 185 return 1; // handle for transitivity 186 } 187 } else { 188 return -1; // handle for transitivity 189 } 190 191 // do fallback 192 return ((Comparable) a).compareTo(b); 193 } 194 toString()195 public String toString() { 196 StringBuffer buffer = new StringBuffer(); 197 boolean isFirst = true; 198 for (Iterator<K> it = rankToName.iterator(); it.hasNext();) { 199 K key = it.next(); 200 if (isFirst) 201 isFirst = false; 202 else 203 buffer.append(" "); 204 buffer.append("<").append(key).append(">"); 205 } 206 return buffer.toString(); 207 } 208 209 /* 210 * (non-Javadoc) 211 * 212 * @see com.ibm.icu.dev.test.util.Freezeble 213 */ isFrozen()214 public boolean isFrozen() { 215 return locked; 216 } 217 218 /* 219 * (non-Javadoc) 220 * 221 * @see com.ibm.icu.dev.test.util.Freezeble 222 */ freeze()223 public MapComparator<K> freeze() { 224 locked = true; 225 return this; 226 } 227 228 /* 229 * (non-Javadoc) 230 * 231 * @see com.ibm.icu.dev.test.util.Freezeble 232 */ 233 @SuppressWarnings("unchecked") cloneAsThawed()234 public MapComparator<K> cloneAsThawed() { 235 try { 236 MapComparator<K> result = (MapComparator<K>) super.clone(); 237 result.locked = false; 238 result.ordering = (Map<K, Integer>) ((TreeMap<K, Integer>) ordering).clone(); 239 result.rankToName = (List<K>) ((ArrayList<K>) rankToName).clone(); 240 return result; 241 } catch (CloneNotSupportedException e) { 242 throw new InternalError("should never happen"); 243 } 244 } 245 }