• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }