blob: 8a898436c017a78b3d7f7bd81a162c4920731e87 [file] [log] [blame]
/*
**********************************************************************
* Copyright (c) 2002-2013, International Business Machines
* Corporation and others. All Rights Reserved.
**********************************************************************
* Author: Mark Davis
**********************************************************************
*/
package org.unicode.cldr.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.unicode.cldr.draft.FileUtilities;
import org.unicode.cldr.util.RegexLookup.Finder;
import com.google.common.base.Splitter;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.text.Transform;
import com.ibm.icu.text.Transliterator;
import com.ibm.icu.text.UTF16;
import com.ibm.icu.text.UnicodeSet;
import com.ibm.icu.text.UnicodeSetIterator;
import com.ibm.icu.util.Freezable;
import com.ibm.icu.util.Output;
import com.ibm.icu.util.TimeZone;
public class CldrUtility {
public static final Charset UTF8 = Charset.forName("utf-8");
public static final boolean BETA = false;
public static final String LINE_SEPARATOR = "\n";
public final static Pattern SEMI_SPLIT = PatternCache.get("\\s*;\\s*");
private static final boolean HANDLEFILE_SHOW_SKIP = false;
// Constant for "∅∅∅". Indicates that a child locale has no value for a
// path even though a parent does.
public static final String NO_INHERITANCE_MARKER = new String(new char[] { 0x2205, 0x2205, 0x2205 });
// Constant for "↑↑↑". Indicates a "passthru" vote to the parent locale. If CLDRFile ever
// finds this value in a data field, writing of the field should be suppressed.
public static final String INHERITANCE_MARKER = new String(new char[] { 0x2191, 0x2191, 0x2191 });
public static final UnicodeSet DIGITS = new UnicodeSet("[0-9]").freeze();
/**
* Very simple class, used to replace variables in a string. For example
* <p>
*
* <pre>
* static VariableReplacer langTag = new VariableReplacer()
* .add("$alpha", "[a-zA-Z]")
* .add("$digit", "[0-9]")
* .add("$alphanum", "[a-zA-Z0-9]")
* .add("$x", "[xX]");
* ...
* String langTagPattern = langTag.replace(...);
* </pre>
*/
public static class VariableReplacer {
// simple implementation for now
private Map<String, String> m = new TreeMap<String, String>(Collections.reverseOrder());
public VariableReplacer add(String variable, String value) {
m.put(variable, value);
return this;
}
public String replace(String source) {
String oldSource;
do {
oldSource = source;
for (Iterator<String> it = m.keySet().iterator(); it.hasNext();) {
String variable = it.next();
String value = m.get(variable);
source = replaceAll(source, variable, value);
}
} while (!source.equals(oldSource));
return source;
}
public String replaceAll(String source, String key, String value) {
while (true) {
int pos = source.indexOf(key);
if (pos < 0) return source;
source = source.substring(0, pos) + value + source.substring(pos + key.length());
}
}
}
public interface LineHandler {
/**
* Return false if line was skipped
*
* @param line
* @return
*/
boolean handle(String line) throws Exception;
}
public static String getPath(String path, String filename) {
if (path == null) {
return null;
}
final File file = filename == null ? new File(path)
: new File(path, filename);
try {
return file.getCanonicalPath() + File.separatorChar;
} catch (IOException e) {
return file.getPath() + File.separatorChar;
}
}
static String getPath(String path) {
return getPath(path, null);
}
public static final String ANALYTICS = "<script type=\"text/javascript\">\n"
+ "var gaJsHost = ((\"https:\" == document.location.protocol) ? \"https://ssl.\" : \"http://www.\");\n"
+ "document.write(unescape(\"%3Cscript src='\" + gaJsHost + \"google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E\"));\n"
+ "</script>\n"
+ "<script type=\"text/javascript\">\n"
+ "try {\n"
+ "var pageTracker = _gat._getTracker(\"UA-7672775-1\");\n"
+ "pageTracker._trackPageview();\n"
+ "} catch(err) {}</script>";
public static final List<String> MINIMUM_LANGUAGES = Arrays.asList(new String[] { "ar", "en", "de", "fr", "hi",
"it", "es", "pt", "ru", "zh", "ja" }); // plus language itself
public static final List<String> MINIMUM_TERRITORIES = Arrays.asList(new String[] { "US", "GB", "DE", "FR", "IT",
"JP", "CN", "IN", "RU", "BR" });
public interface LineComparer {
static final int LINES_DIFFERENT = -1, LINES_SAME = 0, SKIP_FIRST = 1, SKIP_SECOND = 2;
/**
* Returns LINES_DIFFERENT, LINES_SAME, or if one of the lines is ignorable, SKIP_FIRST or SKIP_SECOND
*
* @param line1
* @param line2
* @return
*/
int compare(String line1, String line2);
}
public static class SimpleLineComparator implements LineComparer {
public static final int TRIM = 1, SKIP_SPACES = 2, SKIP_EMPTY = 4, SKIP_CVS_TAGS = 8;
StringIterator si1 = new StringIterator();
StringIterator si2 = new StringIterator();
int flags;
public SimpleLineComparator(int flags) {
this.flags = flags;
}
public int compare(String line1, String line2) {
// first, see if we want to skip one or the other lines
int skipper = 0;
if (line1 == null) {
skipper = SKIP_FIRST;
} else {
if ((flags & TRIM) != 0) line1 = line1.trim();
if ((flags & SKIP_EMPTY) != 0 && line1.length() == 0) skipper = SKIP_FIRST;
}
if (line2 == null) {
skipper = SKIP_SECOND;
} else {
if ((flags & TRIM) != 0) line2 = line2.trim();
if ((flags & SKIP_EMPTY) != 0 && line2.length() == 0) skipper += SKIP_SECOND;
}
if (skipper != 0) {
if (skipper == SKIP_FIRST + SKIP_SECOND) return LINES_SAME; // ok, don't skip both
return skipper;
}
// check for null
if (line1 == null) {
if (line2 == null) return LINES_SAME;
return LINES_DIFFERENT;
}
if (line2 == null) {
return LINES_DIFFERENT;
}
// now check equality
if (line1.equals(line2)) return LINES_SAME;
// if not equal, see if we are skipping spaces
if ((flags & SKIP_CVS_TAGS) != 0) {
if (line1.indexOf('$') >= 0 && line2.indexOf('$') >= 0) {
line1 = stripTags(line1);
line2 = stripTags(line2);
if (line1.equals(line2)) return LINES_SAME;
} else if (line1.startsWith("<!DOCTYPE ldml SYSTEM \"../../common/dtd/")
&& line2.startsWith("<!DOCTYPE ldml SYSTEM \"../../common/dtd/")) {
return LINES_SAME;
}
}
if ((flags & SKIP_SPACES) != 0 && si1.set(line1).matches(si2.set(line2))) return LINES_SAME;
return LINES_DIFFERENT;
}
// private Matcher dtdMatcher = PatternCache.get(
// "\\Q<!DOCTYPE ldml SYSTEM \"http://www.unicode.org/cldr/dtd/\\E.*\\Q/ldml.dtd\">\\E").matcher("");
private String[] CVS_TAGS = { "Revision", "Date" };
private String stripTags(String line) {
// $
// Revision: 8994 $
// $
// Date: 2013-07-03 21:31:17 +0200 (Wed, 03 Jul 2013) $
int pos = line.indexOf('$');
if (pos < 0) return line;
pos++;
int endpos = line.indexOf('$', pos);
if (endpos < 0) return line;
for (int i = 0; i < CVS_TAGS.length; ++i) {
if (!line.startsWith(CVS_TAGS[i], pos)) continue;
line = line.substring(0, pos + CVS_TAGS[i].length()) + line.substring(endpos);
}
return line;
}
}
/**
*
* @param file1
* @param file2
* @param failureLines
* on input, String[2], on output, failing lines
* @param lineComparer
* @return
* @throws IOException
*/
public static boolean areFileIdentical(String file1, String file2, String[] failureLines,
LineComparer lineComparer) throws IOException {
try (BufferedReader br1 = new BufferedReader(new FileReader(file1), 32 * 1024);
BufferedReader br2 = new BufferedReader(new FileReader(file2), 32 * 1024);) {
String line1 = "";
String line2 = "";
int skip = 0;
while (true) {
if ((skip & LineComparer.SKIP_FIRST) == 0) line1 = br1.readLine();
if ((skip & LineComparer.SKIP_SECOND) == 0) line2 = br2.readLine();
if (line1 == null && line2 == null) return true;
if (line1 == null || line2 == null) {
// System.out.println("debug");
}
skip = lineComparer.compare(line1, line2);
if (skip == LineComparer.LINES_DIFFERENT) {
break;
}
}
failureLines[0] = line1 != null ? line1 : "<end of file>";
failureLines[1] = line2 != null ? line2 : "<end of file>";
return false;
}
}
/*
* static String getLineWithoutFluff(BufferedReader br1, boolean first, int flags) throws IOException {
* while (true) {
* String line1 = br1.readLine();
* if (line1 == null) return line1;
* if ((flags & TRIM)!= 0) line1 = line1.trim();
* if ((flags & SKIP_EMPTY)!= 0 && line1.length() == 0) continue;
* return line1;
* }
* }
*/
public final static class StringIterator {
String string;
int position = 0;
char next() {
while (true) {
if (position >= string.length()) return '\uFFFF';
char ch = string.charAt(position++);
if (ch != ' ' && ch != '\t') return ch;
}
}
StringIterator reset() {
position = 0;
return this;
}
StringIterator set(String string) {
this.string = string;
position = 0;
return this;
}
boolean matches(StringIterator other) {
while (true) {
char c1 = next();
char c2 = other.next();
if (c1 != c2) return false;
if (c1 == '\uFFFF') return true;
}
}
/**
* @return Returns the position.
*/
public int getPosition() {
return position;
}
}
public static String[] splitArray(String source, char separator) {
return splitArray(source, separator, false);
}
public static String[] splitArray(String source, char separator, boolean trim) {
List<String> piecesList = splitList(source, separator, trim);
String[] pieces = new String[piecesList.size()];
piecesList.toArray(pieces);
return pieces;
}
public static String[] splitCommaSeparated(String line) {
// items are separated by ','
// each item is of the form abc...
// or "..." (required if a comma or quote is contained)
// " in a field is represented by ""
List<String> result = new ArrayList<String>();
StringBuilder item = new StringBuilder();
boolean inQuote = false;
for (int i = 0; i < line.length(); ++i) {
char ch = line.charAt(i); // don't worry about supplementaries
switch (ch) {
case '"':
inQuote = !inQuote;
// at start or end, that's enough
// if get a quote when we are not in a quote, and not at start, then add it and return to inQuote
if (inQuote && item.length() != 0) {
item.append('"');
inQuote = true;
}
break;
case ',':
if (!inQuote) {
result.add(item.toString());
item.setLength(0);
} else {
item.append(ch);
}
break;
default:
item.append(ch);
break;
}
}
result.add(item.toString());
return result.toArray(new String[result.size()]);
}
public static List<String> splitList(String source, char separator) {
return splitList(source, separator, false, null);
}
public static List<String> splitList(String source, char separator, boolean trim) {
return splitList(source, separator, trim, null);
}
public static List<String> splitList(String source, char separator, boolean trim, List<String> output) {
return splitList(source, Character.toString(separator), trim, output);
}
public static List<String> splitList(String source, String separator) {
return splitList(source, separator, false, null);
}
public static List<String> splitList(String source, String separator, boolean trim) {
return splitList(source, separator, trim, null);
}
public static List<String> splitList(String source, String separator, boolean trim, List<String> output) {
if (output == null) output = new ArrayList<String>();
if (source.length() == 0) return output;
int pos = 0;
do {
int npos = source.indexOf(separator, pos);
if (npos < 0) npos = source.length();
String piece = source.substring(pos, npos);
if (trim) piece = piece.trim();
output.add(piece);
pos = npos + 1;
} while (pos < source.length());
return output;
}
/**
* Protect a collection (as much as Java lets us!) from modification.
* Really, really ugly code, since Java doesn't let us do better.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static <T> T protectCollection(T source) {
// TODO - exclude UnmodifiableMap, Set, ...
if (source instanceof Map) {
Map sourceMap = (Map) source;
Map resultMap = clone(sourceMap);
if (resultMap == null) return (T) sourceMap; // failed
resultMap.clear();
for (Object key : sourceMap.keySet()) {
resultMap.put(protectCollection(key), protectCollection(sourceMap.get(key)));
}
return resultMap instanceof SortedMap ? (T) Collections.unmodifiableSortedMap((SortedMap) resultMap)
: (T) Collections.unmodifiableMap(resultMap);
} else if (source instanceof Collection) {
Collection sourceCollection = (Collection) source;
Collection<Object> resultCollection = clone(sourceCollection);
if (resultCollection == null) return (T) sourceCollection; // failed
resultCollection.clear();
for (Object item : sourceCollection) {
resultCollection.add(protectCollection(item));
}
return sourceCollection instanceof List ? (T) Collections.unmodifiableList((List) sourceCollection)
: sourceCollection instanceof SortedSet ? (T) Collections
.unmodifiableSortedSet((SortedSet) sourceCollection)
: sourceCollection instanceof Set ? (T) Collections.unmodifiableSet((Set) sourceCollection)
: (T) Collections.unmodifiableCollection(sourceCollection);
} else if (source instanceof Freezable) {
Freezable freezableSource = (Freezable) source;
if (freezableSource.isFrozen()) return source;
return (T) ((Freezable) (freezableSource.cloneAsThawed())).freeze();
} else {
return source; // can't protect
}
}
/**
* Protect a collections where we don't need to clone.
* @param source
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static <T> T protectCollectionX(T source) {
// TODO - exclude UnmodifiableMap, Set, ...
if (isImmutable(source)) {
return source;
}
if (source instanceof Map) {
Map sourceMap = (Map) source;
// recurse
LinkedHashMap tempMap = new LinkedHashMap<>(sourceMap); // copy contents
sourceMap.clear();
for (Object key : tempMap.keySet()) {
sourceMap.put(protectCollection(key), protectCollectionX(tempMap.get(key)));
}
return sourceMap instanceof SortedMap ? (T) Collections.unmodifiableSortedMap((SortedMap) sourceMap)
: (T) Collections.unmodifiableMap(sourceMap);
} else if (source instanceof Collection) {
Collection sourceCollection = (Collection) source;
LinkedHashSet tempSet = new LinkedHashSet<>(sourceCollection); // copy contents
sourceCollection.clear();
for (Object item : tempSet) {
sourceCollection.add(protectCollectionX(item));
}
return sourceCollection instanceof List ? (T) Collections.unmodifiableList((List) sourceCollection)
: sourceCollection instanceof SortedSet ? (T) Collections
.unmodifiableSortedSet((SortedSet) sourceCollection)
: sourceCollection instanceof Set ? (T) Collections.unmodifiableSet((Set) sourceCollection)
: (T) Collections.unmodifiableCollection(sourceCollection);
} else if (source instanceof Freezable) {
Freezable freezableSource = (Freezable) source;
return (T) freezableSource.freeze();
} else {
throw new IllegalArgumentException("Can’t protect: " + source.getClass().toString());
}
}
private static final Set<Object> KNOWN_IMMUTABLES = new HashSet<Object>(Arrays.asList(
String.class
));
public static boolean isImmutable(Object source) {
return source == null
|| source instanceof Enum
|| source instanceof Number
|| KNOWN_IMMUTABLES.contains(source.getClass());
}
/**
* Clones T if we can; otherwise returns null.
*
* @param <T>
* @param source
* @return
*/
@SuppressWarnings("unchecked")
private static <T> T clone(T source) {
final Class<? extends Object> class1 = source.getClass();
try {
final Method declaredMethod = class1.getDeclaredMethod("clone", (Class<?>) null);
return (T) declaredMethod.invoke(source, (Object) null);
} catch (Exception e) {
}
try {
final Constructor<? extends Object> declaredMethod = class1.getConstructor((Class<?>) null);
return (T) declaredMethod.newInstance((Object) null);
} catch (Exception e) {
}
return null; // uncloneable
}
/**
* Appends two strings, inserting separator if either is empty
*/
public static String joinWithSeparation(String a, String separator, String b) {
if (a.length() == 0) return b;
if (b.length() == 0) return a;
return a + separator + b;
}
/**
* Appends two strings, inserting separator if either is empty. Modifies first map
*/
public static Map<String, String> joinWithSeparation(Map<String, String> a, String separator, Map<String, String> b) {
for (Iterator<String> it = b.keySet().iterator(); it.hasNext();) {
String key = it.next();
String bvalue = b.get(key);
String avalue = a.get(key);
if (avalue != null) {
if (avalue.trim().equals(bvalue.trim())) continue;
bvalue = joinWithSeparation(avalue, separator, bvalue);
}
a.put(key, bvalue);
}
return a;
}
public static <T> String join(Collection<T> c, String separator) {
return join(c, separator, null);
}
public static String join(Object[] c, String separator) {
return join(c, separator, null);
}
public static <T> String join(Collection<T> c, String separator, Transform<T, String> transform) {
StringBuffer output = new StringBuffer();
boolean isFirst = true;
for (T item : c) {
if (isFirst) {
isFirst = false;
} else {
output.append(separator);
}
output.append(transform != null ? transform.transform(item) : item == null ? item : item.toString());
}
return output.toString();
}
public static <T> String join(T[] c, String separator, Transform<T, String> transform) {
return join(Arrays.asList(c), separator, transform);
}
/**
* Utility like Arrays.asList()
*/
@SuppressWarnings("unchecked")
public static <K, V> Map<K, V> asMap(Object[][] source, Map<K, V> target, boolean reverse) {
int from = 0, to = 1;
if (reverse) {
from = 1;
to = 0;
}
for (int i = 0; i < source.length; ++i) {
if (source[i].length != 2) {
throw new IllegalArgumentException("Source must be array of pairs of strings: "
+ Arrays.asList(source[i]));
}
target.put((K) source[i][from], (V) source[i][to]);
}
return target;
}
public static <K, V> Map<K, V> asMap(Object[][] source) {
return asMap(source, new HashMap<K, V>(), false);
}
/**
* Returns the canonical name for a file.
*/
public static String getCanonicalName(String file) {
try {
return new File(file).getCanonicalPath();
} catch (Exception e) {
return file;
}
}
/**
* Convert a UnicodeSet into a string that can be embedded into a Regex. Handles strings that are in the UnicodeSet,
* Supplementary ranges, and escaping
*
* @param source
* The source set
* @param escaper
* A transliterator that is used to escape the characters according to the requirements of the regex.
* @return
*/
public static String toRegex(UnicodeSet source) {
return toRegex(source, null, false);
}
private static final Transliterator DEFAULT_REGEX_ESCAPER = Transliterator.createFromRules(
"foo",
"([ \\- \\\\ \\[ \\] ]) > '\\' $1 ;"
// + " ([:c:]) > &hex($1);"
+ " ([[:control:][[:z:]&[:ascii:]]]) > &hex($1);",
Transliterator.FORWARD);
/**
* Convert a UnicodeSet into a string that can be embedded into a Regex.
* Handles strings that are in the UnicodeSet, Supplementary ranges, and
* escaping
*
* @param source
* The source set
* @param escaper
* A transliterator that is used to escape the characters according
* to the requirements of the regex. The default puts a \\ before [, -,
* \, and ], and converts controls and Ascii whitespace to hex.
* Alternatives can be supplied. Note that some Regex engines,
* including Java 1.5, don't really deal with escaped supplementaries
* well.
* @param onlyBmp
* Set to true if the Regex only accepts BMP characters. In that
* case, ranges of supplementary characters are converted to lists of
* ranges. For example, [\uFFF0-\U0010000F \U0010100F-\U0010300F]
* converts into:
*
* <pre>
* [\uD800][\uDC00-\uDFFF]
* [\uD801-\uDBBF][\uDC00-\uDFFF]
* [\uDBC0][\uDC00-\uDC0F]
* </pre>
*
* and
*
* <pre>
* [\uDBC4][\uDC0F-\uDFFF]
* [\uDBC5-\uDBCB][\uDC00-\uDFFF]
* [\uDBCC][\uDC00-\uDC0F]
* </pre>
*
* These are then coalesced into a list of alternatives by sharing
* parts where feasible. For example, the above turns into 3 pairs of ranges:
*
* <pre>
* [\uDBC0\uDBCC][\uDC00-\uDC0F]|\uDBC4[\uDC0F-\uDFFF]|[\uD800-\uDBBF\uDBC5-\uDBCB][\uDC00-\uDFFF]
* </pre>
*
* @return escaped string. Something like [a-z] or (?:[a-m]|{zh}) if there is
* a string zh in the set, or a more complicated case for
* supplementaries. <br>
* Special cases: [] returns "", single item returns a string
* (escaped), like [a] => "a", or [{abc}] => "abc"<br>
* Supplementaries are handled specially, as described under onlyBmp.
*/
public static String toRegex(UnicodeSet source, Transliterator escaper, boolean onlyBmp) {
if (escaper == null) {
escaper = DEFAULT_REGEX_ESCAPER;
}
UnicodeSetIterator it = new UnicodeSetIterator(source);
// if there is only one item, return it
if (source.size() == 0) {
return "";
}
if (source.size() == 1) {
it.next();
return escaper.transliterate(it.getString());
}
// otherwise, we figure out what is in the set, and will return
StringBuilder base = new StringBuilder("[");
StringBuilder alternates = new StringBuilder();
Map<UnicodeSet, UnicodeSet> lastToFirst = new TreeMap<UnicodeSet, UnicodeSet>(new UnicodeSetComparator());
int alternateCount = 0;
while (it.nextRange()) {
if (it.codepoint == UnicodeSetIterator.IS_STRING) {
++alternateCount;
alternates.append('|').append(escaper.transliterate(it.string));
} else if (!onlyBmp || it.codepointEnd <= 0xFFFF) { // BMP
addBmpRange(it.codepoint, it.codepointEnd, escaper, base);
} else { // supplementary
if (it.codepoint <= 0xFFFF) {
addBmpRange(it.codepoint, 0xFFFF, escaper, base);
it.codepoint = 0x10000; // reset the range
}
// this gets a bit ugly; we are trying to minimize the extra ranges for supplementaries
// we do this by breaking up X-Y based on the Lead and Trail values for X and Y
// Lx [Tx - Ty]) (if Lx == Ly)
// Lx [Tx - DFFF] | Ly [DC00-Ty] (if Lx == Ly - 1)
// Lx [Tx - DFFF] | [Lx+1 - Ly-1][DC00-DFFF] | Ly [DC00-Ty] (otherwise)
int leadX = UTF16.getLeadSurrogate(it.codepoint);
int trailX = UTF16.getTrailSurrogate(it.codepoint);
int leadY = UTF16.getLeadSurrogate(it.codepointEnd);
int trailY = UTF16.getTrailSurrogate(it.codepointEnd);
if (leadX == leadY) {
addSupplementalRange(leadX, leadX, trailX, trailY, escaper, lastToFirst);
} else {
addSupplementalRange(leadX, leadX, trailX, 0xDFFF, escaper, lastToFirst);
if (leadX != leadY - 1) {
addSupplementalRange(leadX + 1, leadY - 1, 0xDC00, 0xDFFF, escaper, lastToFirst);
}
addSupplementalRange(leadY, leadY, 0xDC00, trailY, escaper, lastToFirst);
}
}
}
// add in the supplementary ranges
if (lastToFirst.size() != 0) {
for (UnicodeSet last : lastToFirst.keySet()) {
++alternateCount;
alternates.append('|').append(toRegex(lastToFirst.get(last), escaper, onlyBmp))
.append(toRegex(last, escaper, onlyBmp));
}
}
// Return the output. We separate cases in order to get the minimal extra apparatus
base.append("]");
if (alternateCount == 0) {
return base.toString();
} else if (base.length() > 2) {
return "(?:" + base + "|" + alternates.substring(1) + ")";
} else if (alternateCount == 1) {
return alternates.substring(1);
} else {
return "(?:" + alternates.substring(1) + ")";
}
}
private static void addSupplementalRange(int leadX, int leadY, int trailX, int trailY, Transliterator escaper,
Map<UnicodeSet, UnicodeSet> lastToFirst) {
System.out.println("\tadding: " + new UnicodeSet(leadX, leadY) + "\t" + new UnicodeSet(trailX, trailY));
UnicodeSet last = new UnicodeSet(trailX, trailY);
UnicodeSet first = lastToFirst.get(last);
if (first == null) {
lastToFirst.put(last, first = new UnicodeSet());
}
first.add(leadX, leadY);
}
private static void addBmpRange(int start, int limit, Transliterator escaper, StringBuilder base) {
base.append(escaper.transliterate(UTF16.valueOf(start)));
if (start != limit) {
base.append("-").append(escaper.transliterate(UTF16.valueOf(limit)));
}
}
public static class UnicodeSetComparator implements Comparator<UnicodeSet> {
public int compare(UnicodeSet o1, UnicodeSet o2) {
return o1.compareTo(o2);
}
}
public static class CollectionComparator<T extends Comparable<T>> implements Comparator<Collection<T>> {
public int compare(Collection<T> o1, Collection<T> o2) {
return UnicodeSet.compare(o1, o2, UnicodeSet.ComparisonStyle.SHORTER_FIRST);
}
}
public static class ComparableComparator<T extends Comparable<T>> implements Comparator<T> {
public int compare(T arg0, T arg1) {
return Utility.checkCompare(arg0, arg1);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public static void addTreeMapChain(Map coverageData, Object... objects) {
Map<Object, Object> base = coverageData;
for (int i = 0; i < objects.length - 2; ++i) {
Map<Object, Object> nextOne = (Map<Object, Object>) base.get(objects[i]);
if (nextOne == null) base.put(objects[i], nextOne = new TreeMap<Object, Object>());
base = nextOne;
}
base.put(objects[objects.length - 2], objects[objects.length - 1]);
}
public static abstract class CollectionTransform<S, T> implements Transform<S, T> {
public abstract T transform(S source);
public Collection<T> transform(Collection<S> input, Collection<T> output) {
return CldrUtility.transform(input, this, output);
}
public Collection<T> transform(Collection<S> input) {
return transform(input, new ArrayList<T>());
}
}
public static <S, T, SC extends Collection<S>, TC extends Collection<T>> TC transform(SC source, Transform<S, T> transform, TC target) {
for (S sourceItem : source) {
T targetItem = transform.transform(sourceItem);
if (targetItem != null) {
target.add(targetItem);
}
}
return target;
}
public static <SK, SV, TK, TV, SM extends Map<SK, SV>, TM extends Map<TK, TV>> TM transform(
SM source, Transform<SK, TK> transformKey, Transform<SV, TV> transformValue, TM target) {
for (Entry<SK, SV> sourceEntry : source.entrySet()) {
TK targetKey = transformKey.transform(sourceEntry.getKey());
TV targetValue = transformValue.transform(sourceEntry.getValue());
if (targetKey != null && targetValue != null) {
target.put(targetKey, targetValue);
}
}
return target;
}
public static abstract class Apply<T> {
public abstract void apply(T item);
public <U extends Collection<T>> void applyTo(U collection) {
for (T item : collection) {
apply(item);
}
}
}
public static abstract class Filter<T> {
public abstract boolean contains(T item);
public <U extends Collection<T>> U retainAll(U c) {
for (Iterator<T> it = c.iterator(); it.hasNext();) {
if (!contains(it.next())) it.remove();
}
return c;
}
public <U extends Collection<T>> U extractMatches(U c, U target) {
for (Iterator<T> it = c.iterator(); it.hasNext();) {
T item = it.next();
if (contains(item)) {
target.add(item);
}
}
return target;
}
public <U extends Collection<T>> U removeAll(U c) {
for (Iterator<T> it = c.iterator(); it.hasNext();) {
if (contains(it.next())) it.remove();
}
return c;
}
public <U extends Collection<T>> U extractNonMatches(U c, U target) {
for (Iterator<T> it = c.iterator(); it.hasNext();) {
T item = it.next();
if (!contains(item)) {
target.add(item);
}
}
return target;
}
}
public static class MatcherFilter<T> extends Filter<T> {
private Matcher matcher;
public MatcherFilter(String pattern) {
this.matcher = PatternCache.get(pattern).matcher("");
}
public MatcherFilter(Matcher matcher) {
this.matcher = matcher;
}
public MatcherFilter<T> set(Matcher matcher) {
this.matcher = matcher;
return this;
}
public MatcherFilter<T> set(String pattern) {
this.matcher = PatternCache.get(pattern).matcher("");
return this;
}
public boolean contains(T o) {
return matcher.reset(o.toString()).matches();
}
}
// static final class HandlingTransform implements Transform<String, Handling> {
// @Override
// public Handling transform(String source) {
// return Handling.valueOf(source);
// }
// }
public static final class PairComparator<K extends Comparable<K>, V extends Comparable<V>> implements java.util.Comparator<Pair<K, V>> {
private Comparator<K> comp1;
private Comparator<V> comp2;
public PairComparator(Comparator<K> comp1, Comparator<V> comp2) {
this.comp1 = comp1;
this.comp2 = comp2;
}
@Override
public int compare(Pair<K, V> o1, Pair<K, V> o2) {
{
K o1First = o1.getFirst();
K o2First = o2.getFirst();
int diff = o1First == null ? (o2First == null ? 0 : -1)
: o2First == null ? 1
: comp1 == null ? o1First.compareTo(o2First)
: comp1.compare(o1First, o2First);
if (diff != 0) {
return diff;
}
}
V o1Second = o1.getSecond();
V o2Second = o2.getSecond();
return o1Second == null ? (o2Second == null ? 0 : -1)
: o2Second == null ? 1
: comp2 == null ? o1Second.compareTo(o2Second)
: comp2.compare(o1Second, o2Second);
}
}
/**
* Fetch data from jar
*
* @param name
* a name residing in the org/unicode/cldr/util/data/ directory, or loading from a jar will break.
*/
public static BufferedReader getUTF8Data(String name) {
if (new File(name).isAbsolute()) {
throw new IllegalArgumentException(
"Path must be relative to org/unicode/cldr/util/data such as 'file.txt' or 'casing/file.txt', but got '"
+ name + "'.");
}
return FileReaders.openFile(CldrUtility.class, "data/" + name);
}
/**
* Fetch data from jar
*
* @param name
* a name residing in the org/unicode/cldr/util/data/ directory, or loading from a jar will break.
*/
public static InputStream getInputStream(String name) {
if (new File(name).isAbsolute()) {
throw new IllegalArgumentException(
"Path must be relative to org/unicode/cldr/util/data such as 'file.txt' or 'casing/file.txt', but got '"
+ name + "'.");
}
return getInputStream(CldrUtility.class, "data/" + name);
}
@SuppressWarnings("resource")
public static InputStream getInputStream(Class<?> callingClass, String relativePath) {
InputStream is = callingClass.getResourceAsStream(relativePath);
// add buffering
return InputStreamFactory.buffer(is);
}
/**
* Takes a Map that goes from Object to Set, and fills in the transpose
*
* @param source_key_valueSet
* @param output_value_key
*/
public static void putAllTransposed(Map<Object, Set<Object>> source_key_valueSet, Map<Object, Object> output_value_key) {
for (Iterator<Object> it = source_key_valueSet.keySet().iterator(); it.hasNext();) {
Object key = it.next();
Set<Object> values = source_key_valueSet.get(key);
for (Iterator<Object> it2 = values.iterator(); it2.hasNext();) {
Object value = it2.next();
output_value_key.put(value, key);
}
}
}
public static int countInstances(String source, String substring) {
int count = 0;
int pos = 0;
while (true) {
pos = source.indexOf(substring, pos) + 1;
if (pos <= 0) break;
count++;
}
return count;
}
public static void registerTransliteratorFromFile(String id, String dir, String filename) {
registerTransliteratorFromFile(id, dir, filename, Transliterator.FORWARD, true);
registerTransliteratorFromFile(id, dir, filename, Transliterator.REVERSE, true);
}
public static void registerTransliteratorFromFile(String id, String dir, String filename, int direction,
boolean reverseID) {
if (filename == null) {
filename = id.replace('-', '_');
filename = filename.replace('/', '_');
filename += ".txt";
}
String rules = getText(dir, filename);
Transliterator t;
int pos = id.indexOf('-');
String rid;
if (pos < 0) {
rid = id + "-Any";
id = "Any-" + id;
} else {
rid = id.substring(pos + 1) + "-" + id.substring(0, pos);
}
if (!reverseID) rid = id;
if (direction == Transliterator.FORWARD) {
Transliterator.unregister(id);
t = Transliterator.createFromRules(id, rules, Transliterator.FORWARD);
Transliterator.registerInstance(t);
System.out.println("Registered new Transliterator: " + id);
}
/*
* String test = "\u049A\u0430\u0437\u0430\u049B";
* System.out.println(t.transliterate(test));
* t = Transliterator.getInstance(id);
* System.out.println(t.transliterate(test));
*/
if (direction == Transliterator.REVERSE) {
Transliterator.unregister(rid);
t = Transliterator.createFromRules(rid, rules, Transliterator.REVERSE);
Transliterator.registerInstance(t);
System.out.println("Registered new Transliterator: " + rid);
}
}
public static String getText(String dir, String filename) {
try {
BufferedReader br = FileUtilities.openUTF8Reader(dir, filename);
StringBuffer buffer = new StringBuffer();
while (true) {
String line = br.readLine();
if (line == null) break;
if (line.length() > 0 && line.charAt(0) == '\uFEFF') line = line.substring(1);
if (line.startsWith("//")) continue;
buffer.append(line).append(CldrUtility.LINE_SEPARATOR);
}
br.close();
String rules = buffer.toString();
return rules;
} catch (IOException e) {
throw (IllegalArgumentException) new IllegalArgumentException("Can't open " + dir + ", " + filename)
.initCause(e);
}
}
public static void callMethod(String methodNames, Class<?> cls) {
for (String methodName : methodNames.split(",")) {
try {
Method method;
try {
method = cls.getMethod(methodName, (Class[]) null);
try {
method.invoke(null, (Object[]) null);
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
System.out.println("No such method: " + methodName);
showMethods(cls);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
public static void showMethods(Class<?> cls) throws ClassNotFoundException {
System.out.println("Possible methods of " + cls.getCanonicalName() + " are: ");
Method[] methods = cls.getMethods();
Set<String> names = new TreeSet<String>();
for (int i = 0; i < methods.length; ++i) {
if (methods[i].getGenericParameterTypes().length != 0) continue;
//int mods = methods[i].getModifiers();
// if (!Modifier.isStatic(mods)) continue;
String name = methods[i].getName();
names.add(name);
}
for (Iterator<String> it = names.iterator(); it.hasNext();) {
System.out.println("\t" + it.next());
}
}
/**
* Breaks lines if they are too long, or if matcher.group(1) != last. Only breaks just before matcher.
*
* @param input
* @param separator
* @param matcher
* must match each possible item. The first group is significant; if different, will cause break
* @return
*/
static public String breakLines(CharSequence input, String separator, Matcher matcher, int width) {
StringBuffer output = new StringBuffer();
String lastPrefix = "";
int lastEnd = 0;
int lastBreakPos = 0;
matcher.reset(input);
while (true) {
boolean match = matcher.find();
if (!match) {
output.append(input.subSequence(lastEnd, input.length()));
break;
}
String prefix = matcher.group(1);
if (!prefix.equalsIgnoreCase(lastPrefix) || matcher.end() - lastBreakPos > width) { // break before?
output.append(separator);
lastBreakPos = lastEnd;
} else if (lastEnd != 0) {
output.append(' ');
}
output.append(input.subSequence(lastEnd, matcher.end()).toString().trim());
lastEnd = matcher.end();
lastPrefix = prefix;
}
return output.toString();
}
public static void showOptions(String[] args) {
// Properties props = System.getProperties();
System.out.println("Arguments: " + join(args, " ")); // + (props == null ? "" : " " + props));
}
public static double roundToDecimals(double input, int places) {
double log10 = Math.log10(input); // 15000 => 4.xxx
double intLog10 = Math.floor(log10);
double scale = Math.pow(10, intLog10 - places + 1);
double factored = Math.round(input / scale) * scale;
// System.out.println("###\t" +input + "\t" + factored);
return factored;
}
/**
* Get a property value, returning the value if there is one (eg -Dkey=value),
* otherwise the default value (for either empty or null).
*
* @param key
* @param valueIfNull
* @param valueIfEmpty
* @return
*/
public static String getProperty(String key, String defaultValue) {
return getProperty(key, defaultValue, defaultValue);
}
/**
* Get a property value, returning the value if there is one, otherwise null.
*/
public static String getProperty(String key) {
return getProperty(key, null, null);
}
/**
* Get a property value, returning the value if there is one (eg -Dkey=value),
* the valueIfEmpty if there is one with no value (eg -Dkey) and the valueIfNull
* if there is no property.
*
* @param key
* @param valueIfNull
* @param valueIfEmpty
* @return
*/
public static String getProperty(String key, String valueIfNull, String valueIfEmpty) {
String result = CLDRConfig.getInstance().getProperty(key);
if (result == null) {
result = valueIfNull;
} else if (result.length() == 0) {
result = valueIfEmpty;
}
return result;
}
public static String hex(byte[] bytes, int start, int end, String separator) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < end; ++i) {
if (result.length() != 0) {
result.append(separator);
}
result.append(Utility.hex(bytes[i] & 0xFF, 2));
}
return result.toString();
}
public static boolean getProperty(String string, boolean b) {
return getProperty(string, b ? "true" : "false", "true").matches("(?i)T|TRUE");
}
public static String checkValidDirectory(String sourceDirectory) {
return checkValidFile(sourceDirectory, true, null);
}
public static String checkValidDirectory(String sourceDirectory, String correction) {
return checkValidFile(sourceDirectory, true, correction);
}
public static String checkValidFile(String sourceDirectory, boolean checkForDirectory, String correction) {
File file = null;
String canonicalPath = null;
try {
file = new File(sourceDirectory);
canonicalPath = file.getCanonicalPath() + File.separatorChar;
} catch (Exception e) {
}
if (file == null || canonicalPath == null || checkForDirectory && !file.isDirectory()) {
throw new RuntimeException("Directory not found: " + sourceDirectory
+ (canonicalPath == null ? "" : " => " + canonicalPath)
+ (correction == null ? "" : CldrUtility.LINE_SEPARATOR + correction));
}
return canonicalPath;
}
/**
* Copy up to matching line (not included). If output is null, then just skip until.
*
* @param oldFile
* file to copy
* @param readUntilPattern
* pattern to search for. If null, goes to end of file.
* @param output
* into to copy into. If null, just skips in the input.
* @param includeMatchingLine
* inclde the matching line when copying.
* @throws IOException
*/
public static void copyUpTo(BufferedReader oldFile, final Pattern readUntilPattern,
final PrintWriter output, boolean includeMatchingLine) throws IOException {
Matcher readUntil = readUntilPattern == null ? null : readUntilPattern.matcher("");
while (true) {
String line = oldFile.readLine();
if (line == null) {
break;
}
if (line.startsWith("\uFEFF")) {
line = line.substring(1);
}
if (readUntil != null && readUntil.reset(line).matches()) {
if (includeMatchingLine && output != null) {
output.println(line);
}
break;
}
if (output != null) {
output.println(line);
}
}
}
private static DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss 'GMT'");
private static DateFormat DATE_ONLY = new SimpleDateFormat("yyyy-MM-dd");
static {
df.setTimeZone(TimeZone.getTimeZone("GMT"));
DATE_ONLY.setTimeZone(TimeZone.getTimeZone("GMT"));
}
public static String isoFormat(Date date) {
synchronized (df) {
return df.format(date);
}
}
public static String isoFormatDateOnly(Date date) {
synchronized (DATE_ONLY) {
return DATE_ONLY.format(date);
}
}
public static <K, V> ConcurrentHashMap<K, V> newConcurrentHashMap() {
// http://ria101.wordpress.com/2011/12/12/concurrenthashmap-avoid-a-common-misuse/
return new ConcurrentHashMap<K, V>(4, 0.9f, 1);
}
public static <K, V> ConcurrentHashMap<K, V> newConcurrentHashMap(Map<K, V> source) {
ConcurrentHashMap<K, V> result = newConcurrentHashMap();
result.putAll(source);
return result;
}
public static boolean equals(Object a, Object b) {
return a == b ? true
: a == null || b == null ? false
: a.equals(b);
}
public static String getDoubleLink(String code) {
final String anchorSafe = TransliteratorUtilities.toHTML.transliterate(code).replace(" ", "_");
return "<a name='" + anchorSafe + "' href='#" + anchorSafe + "'>";
}
public static String getDoubleLinkedText(String anchor, String anchorText) {
return getDoubleLink(anchor) + TransliteratorUtilities.toHTML.transliterate(anchorText).replace("_", " ")
+ "</a>";
}
public static String getDoubleLinkedText(String anchor) {
return getDoubleLinkedText(anchor, anchor);
}
public static String getDoubleLinkMsg() {
return "<a name=''{0}'' href=''#{0}''>{0}</a>";
}
public static String getDoubleLinkMsg2() {
return "<a name=''{0}{1}'' href=''#{0}{1}''>{0}</a>";
}
public static String getCopyrightString() {
// now do the rest
return "Copyright \u00A9 1991-" + Calendar.getInstance().get(Calendar.YEAR) + " Unicode, Inc." + CldrUtility.LINE_SEPARATOR
+ "For terms of use, see http://www.unicode.org/copyright.html" + CldrUtility.LINE_SEPARATOR
+ "Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries." + CldrUtility.LINE_SEPARATOR
+ "CLDR data files are interpreted according to the LDML specification " + "(http://unicode.org/reports/tr35/)";
}
// TODO Move to collection utilities
/**
* Type-safe get
* @param map
* @param key
* @return value
*/
public static <K, V, M extends Map<K, V>> V get(M map, K key) {
return map.get(key);
}
/**
* Type-safe contains
* @param map
* @param key
* @return value
*/
public static <K, C extends Collection<K>> boolean contains(C collection, K key) {
return collection.contains(key);
}
public static <E extends Enum<E>> EnumSet<E> toEnumSet(Class<E> classValue, Collection<String> stringValues) {
EnumSet<E> result = EnumSet.noneOf(classValue);
for (String s : stringValues) {
result.add(Enum.valueOf(classValue, s));
}
return result;
}
public static <K, V, M extends Map<K, V>> M putNew(M map, K key, V value) {
if (!map.containsKey(key)) {
map.put(key, value);
}
return map;
}
public static String[] cleanSemiFields(String line) {
line = cleanLine(line);
return line.isEmpty() ? null : SEMI_SPLIT.split(line);
}
private static String cleanLine(String line) {
int comment = line.indexOf("#");
if (comment >= 0) {
line = line.substring(0, comment);
}
if (line.startsWith("\uFEFF")) {
line = line.substring(1);
}
return line.trim();
}
public static void handleFile(String filename, LineHandler handler) throws IOException {
try (BufferedReader in = getUTF8Data(filename);) {
String line = null;
while ((line = in.readLine()) != null) {
// String line = in.readLine();
// if (line == null) {
// break;
// }
try {
if (!handler.handle(line)) {
if (HANDLEFILE_SHOW_SKIP) {
System.out.println("Skipping line: " + line);
}
}
} catch (Exception e) {
throw (RuntimeException) new IllegalArgumentException("Problem with line: " + line)
.initCause(e);
}
}
}
// in.close();
}
public static <T> T ifNull(T x, T y) {
return x == null
? y
: x;
}
public static <T> T ifSame(T source, T replaceIfSame, T replacement) {
return source == replaceIfSame ? replacement : source;
}
public static <T> T ifEqual(T source, T replaceIfSame, T replacement) {
return Objects.equals(source, replaceIfSame) ? replacement : source;
}
public static <T> Set<T> intersect(Set<T> a, Collection<T> b) {
Set<T> result = new LinkedHashSet<>(a);
result.retainAll(b);
return result;
}
public static <T> Set<T> subtract(Set<T> a, Collection<T> b) {
Set<T> result = new LinkedHashSet<>(a);
result.removeAll(b);
return result;
}
public static <T> void logRegexLookup(TestFmwk testFramework, RegexLookup<T> lookup, String toLookup) {
Output<String[]> arguments = new Output<>();
Output<Finder> matcherFound = new Output<>();
List<String> failures = new ArrayList<String>();
lookup.get(toLookup, null, arguments, matcherFound, failures);
testFramework.logln("lookup arguments: " + (arguments.value == null ? "null" : Arrays.asList(arguments.value)));
testFramework.logln("lookup matcherFound: " + matcherFound);
for (String s : failures) {
testFramework.logln(s);
}
}
public static boolean deepEquals(Object... pairs) {
for (int item = 0; item < pairs.length;) {
if (!Objects.deepEquals(pairs[item++], pairs[item++])) {
return false;
}
}
return true;
}
public static String[] array(Splitter splitter, String source) {
List<String> list = splitter.splitToList(source);
return list.toArray(new String[list.size()]);
}
public static String toHex(String in, boolean javaStyle) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < in.length(); ++i) {
result.append(toHex(in.charAt(i), javaStyle));
}
return result.toString();
}
public static String toHex(int j, boolean javaStyle) {
if (j == '\"') {
return "\\\"";
} else if (j == '\\') {
return "\\\\";
} else if (0x20 < j && j < 0x7F) {
return String.valueOf((char) j);
}
final String hexString = Integer.toHexString(j).toUpperCase();
int gap = 4 - hexString.length();
if (gap < 0) {
gap = 0;
}
String prefix = javaStyle ? "\\u" : "U+";
return prefix + "000".substring(0, gap) + hexString;
}
/**
* get string format for debugging, since Java has a useless display for many items
* @param item
* @return
*/
public static String toString(Object item) {
if (item instanceof Object[]) {
return toString(Arrays.asList((Object[]) item));
} else if (item instanceof Entry) {
return toString(((Entry) item).getKey()) + "≔" + toString(((Entry) item).getValue());
} else if (item instanceof Map) {
return "{" + toString(((Map) item).entrySet()) + "}";
} else if (item instanceof Collection) {
List<String> result = new ArrayList<>();
for (Object subitem : (Collection)item) {
result.add(toString(subitem));
}
return result.toString();
}
return item.toString();
}
}