| package org.unicode.cldr.util; |
| |
| import java.io.File; |
| import java.io.FilenameFilter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import org.unicode.cldr.test.CheckCLDR.Phase; |
| |
| import com.google.common.cache.CacheBuilder; |
| import com.google.common.cache.CacheLoader; |
| import com.google.common.cache.LoadingCache; |
| import com.google.common.collect.ImmutableSet; |
| import com.ibm.icu.dev.test.TestFmwk; |
| import com.ibm.icu.dev.test.TestLog; |
| import com.ibm.icu.text.Collator; |
| import com.ibm.icu.text.RuleBasedCollator; |
| import com.ibm.icu.util.ULocale; |
| import com.ibm.icu.util.VersionInfo; |
| |
| public class CLDRConfig extends Properties { |
| /** |
| * |
| */ |
| private static final long serialVersionUID = -2605254975303398336L; |
| public static boolean DEBUG = false; |
| private static CLDRConfig INSTANCE = null; |
| public static final String SUBCLASS = CLDRConfig.class.getName() + "Impl"; |
| |
| /** |
| * Object to use for synchronization when interacting with Factory |
| */ |
| private static final Object CLDR_FACTORY_SYNC = new Object(); |
| |
| /** |
| * Object to use for synchronization when interacting with Factory |
| */ |
| private static final Object FULL_FACTORY_SYNC = new Object(); |
| |
| /** |
| * Object to use for synchronization when interacting with Factory |
| */ |
| private static final Object EXEMPLARS_FACTORY_SYNC = new Object(); |
| /** |
| * Object to use for synchronization when interacting with Factory |
| */ |
| private static final Object COLLATION_FACTORY_SYNC = new Object(); |
| /** |
| * Object to use for synchronization when interacting with Factory |
| */ |
| private static final Object RBNF_FACTORY_SYNC = new Object(); |
| |
| /** |
| * Object to use for synchronization when interacting with Factory |
| */ |
| private static final Object ANNOTATIONS_FACTORY_SYNC = new Object(); |
| |
| /** |
| * Object used for synchronization when interacting with SupplementalData |
| */ |
| private static final Object SUPPLEMENTAL_DATA_SYNC = new Object(); |
| |
| /** |
| * Object used for synchronization in getCollator() |
| */ |
| private static final Object GET_COLLATOR_SYNC = new Object(); |
| |
| /** |
| * Object used for synchronization in getStandardCodes() |
| */ |
| private static final Object GET_STANDARD_CODES_SYNC = new Object(); |
| |
| /** |
| * Object used for synchronization in getCoverageInfo() |
| */ |
| private static Object COVERAGE_INFO_SYNC = new Object(); |
| |
| public enum Environment { |
| LOCAL, // < == unknown. |
| SMOKETEST, // staging area |
| PRODUCTION, // production server! |
| UNITTEST // unit test setting |
| }; |
| |
| public static CLDRConfig getInstance() { |
| synchronized (CLDRConfig.class) { |
| if (INSTANCE == null) { |
| final String env = System.getProperty("CLDR_ENVIRONMENT"); |
| if (env != null && env.equals(Environment.UNITTEST.name())) { |
| if (DEBUG) { |
| System.err.println("-DCLDR_ENVIRONMENT=" + env + " - not loading " + SUBCLASS); |
| } |
| } else { |
| try { |
| // System.err.println("Attempting to new up a " + SUBCLASS); |
| INSTANCE = (CLDRConfig) (Class.forName(SUBCLASS).newInstance()); |
| |
| if (INSTANCE != null) { |
| System.err.println("Using CLDRConfig: " + INSTANCE.toString() + " - " |
| + INSTANCE.getClass().getName()); |
| } else { |
| if (DEBUG) { |
| // Probably occurred because ( config.getEnvironment() == Environment.UNITTEST ) |
| // see CLDRConfigImpl |
| System.err.println("Note: CLDRConfig Subclass " + |
| SUBCLASS + ".newInstance() returned NULL " + |
| "( this is OK if we aren't inside the SurveyTool's web server )"); |
| } |
| } |
| } catch (ClassNotFoundException e) { |
| // Expected - when not under cldr-apps, this class doesn't exist. |
| } catch (InstantiationException | IllegalAccessException e) { |
| // TODO: log a useful message |
| } |
| } |
| } |
| if (INSTANCE == null) { |
| INSTANCE = new CLDRConfig(); |
| CldrUtility.checkValidDirectory(INSTANCE.getProperty("CLDR_DIR"), |
| "You have to set -DCLDR_DIR=<validdirectory>"); |
| } |
| } |
| return INSTANCE; |
| } |
| |
| String initStack = null; |
| |
| protected CLDRConfig() { |
| initStack = StackTracker.currentStack(); |
| } |
| |
| public String getInitStack() { |
| return initStack; |
| } |
| |
| private CoverageInfo coverageInfo = null; |
| private SupplementalDataInfo supplementalDataInfo; |
| private StandardCodes sc; |
| private Factory cldrFactory; |
| private Factory fullFactory; |
| private Factory mainAndAnnotationsFactory; |
| private Factory commonAndSeedAndMainAndAnnotationsFactory; |
| private Factory exemplarsFactory; |
| private Factory collationFactory; |
| private Factory rbnfFactory; |
| private Factory annotationsFactory; |
| private Factory supplementalFactory; |
| private RuleBasedCollator col; |
| private Phase phase = null; // default |
| |
| private LoadingCache<String, CLDRFile> cldrFileResolvedCache = CacheBuilder.newBuilder() |
| .maximumSize(200) |
| .build( |
| new CacheLoader<String, CLDRFile>() { |
| public CLDRFile load(String locale) { |
| return getFullCldrFactory().make(locale, true); |
| } |
| }); |
| |
| // Unresolved CLDRFiles are smaller than resolved, so we can cache more of them safely. |
| private LoadingCache<String, CLDRFile> cldrFileUnresolvedCache = CacheBuilder.newBuilder() |
| .maximumSize(1000) |
| .build( |
| new CacheLoader<String, CLDRFile>() { |
| public CLDRFile load(String locale) { |
| return getFullCldrFactory().make(locale, false); |
| } |
| }); |
| private TestLog testLog = null; |
| |
| // base level |
| public TestLog setTestLog(TestLog log) { |
| testLog = log; |
| return log; |
| } |
| |
| // for calling "run" |
| public TestFmwk setTestLog(TestFmwk log) { |
| testLog = log; |
| return log; |
| } |
| |
| protected void logln(String msg) { |
| if (testLog != null) { |
| testLog.logln(msg); |
| } else { |
| System.out.println(msg); |
| System.out.flush(); |
| } |
| } |
| |
| public SupplementalDataInfo getSupplementalDataInfo() { |
| synchronized (SUPPLEMENTAL_DATA_SYNC) { |
| if (supplementalDataInfo == null) { |
| supplementalDataInfo = SupplementalDataInfo.getInstance(CLDRPaths.DEFAULT_SUPPLEMENTAL_DIRECTORY); |
| } |
| } |
| return supplementalDataInfo; |
| } |
| |
| public StandardCodes getStandardCodes() { |
| synchronized (GET_STANDARD_CODES_SYNC) { |
| if (sc == null) { |
| sc = StandardCodes.make(); |
| } |
| } |
| return sc; |
| } |
| |
| public CoverageInfo getCoverageInfo() { |
| synchronized (COVERAGE_INFO_SYNC) { |
| if (coverageInfo == null) { |
| coverageInfo = new CoverageInfo(getSupplementalDataInfo()); |
| } |
| } |
| return coverageInfo; |
| } |
| |
| public Factory getCldrFactory() { |
| synchronized (CLDR_FACTORY_SYNC) { |
| if (cldrFactory == null) { |
| cldrFactory = Factory.make(CLDRPaths.MAIN_DIRECTORY, ".*"); |
| } |
| } |
| return cldrFactory; |
| } |
| |
| public Factory getExemplarsFactory() { |
| synchronized (EXEMPLARS_FACTORY_SYNC) { |
| if (exemplarsFactory == null) { |
| exemplarsFactory = Factory.make(CLDRPaths.EXEMPLARS_DIRECTORY, ".*"); |
| } |
| } |
| return exemplarsFactory; |
| } |
| |
| public Factory getCollationFactory() { |
| synchronized (COLLATION_FACTORY_SYNC) { |
| if (collationFactory == null) { |
| collationFactory = Factory.make(CLDRPaths.COLLATION_DIRECTORY, ".*"); |
| } |
| } |
| return collationFactory; |
| } |
| |
| public Factory getRBNFFactory() { |
| synchronized (RBNF_FACTORY_SYNC) { |
| if (rbnfFactory == null) { |
| rbnfFactory = Factory.make(CLDRPaths.RBNF_DIRECTORY, ".*"); |
| } |
| } |
| return rbnfFactory; |
| } |
| |
| public Factory getAnnotationsFactory() { |
| synchronized (ANNOTATIONS_FACTORY_SYNC) { |
| if (annotationsFactory == null) { |
| annotationsFactory = Factory.make(CLDRPaths.ANNOTATIONS_DIRECTORY, ".*"); |
| } |
| } |
| return annotationsFactory; |
| } |
| |
| public Factory getMainAndAnnotationsFactory() { |
| synchronized (FULL_FACTORY_SYNC) { |
| if (mainAndAnnotationsFactory == null) { |
| File[] paths = { |
| new File(CLDRPaths.MAIN_DIRECTORY), |
| new File(CLDRPaths.ANNOTATIONS_DIRECTORY) }; |
| mainAndAnnotationsFactory = SimpleFactory.make(paths, ".*"); |
| } |
| } |
| return mainAndAnnotationsFactory; |
| } |
| |
| static Factory allFactory; |
| public Factory getCommonSeedExemplarsFactory() { |
| synchronized (FULL_FACTORY_SYNC) { |
| if (allFactory == null) { |
| allFactory = SimpleFactory.make(addStandardSubdirectories(CLDR_DATA_DIRECTORIES), ".*"); |
| } |
| } |
| return allFactory; |
| } |
| |
| public Factory getCommonAndSeedAndMainAndAnnotationsFactory() { |
| synchronized (FULL_FACTORY_SYNC) { |
| if (commonAndSeedAndMainAndAnnotationsFactory == null) { |
| File[] paths = { |
| new File(CLDRPaths.MAIN_DIRECTORY), |
| new File(CLDRPaths.ANNOTATIONS_DIRECTORY), |
| new File(CLDRPaths.SEED_DIRECTORY) |
| }; |
| commonAndSeedAndMainAndAnnotationsFactory = SimpleFactory.make(paths, ".*"); |
| } |
| } |
| return commonAndSeedAndMainAndAnnotationsFactory; |
| } |
| |
| public Factory getFullCldrFactory() { |
| synchronized (FULL_FACTORY_SYNC) { |
| if (fullFactory == null) { |
| File[] paths = { new File(CLDRPaths.MAIN_DIRECTORY), new File(CLDRPaths.SEED_DIRECTORY) }; |
| fullFactory = SimpleFactory.make(paths, ".*"); |
| } |
| } |
| return fullFactory; |
| } |
| |
| public Factory getSupplementalFactory() { |
| synchronized (CLDR_FACTORY_SYNC) { |
| if (supplementalFactory == null) { |
| supplementalFactory = Factory.make(CLDRPaths.DEFAULT_SUPPLEMENTAL_DIRECTORY, ".*"); |
| } |
| } |
| return supplementalFactory; |
| } |
| |
| public CLDRFile getEnglish() { |
| return getCLDRFile("en", true); |
| } |
| |
| public CLDRFile getCLDRFile(String locale, boolean resolved) { |
| |
| return resolved ? cldrFileResolvedCache.getUnchecked(locale) : |
| cldrFileUnresolvedCache.getUnchecked(locale); |
| |
| } |
| |
| public CLDRFile getRoot() { |
| return getCLDRFile("root", true); |
| } |
| |
| public Collator getCollator() { |
| synchronized (GET_COLLATOR_SYNC) { |
| if (col == null) { |
| col = (RuleBasedCollator) Collator.getInstance(ULocale.forLanguageTag("en-u-co-emoji")); |
| col.setStrength(Collator.IDENTICAL); |
| col.setNumericCollation(true); |
| col.freeze(); |
| } |
| } |
| return col; |
| } |
| |
| public synchronized Phase getPhase() { |
| if (phase == null) { |
| if (getEnvironment() == Environment.UNITTEST) { |
| phase = Phase.BUILD; |
| } else { |
| phase = Phase.SUBMISSION; |
| } |
| } |
| return phase; |
| } |
| |
| @Override |
| public String getProperty(String key, String d) { |
| String result = getProperty(key); |
| if (result == null) return d; |
| return result; |
| } |
| |
| private Set<String> shown = new HashSet<String>(); |
| |
| private Map<String, String> localSet = null; |
| |
| @Override |
| public String get(Object key) { |
| return getProperty(key.toString()); |
| } |
| |
| @Override |
| public String getProperty(String key) { |
| String result = null; |
| if (localSet != null) { |
| result = localSet.get(key); |
| } |
| if (result == null) { |
| result = System.getProperty(key); |
| } |
| if (result == null) { |
| result = System.getProperty(key.toUpperCase(Locale.ENGLISH)); |
| } |
| if (result == null) { |
| result = System.getProperty(key.toLowerCase(Locale.ENGLISH)); |
| } |
| if (result == null) { |
| result = System.getenv(key); |
| } |
| if (DEBUG && !shown.contains(key)) { |
| logln("-D" + key + "=" + result); |
| shown.add(key); |
| } |
| return result; |
| } |
| |
| private Environment curEnvironment = null; |
| |
| public Environment getEnvironment() { |
| if (curEnvironment == null) { |
| String envString = getProperty("CLDR_ENVIRONMENT"); |
| if (envString != null) { |
| curEnvironment = Environment.valueOf(envString.trim()); |
| } |
| if (curEnvironment == null) { |
| curEnvironment = getDefaultEnvironment(); |
| } |
| } |
| return curEnvironment; |
| } |
| |
| /** |
| * If no environment is defined, what is the default? |
| * @return |
| */ |
| protected Environment getDefaultEnvironment() { |
| return Environment.LOCAL; |
| } |
| |
| public void setEnvironment(Environment environment) { |
| curEnvironment = environment; |
| } |
| |
| /** |
| * For test use only. Will throw an exception in non test environments. |
| * @param k |
| * @param v |
| * @return |
| */ |
| @Override |
| public Object setProperty(String k, String v) { |
| if (getEnvironment() != Environment.UNITTEST) { |
| throw new InternalError("setProperty() only valid in UNITTEST Environment."); |
| } |
| if (localSet == null) { |
| localSet = new ConcurrentHashMap<String, String>(); |
| } |
| shown.remove(k); // show it again with -D |
| return localSet.put(k, v); |
| } |
| |
| @Override |
| public Object put(Object k, Object v) { |
| return setProperty(k.toString(), v.toString()); |
| } |
| |
| /** |
| * Return true if the value indicates 'true' |
| * @param k key |
| * @param defVal default value |
| * @return |
| */ |
| public boolean getProperty(String k, boolean defVal) { |
| String val = getProperty(k, defVal ? "true" : null); |
| if (val == null) { |
| return false; |
| } else { |
| val = val.trim().toLowerCase(); |
| return (val.equals("true") || val.equals("t") || val.equals("yes") || val.equals("y")); |
| } |
| } |
| |
| /** |
| * Return a numeric property |
| * @param k key |
| * @param defVal default value |
| * @return |
| */ |
| public int getProperty(String k, int defVal) { |
| String val = getProperty(k, Integer.toString(defVal)); |
| if (val == null) { |
| return defVal; |
| } else { |
| try { |
| return Integer.parseInt(val); |
| } catch (NumberFormatException nfe) { |
| return defVal; |
| } |
| } |
| } |
| |
| public File getCldrBaseDirectory() { |
| String dir = getProperty("CLDR_DIR", null); |
| if (dir != null) { |
| return new File(dir); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Get all CLDR XML files in the CLDR base directory. |
| * @return |
| */ |
| public Set<File> getAllCLDRFilesEndingWith(final String suffix) { |
| FilenameFilter filter = new FilenameFilter() { |
| public boolean accept(File dir, String name) { |
| return name.endsWith(suffix) && !isJunkFile(name); // skip junk and backup files |
| } |
| }; |
| final File dir = getCldrBaseDirectory(); |
| Set<File> list; |
| list = getCLDRFilesMatching(filter, dir); |
| return list; |
| } |
| |
| /** |
| * Return all CLDR data files matching this filter |
| * @param filter matching filter |
| * @param baseDir base directory, see {@link #getCldrBaseDirectory()} |
| * @return set of files |
| */ |
| public Set<File> getCLDRFilesMatching(FilenameFilter filter, final File baseDir) { |
| Set<File> list; |
| list = new LinkedHashSet<File>(); |
| for (String subdir : getCLDRDataDirectories()) { |
| getFilesRecursively(new File(baseDir, subdir), filter, list); |
| } |
| return list; |
| } |
| |
| /** |
| * TODO: better place for these constants? |
| */ |
| private static final String COMMON_DIR = "common"; |
| /** |
| * TODO: better place for these constants? |
| */ |
| private static final String EXEMPLARS_DIR = "exemplars"; |
| /** |
| * TODO: better place for these constants? |
| */ |
| private static final String SEED_DIR = "seed"; |
| /** |
| * TODO: better place for these constants? |
| */ |
| private static final String KEYBOARDS_DIR = "keyboards"; |
| private static final String MAIN_DIR = "main"; |
| private static final String ANNOTATIONS_DIR = "annotations"; |
| private static final String SUBDIVISIONS_DIR = "subdivisions"; |
| |
| /** |
| * TODO: better place for these constants? |
| */ |
| private static final String CLDR_DATA_DIRECTORIES[] = { COMMON_DIR, SEED_DIR, KEYBOARDS_DIR, EXEMPLARS_DIR }; |
| private static final ImmutableSet<String> STANDARD_SUBDIRS = ImmutableSet.of(MAIN_DIR, ANNOTATIONS_DIR, SUBDIVISIONS_DIR); |
| |
| /** |
| * Get a list of CLDR directories containing actual data |
| * @return an iterable containing the names of all CLDR data subdirectories |
| */ |
| public Iterable<String> getCLDRDataDirectories() { |
| return Arrays.asList(CLDR_DATA_DIRECTORIES); |
| } |
| |
| /** |
| * Given comma separated list "common" or "common,main" return a list of actual files. |
| * Adds subdirectories in STANDARD_SUBDIRS as necessary. |
| */ |
| public File[] getCLDRDataDirectories(String list) { |
| final File dir = getCldrBaseDirectory(); |
| String stubs[] = list.split(","); |
| File[] ret = new File[stubs.length]; |
| for (int i = 0; i < stubs.length; i++) { |
| ret[i] = new File(dir, stubs[i]); |
| } |
| return ret; |
| } |
| |
| |
| /** |
| * Add subdirectories to file list as needed, from STANDARD_SUBDIRS. |
| * <ul><li>map "common","seed" -> "common/main", "seed/main" |
| * <li>but common/main -> common/main |
| * </ul> |
| */ |
| public File[] addStandardSubdirectories(String... base) { |
| return addStandardSubdirectories(fileArrayFromStringArray(getCldrBaseDirectory(), base)); |
| } |
| |
| public File[] addStandardSubdirectories(File... base) { |
| List<File> ret = new ArrayList<>(); |
| //File[] ret = new File[base.length * 2]; |
| for (int i = 0; i < base.length; i++) { |
| File baseFile = base[i]; |
| String name = baseFile.getName(); |
| if (STANDARD_SUBDIRS.contains(name)) { |
| ret.add(baseFile); |
| } else { |
| for (String sub : STANDARD_SUBDIRS) { |
| addIfExists(ret, baseFile, sub); |
| } |
| } |
| } |
| return ret.toArray(new File[ret.size()]); |
| } |
| |
| private File[] fileArrayFromStringArray(File dir, String... subdirNames) { |
| File[] fileList = new File[subdirNames.length]; |
| int i = 0; |
| for (String item : subdirNames) { |
| fileList[i++] = new File(dir, item); |
| } |
| return fileList; |
| } |
| |
| private void addIfExists(List<File> ret, File baseFile, String sub) { |
| File file = new File(baseFile, sub); |
| if (file.exists()) { |
| ret.add(file); |
| } |
| } |
| |
| /** |
| * Utility function. Recursively add to a list of files. Skips ".svn" and junk directories. |
| * @param directory base directory |
| * @param filter filter to restrict files added |
| * @param toAddTo set to add to |
| * @return returns toAddTo. |
| */ |
| public Set<File> getFilesRecursively(File directory, FilenameFilter filter, Set<File> toAddTo) { |
| File files[] = directory.listFiles(); |
| if (files != null) { |
| for (File subfile : files) { |
| if (subfile.isDirectory()) { |
| if (!isJunkFile(subfile.getName())) { |
| getFilesRecursively(subfile, filter, toAddTo); |
| } |
| } else if (filter.accept(directory, subfile.getName())) { |
| toAddTo.add(subfile); |
| } |
| } |
| } |
| return toAddTo; |
| } |
| |
| /** |
| * Is the filename junk? (subversion, backup, etc) |
| * @param name |
| * @return |
| */ |
| public static final boolean isJunkFile(String name) { |
| return name.startsWith(".") || (name.startsWith("#")); // Skip: .svn, .BACKUP, #backup# files. |
| } |
| |
| /** |
| * Get the value of the debug setting for the calling class; assuming that no debugging is wanted if the property |
| * value cannot be found |
| * @param callingClass |
| * @return |
| * @see {@link #getDebugSettingsFor(Class, boolean)} |
| */ |
| public boolean getDebugSettingsFor(Class<?> callingClass) { |
| return getDebugSettingsFor(callingClass, false); |
| } |
| |
| /** |
| * Get the debug settings (whether debugging is enabled for the calling class; This will look for a property corresponding |
| * to the canonical classname +".debug"; if that property cannot be found, the default value will be returned. |
| * @param callingClass |
| * @param defaultValue |
| * @return |
| */ |
| public boolean getDebugSettingsFor(Class<?> callingClass, boolean defaultValue) { |
| // avoid NPE |
| if (callingClass == null) { |
| return defaultValue; |
| } |
| return getProperty(callingClass.getCanonicalName() + ".debug", defaultValue); |
| } |
| |
| /** |
| * Get the URL generator for "general purpose" (non chart) use. |
| * @return |
| */ |
| public CLDRURLS urls() { |
| if (urls == null) { |
| synchronized (this) { |
| urls = internalGetUrls(); |
| } |
| } |
| return urls; |
| } |
| |
| /** |
| * Get the URL generator for "absolute" (chart, email) use. |
| * By default, this is the same as urls. |
| */ |
| public CLDRURLS absoluteUrls() { |
| if (absoluteUrls == null) { |
| synchronized (this) { |
| absoluteUrls = internalGetAbsoluteUrls(); |
| } |
| } |
| return absoluteUrls; |
| } |
| |
| /** |
| * Probably would not need to override this. |
| */ |
| protected CLDRURLS internalGetAbsoluteUrls() { |
| return new StaticCLDRURLS(this.getProperty(CLDRURLS.CLDR_SURVEY_BASE, CLDRURLS.DEFAULT_BASE)); |
| } |
| |
| /** |
| * Override this to provide a different URL source for non-absolute URLs. |
| */ |
| protected CLDRURLS internalGetUrls() { |
| return absoluteUrls(); |
| } |
| |
| private CLDRURLS urls = null; |
| private CLDRURLS absoluteUrls = null; |
| |
| public boolean isCldrVersionBefore(int... version) { |
| return getEnglish().getDtdVersionInfo() |
| .compareTo(getVersion(version)) < 0; |
| } |
| |
| public static VersionInfo getVersion(int... versionInput) { |
| int[] version = new int[4]; |
| for (int i = 0; i < versionInput.length; ++i) { |
| version[i] = versionInput[i]; |
| } |
| return VersionInfo.getInstance(version[0], version[1], version[2], |
| version[3]); |
| } |
| } |