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