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