1 package org.unicode.cldr.util;
2 
3 import java.io.File;
4 import java.io.FilenameFilter;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.HashSet;
8 import java.util.LinkedHashSet;
9 import java.util.List;
10 import java.util.Locale;
11 import java.util.Map;
12 import java.util.Properties;
13 import java.util.Set;
14 import java.util.concurrent.ConcurrentHashMap;
15 
16 import org.unicode.cldr.test.CheckCLDR.Phase;
17 
18 import com.google.common.cache.CacheBuilder;
19 import com.google.common.cache.CacheLoader;
20 import com.google.common.cache.LoadingCache;
21 import com.google.common.collect.ImmutableSet;
22 import com.ibm.icu.dev.test.TestFmwk;
23 import com.ibm.icu.dev.test.TestLog;
24 import com.ibm.icu.text.Collator;
25 import com.ibm.icu.text.RuleBasedCollator;
26 import com.ibm.icu.util.ULocale;
27 import com.ibm.icu.util.VersionInfo;
28 
29 public class CLDRConfig extends Properties {
30     public static boolean SKIP_SEED = System.getProperty("CLDR_SKIP_SEED") != null;
31     /**
32      *
33      */
34     private static final long serialVersionUID = -2605254975303398336L;
35     public static boolean DEBUG = false;
36     private static CLDRConfig INSTANCE = null;
37     public static final String SUBCLASS = CLDRConfig.class.getName() + "Impl";
38 
39     /**
40      * Object to use for synchronization when interacting with Factory
41      */
42     private static final Object CLDR_FACTORY_SYNC = new Object();
43 
44     /**
45      * Object to use for synchronization when interacting with Factory
46      */
47     private static final Object FULL_FACTORY_SYNC = new Object();
48 
49     /**
50      * Object to use for synchronization when interacting with Factory
51      */
52     private static final Object EXEMPLARS_FACTORY_SYNC = new Object();
53     /**
54      * Object to use for synchronization when interacting with Factory
55      */
56     private static final Object COLLATION_FACTORY_SYNC = new Object();
57     /**
58      * Object to use for synchronization when interacting with Factory
59      */
60     private static final Object RBNF_FACTORY_SYNC = new Object();
61 
62     /**
63      * Object to use for synchronization when interacting with Factory
64      */
65     private static final Object ANNOTATIONS_FACTORY_SYNC = new Object();
66 
67     /**
68      * Object to use for synchronization when interacting with Factory
69      */
70     private static final Object SUBDIVISION_FACTORY_SYNC = new Object();
71 
72     /**
73      * Object used for synchronization when interacting with SupplementalData
74      */
75     private static final Object SUPPLEMENTAL_DATA_SYNC = new Object();
76 
77     /**
78      * Object used for synchronization in getCollator()
79      */
80     private static final Object GET_COLLATOR_SYNC = new Object();
81 
82     /**
83      * Object used for synchronization in getCollator()
84      */
85     private static final Object GET_COLLATOR_SYNC_ROOT = new Object();
86 
87     /**
88      * Object used for synchronization in getStandardCodes()
89      */
90     private static final Object GET_STANDARD_CODES_SYNC = new Object();
91 
92     /**
93      * Object used for synchronization in getCoverageInfo()
94      */
95     private static Object COVERAGE_INFO_SYNC = new Object();
96 
97     public enum Environment {
98         LOCAL, // < == unknown.
99         SMOKETEST, // staging area
100         PRODUCTION, // production server!
101         UNITTEST // unit test setting
102     }
103 
getInstance()104     public static CLDRConfig getInstance() {
105         synchronized (CLDRConfig.class) {
106             if (INSTANCE == null) {
107                 final String env = System.getProperty("CLDR_ENVIRONMENT");
108                 if (env != null && env.equals(Environment.UNITTEST.name())) {
109                     if (DEBUG) {
110                         System.err.println("-DCLDR_ENVIRONMENT=" + env + " - not loading " + SUBCLASS);
111                     }
112                 } else {
113                     try {
114                         // System.err.println("Attempting to new up a " + SUBCLASS);
115                         INSTANCE = (CLDRConfig) (Class.forName(SUBCLASS).newInstance());
116 
117                         if (INSTANCE != null) {
118                             System.err.println("Using CLDRConfig: " + INSTANCE.toString() + " - "
119                                 + INSTANCE.getClass().getName());
120                         } else {
121                             if (DEBUG) {
122                                 // Probably occurred because ( config.getEnvironment() == Environment.UNITTEST )
123                                 // see CLDRConfigImpl
124                                 System.err.println("Note: CLDRConfig Subclass " +
125                                     SUBCLASS + ".newInstance() returned NULL " +
126                                     "( this is OK if we aren't inside the SurveyTool's web server )");
127                             }
128                         }
129                     } catch (ClassNotFoundException e) {
130                         // Expected - when not under cldr-apps, this class doesn't exist.
131                     } catch (InstantiationException | IllegalAccessException e) {
132                         // TODO: log a useful message
133                     }
134                 }
135             }
136             if (INSTANCE == null) {
137                 INSTANCE = new CLDRConfig();
138                 CldrUtility.checkValidDirectory(INSTANCE.getProperty("CLDR_DIR"),
139                     "You have to set -DCLDR_DIR=<validdirectory>");
140             }
141         }
142         return INSTANCE;
143     }
144 
145     String initStack = null;
146 
CLDRConfig()147     protected CLDRConfig() {
148         initStack = StackTracker.currentStack();
149     }
150 
getInitStack()151     public String getInitStack() {
152         return initStack;
153     }
154 
155     private CoverageInfo coverageInfo = null;
156     private SupplementalDataInfo supplementalDataInfo;
157     private StandardCodes sc;
158     private Factory cldrFactory;
159     private Factory fullFactory;
160     private Factory mainAndAnnotationsFactory;
161     private Factory commonAndSeedAndMainAndAnnotationsFactory;
162     private Factory exemplarsFactory;
163     private Factory collationFactory;
164     private Factory rbnfFactory;
165     private Factory annotationsFactory;
166     private Factory subdivisionFactory;
167     private Factory supplementalFactory;
168     private RuleBasedCollator colRoot;
169     private RuleBasedCollator col;
170     private Phase phase = null; // default
171 
172     private LoadingCache<String, CLDRFile> cldrFileResolvedCache = CacheBuilder.newBuilder()
173         .maximumSize(200)
174         .build(
175             new CacheLoader<String, CLDRFile>() {
176                 @Override
177                 public CLDRFile load(String locale) {
178                     return getFullCldrFactory().make(locale, true);
179                 }
180             });
181 
182     // Unresolved CLDRFiles are smaller than resolved, so we can cache more of them safely.
183     private LoadingCache<String, CLDRFile> cldrFileUnresolvedCache = CacheBuilder.newBuilder()
184         .maximumSize(1000)
185         .build(
186             new CacheLoader<String, CLDRFile>() {
187                 @Override
188                 public CLDRFile load(String locale) {
189                     return getFullCldrFactory().make(locale, false);
190                 }
191             });
192     private TestLog testLog = null;
193 
194     // base level
setTestLog(TestLog log)195     public TestLog setTestLog(TestLog log) {
196         testLog = log;
197         return log;
198     }
199 
200     // for calling "run"
setTestLog(TestFmwk log)201     public TestFmwk setTestLog(TestFmwk log) {
202         testLog = log;
203         return log;
204     }
205 
logln(String msg)206     protected void logln(String msg) {
207         if (testLog != null) {
208             testLog.logln(msg);
209         } else {
210             System.out.println(msg);
211             System.out.flush();
212         }
213     }
214 
getSupplementalDataInfo()215     public SupplementalDataInfo getSupplementalDataInfo() {
216         synchronized (SUPPLEMENTAL_DATA_SYNC) {
217             if (supplementalDataInfo == null) {
218                 supplementalDataInfo = SupplementalDataInfo.getInstance(CLDRPaths.DEFAULT_SUPPLEMENTAL_DIRECTORY);
219             }
220         }
221         return supplementalDataInfo;
222     }
223 
getStandardCodes()224     public StandardCodes getStandardCodes() {
225         synchronized (GET_STANDARD_CODES_SYNC) {
226             if (sc == null) {
227                 sc = StandardCodes.make();
228             }
229         }
230         return sc;
231     }
232 
getCoverageInfo()233     public CoverageInfo getCoverageInfo() {
234         synchronized (COVERAGE_INFO_SYNC) {
235             if (coverageInfo == null) {
236                 coverageInfo = new CoverageInfo(getSupplementalDataInfo());
237             }
238         }
239         return coverageInfo;
240     }
241 
getCldrFactory()242     public Factory getCldrFactory() {
243         synchronized (CLDR_FACTORY_SYNC) {
244             if (cldrFactory == null) {
245                 cldrFactory = Factory.make(CLDRPaths.MAIN_DIRECTORY, ".*");
246             }
247         }
248         return cldrFactory;
249     }
250 
getExemplarsFactory()251     public Factory getExemplarsFactory() {
252         synchronized (EXEMPLARS_FACTORY_SYNC) {
253             if (exemplarsFactory == null) {
254                 exemplarsFactory = Factory.make(CLDRPaths.EXEMPLARS_DIRECTORY, ".*");
255             }
256         }
257         return exemplarsFactory;
258     }
259 
getCollationFactory()260     public Factory getCollationFactory() {
261         synchronized (COLLATION_FACTORY_SYNC) {
262             if (collationFactory == null) {
263                 collationFactory = Factory.make(CLDRPaths.COLLATION_DIRECTORY, ".*");
264             }
265         }
266         return collationFactory;
267     }
268 
getRBNFFactory()269     public Factory getRBNFFactory() {
270         synchronized (RBNF_FACTORY_SYNC) {
271             if (rbnfFactory == null) {
272                 rbnfFactory = Factory.make(CLDRPaths.RBNF_DIRECTORY, ".*");
273             }
274         }
275         return rbnfFactory;
276     }
277 
getAnnotationsFactory()278     public Factory getAnnotationsFactory() {
279         synchronized (ANNOTATIONS_FACTORY_SYNC) {
280             if (annotationsFactory == null) {
281                 annotationsFactory = Factory.make(CLDRPaths.ANNOTATIONS_DIRECTORY, ".*");
282             }
283         }
284         return annotationsFactory;
285     }
286 
getSubdivisionFactory()287     public Factory getSubdivisionFactory() {
288         synchronized (SUBDIVISION_FACTORY_SYNC) {
289             if (subdivisionFactory == null) {
290                 subdivisionFactory = Factory.make(CLDRPaths.SUBDIVISIONS_DIRECTORY, ".*");
291             }
292         }
293         return subdivisionFactory;
294     }
295 
getMainAndAnnotationsFactory()296     public Factory getMainAndAnnotationsFactory() {
297         synchronized (FULL_FACTORY_SYNC) {
298             if (mainAndAnnotationsFactory == null) {
299                 File[] paths = {
300                     new File(CLDRPaths.MAIN_DIRECTORY),
301                     new File(CLDRPaths.ANNOTATIONS_DIRECTORY) };
302                 mainAndAnnotationsFactory = SimpleFactory.make(paths, ".*");
303             }
304         }
305         return mainAndAnnotationsFactory;
306     }
307 
308     static Factory allFactory;
309 
getCommonSeedExemplarsFactory()310     public Factory getCommonSeedExemplarsFactory() {
311         synchronized (FULL_FACTORY_SYNC) {
312             if (allFactory == null) {
313                 allFactory = SimpleFactory.make(addStandardSubdirectories(CLDR_DATA_DIRECTORIES), ".*");
314             }
315         }
316         return allFactory;
317     }
318 
getCommonAndSeedAndMainAndAnnotationsFactory()319     public Factory getCommonAndSeedAndMainAndAnnotationsFactory() {
320         synchronized (FULL_FACTORY_SYNC) {
321             if (commonAndSeedAndMainAndAnnotationsFactory == null) {
322                 File[] paths = {
323                     new File(CLDRPaths.MAIN_DIRECTORY),
324                     new File(CLDRPaths.ANNOTATIONS_DIRECTORY),
325                     SKIP_SEED ? null : new File(CLDRPaths.SEED_DIRECTORY),
326                         SKIP_SEED ? null : new File(CLDRPaths.SEED_ANNOTATIONS_DIRECTORY)
327                 };
328                 commonAndSeedAndMainAndAnnotationsFactory = SimpleFactory.make(paths, ".*");
329             }
330         }
331         return commonAndSeedAndMainAndAnnotationsFactory;
332     }
333 
getFullCldrFactory()334     public Factory getFullCldrFactory() {
335         synchronized (FULL_FACTORY_SYNC) {
336             if (fullFactory == null) {
337                 File[] paths = {
338                     new File(CLDRPaths.MAIN_DIRECTORY),
339                     SKIP_SEED ? null : new File(CLDRPaths.SEED_DIRECTORY)};
340                 fullFactory = SimpleFactory.make(paths, ".*");
341             }
342         }
343         return fullFactory;
344     }
345 
getSupplementalFactory()346     public Factory getSupplementalFactory() {
347         synchronized (CLDR_FACTORY_SYNC) {
348             if (supplementalFactory == null) {
349                 supplementalFactory = Factory.make(CLDRPaths.DEFAULT_SUPPLEMENTAL_DIRECTORY, ".*");
350             }
351         }
352         return supplementalFactory;
353     }
354 
getEnglish()355     public CLDRFile getEnglish() {
356         return getCLDRFile("en", true);
357     }
358 
getCLDRFile(String locale, boolean resolved)359     public CLDRFile getCLDRFile(String locale, boolean resolved) {
360 
361         return resolved ? cldrFileResolvedCache.getUnchecked(locale) : cldrFileUnresolvedCache.getUnchecked(locale);
362 
363     }
364 
getRoot()365     public CLDRFile getRoot() {
366         return getCLDRFile("root", true);
367     }
368 
getCollatorRoot()369     public Collator getCollatorRoot() {
370         synchronized (GET_COLLATOR_SYNC_ROOT) {
371             if (colRoot == null) {
372                 CLDRFile root = getCollationFactory().make("root", false);
373                 String rules = root.getStringValue("//ldml/collations/collation[@type=\"emoji\"][@visibility=\"external\"]/cr");
374                 try {
375                     colRoot = new RuleBasedCollator(rules);
376                 } catch (Exception e) {
377                     colRoot = (RuleBasedCollator) getCollator();
378                     return colRoot;
379                 }
380                 colRoot.setStrength(Collator.IDENTICAL);
381                 colRoot.setNumericCollation(true);
382                 colRoot.freeze();
383             }
384         }
385         return colRoot;
386     }
387 
getCollator()388     public Collator getCollator() {
389         synchronized (GET_COLLATOR_SYNC) {
390             if (col == null) {
391                 col = (RuleBasedCollator) Collator.getInstance(ULocale.forLanguageTag("en-u-co-emoji"));
392                 col.setStrength(Collator.IDENTICAL);
393                 col.setNumericCollation(true);
394                 col.freeze();
395             }
396         }
397         return col;
398     }
399 
getPhase()400     public synchronized Phase getPhase() {
401         if (phase == null) {
402             if (getEnvironment() == Environment.UNITTEST) {
403                 phase = Phase.BUILD;
404             } else {
405                 phase = Phase.SUBMISSION;
406             }
407         }
408         return phase;
409     }
410 
411     @Override
getProperty(String key, String d)412     public String getProperty(String key, String d) {
413         String result = getProperty(key);
414         if (result == null) return d;
415         return result;
416     }
417 
418     private Set<String> shown = new HashSet<>();
419 
420     private Map<String, String> localSet = null;
421 
422     @Override
get(Object key)423     public String get(Object key) {
424         return getProperty(key.toString());
425     }
426 
427     @Override
getProperty(String key)428     public String getProperty(String key) {
429         String result = null;
430         if (localSet != null) {
431             result = localSet.get(key);
432         }
433         if (result == null) {
434             result = System.getProperty(key);
435         }
436         if (result == null) {
437             result = System.getProperty(key.toUpperCase(Locale.ENGLISH));
438         }
439         if (result == null) {
440             result = System.getProperty(key.toLowerCase(Locale.ENGLISH));
441         }
442         if (result == null) {
443             result = System.getenv(key);
444         }
445         if (DEBUG && !shown.contains(key)) {
446             logln("-D" + key + "=" + result);
447             shown.add(key);
448         }
449         return result;
450     }
451 
452     private Environment curEnvironment = null;
453 
getEnvironment()454     public Environment getEnvironment() {
455         if (curEnvironment == null) {
456             String envString = getProperty("CLDR_ENVIRONMENT");
457             if (envString != null) {
458                 curEnvironment = Environment.valueOf(envString.trim());
459             }
460             if (curEnvironment == null) {
461                 curEnvironment = getDefaultEnvironment();
462             }
463         }
464         return curEnvironment;
465     }
466 
467     /**
468      * If no environment is defined, what is the default?
469      * @return
470      */
getDefaultEnvironment()471     protected Environment getDefaultEnvironment() {
472         return Environment.LOCAL;
473     }
474 
setEnvironment(Environment environment)475     public void setEnvironment(Environment environment) {
476         curEnvironment = environment;
477     }
478 
479     /**
480      * For test use only. Will throw an exception in non test environments.
481      * @param k
482      * @param v
483      * @return
484      */
485     @Override
setProperty(String k, String v)486     public Object setProperty(String k, String v) {
487         if (getEnvironment() != Environment.UNITTEST) {
488             throw new InternalError("setProperty() only valid in UNITTEST Environment.");
489         }
490         if (localSet == null) {
491             localSet = new ConcurrentHashMap<>();
492         }
493         shown.remove(k); // show it again with -D
494         return localSet.put(k, v);
495     }
496 
497     @Override
put(Object k, Object v)498     public Object put(Object k, Object v) {
499         return setProperty(k.toString(), v.toString());
500     }
501 
502     /**
503      * Return true if the value indicates 'true'
504      * @param k key
505      * @param defVal default value
506      * @return
507      */
getProperty(String k, boolean defVal)508     public boolean getProperty(String k, boolean defVal) {
509         String val = getProperty(k, defVal ? "true" : null);
510         if (val == null) {
511             return false;
512         } else {
513             val = val.trim().toLowerCase();
514             return (val.equals("true") || val.equals("t") || val.equals("yes") || val.equals("y"));
515         }
516     }
517 
518     /**
519      * Return a numeric property
520      * @param k key
521      * @param defVal default value
522      * @return
523      */
getProperty(String k, int defVal)524     public int getProperty(String k, int defVal) {
525         String val = getProperty(k, Integer.toString(defVal));
526         if (val == null) {
527             return defVal;
528         } else {
529             try {
530                 return Integer.parseInt(val);
531             } catch (NumberFormatException nfe) {
532                 return defVal;
533             }
534         }
535     }
536 
537     private static class FileWrapper {
538         private File cldrDir = null;
FileWrapper()539         private FileWrapper() {
540             String dir = getInstance().getProperty("CLDR_DIR", null);
541             if (dir != null) {
542                 cldrDir = new File(dir);
543             } else {
544                 cldrDir = null;
545             }
546         }
getCldrDir()547         public File getCldrDir() {
548             return this.cldrDir;
549         }
550         // singleton
551         private static FileWrapper fileWrapperInstance = new FileWrapper();
getFileWrapperInstance()552         public static FileWrapper getFileWrapperInstance() {
553             return fileWrapperInstance;
554         }
555     }
556 
getCldrBaseDirectory()557     public File getCldrBaseDirectory() {
558         return FileWrapper.getFileWrapperInstance().getCldrDir();
559     }
560 
561     /**
562      * Get all CLDR XML files in the CLDR base directory.
563      * @return
564      */
getAllCLDRFilesEndingWith(final String suffix)565     public Set<File> getAllCLDRFilesEndingWith(final String suffix) {
566         FilenameFilter filter = new FilenameFilter() {
567             @Override
568             public boolean accept(File dir, String name) {
569                 return name.endsWith(suffix) && !isJunkFile(name); // skip junk and backup files
570             }
571         };
572         final File dir = getCldrBaseDirectory();
573         Set<File> list;
574         list = getCLDRFilesMatching(filter, dir);
575         return list;
576     }
577 
578     /**
579      * Return all CLDR data files matching this filter
580      * @param filter matching filter
581      * @param baseDir base directory, see {@link #getCldrBaseDirectory()}
582      * @return set of files
583      */
getCLDRFilesMatching(FilenameFilter filter, final File baseDir)584     public Set<File> getCLDRFilesMatching(FilenameFilter filter, final File baseDir) {
585         Set<File> list;
586         list = new LinkedHashSet<>();
587         for (String subdir : getCLDRDataDirectories()) {
588             getFilesRecursively(new File(baseDir, subdir), filter, list);
589         }
590         return list;
591     }
592 
593     /**
594      * TODO: better place for these constants?
595      */
596     private static final String COMMON_DIR = "common";
597     /**
598      * TODO: better place for these constants?
599      */
600     private static final String EXEMPLARS_DIR = "exemplars";
601     /**
602      * TODO: better place for these constants?
603      */
604     private static final String SEED_DIR = "seed";
605     /**
606      * TODO: better place for these constants?
607      */
608     private static final String KEYBOARDS_DIR = "keyboards";
609     private static final String MAIN_DIR = "main";
610     private static final String ANNOTATIONS_DIR = "annotations";
611     private static final String SUBDIVISIONS_DIR = "subdivisions";
612 
613     /**
614      * TODO: better place for these constants?
615      */
616     private static final String CLDR_DATA_DIRECTORIES[] = { COMMON_DIR, SEED_DIR, KEYBOARDS_DIR, EXEMPLARS_DIR };
617     private static final ImmutableSet<String> STANDARD_SUBDIRS = ImmutableSet.of(MAIN_DIR, ANNOTATIONS_DIR, SUBDIVISIONS_DIR);
618 
619     /**
620      * Get a list of CLDR directories containing actual data
621      * @return an iterable containing the names of all CLDR data subdirectories
622      */
getCLDRDataDirectories()623     public Iterable<String> getCLDRDataDirectories() {
624         return Arrays.asList(CLDR_DATA_DIRECTORIES);
625     }
626 
627     /**
628      * Given comma separated list "common" or "common,main" return a list of actual files.
629      * Adds subdirectories in STANDARD_SUBDIRS as necessary.
630      */
getCLDRDataDirectories(String list)631     public File[] getCLDRDataDirectories(String list) {
632         final File dir = getCldrBaseDirectory();
633         String stubs[] = list.split(",");
634         File[] ret = new File[stubs.length];
635         for (int i = 0; i < stubs.length; i++) {
636             ret[i] = new File(dir, stubs[i]);
637         }
638         return ret;
639     }
640 
641     /**
642      * Add subdirectories to file list as needed, from STANDARD_SUBDIRS.
643      * <ul><li>map "common","seed" -> "common/main", "seed/main"
644      * <li>but common/main -> common/main
645      * </ul>
646      */
addStandardSubdirectories(String... base)647     public File[] addStandardSubdirectories(String... base) {
648         return addStandardSubdirectories(fileArrayFromStringArray(getCldrBaseDirectory(), base));
649     }
650 
addStandardSubdirectories(File... base)651     public File[] addStandardSubdirectories(File... base) {
652         List<File> ret = new ArrayList<>();
653         //File[] ret = new File[base.length * 2];
654         for (int i = 0; i < base.length; i++) {
655             File baseFile = base[i];
656             String name = baseFile.getName();
657             if (STANDARD_SUBDIRS.contains(name)) {
658                 ret.add(baseFile);
659             } else {
660                 for (String sub : STANDARD_SUBDIRS) {
661                     addIfExists(ret, baseFile, sub);
662                 }
663             }
664         }
665         return ret.toArray(new File[ret.size()]);
666     }
667 
fileArrayFromStringArray(File dir, String... subdirNames)668     public File[] fileArrayFromStringArray(File dir, String... subdirNames) {
669         File[] fileList = new File[subdirNames.length];
670         int i = 0;
671         for (String item : subdirNames) {
672             fileList[i++] = new File(dir, item);
673         }
674         return fileList;
675     }
676 
addIfExists(List<File> ret, File baseFile, String sub)677     private void addIfExists(List<File> ret, File baseFile, String sub) {
678         File file = new File(baseFile, sub);
679         if (file.exists()) {
680             ret.add(file);
681         }
682     }
683 
684     /**
685      * Utility function. Recursively add to a list of files. Skips ".svn" and junk directories.
686      * @param directory base directory
687      * @param filter filter to restrict files added
688      * @param toAddTo set to add to
689      * @return returns toAddTo.
690      */
getFilesRecursively(File directory, FilenameFilter filter, Set<File> toAddTo)691     public Set<File> getFilesRecursively(File directory, FilenameFilter filter, Set<File> toAddTo) {
692         File files[] = directory.listFiles();
693         if (files != null) {
694             for (File subfile : files) {
695                 if (subfile.isDirectory()) {
696                     if (!isJunkFile(subfile.getName())) {
697                         getFilesRecursively(subfile, filter, toAddTo);
698                     }
699                 } else if (filter.accept(directory, subfile.getName())) {
700                     toAddTo.add(subfile);
701                 }
702             }
703         }
704         return toAddTo;
705     }
706 
707     /**
708      * Is the filename junk?  (subversion, backup, etc)
709      * @param name
710      * @return
711      */
isJunkFile(String name)712     public static final boolean isJunkFile(String name) {
713         return name.startsWith(".") || (name.startsWith("#")); // Skip:  .svn, .BACKUP,  #backup# files.
714     }
715 
716     /**
717      * Get the value of the debug setting for the calling class; assuming that no debugging is wanted if the property
718      * value cannot be found
719      * @param callingClass
720      * @return
721      * @see {@link #getDebugSettingsFor(Class, boolean)}
722      */
getDebugSettingsFor(Class<?> callingClass)723     public boolean getDebugSettingsFor(Class<?> callingClass) {
724         return getDebugSettingsFor(callingClass, false);
725     }
726 
727     /**
728      * Get the debug settings (whether debugging is enabled for the calling class; This will look for a property corresponding
729      * to the canonical classname +".debug"; if that property cannot be found, the default value will be returned.
730      * @param callingClass
731      * @param defaultValue
732      * @return
733      */
getDebugSettingsFor(Class<?> callingClass, boolean defaultValue)734     public boolean getDebugSettingsFor(Class<?> callingClass, boolean defaultValue) {
735         // avoid NPE
736         if (callingClass == null) {
737             return defaultValue;
738         }
739         return getProperty(callingClass.getCanonicalName() + ".debug", defaultValue);
740     }
741 
742     /**
743      * Get the URL generator for "general purpose" (non chart) use.
744      * @return
745      */
urls()746     public CLDRURLS urls() {
747         if (urls == null) {
748             synchronized (this) {
749                 urls = internalGetUrls();
750             }
751         }
752         return urls;
753     }
754 
755     /**
756      * Get the URL generator for "absolute" (chart, email) use.
757      * By default, this is the same as urls.
758      */
absoluteUrls()759     public CLDRURLS absoluteUrls() {
760         if (absoluteUrls == null) {
761             synchronized (this) {
762                 absoluteUrls = internalGetAbsoluteUrls();
763             }
764         }
765         return absoluteUrls;
766     }
767 
768     /**
769      * Probably would not need to override this.
770      */
internalGetAbsoluteUrls()771     protected CLDRURLS internalGetAbsoluteUrls() {
772         return new StaticCLDRURLS(this.getProperty(CLDRURLS.CLDR_SURVEY_BASE, CLDRURLS.DEFAULT_BASE));
773     }
774 
775     /**
776      * Override this to provide a different URL source for non-absolute URLs.
777      */
internalGetUrls()778     protected CLDRURLS internalGetUrls() {
779         return absoluteUrls();
780     }
781 
782     private CLDRURLS urls = null;
783     private CLDRURLS absoluteUrls = null;
784 
isCldrVersionBefore(int... version)785     public boolean isCldrVersionBefore(int... version) {
786         return getEnglish().getDtdVersionInfo()
787             .compareTo(getVersion(version)) < 0;
788     }
789 
getVersion(int... versionInput)790     public static VersionInfo getVersion(int... versionInput) {
791         int[] version = new int[4];
792         for (int i = 0; i < versionInput.length; ++i) {
793             version[i] = versionInput[i];
794         }
795         return VersionInfo.getInstance(version[0], version[1], version[2],
796             version[3]);
797     }
798 }
799