ICU-4700 more cldr tools

X-SVN-Rev: 18830
This commit is contained in:
Mark Davis 2005-11-24 09:32:39 +00:00
parent c932702a9c
commit 9a15ff31a2
9 changed files with 818 additions and 221 deletions

View File

@ -9,6 +9,7 @@
package com.ibm.icu.dev.test.format; package com.ibm.icu.dev.test.format;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
@ -35,7 +36,8 @@ public class GlobalizationPreferencesTest {
private static final String[] WidthNames = {"abbreviated", "wide", "narrow"}; private static final String[] WidthNames = {"abbreviated", "wide", "narrow"};
public static void main(String[] args) throws IOException { 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 { try {
Date now = new Date(); Date now = new Date();
@ -49,13 +51,13 @@ public class GlobalizationPreferencesTest {
out.println("Check defaulting"); 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"}; 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) { for (int i = 0; i < localeList.length; ++i) {
lPreferences.setULocales(localeList[i]); lPreferences.setLocales(localeList[i]);
out.println("\tdefaults for: \t" + localeList[i] + "\t" out.println("\tdefaults for: \t" + localeList[i] + "\t"
+ lPreferences.getULocales() + lPreferences.getLocales()
+ ", \t" + lPreferences.getTerritory() + ", \t" + lPreferences.getTerritory()
+ ", \t" + lPreferences.getCurrency() + ", \t" + lPreferences.getCurrency()
+ ", \t" + lPreferences.getCalendar().getClass() + ", \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("\tdate: \t" + lPreferences.getDateFormat(DateFormat.FULL, GlobalizationPreferences.NONE).format(now));
out.println("setting locale to Germany"); 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("\tdate: \t" + lPreferences.getDateFormat(DateFormat.FULL, GlobalizationPreferences.NONE).format(now));
out.println("setting date locale to France"); out.println("setting date locale to France");
lPreferences.setDateLocale(ULocale.FRANCE); lPreferences.setDateLocale(ULocale.FRANCE);
out.println("\tdate: \t" + lPreferences.getDateFormat(DateFormat.FULL, GlobalizationPreferences.NONE).format(now)); 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)"); out.println("setting date format to yyyy-MMM-dd (Italy)");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MMM-dd",ULocale.ITALY); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MMM-dd",ULocale.ITALY);
lPreferences.setDateFormat(DateFormat.FULL, GlobalizationPreferences.NONE, sdf); lPreferences.setDateFormat(DateFormat.FULL, GlobalizationPreferences.NONE, sdf);
@ -111,11 +117,21 @@ public class GlobalizationPreferencesTest {
lPreferences.setNumberLocale(new ULocale("hi-IN")); lPreferences.setNumberLocale(new ULocale("hi-IN"));
out.println("\tcurrency: \t" + lPreferences.getNumberFormat(GlobalizationPreferences.CURRENCY).format(1234.567)); 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 // now try a fallback within locales
out.println(); out.println();
out.println("Display Names"); out.println("Display Names");
lPreferences.setULocales(new ULocale[]{new ULocale("as"),new ULocale("pl"),new ULocale("fr")}); lPreferences.setLocales(new ULocale[]{new ULocale("as"),new ULocale("pl"),new ULocale("fr")});
out.println("Trying fallback for multiple locales: " + lPreferences.getULocales()); out.println("Trying fallback for multiple locales: " + lPreferences.getLocales());
String[][] testItems = { String[][] testItems = {
{GlobalizationPreferences.LOCALEID+"", "as_FR", "en_RU","haw_CA","se_Cyrl_AT"}, {GlobalizationPreferences.LOCALEID+"", "as_FR", "en_RU","haw_CA","se_Cyrl_AT"},
{GlobalizationPreferences.LANGUAGEID+"", "as", "en","haw","se","kok"}, {GlobalizationPreferences.LANGUAGEID+"", "as", "en","haw","se","kok"},

View File

@ -42,75 +42,6 @@ public class BagFormatter {
SHOW_FILES = showFiles; SHOW_FILES = showFiles;
} }
private static final String BASE_RULES =
"'<' > '&lt;' ;" +
"'<' < '&'[lL][Tt]';' ;" +
"'&' > '&amp;' ;" +
"'&' < '&'[aA][mM][pP]';' ;" +
"'>' < '&'[gG][tT]';' ;" +
"'\"' < '&'[qQ][uU][oO][tT]';' ; " +
"'' < '&'[aA][pP][oO][sS]';' ; ";
private static final String CONTENT_RULES =
"'>' > '&gt;' ;";
private static final String HTML_RULES = BASE_RULES + CONTENT_RULES +
"'\"' > '&quot;' ; ";
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 +
"'' > '&apos;' ; ";
/*
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 "&amp;" and "&lt;" respectively. The right angle bracket (>) MAY
be represented using the string "&gt;", and MUST, for compatibility, be
escaped using either "&gt;" 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 "&apos;", and
the double-quote character (") as "&quot;".
*/
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); public static final PrintWriter CONSOLE = new PrintWriter(System.out,true);
private static PrintWriter log = CONSOLE; private static PrintWriter log = CONSOLE;

View File

@ -61,4 +61,80 @@ public class TransliteratorUtilities {
br.close(); br.close();
return buffer.toString(); return buffer.toString();
} }
private static final String BASE_RULES =
":: (hex-any/xml);" +
":: (hex-any/xml10);" +
"'<' > '&lt;' ;" +
"'<' < '&'[lL][Tt]';' ;" +
"'&' > '&amp;' ;" +
"'&' < '&'[aA][mM][pP]';' ;" +
"'>' < '&'[gG][tT]';' ;" +
"'\"' < '&'[qQ][uU][oO][tT]';' ; " +
"'' < '&'[aA][pP][oO][sS]';' ; ";
private static final String CONTENT_RULES =
"'>' > '&gt;' ;";
private static final String HTML_RULES = BASE_RULES + CONTENT_RULES +
"'\"' > '&quot;' ; ";
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 +
"'' > '&apos;' ; "
;
/*
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 "&amp;" and "&lt;" respectively. The right angle bracket (>) MAY
be represented using the string "&gt;", and MUST, for compatibility, be
escaped using either "&gt;" 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 "&apos;", and
the double-quote character (") as "&quot;".
*/
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);
} }

View File

@ -20,6 +20,8 @@ import java.util.TreeMap;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.ibm.icu.impl.Utility; 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.SymbolTable;
import com.ibm.icu.text.UTF16; import com.ibm.icu.text.UTF16;
import com.ibm.icu.text.UnicodeMatcher; import com.ibm.icu.text.UnicodeMatcher;
@ -215,7 +217,7 @@ Name: Unicode_1_Name
public final UnicodeSet getSet(String propertyValue) { public final UnicodeSet getSet(String propertyValue) {
return getSet(propertyValue,null); return getSet(propertyValue,null);
} }
public final UnicodeSet getSet(Matcher matcher) { public final UnicodeSet getSet(PatternMatcher matcher) {
return getSet(matcher,null); return getSet(matcher,null);
} }
@ -229,7 +231,7 @@ Name: Unicode_1_Name
public static final String UNUSED = "??"; 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 (result == null) result = new UnicodeSet();
if (isType(STRING_OR_MISC_MASK)) { if (isType(STRING_OR_MISC_MASK)) {
for (int i = 0; i <= 0x10FFFF; ++i) { for (int i = 0; i <= 0x10FFFF; ++i) {
@ -605,12 +607,12 @@ Name: Unicode_1_Name
} }
return result; return result;
} }
InverseMatcher inverseMatcher = new InverseMatcher(); InversePatternMatcher inverseMatcher = new InversePatternMatcher();
/** /**
* Format is: * Format is:
* propname ('=' | '!=') propvalue ( '|' propValue )* * 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('='); int equalPos = propAndValue.indexOf('=');
String prop = propAndValue.substring(0,equalPos); String prop = propAndValue.substring(0,equalPos);
String value = propAndValue.substring(equalPos+1); String value = propAndValue.substring(equalPos+1);
@ -632,7 +634,7 @@ Name: Unicode_1_Name
return up.getSet(matcher.set(value), result); 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); return getSet(propAndValue, matcher, null);
} }
public final UnicodeSet getSet(String propAndValue) { public final UnicodeSet getSet(String propAndValue) {
@ -855,57 +857,51 @@ Name: Unicode_1_Name
} }
} }
public interface Matcher { public interface PatternMatcher extends ObjectMatcher {
/** public PatternMatcher set(String pattern);
* 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 static class InverseMatcher implements Matcher { public static class InversePatternMatcher extends InverseMatcher implements PatternMatcher {
Matcher other; PatternMatcher other;
public Matcher set(Matcher toInverse) { public PatternMatcher set(PatternMatcher toInverse) {
other = toInverse; other = toInverse;
return this; return this;
} }
public boolean matches(String value) { public boolean matches(Object value) {
return !other.matches(value); return !other.matches(value);
} }
public Matcher set(String pattern) { public PatternMatcher set(String pattern) {
other.set(pattern); other.set(pattern);
return this; return this;
} }
} }
public static class SimpleMatcher implements Matcher { public static class SimpleMatcher implements PatternMatcher {
Comparator comparator; Comparator comparator;
String pattern; String pattern;
public SimpleMatcher(String pattern, Comparator comparator) { public SimpleMatcher(String pattern, Comparator comparator) {
this.comparator = comparator; this.comparator = comparator;
this.pattern = pattern; this.pattern = pattern;
} }
public boolean matches(String value) { public boolean matches(Object value) {
if (comparator == null) return pattern.equals(value); if (comparator == null) return pattern.equals(value);
return comparator.compare(pattern, value) == 0; return comparator.compare(pattern, value) == 0;
} }
public Matcher set(String pattern) { public PatternMatcher set(String pattern) {
this.pattern = pattern; this.pattern = pattern;
return this; return this;
} }
} }
public static class RegexMatcher implements UnicodeProperty.Matcher { public static class RegexMatcher implements UnicodeProperty.PatternMatcher {
private java.util.regex.Matcher matcher; private java.util.regex.Matcher matcher;
public UnicodeProperty.Matcher set(String pattern) { public UnicodeProperty.PatternMatcher set(String pattern) {
matcher = Pattern.compile(pattern).matcher(""); matcher = Pattern.compile(pattern).matcher("");
return this; return this;
} }
public boolean matches(String value) { public boolean matches(Object value) {
matcher.reset(value); matcher.reset(value.toString());
return matcher.matches(); return matcher.matches();
} }
} }

View File

@ -13,9 +13,11 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.SortedSet; import java.util.SortedSet;
//#ifndef FOUNDATION //#ifndef FOUNDATION
import java.util.regex.Matcher; import java.util.regex.Matcher;
//#endif //#endif
import com.ibm.icu.text.Transliterator; import com.ibm.icu.text.Transliterator;
import com.ibm.icu.text.UTF16; import com.ibm.icu.text.UTF16;
import com.ibm.icu.text.UnicodeSet; 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 * Utilities that ought to be on collections, but aren't
*/ */
public final class CollectionUtilities { 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() * Utility like Arrays.asList()
*/ */
@ -90,11 +113,25 @@ public final class CollectionUtilities {
return bestSoFar; return bestSoFar;
} }
public interface Filter { public interface ObjectMatcher {
/**
* Must handle null, never throw exception
*/
boolean matches(Object o); 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();) { for (Iterator it = c.iterator(); it.hasNext();) {
Object item = it.next(); Object item = it.next();
if (f.matches(item)) it.remove(); if (f.matches(item)) it.remove();
@ -102,7 +139,7 @@ public final class CollectionUtilities {
return c; 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();) { for (Iterator it = c.iterator(); it.hasNext();) {
Object item = it.next(); Object item = it.next();
if (!f.matches(item)) it.remove(); if (!f.matches(item)) it.remove();

View File

@ -112,6 +112,7 @@ public class PrettyPrinter {
* @return formatted UnicodeSet * @return formatted UnicodeSet
*/ */
public String toPattern(UnicodeSet uset) { public String toPattern(UnicodeSet uset) {
first = true;
// make sure that comparison separates all strings, even canonically equivalent ones // make sure that comparison separates all strings, even canonically equivalent ones
Set orderedStrings = new TreeSet(ordering); Set orderedStrings = new TreeSet(ordering);
for (UnicodeSetIterator it = new UnicodeSetIterator(uset); it.next();) { for (UnicodeSetIterator it = new UnicodeSetIterator(uset); it.next();) {

View File

@ -3447,7 +3447,7 @@ public class UnicodeSet extends UnicodeFilter {
* @deprecated * @deprecated
* @author medavis * @author medavis
*/ */
abstract static class XSymbolTable implements SymbolTable { abstract public static class XSymbolTable implements SymbolTable {
public UnicodeMatcher lookupMatcher(int i) { public UnicodeMatcher lookupMatcher(int i) {
return null; return null;
} }

View File

@ -7,15 +7,304 @@
package com.ibm.icu.util; package com.ibm.icu.util;
/** /**
* Provides a flexible mechanism for controlling access, without requiring that a class be immutable. * <pre>
* Once locked, an object can never be unlocked, so it is thread-safe from that point onward. * DRAFT
* The implementation of both methods must be synchronized. * Copyright (C) 2005, International Business Machines Corporation and
* Once the object has been locked, it must guarantee that no changes can be made to it. * others. All Rights Reserved.
* Any attempt to alter it must raise an UnsupportedOperationException exception. * </pre>
* 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, * Provides a flexible mechanism for controlling access, without requiring that
* or must also raise exceptions if any attempt to modify them is made. Of course, the object can return clones * a class be immutable. Once locked, an object can never be unlocked, so it is
* of internal objects, since those are safe. * @author davis * 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 (&quot;depend on the kindness of
* strangers&quot;) or the Andy Grove approach (&quot;Only the Paranoid
* Survive&quot;).
* </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) {
* &lt;font color=&quot;#0000FF&quot;&gt; &lt;b&gt;// unsafe getter&lt;/b&gt;
* &lt;/font&gt; A a = x.getA();
* Collection col = a.get_b();
* col.add(something);&lt;font color=&quot;#0000FF&quot;&gt; // a has now been changed, and x too
* &lt;/font&gt;
* &lt;font color=&quot;#0000FF&quot;&gt;&lt;b&gt;// unsafe constructor&lt;/b&gt;&lt;/font&gt;
* a = new A(col, col);
* y.store(a);
* col.add(something);&lt;font color=&quot;#0000FF&quot;&gt; // a has now been changed, and y too
*
* &lt;/font&gt;}
* </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(&quot;Attempt to modify frozen object&quot;);
* }
* </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 &quot;this&quot; 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 &amp; modify
*
* void setStuff(Collection x) {
* stuff = x;
* } // caller could keep reference &amp; modify
*
* MyClass(Collection x) {
* stuff = x;
* } // caller could keep reference &amp; 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 { public interface Freezable extends Cloneable {
/** /**

View File

@ -23,11 +23,23 @@ import java.util.regex.Pattern;
import com.ibm.icu.impl.Utility; import com.ibm.icu.impl.Utility;
import com.ibm.icu.impl.ZoneMeta; import com.ibm.icu.impl.ZoneMeta;
import com.ibm.icu.text.Collator;
import com.ibm.icu.text.DateFormat; 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.NumberFormat;
import com.ibm.icu.text.SimpleDateFormat; 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 * This convenience class provides a mechanism for bundling together different
* globalization preferences. It includes: * globalization preferences. It includes:
* <ul> * <ul>
@ -37,6 +49,57 @@ import com.ibm.icu.text.SimpleDateFormat;
* <li>A timezone</li> * <li>A timezone</li>
* <li>A calendar</li> * <li>A calendar</li>
* <li>A collator (for language-sensitive sorting, searching, and matching).</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> * <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 * 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 * 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 * @internal
* @deprecated ICU 3.4.2 * @deprecated ICU 3.4.2
>>>>>>> 1.8
*/ */
public class GlobalizationPreferences { public class GlobalizationPreferences implements Freezable {
/** /**
* Number Format types * 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. * @param locales list of locales in priority order, eg {"be", "fr"} for Breton first, then French if that fails.
* @return this, for chaining * @return this, for chaining
*/ */
public GlobalizationPreferences setULocales(List locales) { public GlobalizationPreferences setLocales(List locales) {
this.locales = new ArrayList(locales); if (isFrozen()) {
explicitLocales = true; throw new UnsupportedOperationException("Attempt to modify immutable object");
if (!explicitTerritory) guessTerritory(); }
if (!explicitCurrency) guessCurrency(); if (locales.size() == 0) {
if (!explicitTimezone) guessTimeZone(); this.locales = locales.get(0);
if (!explicitCalendar) guessCalendar(); } else {
this.locales = new ArrayList(locales); // clone for safety
}
return this; return this;
} }
/** /**
* Get a copy of the language/locale priority list
* @return a copy of the language/locale priority list. * @return a copy of the language/locale priority list.
*/ */
public List getULocales() { public List getLocales() {
return new ArrayList(locales); // clone for safety 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 * 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) { public ULocale getLocale(int index) {
return (ULocale)locales.get(i); 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. * 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 * @param uLocales list of locales in an array
* @return this, for chaining * @return this, for chaining
*/ */
public GlobalizationPreferences setULocales(ULocale[] uLocales) { public GlobalizationPreferences setLocales(ULocale[] uLocales) {
return setULocales(Arrays.asList(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. * 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 * @param uLocale single locale
* @return this, for chaining * @return this, for chaining
*/ */
public GlobalizationPreferences setULocales(ULocale uLocale) { public GlobalizationPreferences setLocale(ULocale uLocale) {
return setULocales(new ULocale[]{uLocale}); if (isFrozen()) {
throw new UnsupportedOperationException("Attempt to modify immutable object");
}
return setLocales(new ULocale[]{uLocale});
} }
//#ifndef FOUNDATION //#ifndef FOUNDATION
/** /**
* Convenience routine for setting the locale priority list from an Accept-Language string. * 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) * @param acceptLanguageString Accept-Language list, as defined by Section 14.4 of the RFC 2616 (HTTP 1.1)
* @return this, for chaining * @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 ] ) Accept-Language = "Accept-Language" ":" 1#( language-range [ ";" "q" "=" qvalue ] )
x matches x-... x matches x-...
@ -160,7 +252,7 @@ public class GlobalizationPreferences {
result.add(0, new ULocale((String)it2.next())); result.add(0, new ULocale((String)it2.next()));
} }
} }
return setULocales(result); return setLocales(result);
} }
//#endif //#endif
@ -172,11 +264,10 @@ public class GlobalizationPreferences {
* @return this, for chaining * @return this, for chaining
*/ */
public GlobalizationPreferences setTerritory(String territory) { public GlobalizationPreferences setTerritory(String territory) {
if (isFrozen()) {
throw new UnsupportedOperationException("Attempt to modify immutable object");
}
this.territory = territory; this.territory = territory;
explicitTerritory = true;
if (!explicitCurrency) guessCurrency();
if (!explicitTimezone) guessTimeZone();
if (!explicitCalendar) guessCalendar();
return this; return this;
} }
/** /**
@ -184,6 +275,7 @@ public class GlobalizationPreferences {
* @return territory code, explicit or implicit. * @return territory code, explicit or implicit.
*/ */
public String getTerritory() { public String getTerritory() {
if (territory == null) return guessTerritory();
return territory; // immutable, so don't need to clone return territory; // immutable, so don't need to clone
} }
@ -193,8 +285,10 @@ public class GlobalizationPreferences {
* @return this, for chaining * @return this, for chaining
*/ */
public GlobalizationPreferences setCurrency(Currency currency) { public GlobalizationPreferences setCurrency(Currency currency) {
if (isFrozen()) {
throw new UnsupportedOperationException("Attempt to modify immutable object");
}
this.currency = currency; this.currency = currency;
explicitCurrency = true;
return this; return this;
} }
/** /**
@ -202,6 +296,7 @@ public class GlobalizationPreferences {
* @return currency code, explicit or implicit. * @return currency code, explicit or implicit.
*/ */
public Currency getCurrency() { public Currency getCurrency() {
if (currency == null) return guessCurrency();
return currency; // immutable, so don't have to clone return currency; // immutable, so don't have to clone
} }
@ -211,42 +306,79 @@ public class GlobalizationPreferences {
* @return this, for chaining * @return this, for chaining
*/ */
public GlobalizationPreferences setCalendar(Calendar calendar) { public GlobalizationPreferences setCalendar(Calendar calendar) {
this.calendar = calendar; if (isFrozen()) {
explicitCalendar = true; throw new UnsupportedOperationException("Attempt to modify immutable object");
}
this.calendar = calendar;
return this; return this;
} }
/** /**
* Get a copy of the calendar according to the settings. * Get a copy of the calendar according to the settings.
* @return currency code, explicit or implicit. * @return calendar explicit or implicit.
*/ */
public Calendar getCalendar() { 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. * Sets the timezone ID. If this has not been set, uses default for territory.
* @param timezone a valid TZID (see UTS#35). * @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; this.timezone = timezone;
explicitTimezone = true;
return this; return this;
} }
/** /**
* Get the timezone. It was either explicitly set, or is heuristically computed from other settings. * Get the timezone. It was either explicitly set, or is heuristically computed from other settings.
* @return timezone, either implicitly or explicitly set * @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 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. * Set the date locale.
* @param dateLocale If not null, overrides the locale priority list for all the date formats. * @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) { public GlobalizationPreferences setDateLocale(ULocale dateLocale) {
if (isFrozen()) {
throw new UnsupportedOperationException("Attempt to modify immutable object");
}
this.dateLocale = dateLocale; this.dateLocale = dateLocale;
return this; return this;
} }
@ -255,15 +387,18 @@ public class GlobalizationPreferences {
* @return date locale. Null if none was set explicitly. * @return date locale. Null if none was set explicitly.
*/ */
public ULocale getDateLocale() { public ULocale getDateLocale() {
return dateLocale; return dateLocale != null ? dateLocale : getLocale(0);
} }
/** /**
* Set the number locale. * Set the number locale.
* @param numberLocale If not null, overrides the locale priority list for all the date formats. * @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) { public GlobalizationPreferences setNumberLocale(ULocale numberLocale) {
if (isFrozen()) {
throw new UnsupportedOperationException("Attempt to modify immutable object");
}
this.numberLocale = numberLocale; this.numberLocale = numberLocale;
return this; return this;
} }
@ -273,7 +408,7 @@ public class GlobalizationPreferences {
* @return number locale. Null if none was set explicitly. * @return number locale. Null if none was set explicitly.
*/ */
public ULocale getNumberLocale() { 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) { public String getDisplayName(String id, int type) {
String result = id; String result = id;
for (Iterator it = locales.iterator(); it.hasNext();) { for (Iterator it = getLocales().iterator(); it.hasNext();) {
ULocale locale = (ULocale) it.next(); ULocale locale = (ULocale) it.next();
switch (type) { switch (type) {
case LOCALEID: case LOCALEID:
@ -328,9 +463,9 @@ public class GlobalizationPreferences {
// TODO, have method that doesn't require us to create a timezone // TODO, have method that doesn't require us to create a timezone
// fix other hacks // fix other hacks
// hack for couldn't match // 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 //#ifndef FOUNDATION
if (badTimezone.reset(result).matches()) continue; if (badTimeZone.reset(result).matches()) continue;
//#endif //#endif
break; break;
default: default:
@ -344,51 +479,80 @@ public class GlobalizationPreferences {
} }
//#ifndef FOUNDATION //#ifndef FOUNDATION
// TODO remove need for this // 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 //#endif
/** /**
* Set an explicit date format. Overrides both the date locale, and the locale priority list * 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, * 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. * where only the date or time format individually is being set.
* @param dateStyle * @param dateStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
* @param timeStyle * @param timeStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
* @param format * @param format
* @return this, for chaining * @return this, for chaining
*/ */
public GlobalizationPreferences setDateFormat(int dateStyle, int timeStyle, DateFormat format) { 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 dateFormats[dateStyle][timeStyle] = (DateFormat) format.clone(); // for safety
return this; 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 * 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. * 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, * 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. * where only the date or time format individually is being gotten.
* @param dateStyle * @param dateStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
* @param timeStyle * @param timeStyle NONE, or DateFormat.FULL, LONG, MEDIUM, SHORT
* @return a DateFormat, according to the above description * @return a DateFormat, according to the above description
*/ */
public DateFormat getDateFormat(int dateStyle, int timeStyle) { public DateFormat getDateFormat(int dateStyle, int timeStyle) {
try { try {
DateFormat result = null; 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) { if (result != null) {
result = (DateFormat) result.clone(); // clone for safety result = (DateFormat) result.clone(); // clone for safety
result.setCalendar(calendar); result.setCalendar(getCalendar());
} else { } else {
// In the case of date formats, we don't have to look at more than one // In the case of date formats, we don't have to look at more than one
// locale. May be different for other cases // locale. May be different for other cases
ULocale currentLocale = dateLocale != null ? dateLocale : (ULocale)locales.get(0);
// TODO Make this one function. // TODO Make this one function.
if (timeStyle == NONE) { if (timeStyle == NONE) {
result = DateFormat.getDateInstance(calendar, dateStyle, currentLocale); result = DateFormat.getDateInstance(getCalendar(), dateStyle, getDateLocale());
} else if (dateStyle == NONE) { } else if (dateStyle == NONE) {
result = DateFormat.getTimeInstance(calendar, timeStyle, currentLocale); result = DateFormat.getTimeInstance(getCalendar(), timeStyle, getDateLocale());
} else { } else {
result = DateFormat.getDateTimeInstance(calendar, dateStyle, timeStyle, currentLocale); result = DateFormat.getDateTimeInstance(getCalendar(), dateStyle, timeStyle, getDateLocale());
} }
} }
return result; return result;
@ -404,32 +568,39 @@ public class GlobalizationPreferences {
/** /**
* Gets a number format according to the current settings. * Gets a number format according to the current settings.
* If there is an explicit (non-null) number * 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, * 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. * where only the date or time format individually is being gotten.
* @param style CURRENCY, NUMBER, INTEGER, SCIENTIFIC, PERCENT
*/ */
public NumberFormat getNumberFormat(int style) { public NumberFormat getNumberFormat(int style) {
try { try {
NumberFormat result = null; 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) { if (result != null) {
result = (NumberFormat) result.clone(); // clone for safety result = (NumberFormat) result.clone(); // clone for safety (later optimize)
if (style == CURRENCY) { if (style == CURRENCY) {
result.setCurrency(currency); result.setCurrency(getCurrency());
} }
return result; return result;
} }
// In the case of date formats, we don't have to look at more than one // In the case of date formats, we don't have to look at more than one
// locale. May be different for other cases // locale. May be different for other cases
ULocale currentLocale = numberLocale != null ? numberLocale : (ULocale)locales.get(0);
switch (style) { switch (style) {
case NUMBER: return NumberFormat.getInstance(currentLocale); case NUMBER: return NumberFormat.getInstance(getNumberLocale());
case SCIENTIFIC: return NumberFormat.getScientificInstance(currentLocale); case SCIENTIFIC: return NumberFormat.getScientificInstance(getNumberLocale());
case INTEGER: return NumberFormat.getIntegerInstance(currentLocale); case INTEGER: return NumberFormat.getIntegerInstance(getNumberLocale());
case PERCENT: return NumberFormat.getPercentInstance(currentLocale); case PERCENT: return NumberFormat.getPercentInstance(getNumberLocale());
case CURRENCY: result = NumberFormat.getCurrencyInstance(currentLocale); case CURRENCY: result = NumberFormat.getCurrencyInstance(getNumberLocale());
result.setCurrency(currency); result.setCurrency(getCurrency());
return result; return result;
} }
} catch (RuntimeException e) {} } catch (RuntimeException e) {}
throw new IllegalArgumentException(); // fix later 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. * 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) { 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 numberFormats[style] = (NumberFormat) format.clone(); // for safety
return this; return this;
} }
/** /**
* Restore the object to the initial state. * Sets a number format explicitly. Overrides the number locale and the general locale settings.
* @return the object, for chaining * @return this, for chaining
*/ */
public GlobalizationPreferences clear() { public GlobalizationPreferences setNumberFormat(int style, String formatPattern) {
explicitLocales = explicitTerritory = explicitCurrency = explicitTimezone = explicitCalendar = false; if (isFrozen()) {
locales.add(ULocale.getDefault()); throw new UnsupportedOperationException("Attempt to modify immutable object");
if (!explicitTerritory) guessTerritory(); }
if (!explicitCurrency) guessCurrency(); if (numberFormats == null) numberFormats = new Object[NUMBER_LIMIT];
if (!explicitTimezone) guessTimeZone(); // check to make sure it compiles
if (!explicitCalendar) guessCalendar(); 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; 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. // 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(); ULocale locale = (ULocale)it.next();
String temp = locale.getCountry(); result = locale.getCountry();
if (temp.length() != 0) { if (result.length() != 0) {
territory = temp; return result;
return;
} }
} }
// if not, guess from the first language tag, or maybe from intersection of languages, eg nl + fr => BE // if not, guess from the first language tag, or maybe from intersection of languages, eg nl + fr => BE
// TODO fix using real data // TODO fix using real data
// for now, just use fixed values // for now, just use fixed values
ULocale firstLocale = (ULocale)locales.iterator().next(); ULocale firstLocale = getLocale(0);
String language = firstLocale.getLanguage(); String language = firstLocale.getLanguage();
String script = firstLocale.getScript(); String script = firstLocale.getScript();
territory = null; result = null;
if (script.length() != 0) { 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 (result == null) result = (String) language_territory_hack_map.get(language);
if (territory == null) territory = "US"; // need *some* default 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 // TODO fix using real data
// for single-zone countries, pick that zone // for single-zone countries, pick that zone
// for others, pick the most populous 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. // NOTE: in a few cases can do better by looking at language.
// Eg haw+US should go to Pacific/Honolulu // Eg haw+US should go to Pacific/Honolulu
// fr+CA should go to America/Montreal // 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) { if (timezoneString == null) {
String[] attempt = ZoneMeta.getAvailableIDs(territory); String[] attempt = ZoneMeta.getAvailableIDs(getTerritory());
if (attempt.length == 0) { if (attempt.length == 0) {
timezoneString = "Etc/GMT"; // gotta do something timezoneString = "Etc/GMT"; // gotta do something
} else { } else {
@ -508,35 +735,39 @@ public class GlobalizationPreferences {
timezoneString = attempt[i]; 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 // TODO add better API
calendar = Calendar.getInstance(new ULocale("und-" + territory)); return Calendar.getInstance(new ULocale("und-" + getTerritory()));
} }
// PRIVATES // PRIVATES
private ArrayList locales = new ArrayList(); private Object locales;
private String territory; private String territory;
private Currency currency; private Currency currency;
private TimeZone timezone; private TimeZone timezone;
private Calendar calendar; private Calendar calendar;
private boolean explicitLocales; private Collator collator;
private boolean explicitTerritory;
private boolean explicitCurrency;
private boolean explicitTimezone;
private boolean explicitCalendar;
private ULocale dateLocale; private ULocale dateLocale;
private DateFormat[][] dateFormats; private Object[][] dateFormats;
private ULocale numberLocale; 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 Map language_territory_hack_map = new HashMap();
private static final String[][] language_territory_hack = { private static final String[][] language_territory_hack = {
{"af", "ZA"}, {"af", "ZA"},
@ -737,4 +968,24 @@ public class GlobalizationPreferences {
territory_tzid_hack_map.put(territory_tzid_hack[i][0],territory_tzid_hack[i][1]); 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;
}
}
} }