• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.unicode.cldr.test;
2 
3 import java.util.Map;
4 import java.util.concurrent.ConcurrentHashMap;
5 
6 import org.unicode.cldr.util.PathStarrer;
7 
8 /**
9  * Cache example html strings for ExampleGenerator.
10  *
11  * Essentially, the cache simply maps from xpath+value to html.
12  *
13  * The complexity of this class is mostly for the sake of handling dependencies where the
14  * example for pathB+valueB depends not only on pathB and valueB, but also on the current
15  * <em>winning</em> values of pathA1, pathA2, ...
16  *
17  * Some examples in the cache must get cleared when a changed winning value for a path makes
18  * the cached examples for other paths possibly no longer correct.
19 
20  * For example, let pathA = "//ldml/localeDisplayNames/languages/language[@type=\"aa\"]"
21  * and pathB = "//ldml/localeDisplayNames/territories/territory[@type=\"DJ\"]". The values,
22  * in locale fr, might be "afar" for pathA and "Djibouti" for pathB. The example for pathB
23  * might include "afar (Djibouti)", which depends on the values of both pathA and pathB.
24  *
25  * Each ExampleGenerator object, which is for one locale, has its own ExampleCache object.
26  *
27  * This cache is internal to each ExampleGenerator. Compare TestCache.exampleGeneratorCache,
28  * which is at a higher level, caching entire ExampleGenerator objects, one for each locale.
29  *
30  * Unlike TestCache.exampleGeneratorCache, this cache doesn't get cleared to conserve memory,
31  * only to adapt to changed winning values.
32  */
33 class ExampleCache {
34     /**
35      * An ExampleCacheItem is a temporary container for the info
36      * needed to get and/or put one item in the cache.
37      */
38     class ExampleCacheItem {
39         private String xpath;
40         private String value;
41 
42         /**
43          * starredPath, the "starred" version of xpath, is the key for the highest level
44          * of the cache, which is nested.
45          *
46          * Compare starred "//ldml/localeDisplayNames/languages/language[@type=\"*\"]"
47          * with starless "//ldml/localeDisplayNames/languages/language[@type=\"aa\"]".
48          * There are fewer starred paths than starless paths.
49          * ExampleDependencies.dependencies has starred paths for that reason.
50          */
51         private String starredPath = null;
52 
53         /**
54          * The cache maps each starredPath to a pathMap, which in turn maps each starless path
55          * to a valueMap.
56          */
57         private Map<String, Map<String, String>> pathMap = null;
58 
59         /**
60          * Finally the valueMap maps the value to the example html.
61          */
62         private Map<String, String> valueMap = null;
63 
ExampleCacheItem(String xpath, String value)64         ExampleCacheItem(String xpath, String value) {
65             this.xpath = xpath;
66             this.value = value;
67         }
68 
69         /**
70          * Get the cached example html for this item, based on its xpath and value
71          *
72          * The HTML string shows example(s) using that value for that path, for the locale
73          * of the ExampleGenerator we're connected to.
74          *
75          * @return the example html or null
76          */
getExample()77         String getExample() {
78             if (!cachingIsEnabled) {
79                 return null;
80             }
81             String result = null;
82             starredPath = pathStarrer.set(xpath);
83             pathMap = cache.get(starredPath);
84             if (pathMap != null) {
85                 valueMap = pathMap.get(xpath);
86                 if (valueMap != null) {
87                     result = valueMap.get(value);
88                 }
89             }
90             if (cacheOnly && result == NONE) {
91                 throw new InternalError("getExampleHtml cacheOnly not found: " + xpath + ", " + value);
92             }
93             return (result == NONE) ? null : result;
94         }
95 
putExample(String result)96         void putExample(String result) {
97             if (cachingIsEnabled) {
98                 if (pathMap == null) {
99                     pathMap = new ConcurrentHashMap<>();
100                     cache.put(starredPath, pathMap);
101                 }
102                 if (valueMap == null) {
103                     valueMap = new ConcurrentHashMap<>();
104                     pathMap.put(xpath, valueMap);
105                 }
106                 valueMap.put(value, (result == null) ? NONE : result);
107             }
108         }
109     }
110 
111     /**
112      * AVOID_CLEARING_CACHE: a performance optimization. Should be true except for testing.
113      * Only remove keys for which the examples may be affected by this change.
114      *
115      * All paths of type “A” (i.e., all that have dependencies) have keys in ExampleDependencies.dependencies.
116      * For any other path given as the argument to this function, there should be no need to clear the cache.
117      * When there are dependencies, only remove the keys for paths that are dependent on this path.
118      *
119      * Reference: https://unicode-org.atlassian.net/browse/CLDR-13636
120      */
121     private static final boolean AVOID_CLEARING_CACHE = true;
122 
123     /**
124      * Avoid storing null in the cache, but do store NONE as a way to remember
125      * there is no example html for the given xpath and value. This is probably
126      * faster than calling constructExampleHtml again and again to get null every
127      * time, if nothing at all were stored in the cache.
128      */
129     private static final String NONE = "\uFFFF";
130 
131     /**
132      * The nested cache mapping is: starredPath → (starlessPath → (value → html)).
133      */
134     private final Map<String, Map<String, Map<String, String>>> cache = new ConcurrentHashMap<>();
135 
136     /**
137      * The PathStarrer is for getting starredPath from an ordinary (starless) path.
138      * Inclusion of starred paths enables performance improvement with AVOID_CLEARING_CACHE.
139      */
140     private final PathStarrer pathStarrer = new PathStarrer().setSubstitutionPattern("*");
141 
142     /**
143      * For testing, caching can be disabled for some ExampleCaches while still
144      * enabled for others.
145      */
146     private boolean cachingIsEnabled = true;
147 
setCachingEnabled(boolean enabled)148     void setCachingEnabled(boolean enabled) {
149         cachingIsEnabled = enabled;
150     }
151 
152     /**
153      * For testing, we can switch some ExampleCaches into a special "cache only"
154      * mode, where they will throw an exception if queried for a path+value that isn't
155      * already in the cache. See TestExampleGeneratorDependencies.
156      */
157     private boolean cacheOnly = false;
158 
setCacheOnly(boolean only)159     void setCacheOnly(boolean only) {
160         this.cacheOnly = only;
161     }
162 
163     /**
164      * Clear the cached examples for any paths whose examples might depend on the
165      * winning value of the given path, since the winning value of the given path has changed.
166      *
167      * There is no need to update the example(s) for the given path itself, since
168      * the cache key includes path+value and therefore each path+value has its own
169      * example, regardless of which value is winning. There is a need to update
170      * the examples for OTHER paths whose examples depend on the winning value
171      * of the given path.
172      *
173      * @param xpath the path whose winning value has changed
174      *
175      * Called by ExampleGenerator.updateCache
176      */
update(String xpath)177     void update(String xpath) {
178         if (AVOID_CLEARING_CACHE) {
179             String starredA = pathStarrer.set(xpath);
180             for (String starredB : ExampleDependencies.dependencies.get(starredA)) {
181                 cache.remove(starredB);
182             }
183         } else {
184             cache.clear();
185         }
186     }
187 }
188