xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/main/java/org/unicode/cldr/util/Factory.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
1 package org.unicode.cldr.util;
2 
3 import com.google.common.base.Suppliers;
4 import java.io.File;
5 import java.util.ArrayList;
6 import java.util.Collections;
7 import java.util.Iterator;
8 import java.util.List;
9 import java.util.Set;
10 import java.util.TreeSet;
11 import java.util.function.Supplier;
12 import org.unicode.cldr.test.TestCache;
13 import org.unicode.cldr.util.CLDRFile.DraftStatus;
14 import org.unicode.cldr.util.CLDRLocale.SublocaleProvider;
15 import org.unicode.cldr.util.SupplementalDataInfo.ParentLocaleComponent;
16 import org.unicode.cldr.util.XMLSource.ResolvingSource;
17 
18 /**
19  * A factory is the normal method to produce a set of CLDRFiles from a directory of XML files. See
20  * SimpleFactory for a concrete subclass.
21  */
22 public abstract class Factory implements SublocaleProvider {
23     private boolean ignoreExplicitParentLocale = false;
24 
25     /**
26      * Whether to ignore explicit parent locale / fallback script behavior with a resolving source.
27      *
28      * <p>Long story short, call setIgnoreExplictParentLocale(true) for collation trees.
29      */
setIgnoreExplicitParentLocale(boolean newIgnore)30     public Factory setIgnoreExplicitParentLocale(boolean newIgnore) {
31         ignoreExplicitParentLocale = newIgnore;
32         return this;
33     }
34 
35     /** Flag to set more verbose output in makeServolingSource */
36     private static final boolean DEBUG_FACTORY = false;
37 
38     private File supplementalDirectory = null;
39 
40     /**
41      * Note, the source director(ies) may be a list (seed/common). Therefore, this function is
42      * deprecated
43      *
44      * @deprecated
45      * @return the first directory
46      */
47     @Deprecated
getSourceDirectory()48     public String getSourceDirectory() {
49         return getSourceDirectories()[0].getAbsolutePath();
50     }
51 
52     /**
53      * Note, the source director(ies) may be a list (seed/common).
54      *
55      * @return the first directory
56      */
getSourceDirectories()57     public abstract File[] getSourceDirectories();
58 
59     /**
60      * Which source directory does this particular localeID belong to?
61      *
62      * @param localeID
63      * @return
64      */
65     @Deprecated
getSourceDirectoryForLocale(String localeID)66     public final File getSourceDirectoryForLocale(String localeID) {
67         List<File> temp = getSourceDirectoriesForLocale(localeID);
68         return temp == null ? null : temp.get(0);
69     }
70 
71     /**
72      * Classify the tree according to type (maturity)
73      *
74      * @author srl
75      */
76     public enum SourceTreeType {
77         common,
78         seed,
79         other
80     }
81 
82     /**
83      * Returns the source tree type of either an XML file or its parent directory.
84      *
85      * @param fileOrDir
86      * @return
87      */
getSourceTreeType(File fileOrDir)88     public static final SourceTreeType getSourceTreeType(File fileOrDir) {
89         if (fileOrDir == null) return null;
90         File parentDir = fileOrDir.isFile() ? fileOrDir.getParentFile() : fileOrDir;
91         File grandparentDir = parentDir.getParentFile();
92 
93         try {
94             return SourceTreeType.valueOf(grandparentDir.getName());
95         } catch (IllegalArgumentException iae) {
96             try {
97                 return SourceTreeType.valueOf(parentDir.getName());
98             } catch (IllegalArgumentException iae2) {
99                 return SourceTreeType.other;
100             }
101         }
102     }
103 
104     public enum DirectoryType {
105         main,
106         supplemental,
107         bcp47,
108         casing,
109         collation,
110         dtd,
111         rbnf,
112         segments,
113         transforms,
114         other
115     }
116 
getDirectoryType(File fileOrDir)117     public static final DirectoryType getDirectoryType(File fileOrDir) {
118         if (fileOrDir == null) return null;
119         File parentDir = fileOrDir.isFile() ? fileOrDir.getParentFile() : fileOrDir;
120 
121         try {
122             return DirectoryType.valueOf(parentDir.getName());
123         } catch (IllegalArgumentException iae2) {
124             return DirectoryType.other;
125         }
126     }
127 
handleMake( String localeID, boolean resolved, DraftStatus madeWithMinimalDraftStatus)128     protected abstract CLDRFile handleMake(
129             String localeID, boolean resolved, DraftStatus madeWithMinimalDraftStatus);
130 
make( String localeID, boolean resolved, DraftStatus madeWithMinimalDraftStatus)131     public CLDRFile make(
132             String localeID, boolean resolved, DraftStatus madeWithMinimalDraftStatus) {
133         return handleMake(localeID, resolved, madeWithMinimalDraftStatus)
134                 .setSupplementalDirectory(getSupplementalDirectory());
135     }
136 
make(String localeID, boolean resolved, boolean includeDraft)137     public CLDRFile make(String localeID, boolean resolved, boolean includeDraft) {
138         return make(
139                 localeID, resolved, includeDraft ? DraftStatus.unconfirmed : DraftStatus.approved);
140     }
141 
make(String localeID, boolean resolved)142     public CLDRFile make(String localeID, boolean resolved) {
143         return make(localeID, resolved, getMinimalDraftStatus());
144     }
145 
makeWithFallback(String localeID)146     public CLDRFile makeWithFallback(String localeID) {
147         return makeWithFallback(localeID, getMinimalDraftStatus());
148     }
149 
makeWithFallback(String localeID, DraftStatus madeWithMinimalDraftStatus)150     public CLDRFile makeWithFallback(String localeID, DraftStatus madeWithMinimalDraftStatus) {
151         String currentLocaleID = localeID;
152         Set<String> availableLocales = this.getAvailable();
153         while (!availableLocales.contains(currentLocaleID) && !"root".equals(currentLocaleID)) {
154             currentLocaleID =
155                     LocaleIDParser.getParent(
156                             currentLocaleID,
157                             ignoreExplicitParentLocale
158                                     ? ParentLocaleComponent.collations
159                                     : ParentLocaleComponent.main);
160         }
161         return make(currentLocaleID, true, madeWithMinimalDraftStatus);
162     }
163 
makeResolvingSource(List<XMLSource> sources)164     public static XMLSource makeResolvingSource(List<XMLSource> sources) {
165         return new ResolvingSource(sources);
166     }
167 
168     /**
169      * Temporary wrapper for creating an XMLSource. This is a hack and should only be used in the
170      * Survey Tool for now.
171      *
172      * @param localeID
173      * @return
174      */
makeSource(String localeID)175     public final XMLSource makeSource(String localeID) {
176         return make(localeID, false).dataSource;
177     }
178 
179     /**
180      * Creates a resolving source for the given locale ID.
181      *
182      * @param localeID
183      * @param madeWithMinimalDraftStatus
184      * @return
185      */
makeResolvingSource( String localeID, DraftStatus madeWithMinimalDraftStatus)186     protected ResolvingSource makeResolvingSource(
187             String localeID, DraftStatus madeWithMinimalDraftStatus) {
188         List<XMLSource> sourceList = new ArrayList<>();
189         String curLocale = localeID;
190         while (curLocale != null) {
191             if (DEBUG_FACTORY) {
192                 System.out.println(
193                         "Factory.makeResolvingSource: calling handleMake for locale "
194                                 + curLocale
195                                 + " and MimimalDraftStatus "
196                                 + madeWithMinimalDraftStatus);
197             }
198             CLDRFile file = handleMake(curLocale, false, madeWithMinimalDraftStatus);
199             if (file == null) {
200                 throw new NullPointerException(
201                         this + ".handleMake returned a null CLDRFile for " + curLocale);
202             }
203             XMLSource source = file.dataSource;
204             registerXmlSource(source);
205             sourceList.add(source);
206             curLocale =
207                     LocaleIDParser.getParent(
208                             curLocale,
209                             ignoreExplicitParentLocale
210                                     ? ParentLocaleComponent.collations
211                                     : ParentLocaleComponent.main);
212         }
213         return new ResolvingSource(sourceList);
214     }
215 
getMinimalDraftStatus()216     public abstract DraftStatus getMinimalDraftStatus();
217 
218     /**
219      * Convenience static
220      *
221      * @param path
222      * @param string
223      * @return
224      */
make(String path, String string)225     public static Factory make(String path, String string) {
226         try {
227             return SimpleFactory.make(path, string);
228         } catch (Exception e) {
229             throw new IllegalArgumentException("path: " + path + "; string: " + string, e);
230         }
231     }
232 
233     /**
234      * Convenience static
235      *
236      * @param mainDirectory
237      * @param string
238      * @param approved
239      * @return
240      */
make(String mainDirectory, String string, DraftStatus approved)241     public static Factory make(String mainDirectory, String string, DraftStatus approved) {
242         return SimpleFactory.make(mainDirectory, string, approved);
243     }
244 
245     /** Get a set of the available locales for the factory. */
getAvailable()246     public Set<String> getAvailable() {
247         return Collections.unmodifiableSet(handleGetAvailable());
248     }
249 
handleGetAvailable()250     protected abstract Set<String> handleGetAvailable();
251 
252     /** Get a set of the available language locales (according to isLanguage). */
getAvailableLanguages()253     public Set<String> getAvailableLanguages() {
254         Set<String> result = new TreeSet<>();
255         for (Iterator<String> it = handleGetAvailable().iterator(); it.hasNext(); ) {
256             String s = it.next();
257             if (XPathParts.isLanguage(s)) result.add(s);
258         }
259         return result;
260     }
261 
262     /**
263      * Get a set of the locales that have the given parent (according to isSubLocale())
264      *
265      * @param isProper if false, then parent itself will match
266      */
getAvailableWithParent(String parent, boolean isProper)267     public Set<String> getAvailableWithParent(String parent, boolean isProper) {
268         Set<String> result = new TreeSet<>();
269 
270         for (Iterator<String> it = handleGetAvailable().iterator(); it.hasNext(); ) {
271             String s = it.next();
272             int relation = XPathParts.isSubLocale(parent, s);
273             if (relation >= 0 && !(isProper && relation == 0)) result.add(s);
274         }
275         return result;
276     }
277 
getSupplementalDirectory()278     public File getSupplementalDirectory() {
279         return supplementalDirectory;
280     }
281 
282     /**
283      * Sets the supplemental directory to be used by this Factory and CLDRFiles created by this
284      * Factory.
285      *
286      * @param supplementalDirectory
287      * @return
288      */
setSupplementalDirectory(File supplementalDirectory)289     public Factory setSupplementalDirectory(File supplementalDirectory) {
290         this.supplementalDirectory = supplementalDirectory;
291         return this;
292     }
293 
294     // TODO(jchye): Clean this up.
getSupplementalData()295     public CLDRFile getSupplementalData() {
296         try {
297             return make("supplementalData", false);
298         } catch (RuntimeException e) {
299             return Factory.make(getSupplementalDirectory().getPath(), ".*")
300                     .make("supplementalData", false);
301         }
302     }
303 
getSupplementalMetadata()304     public CLDRFile getSupplementalMetadata() {
305         try {
306             return make("supplementalMetadata", false);
307         } catch (RuntimeException e) {
308             return Factory.make(getSupplementalDirectory().getPath(), ".*")
309                     .make("supplementalMetadata", false);
310         }
311     }
312 
313     /** These factory implementations don't do any caching. */
314     @Override
subLocalesOf(CLDRLocale forLocale)315     public Set<CLDRLocale> subLocalesOf(CLDRLocale forLocale) {
316         return calculateSubLocalesOf(forLocale, getAvailableCLDRLocales());
317     }
318 
319     /**
320      * Helper function.
321      *
322      * @return
323      */
getAvailableCLDRLocales()324     public Set<CLDRLocale> getAvailableCLDRLocales() {
325         return CLDRLocale.getInstance(getAvailable());
326     }
327 
328     /**
329      * Helper function. Does not cache.
330      *
331      * @param locale
332      * @param available
333      * @return
334      */
calculateSubLocalesOf(CLDRLocale locale, Set<CLDRLocale> available)335     public Set<CLDRLocale> calculateSubLocalesOf(CLDRLocale locale, Set<CLDRLocale> available) {
336         Set<CLDRLocale> sub = new TreeSet<>();
337         for (CLDRLocale l : available) {
338             if (l.getParent() == locale) {
339                 sub.add(l);
340             }
341         }
342         return sub;
343     }
344 
345     /**
346      * Get all of the files in the source directories that match localeName (which is really xml
347      * file name).
348      *
349      * @param localeName
350      * @return
351      */
getSourceDirectoriesForLocale(String localeName)352     public abstract List<File> getSourceDirectoriesForLocale(String localeName);
353 
354     /** thread-safe lazy load of TestCache */
355     private Supplier<TestCache> testCache = Suppliers.memoize(() -> new TestCache(this));
356 
357     /** subclass contructor */
Factory()358     protected Factory() {}
359 
360     /** get the TestCache owned by this Factory */
getTestCache()361     public final TestCache getTestCache() {
362         return testCache.get();
363     }
364 
365     /**
366      * Subclasses must call this on every actual XMLSource created so that the TestCache can listen
367      * for changes
368      */
registerXmlSource(XMLSource x)369     protected final XMLSource registerXmlSource(XMLSource x) {
370         if (!x.isFrozen()) {
371             x.addListener(getTestCache());
372         }
373         return x;
374     }
375 
376     /** register the XMLSource inside this file */
registerXmlSource(CLDRFile rawFile)377     protected CLDRFile registerXmlSource(CLDRFile rawFile) {
378         if (!rawFile.isFrozen()) {
379             registerXmlSource(rawFile.dataSource);
380         }
381         return rawFile;
382     }
383 }
384