ICU-4700 more cldr tools
X-SVN-Rev: 18830
This commit is contained in:
parent
c932702a9c
commit
9a15ff31a2
@ -9,6 +9,7 @@
|
||||
package com.ibm.icu.dev.test.format;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
@ -35,7 +36,8 @@ public class GlobalizationPreferencesTest {
|
||||
private static final String[] WidthNames = {"abbreviated", "wide", "narrow"};
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
PrintWriter out = BagFormatter.openUTF8Writer("c:/", "tempFile.txt");
|
||||
PrintStream out = System.out;
|
||||
//PrintWriter out = BagFormatter.openUTF8Writer("c:/", "tempFile.txt");
|
||||
try {
|
||||
Date now = new Date();
|
||||
|
||||
@ -49,13 +51,13 @@ public class GlobalizationPreferencesTest {
|
||||
out.println("Check defaulting");
|
||||
String[] localeList = {"fr_BE;q=0.5,de", "fr_BE,de", "fr", "en_NZ", "en", "en-TH", "zh-Hant", "zh-MO", "zh", "it", "as", "haw", "ar-EG", "ar", "qqq"};
|
||||
for (int i = 0; i < localeList.length; ++i) {
|
||||
lPreferences.setULocales(localeList[i]);
|
||||
lPreferences.setLocales(localeList[i]);
|
||||
out.println("\tdefaults for: \t" + localeList[i] + "\t"
|
||||
+ lPreferences.getULocales()
|
||||
+ lPreferences.getLocales()
|
||||
+ ", \t" + lPreferences.getTerritory()
|
||||
+ ", \t" + lPreferences.getCurrency()
|
||||
+ ", \t" + lPreferences.getCalendar().getClass()
|
||||
+ ", \t" + lPreferences.getTimezone().getID()
|
||||
+ ", \t" + lPreferences.getTimeZone().getID()
|
||||
);
|
||||
}
|
||||
|
||||
@ -66,13 +68,17 @@ public class GlobalizationPreferencesTest {
|
||||
out.println("\tdate: \t" + lPreferences.getDateFormat(DateFormat.FULL, GlobalizationPreferences.NONE).format(now));
|
||||
|
||||
out.println("setting locale to Germany");
|
||||
lPreferences.setULocales(ULocale.GERMANY);
|
||||
lPreferences.setLocale(ULocale.GERMANY);
|
||||
out.println("\tdate: \t" + lPreferences.getDateFormat(DateFormat.FULL, GlobalizationPreferences.NONE).format(now));
|
||||
|
||||
out.println("setting date locale to France");
|
||||
lPreferences.setDateLocale(ULocale.FRANCE);
|
||||
out.println("\tdate: \t" + lPreferences.getDateFormat(DateFormat.FULL, GlobalizationPreferences.NONE).format(now));
|
||||
|
||||
out.println("setting explicit pattern");
|
||||
lPreferences.setDateFormat(DateFormat.FULL, GlobalizationPreferences.NONE, "GGG yyyy+MMM+DD vvvv");
|
||||
out.println("\tdate: \t" + lPreferences.getDateFormat(DateFormat.FULL, GlobalizationPreferences.NONE).format(now));
|
||||
|
||||
out.println("setting date format to yyyy-MMM-dd (Italy)");
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MMM-dd",ULocale.ITALY);
|
||||
lPreferences.setDateFormat(DateFormat.FULL, GlobalizationPreferences.NONE, sdf);
|
||||
@ -111,11 +117,21 @@ public class GlobalizationPreferencesTest {
|
||||
lPreferences.setNumberLocale(new ULocale("hi-IN"));
|
||||
out.println("\tcurrency: \t" + lPreferences.getNumberFormat(GlobalizationPreferences.CURRENCY).format(1234.567));
|
||||
|
||||
out.println();
|
||||
out.println("Comparison");
|
||||
out.println("setting number locale to Germany");
|
||||
lPreferences.setLocale(ULocale.GERMANY);
|
||||
out.println("\tcompare: \u00e4 & z \t" + lPreferences.getCollator().compare("\u00e4", "z"));
|
||||
|
||||
out.println("setting number locale to Swedish");
|
||||
lPreferences.setLocale(new ULocale("sv"));
|
||||
out.println("\tcompare: \u00e4 & z \t" + lPreferences.getCollator().compare("\u00e4", "z"));
|
||||
|
||||
// now try a fallback within locales
|
||||
out.println();
|
||||
out.println("Display Names");
|
||||
lPreferences.setULocales(new ULocale[]{new ULocale("as"),new ULocale("pl"),new ULocale("fr")});
|
||||
out.println("Trying fallback for multiple locales: " + lPreferences.getULocales());
|
||||
lPreferences.setLocales(new ULocale[]{new ULocale("as"),new ULocale("pl"),new ULocale("fr")});
|
||||
out.println("Trying fallback for multiple locales: " + lPreferences.getLocales());
|
||||
String[][] testItems = {
|
||||
{GlobalizationPreferences.LOCALEID+"", "as_FR", "en_RU","haw_CA","se_Cyrl_AT"},
|
||||
{GlobalizationPreferences.LANGUAGEID+"", "as", "en","haw","se","kok"},
|
||||
|
@ -42,75 +42,6 @@ public class BagFormatter {
|
||||
SHOW_FILES = showFiles;
|
||||
}
|
||||
|
||||
private static final String BASE_RULES =
|
||||
"'<' > '<' ;" +
|
||||
"'<' < '&'[lL][Tt]';' ;" +
|
||||
"'&' > '&' ;" +
|
||||
"'&' < '&'[aA][mM][pP]';' ;" +
|
||||
"'>' < '&'[gG][tT]';' ;" +
|
||||
"'\"' < '&'[qQ][uU][oO][tT]';' ; " +
|
||||
"'' < '&'[aA][pP][oO][sS]';' ; ";
|
||||
|
||||
private static final String CONTENT_RULES =
|
||||
"'>' > '>' ;";
|
||||
|
||||
private static final String HTML_RULES = BASE_RULES + CONTENT_RULES +
|
||||
"'\"' > '"' ; ";
|
||||
|
||||
private static final String HTML_RULES_CONTROLS = HTML_RULES +
|
||||
"([[:C:][:Z:][:whitespace:][:Default_Ignorable_Code_Point:]]) > &hex/unicode($1) ; ";
|
||||
|
||||
private static final String XML_RULES = HTML_RULES +
|
||||
"'' > ''' ; ";
|
||||
|
||||
/*
|
||||
The ampersand character (&) and the left angle bracket (<) MUST NOT appear
|
||||
|
||||
in their literal form, except when used as markup delimiters, or within a
|
||||
|
||||
comment, a processing instruction, or a CDATA section. If they are needed
|
||||
|
||||
elsewhere, they MUST be escaped using either numeric character references or
|
||||
|
||||
the strings "&" and "<" respectively. The right angle bracket (>) MAY
|
||||
|
||||
be represented using the string ">", and MUST, for compatibility, be
|
||||
|
||||
escaped using either ">" or a character reference when it appears in the string
|
||||
|
||||
"]]>" in content, when that string is not marking the end of a CDATA section.
|
||||
|
||||
In the content of elements, character data is any string of characters which does
|
||||
|
||||
not contain the start-delimiter of any markup and does not include the
|
||||
|
||||
CDATA-section-close delimiter, "]]>". In a CDATA section, character data is
|
||||
|
||||
any string of characters not including the CDATA-section-close delimiter,
|
||||
|
||||
"]]>".
|
||||
|
||||
To allow attribute values to contain both single and double quotes, the
|
||||
|
||||
apostrophe or single-quote character (') MAY be represented as "'", and
|
||||
|
||||
the double-quote character (") as """.
|
||||
|
||||
|
||||
*/
|
||||
|
||||
public static final Transliterator toXML = Transliterator.createFromRules(
|
||||
"any-xml", XML_RULES, Transliterator.FORWARD);
|
||||
public static final Transliterator fromXML = Transliterator.createFromRules(
|
||||
"xml-any", XML_RULES, Transliterator.REVERSE);
|
||||
|
||||
public static final Transliterator toHTML = Transliterator.createFromRules(
|
||||
"any-html", HTML_RULES, Transliterator.FORWARD);
|
||||
public static final Transliterator toHTMLControl = Transliterator.createFromRules(
|
||||
"any-html", HTML_RULES_CONTROLS, Transliterator.FORWARD);
|
||||
public static final Transliterator fromHTML = Transliterator.createFromRules(
|
||||
"html-any", HTML_RULES, Transliterator.REVERSE);
|
||||
|
||||
public static final PrintWriter CONSOLE = new PrintWriter(System.out,true);
|
||||
|
||||
private static PrintWriter log = CONSOLE;
|
||||
|
@ -61,4 +61,80 @@ public class TransliteratorUtilities {
|
||||
br.close();
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private static final String BASE_RULES =
|
||||
":: (hex-any/xml);" +
|
||||
":: (hex-any/xml10);" +
|
||||
"'<' > '<' ;" +
|
||||
"'<' < '&'[lL][Tt]';' ;" +
|
||||
"'&' > '&' ;" +
|
||||
"'&' < '&'[aA][mM][pP]';' ;" +
|
||||
"'>' < '&'[gG][tT]';' ;" +
|
||||
"'\"' < '&'[qQ][uU][oO][tT]';' ; " +
|
||||
"'' < '&'[aA][pP][oO][sS]';' ; ";
|
||||
|
||||
private static final String CONTENT_RULES =
|
||||
"'>' > '>' ;";
|
||||
|
||||
private static final String HTML_RULES = BASE_RULES + CONTENT_RULES +
|
||||
"'\"' > '"' ; ";
|
||||
|
||||
private static final String HTML_RULES_CONTROLS = HTML_RULES +
|
||||
":: [[:C:][:Z:][:whitespace:][:Default_Ignorable_Code_Point:]] hex/unicode ; ";
|
||||
|
||||
private static final String HTML_RULES_ASCII = HTML_RULES +
|
||||
":: [[:C:][:^ASCII:]] any-hex/xml ; ";
|
||||
|
||||
private static final String XML_RULES = HTML_RULES +
|
||||
"'' > ''' ; "
|
||||
;
|
||||
|
||||
/*
|
||||
The ampersand character (&) and the left angle bracket (<) MUST NOT appear
|
||||
|
||||
in their literal form, except when used as markup delimiters, or within a
|
||||
|
||||
comment, a processing instruction, or a CDATA section. If they are needed
|
||||
|
||||
elsewhere, they MUST be escaped using either numeric character references or
|
||||
|
||||
the strings "&" and "<" respectively. The right angle bracket (>) MAY
|
||||
|
||||
be represented using the string ">", and MUST, for compatibility, be
|
||||
|
||||
escaped using either ">" or a character reference when it appears in the string
|
||||
|
||||
"]]>" in content, when that string is not marking the end of a CDATA section.
|
||||
|
||||
In the content of elements, character data is any string of characters which does
|
||||
|
||||
not contain the start-delimiter of any markup and does not include the
|
||||
|
||||
CDATA-section-close delimiter, "]]>". In a CDATA section, character data is
|
||||
|
||||
any string of characters not including the CDATA-section-close delimiter,
|
||||
|
||||
"]]>".
|
||||
|
||||
To allow attribute values to contain both single and double quotes, the
|
||||
|
||||
apostrophe or single-quote character (') MAY be represented as "'", and
|
||||
|
||||
the double-quote character (") as """.
|
||||
|
||||
|
||||
*/
|
||||
|
||||
public static final Transliterator toXML = Transliterator.createFromRules(
|
||||
"any-xml", XML_RULES, Transliterator.FORWARD);
|
||||
public static final Transliterator fromXML = Transliterator.createFromRules(
|
||||
"xml-any", XML_RULES, Transliterator.REVERSE);
|
||||
public static final Transliterator toHTML = Transliterator.createFromRules(
|
||||
"any-html", HTML_RULES, Transliterator.FORWARD);
|
||||
public static final Transliterator toHTMLControl = Transliterator.createFromRules(
|
||||
"any-html", HTML_RULES_CONTROLS, Transliterator.FORWARD);
|
||||
public static final Transliterator toHTMLAscii = Transliterator.createFromRules(
|
||||
"any-html", HTML_RULES_ASCII, Transliterator.FORWARD);
|
||||
public static final Transliterator fromHTML = Transliterator.createFromRules(
|
||||
"html-any", HTML_RULES, Transliterator.REVERSE);
|
||||
}
|
@ -20,6 +20,8 @@ import java.util.TreeMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.ibm.icu.impl.Utility;
|
||||
import com.ibm.icu.impl.CollectionUtilities.InverseMatcher;
|
||||
import com.ibm.icu.impl.CollectionUtilities.ObjectMatcher;
|
||||
import com.ibm.icu.text.SymbolTable;
|
||||
import com.ibm.icu.text.UTF16;
|
||||
import com.ibm.icu.text.UnicodeMatcher;
|
||||
@ -215,7 +217,7 @@ Name: Unicode_1_Name
|
||||
public final UnicodeSet getSet(String propertyValue) {
|
||||
return getSet(propertyValue,null);
|
||||
}
|
||||
public final UnicodeSet getSet(Matcher matcher) {
|
||||
public final UnicodeSet getSet(PatternMatcher matcher) {
|
||||
return getSet(matcher,null);
|
||||
}
|
||||
|
||||
@ -229,7 +231,7 @@ Name: Unicode_1_Name
|
||||
|
||||
public static final String UNUSED = "??";
|
||||
|
||||
public final UnicodeSet getSet(Matcher matcher, UnicodeSet result) {
|
||||
public final UnicodeSet getSet(PatternMatcher matcher, UnicodeSet result) {
|
||||
if (result == null) result = new UnicodeSet();
|
||||
if (isType(STRING_OR_MISC_MASK)) {
|
||||
for (int i = 0; i <= 0x10FFFF; ++i) {
|
||||
@ -605,12 +607,12 @@ Name: Unicode_1_Name
|
||||
}
|
||||
return result;
|
||||
}
|
||||
InverseMatcher inverseMatcher = new InverseMatcher();
|
||||
InversePatternMatcher inverseMatcher = new InversePatternMatcher();
|
||||
/**
|
||||
* Format is:
|
||||
* propname ('=' | '!=') propvalue ( '|' propValue )*
|
||||
*/
|
||||
public final UnicodeSet getSet(String propAndValue, Matcher matcher, UnicodeSet result) {
|
||||
public final UnicodeSet getSet(String propAndValue, PatternMatcher matcher, UnicodeSet result) {
|
||||
int equalPos = propAndValue.indexOf('=');
|
||||
String prop = propAndValue.substring(0,equalPos);
|
||||
String value = propAndValue.substring(equalPos+1);
|
||||
@ -632,7 +634,7 @@ Name: Unicode_1_Name
|
||||
return up.getSet(matcher.set(value), result);
|
||||
}
|
||||
|
||||
public final UnicodeSet getSet(String propAndValue, Matcher matcher) {
|
||||
public final UnicodeSet getSet(String propAndValue, PatternMatcher matcher) {
|
||||
return getSet(propAndValue, matcher, null);
|
||||
}
|
||||
public final UnicodeSet getSet(String propAndValue) {
|
||||
@ -855,57 +857,51 @@ Name: Unicode_1_Name
|
||||
}
|
||||
}
|
||||
|
||||
public interface Matcher {
|
||||
/**
|
||||
* Must be able to handle null
|
||||
* @param value
|
||||
* @return true if the value matches
|
||||
*/
|
||||
public boolean matches(String value);
|
||||
public Matcher set(String pattern);
|
||||
public interface PatternMatcher extends ObjectMatcher {
|
||||
public PatternMatcher set(String pattern);
|
||||
}
|
||||
|
||||
public static class InverseMatcher implements Matcher {
|
||||
Matcher other;
|
||||
public Matcher set(Matcher toInverse) {
|
||||
public static class InversePatternMatcher extends InverseMatcher implements PatternMatcher {
|
||||
PatternMatcher other;
|
||||
public PatternMatcher set(PatternMatcher toInverse) {
|
||||
other = toInverse;
|
||||
return this;
|
||||
}
|
||||
public boolean matches(String value) {
|
||||
public boolean matches(Object value) {
|
||||
return !other.matches(value);
|
||||
}
|
||||
public Matcher set(String pattern) {
|
||||
public PatternMatcher set(String pattern) {
|
||||
other.set(pattern);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SimpleMatcher implements Matcher {
|
||||
public static class SimpleMatcher implements PatternMatcher {
|
||||
Comparator comparator;
|
||||
String pattern;
|
||||
public SimpleMatcher(String pattern, Comparator comparator) {
|
||||
this.comparator = comparator;
|
||||
this.pattern = pattern;
|
||||
}
|
||||
public boolean matches(String value) {
|
||||
public boolean matches(Object value) {
|
||||
if (comparator == null) return pattern.equals(value);
|
||||
return comparator.compare(pattern, value) == 0;
|
||||
}
|
||||
public Matcher set(String pattern) {
|
||||
public PatternMatcher set(String pattern) {
|
||||
this.pattern = pattern;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RegexMatcher implements UnicodeProperty.Matcher {
|
||||
public static class RegexMatcher implements UnicodeProperty.PatternMatcher {
|
||||
private java.util.regex.Matcher matcher;
|
||||
|
||||
public UnicodeProperty.Matcher set(String pattern) {
|
||||
public UnicodeProperty.PatternMatcher set(String pattern) {
|
||||
matcher = Pattern.compile(pattern).matcher("");
|
||||
return this;
|
||||
}
|
||||
public boolean matches(String value) {
|
||||
matcher.reset(value);
|
||||
public boolean matches(Object value) {
|
||||
matcher.reset(value.toString());
|
||||
return matcher.matches();
|
||||
}
|
||||
}
|
||||
|
@ -13,9 +13,11 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
|
||||
//#ifndef FOUNDATION
|
||||
import java.util.regex.Matcher;
|
||||
//#endif
|
||||
|
||||
import com.ibm.icu.text.Transliterator;
|
||||
import com.ibm.icu.text.UTF16;
|
||||
import com.ibm.icu.text.UnicodeSet;
|
||||
@ -25,6 +27,27 @@ import com.ibm.icu.text.UnicodeSetIterator;
|
||||
* Utilities that ought to be on collections, but aren't
|
||||
*/
|
||||
public final class CollectionUtilities {
|
||||
|
||||
public static String join(Object[] array, String separator) {
|
||||
StringBuffer result = new StringBuffer();
|
||||
for (int i = 0; i < array.length; ++i) {
|
||||
if (i != 0) result.append(separator);
|
||||
result.append(array[i]);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String join(Collection collection, String separator) {
|
||||
StringBuffer result = new StringBuffer();
|
||||
boolean first = true;
|
||||
for (Iterator it = collection.iterator(); it.hasNext();) {
|
||||
if (first) first = false;
|
||||
else result.append(separator);
|
||||
result.append(it.next());
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility like Arrays.asList()
|
||||
*/
|
||||
@ -90,11 +113,25 @@ public final class CollectionUtilities {
|
||||
return bestSoFar;
|
||||
}
|
||||
|
||||
public interface Filter {
|
||||
public interface ObjectMatcher {
|
||||
/**
|
||||
* Must handle null, never throw exception
|
||||
*/
|
||||
boolean matches(Object o);
|
||||
}
|
||||
|
||||
public static class InverseMatcher implements ObjectMatcher {
|
||||
ObjectMatcher other;
|
||||
public ObjectMatcher set(ObjectMatcher toInverse) {
|
||||
other = toInverse;
|
||||
return this;
|
||||
}
|
||||
public boolean matches(Object value) {
|
||||
return !other.matches(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static Collection removeAll(Collection c, Filter f) {
|
||||
public static Collection removeAll(Collection c, ObjectMatcher f) {
|
||||
for (Iterator it = c.iterator(); it.hasNext();) {
|
||||
Object item = it.next();
|
||||
if (f.matches(item)) it.remove();
|
||||
@ -102,7 +139,7 @@ public final class CollectionUtilities {
|
||||
return c;
|
||||
}
|
||||
|
||||
public static Collection retainAll(Collection c, Filter f) {
|
||||
public static Collection retainAll(Collection c, ObjectMatcher f) {
|
||||
for (Iterator it = c.iterator(); it.hasNext();) {
|
||||
Object item = it.next();
|
||||
if (!f.matches(item)) it.remove();
|
||||
|
@ -112,6 +112,7 @@ public class PrettyPrinter {
|
||||
* @return formatted UnicodeSet
|
||||
*/
|
||||
public String toPattern(UnicodeSet uset) {
|
||||
first = true;
|
||||
// make sure that comparison separates all strings, even canonically equivalent ones
|
||||
Set orderedStrings = new TreeSet(ordering);
|
||||
for (UnicodeSetIterator it = new UnicodeSetIterator(uset); it.next();) {
|
||||
|
@ -3447,7 +3447,7 @@ public class UnicodeSet extends UnicodeFilter {
|
||||
* @deprecated
|
||||
* @author medavis
|
||||
*/
|
||||
abstract static class XSymbolTable implements SymbolTable {
|
||||
abstract public static class XSymbolTable implements SymbolTable {
|
||||
public UnicodeMatcher lookupMatcher(int i) {
|
||||
return null;
|
||||
}
|
||||
|
@ -7,15 +7,304 @@
|
||||
package com.ibm.icu.util;
|
||||
|
||||
/**
|
||||
* Provides a flexible mechanism for controlling access, without requiring that a class be immutable.
|
||||
* Once locked, an object can never be unlocked, so it is thread-safe from that point onward.
|
||||
* The implementation of both methods must be synchronized.
|
||||
* Once the object has been locked, it must guarantee that no changes can be made to it.
|
||||
* Any attempt to alter it must raise an UnsupportedOperationException exception.
|
||||
* This means that when the object returns internal objects,
|
||||
* or if anyone has references to those internal objects, that those internal objects must either be immutable,
|
||||
* or must also raise exceptions if any attempt to modify them is made. Of course, the object can return clones
|
||||
* of internal objects, since those are safe. * @author davis
|
||||
* <pre>
|
||||
* DRAFT
|
||||
* Copyright (C) 2005, International Business Machines Corporation and
|
||||
* others. All Rights Reserved.
|
||||
* </pre>
|
||||
*
|
||||
* Provides a flexible mechanism for controlling access, without requiring that
|
||||
* a class be immutable. Once locked, an object can never be unlocked, so it is
|
||||
* thread-safe from that point onward. The implementation of both methods must
|
||||
* be synchronized. Once the object has been locked, it must guarantee that no
|
||||
* changes can be made to it. Any attempt to alter it must raise an
|
||||
* UnsupportedOperationException exception. This means that when the object
|
||||
* returns internal objects, or if anyone has references to those internal
|
||||
* objects, that those internal objects must either be immutable, or must also
|
||||
* raise exceptions if any attempt to modify them is made. Of course, the object
|
||||
* can return clones of internal objects, since those are safe.
|
||||
* <h2>Background</h2>
|
||||
* <p>
|
||||
* There are often times when you need objects to be objects 'safe', so that
|
||||
* they can't be modified. Examples are when objects need to be thread-safe, or
|
||||
* in writing robust code, or in caches. If you are only creating your own
|
||||
* objects, you can guarantee this, of course -- but only if you don't make a
|
||||
* mistake. If you have objects handed into you, or are creating objects using
|
||||
* others handed into you, it is a different story. It all comes down to whether
|
||||
* you want to take the Blanche Dubois approach ("depend on the kindness of
|
||||
* strangers") or the Andy Grove approach ("Only the Paranoid
|
||||
* Survive").
|
||||
* </p>
|
||||
* <p>
|
||||
* For example, suppose we have a simple class:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public class A {
|
||||
* protected Collection b;
|
||||
*
|
||||
* protected Collection c;
|
||||
*
|
||||
* public Collection get_b() {
|
||||
* return b;
|
||||
* }
|
||||
*
|
||||
* public Collection get_c() {
|
||||
* return c;
|
||||
* }
|
||||
*
|
||||
* public A(Collection new_b, Collection new_c) {
|
||||
* b = new_b;
|
||||
* c = new_c;
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Since the class doesn't have any setters, someone might think that it is
|
||||
* immutable. You know where this is leading, of course; this class is unsafe in
|
||||
* a number of ways. The following illustrates that.
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public test1(SupposedlyImmutableClass x, SafeStorage y) {
|
||||
* <font color="#0000FF"> <b>// unsafe getter</b>
|
||||
* </font> A a = x.getA();
|
||||
* Collection col = a.get_b();
|
||||
* col.add(something);<font color="#0000FF"> // a has now been changed, and x too
|
||||
* </font>
|
||||
* <font color="#0000FF"><b>// unsafe constructor</b></font>
|
||||
* a = new A(col, col);
|
||||
* y.store(a);
|
||||
* col.add(something);<font color="#0000FF"> // a has now been changed, and y too
|
||||
*
|
||||
* </font>}
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* There are a few different techniques for having safe classes.
|
||||
* </p>
|
||||
* <ol>
|
||||
* <li>Const objects. In C++, you can declare parameters const.</li>
|
||||
* <li>Immutable wrappers. For example, you can put a collection in an
|
||||
* immutable wrapper.</li>
|
||||
* <li>Always-Immutable objects. Java uses this approach, with a few
|
||||
* variations. Examples:
|
||||
* <ol>
|
||||
* <li>Simple. Once a Color is created (eg from R, G, and B integers) it is
|
||||
* immutable.</li>
|
||||
* <li>Builder Class. There is a separate 'builder' class. For example,
|
||||
* modifiable Strings are created using StringBuffer (which doesn't have the
|
||||
* full String API available). Once you want an immutable form, you create one
|
||||
* with toString().</li>
|
||||
* <li>Primitives. These are always safe, since they are copied on input/output
|
||||
* from methods.</li>
|
||||
* </ol>
|
||||
* </li>
|
||||
* <li>Cloning. Where you need an object to be safe, you clone it.</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* There are advantages and disadvantages of each of these.
|
||||
* </p>
|
||||
* <ol>
|
||||
* <li>Const provides a certain level of protection, but since const can be and
|
||||
* is often cast away, it only protects against most inadvertent mistakes. It
|
||||
* also offers no threading protection, since anyone who has a pointer to the
|
||||
* (unconst) object in another thread can mess you up.</li>
|
||||
* <li>Immutable wrappers are safer than const in that the constness can't be
|
||||
* cast away. But other than that they have all the same problems: not safe if
|
||||
* someone else keeps hold of the original object, or if any of the objects
|
||||
* returned by the class are mutable.</li>
|
||||
* <li>Always-Immutable Objects are safe, but usage can require excessive
|
||||
* object creation.</li>
|
||||
* <li>Cloning is only safe if the object truly has a 'safe' clone; defined as
|
||||
* one that <i>ensures that no change to the clone affects the original</i>.
|
||||
* Unfortunately, many objects don't have a 'safe' clone, and always cloning can
|
||||
* require excessive object creation.</li>
|
||||
* </ol>
|
||||
* <h2>Freezable Model</h2>
|
||||
* <p>
|
||||
* The <code>Freezable</code> model supplements these choices by giving you
|
||||
* the ability to build up an object by calling various methods, then when it is
|
||||
* in a final state, you can <i>make</i> it immutable. Once immutable, an
|
||||
* object cannot <i>ever </i>be modified, and is completely thread-safe: that
|
||||
* is, multiple threads can have references to it without any synchronization.
|
||||
* If someone needs a mutable version of an object, they can use
|
||||
* <code>cloneAsThawed()</code>, and modify the copy. This provides a simple,
|
||||
* effective mechanism for safe classes in circumstances where the alternatives
|
||||
* are insufficient or clumsy. (If an object is shared before it is immutable,
|
||||
* then it is the responsibility of each thread to mutex its usage (as with
|
||||
* other objects).)
|
||||
* </p>
|
||||
* <p>
|
||||
* Here is what needs to be done to implement this interface, depending on the
|
||||
* type of the object.
|
||||
* </p>
|
||||
* <h3><b>Immutable Objects</b></h3>
|
||||
* <p>
|
||||
* These are the easiest. You just use the interface to reflect that, by adding
|
||||
* the following:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public class A implements Freezable {
|
||||
* ...
|
||||
* public final boolean isFrozen() {return true;}
|
||||
* public final Object freeze() {return this;}
|
||||
* public final Object cloneAsThawed() { return this; }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* These can be final methods because subclasses of immutable objects must
|
||||
* themselves be immutable. (Note: <code>freeze</code> is returning
|
||||
* <code>this</code> for chaining.)
|
||||
* </p>
|
||||
* <h3><b>Mutable Objects</b></h3>
|
||||
* <p>
|
||||
* Add a protected 'flagging' field:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* protected boolean immutable;
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Add the following methods:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public final boolean isFrozen() {
|
||||
* return frozen;
|
||||
* };
|
||||
*
|
||||
* public Object freeze() {
|
||||
* frozen = true;
|
||||
* return this;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Add a <code>cloneAsThawed()</code> method following the normal pattern for
|
||||
* <code>clone()</code>, except that <code>frozen=false</code> in the new
|
||||
* clone.
|
||||
* </p>
|
||||
* <p>
|
||||
* Then take the setters (that is, any method that can change the internal state
|
||||
* of the object), and add the following as the first statement:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* if (isFrozen()) {
|
||||
* throw new UnsupportedOperationException("Attempt to modify frozen object");
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <h4><b>Subclassing</b></h4>
|
||||
* <p>
|
||||
* Any subclass of a <code>Freezable</code> will just use its superclass's
|
||||
* flagging field. It must override <code>freeze()</code> and
|
||||
* <code>cloneAsThawed()</code> to call the superclass, but normally does not
|
||||
* override <code>isFrozen()</code>. It must then just pay attention to its
|
||||
* own getters, setters and fields.
|
||||
* </p>
|
||||
* <h4><b>Internal Caches</b></h4>
|
||||
* <p>
|
||||
* Internal caches are cases where the object is logically unmodified, but
|
||||
* internal state of the object changes. For example, there are const C++
|
||||
* functions that cast away the const on the "this" pointer in order
|
||||
* to modify an object cache. These cases are handled by mutexing the internal
|
||||
* cache to ensure thread-safety. For example, suppose that UnicodeSet had an
|
||||
* internal marker to the last code point accessed. In this case, the field is
|
||||
* not externally visible, so the only thing you need to do is to synchronize
|
||||
* the field for thread safety.
|
||||
* </p>
|
||||
* <h4>Unsafe Internal Access</h4>
|
||||
* <p>
|
||||
* Internal fields are called <i>safe</i> if they are either
|
||||
* <code>frozen</code> or immutable (such as String or primitives). If you've
|
||||
* never allowed internal access to these, then you are all done. For example,
|
||||
* converting UnicodeSet to be <code>Freezable</code> is just accomplished
|
||||
* with the above steps. But remember that you <i><b>have</b></i> allowed
|
||||
* access to unsafe internals if you have any code like the following, in a
|
||||
* getter, setter, or constructor:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* Collection getStuff() {
|
||||
* return stuff;
|
||||
* } // caller could keep reference & modify
|
||||
*
|
||||
* void setStuff(Collection x) {
|
||||
* stuff = x;
|
||||
* } // caller could keep reference & modify
|
||||
*
|
||||
* MyClass(Collection x) {
|
||||
* stuff = x;
|
||||
* } // caller could keep reference & modify
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* These also illustrated in the code sample in <b>Background</b> above.
|
||||
* </p>
|
||||
* <p>
|
||||
* To deal with unsafe internals, the simplest course of action is to do the
|
||||
* work in the <code>
|
||||
freeze()</code> function. Just make all of your internal
|
||||
* fields frozen, and set the frozen flag. Any subsequent getter/setter will
|
||||
* work properly. Here is an example:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* public Object freeze() {
|
||||
* if (!frozen) {
|
||||
* foo.freeze();
|
||||
* frozen = true;
|
||||
* }
|
||||
* return this;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* If the field is a <code>Collection</code> or <code>Map</code>, then to
|
||||
* make it frozen you have two choices. If you have never allowed access to the
|
||||
* collection from outside your object, then just wrap it to prevent future
|
||||
* modification.
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* zone_to_country = Collections.unmodifiableMap(zone_to_country);
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* If you have <i>ever</i> allowed access, then do a <code>clone()</code>
|
||||
* before wrapping it.
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* zone_to_country = Collections.unmodifiableMap(zone_to_country.clone());
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* If a collection <i>(or any other container of objects)</i> itself can
|
||||
* contain mutable objects, then for a safe clone you need to recurse through it
|
||||
* to make the entire collection immutable. The recursing code should pick the
|
||||
* most specific collection available, to avoid the necessity of later
|
||||
* downcasing.
|
||||
* </p>
|
||||
* <blockquote>
|
||||
* <p>
|
||||
* <b>Note: </b>An annoying flaw in Java is that the generic collections, like
|
||||
* <code>Map</code> or <code>Set</code>, don't have a <code>clone()</code>
|
||||
* operation. When you don't know the type of the collection, the simplest
|
||||
* course is to just create a new collection:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* zone_to_country = Collections.unmodifiableMap(new HashMap(zone_to_country));
|
||||
* </pre>
|
||||
*
|
||||
* </blockquote>
|
||||
*/
|
||||
public interface Freezable extends Cloneable {
|
||||
/**
|
||||
|
@ -23,11 +23,23 @@ import java.util.regex.Pattern;
|
||||
|
||||
import com.ibm.icu.impl.Utility;
|
||||
import com.ibm.icu.impl.ZoneMeta;
|
||||
import com.ibm.icu.text.Collator;
|
||||
import com.ibm.icu.text.DateFormat;
|
||||
import com.ibm.icu.text.DecimalFormat;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.NumberFormat;
|
||||
import com.ibm.icu.text.SimpleDateFormat;
|
||||
|
||||
/**
|
||||
* Copyright (C) 2004-2005, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved.
|
||||
*/
|
||||
/**
|
||||
* <pre>
|
||||
* DRAFT
|
||||
* Copyright (C) 2005, International Business Machines Corporation and
|
||||
* others. All Rights Reserved.
|
||||
* </pre>
|
||||
*
|
||||
* This convenience class provides a mechanism for bundling together different
|
||||
* globalization preferences. It includes:
|
||||
* <ul>
|
||||
@ -37,6 +49,57 @@ import com.ibm.icu.text.SimpleDateFormat;
|
||||
* <li>A timezone</li>
|
||||
* <li>A calendar</li>
|
||||
* <li>A collator (for language-sensitive sorting, searching, and matching).</li>
|
||||
<<<<<<< GlobalizationPreferences.java
|
||||
* <li>And explicit overrides for date/time formats, etc.</li>
|
||||
* </ul>
|
||||
* The class will heuristically compute implicit, heuristic values for the above
|
||||
* based on available data if explicit values are not supplied. These implicit
|
||||
* values can be presented to users for confirmation, or replacement if the
|
||||
* values are incorrect.
|
||||
* <p>
|
||||
* To reset any explicit field so that it will get heuristic values, pass in
|
||||
* null. For example, myPreferences.setLocale(null);
|
||||
* <p>
|
||||
* All of the heuristics can be customized by subclasses, by overriding
|
||||
* getTerritory(), guessCollator(), etc.
|
||||
* <p>
|
||||
* The class also supplies display names for languages, scripts, territories,
|
||||
* currencies, timezones, etc. These are computed according to the
|
||||
* locale/language preference list. Thus, if the preference is Breton; French;
|
||||
* English, then the display name for a language will be returned in Breton if
|
||||
* available, otherwise in French if available, otherwise in English.
|
||||
* <p>
|
||||
* The codes used to reference territory, currency, etc. are as defined elsewhere in ICU,
|
||||
* and are taken from CLDR (which reflects RFC 3066bis usage, ISO 4217, and the
|
||||
* TZ Timezone database identifiers).
|
||||
* <p>
|
||||
* <b>This is at a prototype stage, and has not incorporated all the design
|
||||
* changes that we would like yet; further feedback is welcome.</b></p>
|
||||
* <p>
|
||||
* TODO:<ul>
|
||||
* <li>Separate out base class</li>
|
||||
* <li>Add BreakIterator</li>
|
||||
* <li>Add Holidays</li>
|
||||
* <li>Add convenience to get/take Locale as well as ULocale.</li>
|
||||
* <li>Add getResourceBundle(String baseName, ClassLoader loader);</li>
|
||||
* <li>Add getFallbackLocales();</li>
|
||||
* <li>Add Lenient datetime formatting when that is available.</li>
|
||||
* <li>Should this be serializable?</li>
|
||||
* <li>Other utilities?</li>
|
||||
* </ul>
|
||||
* Note:
|
||||
* <ul>
|
||||
* <li>to get the display name for the first day of the week, use the calendar +
|
||||
* display names.</li>
|
||||
* <li>to get the work days, ask the calendar (when that is available).</li>
|
||||
* <li>to get papersize / measurement system/bidi-orientation, ask the locale
|
||||
* (when that is available there)</li>
|
||||
* <li>to get the field order in a date, and whether a time is 24hour or not,
|
||||
* ask the DateFormat (when that is available there)</li>
|
||||
* <li>it will support HOST locale when it becomes available (it is a special
|
||||
* locale that will ask the services to use the host platform's values).</li>
|
||||
* </ul>
|
||||
=======
|
||||
* <li>And explicit overrides for date/time formats, etc.</li></ul>
|
||||
* The class will heuristically compute implicit, heuristic values for the above based on available
|
||||
* data if explicit values are not supplied. These implicit values can be presented to users
|
||||
@ -50,8 +113,9 @@ import com.ibm.icu.text.SimpleDateFormat;
|
||||
*
|
||||
* @internal
|
||||
* @deprecated ICU 3.4.2
|
||||
>>>>>>> 1.8
|
||||
*/
|
||||
public class GlobalizationPreferences {
|
||||
public class GlobalizationPreferences implements Freezable {
|
||||
/**
|
||||
* Number Format types
|
||||
*/
|
||||
@ -75,57 +139,85 @@ public class GlobalizationPreferences {
|
||||
* @param locales list of locales in priority order, eg {"be", "fr"} for Breton first, then French if that fails.
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setULocales(List locales) {
|
||||
this.locales = new ArrayList(locales);
|
||||
explicitLocales = true;
|
||||
if (!explicitTerritory) guessTerritory();
|
||||
if (!explicitCurrency) guessCurrency();
|
||||
if (!explicitTimezone) guessTimeZone();
|
||||
if (!explicitCalendar) guessCalendar();
|
||||
public GlobalizationPreferences setLocales(List locales) {
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
if (locales.size() == 0) {
|
||||
this.locales = locales.get(0);
|
||||
} else {
|
||||
this.locales = new ArrayList(locales); // clone for safety
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Get a copy of the language/locale priority list
|
||||
* @return a copy of the language/locale priority list.
|
||||
*/
|
||||
public List getULocales() {
|
||||
return new ArrayList(locales); // clone for safety
|
||||
public List getLocales() {
|
||||
List result = new ArrayList(); // clone for safety
|
||||
if (locales == null) {
|
||||
result = guessLocales();
|
||||
} else if (locales instanceof ULocale) {
|
||||
result.add(locales);
|
||||
} else {
|
||||
result.addAll((List)locales);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function for getting the locales in priority order
|
||||
* @return first item.
|
||||
* @param index The index (0..n) of the desired item.
|
||||
* @return desired item.
|
||||
*/
|
||||
public ULocale getULocale(int i) {
|
||||
return (ULocale)locales.get(i);
|
||||
public ULocale getLocale(int index) {
|
||||
if (locales == null) {
|
||||
return (ULocale)guessLocales().get(index);
|
||||
} else if (locales instanceof ULocale) {
|
||||
if (index != 0) throw new IllegalArgumentException("Out of bounds: " + index);
|
||||
return (ULocale)locales;
|
||||
} else {
|
||||
return (ULocale)((List)locales).get(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience routine for setting the language/locale priority list from an array.
|
||||
* @see #setULocales(List locales)
|
||||
* @see #setLocales(List locales)
|
||||
* @param uLocales list of locales in an array
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setULocales(ULocale[] uLocales) {
|
||||
return setULocales(Arrays.asList(uLocales));
|
||||
public GlobalizationPreferences setLocales(ULocale[] uLocales) {
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
return setLocales(Arrays.asList(uLocales));
|
||||
}
|
||||
/**
|
||||
* Convenience routine for setting the language/locale priority list from a single locale/language.
|
||||
* @see #setULocales(List locales)
|
||||
* @see #setLocales(List locales)
|
||||
* @param uLocale single locale
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setULocales(ULocale uLocale) {
|
||||
return setULocales(new ULocale[]{uLocale});
|
||||
public GlobalizationPreferences setLocale(ULocale uLocale) {
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
return setLocales(new ULocale[]{uLocale});
|
||||
}
|
||||
|
||||
//#ifndef FOUNDATION
|
||||
/**
|
||||
* Convenience routine for setting the locale priority list from an Accept-Language string.
|
||||
* @see #setULocales(List locales)
|
||||
* @see #setLocales(List locales)
|
||||
* @param acceptLanguageString Accept-Language list, as defined by Section 14.4 of the RFC 2616 (HTTP 1.1)
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setULocales(String acceptLanguageString) {
|
||||
public GlobalizationPreferences setLocales(String acceptLanguageString) {
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
/*
|
||||
Accept-Language = "Accept-Language" ":" 1#( language-range [ ";" "q" "=" qvalue ] )
|
||||
x matches x-...
|
||||
@ -160,7 +252,7 @@ public class GlobalizationPreferences {
|
||||
result.add(0, new ULocale((String)it2.next()));
|
||||
}
|
||||
}
|
||||
return setULocales(result);
|
||||
return setLocales(result);
|
||||
}
|
||||
//#endif
|
||||
|
||||
@ -172,11 +264,10 @@ public class GlobalizationPreferences {
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setTerritory(String territory) {
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
this.territory = territory;
|
||||
explicitTerritory = true;
|
||||
if (!explicitCurrency) guessCurrency();
|
||||
if (!explicitTimezone) guessTimeZone();
|
||||
if (!explicitCalendar) guessCalendar();
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
@ -184,6 +275,7 @@ public class GlobalizationPreferences {
|
||||
* @return territory code, explicit or implicit.
|
||||
*/
|
||||
public String getTerritory() {
|
||||
if (territory == null) return guessTerritory();
|
||||
return territory; // immutable, so don't need to clone
|
||||
}
|
||||
|
||||
@ -193,8 +285,10 @@ public class GlobalizationPreferences {
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setCurrency(Currency currency) {
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
this.currency = currency;
|
||||
explicitCurrency = true;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
@ -202,6 +296,7 @@ public class GlobalizationPreferences {
|
||||
* @return currency code, explicit or implicit.
|
||||
*/
|
||||
public Currency getCurrency() {
|
||||
if (currency == null) return guessCurrency();
|
||||
return currency; // immutable, so don't have to clone
|
||||
}
|
||||
|
||||
@ -211,42 +306,79 @@ public class GlobalizationPreferences {
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setCalendar(Calendar calendar) {
|
||||
this.calendar = calendar;
|
||||
explicitCalendar = true;
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
this.calendar = calendar;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Get a copy of the calendar according to the settings.
|
||||
* @return currency code, explicit or implicit.
|
||||
* @return calendar explicit or implicit.
|
||||
*/
|
||||
public Calendar getCalendar() {
|
||||
return (Calendar) calendar.clone(); // clone for safety
|
||||
if (calendar == null) return guessCalendar();
|
||||
Calendar temp = (Calendar) calendar.clone(); // clone for safety
|
||||
temp.setTimeZone(getTimeZone());
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timezone ID. If this has not been set, uses default for territory.
|
||||
* @param timezone a valid TZID (see UTS#35).
|
||||
* @return the object, for chaining.
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setTimezone(TimeZone timezone) {
|
||||
public GlobalizationPreferences setTimeZone(TimeZone timezone) {
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
this.timezone = timezone;
|
||||
explicitTimezone = true;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Get the timezone. It was either explicitly set, or is heuristically computed from other settings.
|
||||
* @return timezone, either implicitly or explicitly set
|
||||
*/
|
||||
public TimeZone getTimezone() {
|
||||
public TimeZone getTimeZone() {
|
||||
if (timezone == null) return guessTimeZone();
|
||||
return (TimeZone) timezone.clone(); // clone for safety
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a copy of the collator according to the settings.
|
||||
* @return collator explicit or implicit.
|
||||
*/
|
||||
public Collator getCollator() {
|
||||
if (collator == null) return guessCollator();
|
||||
try {
|
||||
return (Collator) collator.clone(); // clone for safety
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new InternalError("Error in cloning collator");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set the collator for this object.
|
||||
* @param collator
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setCollator(Collator collator) {
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
this.collator = collator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the date locale.
|
||||
* @param dateLocale If not null, overrides the locale priority list for all the date formats.
|
||||
* @return the object, for chaining
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setDateLocale(ULocale dateLocale) {
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
this.dateLocale = dateLocale;
|
||||
return this;
|
||||
}
|
||||
@ -255,15 +387,18 @@ public class GlobalizationPreferences {
|
||||
* @return date locale. Null if none was set explicitly.
|
||||
*/
|
||||
public ULocale getDateLocale() {
|
||||
return dateLocale;
|
||||
return dateLocale != null ? dateLocale : getLocale(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number locale.
|
||||
* @param numberLocale If not null, overrides the locale priority list for all the date formats.
|
||||
* @return the object, for chaining
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setNumberLocale(ULocale numberLocale) {
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
this.numberLocale = numberLocale;
|
||||
return this;
|
||||
}
|
||||
@ -273,7 +408,7 @@ public class GlobalizationPreferences {
|
||||
* @return number locale. Null if none was set explicitly.
|
||||
*/
|
||||
public ULocale getNumberLocale() {
|
||||
return numberLocale;
|
||||
return numberLocale != null ? numberLocale : getLocale(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -285,7 +420,7 @@ public class GlobalizationPreferences {
|
||||
*/
|
||||
public String getDisplayName(String id, int type) {
|
||||
String result = id;
|
||||
for (Iterator it = locales.iterator(); it.hasNext();) {
|
||||
for (Iterator it = getLocales().iterator(); it.hasNext();) {
|
||||
ULocale locale = (ULocale) it.next();
|
||||
switch (type) {
|
||||
case LOCALEID:
|
||||
@ -328,9 +463,9 @@ public class GlobalizationPreferences {
|
||||
// TODO, have method that doesn't require us to create a timezone
|
||||
// fix other hacks
|
||||
// hack for couldn't match
|
||||
// note, compiling with FOUNDATION omits this check for now
|
||||
// note, compiling with FOUNDATION omits this check for now
|
||||
//#ifndef FOUNDATION
|
||||
if (badTimezone.reset(result).matches()) continue;
|
||||
if (badTimeZone.reset(result).matches()) continue;
|
||||
//#endif
|
||||
break;
|
||||
default:
|
||||
@ -344,51 +479,80 @@ public class GlobalizationPreferences {
|
||||
}
|
||||
//#ifndef FOUNDATION
|
||||
// TODO remove need for this
|
||||
private static final Matcher badTimezone = Pattern.compile("[A-Z]{2}|.*\\s\\([A-Z]{2}\\)").matcher("");
|
||||
private static final Matcher badTimeZone = Pattern.compile("[A-Z]{2}|.*\\s\\([A-Z]{2}\\)").matcher("");
|
||||
//#endif
|
||||
|
||||
|
||||
/**
|
||||
* Set an explicit date format. Overrides both the date locale, and the locale priority list
|
||||
* for a particular combination of dateStyle and timeStyle. NONE should be used if for the style,
|
||||
* where only the date or time format individually is being set.
|
||||
* @param dateStyle
|
||||
* @param timeStyle
|
||||
* @param dateStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
|
||||
* @param timeStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
|
||||
* @param format
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setDateFormat(int dateStyle, int timeStyle, DateFormat format) {
|
||||
if (dateFormats == null) dateFormats = new DateFormat[NONE+1][NONE+1];
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
if (dateFormats == null) dateFormats = new Object[NONE+1][NONE+1];
|
||||
dateFormats[dateStyle][timeStyle] = (DateFormat) format.clone(); // for safety
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an explicit date format. Overrides both the date locale, and the locale priority list
|
||||
* for a particular combination of dateStyle and timeStyle. NONE should be used if for the style,
|
||||
* where only the date or time format individually is being set.
|
||||
* @param dateStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
|
||||
* @param timeStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
|
||||
* @param formatPattern date pattern, eg "yyyy-MMM-dd"
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setDateFormat(int dateStyle, int timeStyle, String formatPattern) {
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
if (dateFormats == null) dateFormats = new Object[NONE+1][NONE+1];
|
||||
// test the format to make sure it won't throw an error later
|
||||
new SimpleDateFormat(formatPattern, getDateLocale());
|
||||
dateFormats[dateStyle][timeStyle] = formatPattern; // for safety
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Gets a date format according to the current settings. If there is an explicit (non-null) date/time
|
||||
* format set, a copy of that is returned. Otherwise, if there is a non-null date locale, that is used.
|
||||
* Otherwise, the language priority list is used. NONE should be used for the style,
|
||||
* where only the date or time format individually is being gotten.
|
||||
* @param dateStyle
|
||||
* @param timeStyle
|
||||
* @param dateStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
|
||||
* @param timeStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
|
||||
* @return a DateFormat, according to the above description
|
||||
*/
|
||||
public DateFormat getDateFormat(int dateStyle, int timeStyle) {
|
||||
try {
|
||||
DateFormat result = null;
|
||||
if (dateFormats != null) result = dateFormats[dateStyle][timeStyle];
|
||||
if (dateFormats != null) { // and override can either be a string or a pattern
|
||||
Object temp = dateFormats[dateStyle][timeStyle];
|
||||
if (temp instanceof DateFormat) {
|
||||
result = (DateFormat) temp;
|
||||
} else {
|
||||
result = new SimpleDateFormat((String)temp, getDateLocale());
|
||||
}
|
||||
}
|
||||
if (result != null) {
|
||||
result = (DateFormat) result.clone(); // clone for safety
|
||||
result.setCalendar(calendar);
|
||||
result.setCalendar(getCalendar());
|
||||
} else {
|
||||
// In the case of date formats, we don't have to look at more than one
|
||||
// locale. May be different for other cases
|
||||
ULocale currentLocale = dateLocale != null ? dateLocale : (ULocale)locales.get(0);
|
||||
// TODO Make this one function.
|
||||
if (timeStyle == NONE) {
|
||||
result = DateFormat.getDateInstance(calendar, dateStyle, currentLocale);
|
||||
result = DateFormat.getDateInstance(getCalendar(), dateStyle, getDateLocale());
|
||||
} else if (dateStyle == NONE) {
|
||||
result = DateFormat.getTimeInstance(calendar, timeStyle, currentLocale);
|
||||
result = DateFormat.getTimeInstance(getCalendar(), timeStyle, getDateLocale());
|
||||
} else {
|
||||
result = DateFormat.getDateTimeInstance(calendar, dateStyle, timeStyle, currentLocale);
|
||||
result = DateFormat.getDateTimeInstance(getCalendar(), dateStyle, timeStyle, getDateLocale());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -404,32 +568,39 @@ public class GlobalizationPreferences {
|
||||
/**
|
||||
* Gets a number format according to the current settings.
|
||||
* If there is an explicit (non-null) number
|
||||
* format set, a copy of that is returned. Otherwise, if there is a non-null date locale, that is used.
|
||||
* format set, a copy of that is returned. Otherwise, if there is a non-null number locale, that is used.
|
||||
* Otherwise, the language priority list is used. NONE should be used for the style,
|
||||
* where only the date or time format individually is being gotten.
|
||||
* @param style CURRENCY, NUMBER, INTEGER, SCIENTIFIC, PERCENT
|
||||
*/
|
||||
public NumberFormat getNumberFormat(int style) {
|
||||
try {
|
||||
NumberFormat result = null;
|
||||
if (numberFormats != null) result = numberFormats[style];
|
||||
if (numberFormats != null) {
|
||||
Object temp = numberFormats[style];
|
||||
if (temp instanceof NumberFormat) {
|
||||
result = (NumberFormat) temp;
|
||||
} else {
|
||||
result = new DecimalFormat((String)temp, new DecimalFormatSymbols(getDateLocale()));
|
||||
}
|
||||
}
|
||||
if (result != null) {
|
||||
result = (NumberFormat) result.clone(); // clone for safety
|
||||
result = (NumberFormat) result.clone(); // clone for safety (later optimize)
|
||||
if (style == CURRENCY) {
|
||||
result.setCurrency(currency);
|
||||
result.setCurrency(getCurrency());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// In the case of date formats, we don't have to look at more than one
|
||||
// locale. May be different for other cases
|
||||
ULocale currentLocale = numberLocale != null ? numberLocale : (ULocale)locales.get(0);
|
||||
switch (style) {
|
||||
case NUMBER: return NumberFormat.getInstance(currentLocale);
|
||||
case SCIENTIFIC: return NumberFormat.getScientificInstance(currentLocale);
|
||||
case INTEGER: return NumberFormat.getIntegerInstance(currentLocale);
|
||||
case PERCENT: return NumberFormat.getPercentInstance(currentLocale);
|
||||
case CURRENCY: result = NumberFormat.getCurrencyInstance(currentLocale);
|
||||
result.setCurrency(currency);
|
||||
return result;
|
||||
case NUMBER: return NumberFormat.getInstance(getNumberLocale());
|
||||
case SCIENTIFIC: return NumberFormat.getScientificInstance(getNumberLocale());
|
||||
case INTEGER: return NumberFormat.getIntegerInstance(getNumberLocale());
|
||||
case PERCENT: return NumberFormat.getPercentInstance(getNumberLocale());
|
||||
case CURRENCY: result = NumberFormat.getCurrencyInstance(getNumberLocale());
|
||||
result.setCurrency(getCurrency());
|
||||
return result;
|
||||
}
|
||||
} catch (RuntimeException e) {}
|
||||
throw new IllegalArgumentException(); // fix later
|
||||
@ -437,55 +608,111 @@ public class GlobalizationPreferences {
|
||||
|
||||
/**
|
||||
* Sets a number format explicitly. Overrides the number locale and the general locale settings.
|
||||
* @param style CURRENCY, NUMBER, INTEGER, SCIENTIFIC, PERCENT
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences setNumberFormat(int style, DateFormat format) {
|
||||
if (numberFormats == null) numberFormats = new NumberFormat[NUMBER_LIMIT];
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
if (numberFormats == null) numberFormats = new Object[NUMBER_LIMIT];
|
||||
numberFormats[style] = (NumberFormat) format.clone(); // for safety
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the object to the initial state.
|
||||
* @return the object, for chaining
|
||||
* Sets a number format explicitly. Overrides the number locale and the general locale settings.
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences clear() {
|
||||
explicitLocales = explicitTerritory = explicitCurrency = explicitTimezone = explicitCalendar = false;
|
||||
locales.add(ULocale.getDefault());
|
||||
if (!explicitTerritory) guessTerritory();
|
||||
if (!explicitCurrency) guessCurrency();
|
||||
if (!explicitTimezone) guessTimeZone();
|
||||
if (!explicitCalendar) guessCalendar();
|
||||
public GlobalizationPreferences setNumberFormat(int style, String formatPattern) {
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
if (numberFormats == null) numberFormats = new Object[NUMBER_LIMIT];
|
||||
// check to make sure it compiles
|
||||
new DecimalFormat((String)formatPattern, new DecimalFormatSymbols(getDateLocale()));
|
||||
numberFormats[style] = formatPattern; // for safety
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the object to the initial state.
|
||||
* @return this, for chaining
|
||||
*/
|
||||
public GlobalizationPreferences reset() {
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
territory = null;
|
||||
calendar = null;
|
||||
collator = null;
|
||||
timezone = null;
|
||||
currency = null;
|
||||
dateFormats = null;
|
||||
numberFormats = null;
|
||||
dateLocale = null;
|
||||
numberLocale = null;
|
||||
locales = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
// protected helper functions
|
||||
protected void guessTerritory() {
|
||||
/**
|
||||
* This function can be overridden by subclasses to use different heuristics.
|
||||
*/
|
||||
protected String guessTerritory() {
|
||||
String result;
|
||||
// pass through locales to see if there is a territory.
|
||||
for (Iterator it = locales.iterator(); it.hasNext();) {
|
||||
for (Iterator it = getLocales().iterator(); it.hasNext();) {
|
||||
ULocale locale = (ULocale)it.next();
|
||||
String temp = locale.getCountry();
|
||||
if (temp.length() != 0) {
|
||||
territory = temp;
|
||||
return;
|
||||
result = locale.getCountry();
|
||||
if (result.length() != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// if not, guess from the first language tag, or maybe from intersection of languages, eg nl + fr => BE
|
||||
// TODO fix using real data
|
||||
// for now, just use fixed values
|
||||
ULocale firstLocale = (ULocale)locales.iterator().next();
|
||||
ULocale firstLocale = getLocale(0);
|
||||
String language = firstLocale.getLanguage();
|
||||
String script = firstLocale.getScript();
|
||||
territory = null;
|
||||
result = null;
|
||||
if (script.length() != 0) {
|
||||
territory = (String) language_territory_hack_map.get(language + "_" + script);
|
||||
result = (String) language_territory_hack_map.get(language + "_" + script);
|
||||
}
|
||||
if (territory == null) territory = (String) language_territory_hack_map.get(language);
|
||||
if (territory == null) territory = "US"; // need *some* default
|
||||
if (result == null) result = (String) language_territory_hack_map.get(language);
|
||||
if (result == null) result = "US"; // need *some* default
|
||||
return result;
|
||||
}
|
||||
protected void guessCurrency() {
|
||||
currency = Currency.getInstance(new ULocale("und-" + territory));
|
||||
/**
|
||||
* This function can be overridden by subclasses to use different heuristics
|
||||
*/
|
||||
protected Currency guessCurrency() {
|
||||
return Currency.getInstance(new ULocale("und-" + getTerritory()));
|
||||
}
|
||||
protected void guessTimeZone() {
|
||||
/**
|
||||
* This function can be overridden by subclasses to use different heuristics
|
||||
* <b>It MUST return a 'safe' value,
|
||||
* one whose modification will not affect this object.</b>
|
||||
*/
|
||||
protected List guessLocales() {
|
||||
List result = new ArrayList(0);
|
||||
result.add(ULocale.getDefault());
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* This function can be overridden by subclasses to use different heuristics.
|
||||
* <b>It MUST return a 'safe' value,
|
||||
* one whose modification will not affect this object.</b>
|
||||
*/
|
||||
protected Collator guessCollator() {
|
||||
return Collator.getInstance(getLocale(0));
|
||||
}
|
||||
/**
|
||||
* This function can be overridden by subclasses to use different heuristics.
|
||||
* <b>It MUST return a 'safe' value,
|
||||
* one whose modification will not affect this object.</b>
|
||||
*/
|
||||
protected TimeZone guessTimeZone() {
|
||||
// TODO fix using real data
|
||||
// for single-zone countries, pick that zone
|
||||
// for others, pick the most populous zone
|
||||
@ -493,9 +720,9 @@ public class GlobalizationPreferences {
|
||||
// NOTE: in a few cases can do better by looking at language.
|
||||
// Eg haw+US should go to Pacific/Honolulu
|
||||
// fr+CA should go to America/Montreal
|
||||
String timezoneString = (String) territory_tzid_hack_map.get(territory);
|
||||
String timezoneString = (String) territory_tzid_hack_map.get(getTerritory());
|
||||
if (timezoneString == null) {
|
||||
String[] attempt = ZoneMeta.getAvailableIDs(territory);
|
||||
String[] attempt = ZoneMeta.getAvailableIDs(getTerritory());
|
||||
if (attempt.length == 0) {
|
||||
timezoneString = "Etc/GMT"; // gotta do something
|
||||
} else {
|
||||
@ -508,35 +735,39 @@ public class GlobalizationPreferences {
|
||||
timezoneString = attempt[i];
|
||||
}
|
||||
}
|
||||
timezone = TimeZone.getTimeZone(timezoneString);
|
||||
return TimeZone.getTimeZone(timezoneString);
|
||||
}
|
||||
protected void guessCalendar() {
|
||||
/**
|
||||
* This function can be overridden by subclasses to use different heuristics.
|
||||
* <b>It MUST return a 'safe' value,
|
||||
* one whose modification will not affect this object.</b>
|
||||
*/
|
||||
protected Calendar guessCalendar() {
|
||||
// TODO add better API
|
||||
calendar = Calendar.getInstance(new ULocale("und-" + territory));
|
||||
return Calendar.getInstance(new ULocale("und-" + getTerritory()));
|
||||
}
|
||||
|
||||
// PRIVATES
|
||||
|
||||
private ArrayList locales = new ArrayList();
|
||||
private Object locales;
|
||||
private String territory;
|
||||
private Currency currency;
|
||||
private TimeZone timezone;
|
||||
private Calendar calendar;
|
||||
private boolean explicitLocales;
|
||||
private boolean explicitTerritory;
|
||||
private boolean explicitCurrency;
|
||||
private boolean explicitTimezone;
|
||||
private boolean explicitCalendar;
|
||||
private Collator collator;
|
||||
|
||||
private ULocale dateLocale;
|
||||
private DateFormat[][] dateFormats;
|
||||
private Object[][] dateFormats;
|
||||
private ULocale numberLocale;
|
||||
private NumberFormat[] numberFormats;
|
||||
private Object[] numberFormats;
|
||||
|
||||
{
|
||||
clear();
|
||||
reset();
|
||||
}
|
||||
//
|
||||
|
||||
/** WARNING: All of this data is temporary, until we start importing from CLDR!!!
|
||||
*
|
||||
*/
|
||||
private static final Map language_territory_hack_map = new HashMap();
|
||||
private static final String[][] language_territory_hack = {
|
||||
{"af", "ZA"},
|
||||
@ -737,4 +968,24 @@ public class GlobalizationPreferences {
|
||||
territory_tzid_hack_map.put(territory_tzid_hack[i][0],territory_tzid_hack[i][1]);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean frozen;
|
||||
public boolean isFrozen() {
|
||||
return frozen;
|
||||
}
|
||||
public Object freeze() {
|
||||
frozen = true;
|
||||
return this;
|
||||
}
|
||||
public Object cloneAsThawed() {
|
||||
try {
|
||||
GlobalizationPreferences result = (GlobalizationPreferences) clone();
|
||||
result.frozen = false;
|
||||
return result;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
// will always work
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user