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;
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"},

View File

@ -42,75 +42,6 @@ public class BagFormatter {
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);
private static PrintWriter log = CONSOLE;

View File

@ -61,4 +61,80 @@ public class TransliteratorUtilities {
br.close();
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 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();
}
}

View File

@ -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();

View File

@ -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();) {

View File

@ -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;
}

View File

@ -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 (&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 {
/**

View File

@ -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;
}
}
}