1 package org.unicode.cldr.test; 2 3 import java.util.HashMap; 4 import java.util.HashSet; 5 import java.util.Map; 6 import java.util.Map.Entry; 7 import java.util.Set; 8 import java.util.TreeMap; 9 import java.util.regex.Matcher; 10 import java.util.regex.Pattern; 11 12 import org.unicode.cldr.util.CLDRFile; 13 import org.unicode.cldr.util.CLDRFile.Status; 14 import org.unicode.cldr.util.DateTimeCanonicalizer.DateTimePatternType; 15 import org.unicode.cldr.util.PatternCache; 16 17 import com.ibm.icu.text.DateTimePatternGenerator; 18 import com.ibm.icu.text.DateTimePatternGenerator.VariableField; 19 20 /** 21 * Class for computing the date order of date formats. 22 * This class is was originally package-visible, but has been modified to public 23 * for the sake of the unit test. 24 */ 25 public class DateOrder implements Comparable<DateOrder> { 26 private int etype1; 27 private int etype2; 28 DateOrder(int a, int b)29 public DateOrder(int a, int b) { 30 etype1 = a; 31 etype2 = b; 32 } 33 34 @Override equals(Object obj)35 public boolean equals(Object obj) { 36 DateOrder that = (DateOrder) obj; 37 return that.etype1 == etype1 && that.etype2 == etype2; 38 } 39 40 @Override hashCode()41 public int hashCode() { 42 return etype1 * 37 + etype2; 43 } 44 45 @Override toString()46 public String toString() { 47 return "<" + toString2(etype1) + "," + toString2(etype2) + ">"; 48 } 49 toString2(int etype)50 private String toString2(int etype) { 51 switch (etype >> 1) { 52 53 } 54 return (VariableField.getCanonicalCode(etype >> 1)) + ((etype & 1) == 0 ? "" : "ⁿ"); 55 } 56 57 @Override compareTo(DateOrder that)58 public int compareTo(DateOrder that) { 59 int diff; 60 if (0 != (diff = etype1 - that.etype1)) { 61 return diff; 62 } 63 return etype2 - that.etype2; 64 } 65 getOrderingInfo(CLDRFile plain, CLDRFile resolved, DateTimePatternGenerator.FormatParser fp)66 public static Map<String, Map<DateOrder, String>> getOrderingInfo(CLDRFile plain, CLDRFile resolved, 67 DateTimePatternGenerator.FormatParser fp) { 68 Map<String, Map<DateOrder, String>> pathsWithConflictingOrder2sample = new HashMap<String, Map<DateOrder, String>>(); 69 Status status = new Status(); 70 try { 71 Map<String, Map<DateOrder, Set<String>>> type2order2set = new HashMap<String, Map<DateOrder, Set<String>>>(); 72 Matcher typeMatcher = PatternCache.get("\\[@type=\"([^\"]*)\"]").matcher(""); 73 int[] soFar = new int[50]; 74 int lenSoFar = 0; 75 for (String path : resolved) { 76 if (DateTimePatternType.STOCK_AVAILABLE_INTERVAL_PATTERNS.contains(DateTimePatternType.fromPath(path))) { 77 if (path.contains("[@id=\"Ed\"]")) { 78 continue; 79 } 80 if (!path.equals(status.pathWhereFound)) { 81 continue; 82 } 83 typeMatcher.reset(path).find(); 84 String type = typeMatcher.group(1); 85 Map<DateOrder, Set<String>> pairCount = type2order2set.get(type); 86 if (pairCount == null) { 87 type2order2set.put(type, pairCount = new HashMap<DateOrder, Set<String>>()); 88 } 89 boolean isInterval = path.contains("intervalFormatItem"); 90 lenSoFar = 0; 91 String value = resolved.getStringValue(path); 92 // register a comparison for all of the items so far 93 for (Object item : fp.set(value).getItems()) { 94 if (item instanceof VariableField) { 95 VariableField variable = (VariableField) item; 96 int eType = variable.getType() * 2 + (variable.isNumeric() ? 1 : 0); 97 if (isInterval && find(eType, soFar, lenSoFar)) { 98 lenSoFar = 0; // restart the clock 99 soFar[lenSoFar++] = eType; 100 continue; 101 } 102 for (int i = 0; i < lenSoFar; ++i) { 103 DateOrder order = new DateOrder(soFar[i], eType); 104 Set<String> paths = pairCount.get(order); 105 if (paths == null) { 106 pairCount.put(order, paths = new HashSet<String>()); 107 } 108 paths.add(path); 109 } 110 soFar[lenSoFar++] = eType; 111 } 112 } 113 } 114 } 115 // determine conflicts, and mark 116 for (Entry<String, Map<DateOrder, Set<String>>> typeAndOrder2set : type2order2set.entrySet()) { 117 Map<DateOrder, Set<String>> pairCount = typeAndOrder2set.getValue(); 118 HashSet<DateOrder> alreadySeen = new HashSet<DateOrder>(); 119 for (Entry<DateOrder, Set<String>> entry : pairCount.entrySet()) { 120 DateOrder thisOrder = entry.getKey(); 121 if (alreadySeen.contains(thisOrder)) { 122 continue; 123 } 124 DateOrder reverseOrder = new DateOrder(thisOrder.etype2, thisOrder.etype1); 125 Set<String> reverseSet = pairCount.get(reverseOrder); 126 DateOrder sample = thisOrder.compareTo(reverseOrder) < 0 ? thisOrder : reverseOrder; 127 128 Set<String> thisPaths = entry.getValue(); 129 if (reverseSet != null) { 130 addConflictingPaths(plain, sample, reverseSet, thisPaths, pathsWithConflictingOrder2sample); 131 addConflictingPaths(plain, sample, thisPaths, reverseSet, pathsWithConflictingOrder2sample); 132 alreadySeen.add(reverseOrder); 133 } 134 } 135 } 136 // for debugging, show conflicts 137 if (CheckDates.GREGORIAN_ONLY) { 138 for (Entry<String, Map<DateOrder, String>> entry : pathsWithConflictingOrder2sample.entrySet()) { 139 String path1 = entry.getKey(); 140 String locale1 = resolved.getSourceLocaleID(path1, status); 141 String value1 = resolved.getStringValue(path1); 142 Map<DateOrder, String> orderString = entry.getValue(); 143 for (Entry<DateOrder, String> entry2 : orderString.entrySet()) { 144 DateOrder order2 = entry2.getKey(); 145 String path2 = entry2.getValue(); 146 String locale2 = resolved.getSourceLocaleID(path2, status); 147 String value2 = resolved.getStringValue(path2); 148 System.out.println(order2 + "\t" + value1 + "\t" + value2 + "\t" + locale1 + "\t" + locale2 149 + "\t" + path1 + "\t" + path2); 150 } 151 } 152 } 153 } catch (RuntimeException e) { 154 throw e; 155 } 156 return pathsWithConflictingOrder2sample; 157 } 158 159 /** 160 * Add paths with a conflicting date order to the specified map. 161 * 162 * @param cldrFile 163 * @param order 164 * @param paths 165 * the set of paths to add conflicting paths for 166 * @param conflictingPaths 167 * the set of conflicting paths 168 * @param pathsWithConflictingOrder2sample 169 */ 170 private static void addConflictingPaths(CLDRFile cldrFile, DateOrder order, Set<String> paths, 171 Set<String> conflictingPaths, Map<String, Map<DateOrder, String>> pathsWithConflictingOrder2sample) { 172 for (String first : paths) { 173 FormatType firstType = FormatType.getType(first); 174 for (String otherPath : conflictingPaths) { 175 FormatType otherType = FormatType.getType(otherPath); 176 // Add the first conflicting path that has a high enough 177 // importance to be considered. 178 if (!otherType.isLessImportantThan(firstType)) { 179 addItem(cldrFile, first, order, otherPath, pathsWithConflictingOrder2sample); 180 break; 181 } 182 } 183 } 184 } 185 186 private static boolean find(int eType, int[] soFar, int lenSoFar) { 187 for (int i = 0; i < lenSoFar; ++i) { 188 if (eType == soFar[i]) { 189 return true; 190 } 191 } 192 return false; 193 } 194 195 private static void addItem(CLDRFile plain, String path, DateOrder sample, 196 String conflictingPath, Map<String, Map<DateOrder, String>> pathsWithConflictingOrder2sample) { 197 String value = plain.getStringValue(path); 198 if (value == null) { 199 return; 200 } 201 Map<DateOrder, String> order2path = pathsWithConflictingOrder2sample.get(path); 202 if (order2path == null) { 203 pathsWithConflictingOrder2sample.put(path, order2path = new TreeMap<DateOrder, String>()); 204 } 205 order2path.put(sample, conflictingPath); 206 } 207 208 /** 209 * Enum for deciding the priority of paths for checking date order 210 * consistency. 211 */ 212 private enum FormatType { 213 DATE(3), TIME(3), AVAILABLE(2), INTERVAL(1); 214 private static final Pattern DATETIME_PATTERN = PatternCache.get("/(date|time|available|interval)Formats"); 215 // Types with a higher value have higher importance. 216 private int importance; 217 218 private FormatType(int importance) { 219 this.importance = importance; 220 } 221 222 /** 223 * @param path 224 * @return the format type of the specified path 225 */ 226 public static FormatType getType(String path) { 227 Matcher matcher = DATETIME_PATTERN.matcher(path); 228 if (matcher.find()) { 229 return FormatType.valueOf(matcher.group(1).toUpperCase()); 230 } 231 throw new IllegalArgumentException("Path is not a datetime format type: " + path); 232 } 233 234 /** 235 * @return true if this FormatType is of lower importance than otherType 236 */ 237 public boolean isLessImportantThan(FormatType otherType) { 238 return otherType.importance - importance > 0; 239 } 240 } 241 }