mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-07 14:31:31 +00:00
ICU-1949 ICUService
initial check in of icuservice code X-SVN-Rev: 8902
This commit is contained in:
parent
31903ee436
commit
3d6edab415
7 changed files with 2641 additions and 0 deletions
571
icu4j/src/com/ibm/icu/dev/test/util/ICUServiceTest.java
Normal file
571
icu4j/src/com/ibm/icu/dev/test/util/ICUServiceTest.java
Normal file
|
@ -0,0 +1,571 @@
|
|||
package com.ibm.icu.dev.test.util;
|
||||
|
||||
import com.ibm.icu.dev.test.TestFmwk;
|
||||
import com.ibm.icu.impl.ICUService;
|
||||
import com.ibm.icu.impl.ICUService.Factory;
|
||||
import com.ibm.icu.impl.ICUService.Key;
|
||||
import com.ibm.icu.impl.ICUService.ServiceListener;
|
||||
import com.ibm.icu.impl.LocaleUtility;
|
||||
import com.ibm.icu.impl.ICULocaleData;
|
||||
import com.ibm.icu.impl.ICULocaleService;
|
||||
import com.ibm.icu.impl.ICULocaleService.LocaleKey;
|
||||
import com.ibm.icu.impl.ICULocaleService.MultipleKeyFactory;
|
||||
import com.ibm.icu.impl.ICULocaleService.ICUResourceBundleFactory;
|
||||
import java.util.Arrays;
|
||||
import java.util.EventListener;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class ICUServiceTest extends TestFmwk
|
||||
{
|
||||
public static void main(String[] args) throws Exception {
|
||||
ICUServiceTest test = new ICUServiceTest();
|
||||
test.run(args);
|
||||
}
|
||||
|
||||
private String lrmsg(String message, Object lhs, Object rhs) {
|
||||
return message + " lhs: " + lhs + " rhs: " + rhs;
|
||||
}
|
||||
|
||||
public void confirmBoolean(String message, boolean val) {
|
||||
logln(message, val, !val);
|
||||
}
|
||||
|
||||
public void confirmEqual(String message, Object lhs, Object rhs) {
|
||||
logln(lrmsg(message, lhs, rhs), lhs == null ? rhs == null : lhs.equals(rhs));
|
||||
}
|
||||
|
||||
public void confirmIdentical(String message, Object lhs, Object rhs) {
|
||||
logln(lrmsg(message, lhs, rhs), lhs == rhs);
|
||||
}
|
||||
|
||||
public void confirmIdentical(String message, int lhs, int rhs) {
|
||||
logln(message + " lhs: " + lhs + " rhs: " + rhs, lhs == rhs);
|
||||
}
|
||||
|
||||
// use locale keys
|
||||
static final class TestService extends ICUService {
|
||||
protected Key createKey(String id) {
|
||||
return LocaleKey.createWithCanonicalFallback(id, null); // no fallback locale
|
||||
}
|
||||
}
|
||||
|
||||
public void testAPI() {
|
||||
// create a service using locale keys,
|
||||
ICUService service = new TestService();
|
||||
|
||||
// register an object with one locale,
|
||||
// search for an object with a more specific locale
|
||||
// should return the original object
|
||||
Integer singleton0 = new Integer(0);
|
||||
service.registerObject(singleton0, "en_US");
|
||||
Object result = service.get("en_US_FOO");
|
||||
confirmIdentical("1) en_US_FOO -> en_US", result, singleton0);
|
||||
|
||||
// register a new object with the more specific locale
|
||||
// search for an object with that locale
|
||||
// should return the new object
|
||||
Integer singleton1 = new Integer(1);
|
||||
service.registerObject(singleton1, "en_US_FOO");
|
||||
result = service.get("en_US_FOO");
|
||||
confirmIdentical("2) en_US_FOO -> en_US_FOO", result, singleton1);
|
||||
|
||||
// search for an object that falls back to the first registered locale
|
||||
result = service.get("en_US_BAR");
|
||||
confirmIdentical("3) en_US_BAR -> en_US", result, singleton0);
|
||||
|
||||
// get a list of the factories, should be two
|
||||
List factories = service.factories();
|
||||
confirmIdentical("4) factory size", factories.size(), 2);
|
||||
|
||||
// register a new object with yet another locale
|
||||
// original factory list is unchanged
|
||||
Integer singleton2 = new Integer(2);
|
||||
service.registerObject(singleton2, "en");
|
||||
confirmIdentical("5) factory size", factories.size(), 2);
|
||||
|
||||
// search for an object with the new locale
|
||||
// stack of factories is now en, en_US_FOO, en_US
|
||||
// search for en_US should still find en_US object
|
||||
result = service.get("en_US_BAR");
|
||||
confirmIdentical("6) en_US_BAR -> en_US", result, singleton0);
|
||||
|
||||
// register a new object with an old id, should hide earlier factory using this id, but leave it there
|
||||
Integer singleton3 = new Integer(3);
|
||||
service.registerObject(singleton3, "en_US");
|
||||
factories = service.factories();
|
||||
confirmIdentical("9) factory size", factories.size(), 4);
|
||||
|
||||
// should get data from that new factory
|
||||
result = service.get("en_US_BAR");
|
||||
confirmIdentical("10) en_US_BAR -> (3)", result, singleton3);
|
||||
|
||||
// remove new factory
|
||||
// should have fewer factories again
|
||||
service.unregisterFactory((Factory)factories.get(0));
|
||||
factories = service.factories();
|
||||
confirmIdentical("11) factory size", factories.size(), 3);
|
||||
|
||||
// should get original data again after remove factory
|
||||
result = service.get("en_US_BAR");
|
||||
confirmIdentical("12) en_US_BAR -> 0", result, singleton0);
|
||||
|
||||
// shouldn't find unregistered ids
|
||||
result = service.get("foo");
|
||||
confirmIdentical("13) foo -> null", result, null);
|
||||
|
||||
// should find non-canonical strings
|
||||
String[] resultID = new String[1];
|
||||
result = service.get("EN_us_fOo", resultID);
|
||||
confirmEqual("14) find non-canonical", resultID[0], "en_US_FOO");
|
||||
|
||||
// should be able to register non-canonical strings and get them canonicalized
|
||||
service.registerObject(singleton3, "eN_ca_dUde");
|
||||
result = service.get("En_Ca_DuDe", resultID);
|
||||
confirmEqual("15) register non-canonical", resultID[0], "en_CA_DUDE");
|
||||
|
||||
// should be able to register invisible factories, these will not
|
||||
// be visible by default, but if you know the secret password you
|
||||
// can still access these services...
|
||||
Integer singleton4 = new Integer(4);
|
||||
service.registerObject(singleton4, "en_US_BAR", false);
|
||||
result = service.get("en_US_BAR");
|
||||
confirmIdentical("17) get invisible", result, singleton4);
|
||||
|
||||
// should not be able to locate invisible services
|
||||
Set ids = service.getVisibleIDs();
|
||||
confirmBoolean("18) find invisible", !ids.contains("en_US_BAR"));
|
||||
|
||||
service.reset();
|
||||
// an anonymous factory than handles all ids
|
||||
{
|
||||
Factory factory = new Factory() {
|
||||
public Object create(Key key) {
|
||||
return LocaleUtility.getLocaleFromName(key.currentID());
|
||||
}
|
||||
|
||||
public void updateVisibleIDs(Map result) {
|
||||
}
|
||||
|
||||
public String getDisplayName(String id, Locale l) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
service.registerFactory(factory);
|
||||
|
||||
// anonymous factory will still handle the id
|
||||
result = service.get(Locale.US.toString());
|
||||
confirmEqual("21) locale", result, Locale.US);
|
||||
|
||||
// still normalizes id
|
||||
result = service.get("EN_US_BAR");
|
||||
confirmEqual("22) locale", result, LocaleUtility.getLocaleFromName("en_US_BAR"));
|
||||
|
||||
// we can override for particular ids
|
||||
service.registerObject(singleton3, "en_US_BAR");
|
||||
result = service.get("en_US_BAR");
|
||||
confirmIdentical("23) override super", result, singleton3);
|
||||
}
|
||||
|
||||
// empty service should not recognize anything
|
||||
service.reset();
|
||||
result = service.get("en_US");
|
||||
confirmIdentical("24) empty", result, null);
|
||||
|
||||
// create a custom multiple key factory
|
||||
{
|
||||
String[] xids = { "en_US_VALLEY_GIRL",
|
||||
"en_US_VALLEY_BOY",
|
||||
"en_US_SURFER_GAL",
|
||||
"en_US_SURFER_DUDE"
|
||||
};
|
||||
service.registerFactory(new TestMultipleFactory(xids));
|
||||
}
|
||||
|
||||
// iterate over the visual ids returned by the multiple factory
|
||||
{
|
||||
Set vids = service.getVisibleIDs();
|
||||
Iterator iter = vids.iterator();
|
||||
int count = 0;
|
||||
while (iter.hasNext()) {
|
||||
++count;
|
||||
logln(" " + iter.next());
|
||||
}
|
||||
// four visible ids
|
||||
confirmIdentical("25) visible ids", count, 4);
|
||||
}
|
||||
|
||||
// iterate over the display names
|
||||
{
|
||||
Map dids = service.getDisplayNames(Locale.GERMANY);
|
||||
Iterator iter = dids.entrySet().iterator();
|
||||
int count = 0;
|
||||
while (iter.hasNext()) {
|
||||
++count;
|
||||
Entry e = (Entry)iter.next();
|
||||
logln(" " + e.getKey() + " -- > " + e.getValue());
|
||||
}
|
||||
// four display names, in german
|
||||
confirmIdentical("26) display names", count, 4);
|
||||
}
|
||||
|
||||
// no valid display name
|
||||
confirmIdentical("27) get display name", service.getDisplayName("en_US_VALLEY_GEEK"), null);
|
||||
|
||||
{
|
||||
String name = service.getDisplayName("en_US_SURFER_DUDE", Locale.US);
|
||||
confirmEqual("28) get display name", name, "English (United States,SURFER,DUDE)");
|
||||
}
|
||||
|
||||
// register another multiple factory
|
||||
{
|
||||
String[] xids = {
|
||||
"en_US_SURFER_GAL", "en_US_SILICON", "en_US_SILICON_GEEK", "en_US"
|
||||
};
|
||||
service.registerFactory(new TestMultipleFactory(xids, "Rad dude"));
|
||||
}
|
||||
|
||||
// this time, we have seven display names (we replaced surfer gal)
|
||||
{
|
||||
Map dids = service.getDisplayNames(LocaleUtility.getLocaleFromName("es"));
|
||||
Iterator iter = dids.entrySet().iterator();
|
||||
int count = 0;
|
||||
while (iter.hasNext()) {
|
||||
++count;
|
||||
Entry e = (Entry)iter.next();
|
||||
logln(" " + e.getKey() + " -- > " + e.getValue());
|
||||
}
|
||||
// seven display names, in spanish
|
||||
confirmIdentical("29) display names", count, 7);
|
||||
}
|
||||
|
||||
// we should get the display name corresponding to the actual id
|
||||
// returned by the id we used.
|
||||
{
|
||||
String[] actualID = new String[1];
|
||||
String id = "en_us_silicon_dude";
|
||||
String dude = (String)service.get(id, actualID);
|
||||
if (dude != null) {
|
||||
String displayName = service.getDisplayName(actualID[0], Locale.US);
|
||||
logln("found actual: " + dude + " with display name: " + displayName);
|
||||
confirmBoolean("30) found display name for actual", displayName != null);
|
||||
|
||||
displayName = service.getDisplayName(id, Locale.US);
|
||||
logln("found query: " + dude + " with display name: " + displayName);
|
||||
confirmBoolean("31) found display name for query", displayName == null);
|
||||
} else {
|
||||
errln("30) service could not find entry for " + id);
|
||||
}
|
||||
|
||||
id = "en_US_BOZO";
|
||||
String bozo = (String)service.get(id, actualID);
|
||||
if (bozo != null) {
|
||||
String displayName = service.getDisplayName(actualID[0], Locale.US);
|
||||
logln("found actual: " + bozo + " with display name: " + displayName);
|
||||
confirmBoolean("32) found display name for actual", displayName != null);
|
||||
|
||||
displayName = service.getDisplayName(id, Locale.US);
|
||||
logln("found actual: " + bozo + " with display name: " + displayName);
|
||||
confirmBoolean("33) found display name for query", displayName == null);
|
||||
} else {
|
||||
errln("32) service could not find entry for " + id);
|
||||
}
|
||||
}
|
||||
|
||||
// hiding factory should obscure 'sublocales'
|
||||
{
|
||||
String[] xids = {
|
||||
"en_US_VALLEY", "en_US_SILICON"
|
||||
};
|
||||
service.registerFactory(new TestHidingFactory(xids));
|
||||
}
|
||||
|
||||
{
|
||||
Map dids = service.getDisplayNames();
|
||||
Iterator iter = dids.entrySet().iterator();
|
||||
int count = 0;
|
||||
while (iter.hasNext()) {
|
||||
++count;
|
||||
Entry e = (Entry)iter.next();
|
||||
logln(" " + e.getKey() + " -- > " + e.getValue());
|
||||
}
|
||||
confirmIdentical("31 hiding factory", count, 5);
|
||||
}
|
||||
|
||||
{
|
||||
Set xids = service.getVisibleIDs();
|
||||
Iterator iter = xids.iterator();
|
||||
while (iter.hasNext()) {
|
||||
String xid = (String)iter.next();
|
||||
logln(xid + "? " + service.get(xid));
|
||||
}
|
||||
|
||||
logln("valleygirl? " + service.get("en_US_VALLEY_GIRL"));
|
||||
logln("valleyboy? " + service.get("en_US_VALLEY_BOY"));
|
||||
logln("valleydude? " + service.get("en_US_VALLEY_DUDE"));
|
||||
logln("surfergirl? " + service.get("en_US_SURFER_GIRL"));
|
||||
}
|
||||
|
||||
// resource bundle factory.
|
||||
service.reset();
|
||||
service.registerFactory(new ICUResourceBundleFactory("Countries;Languages", true));
|
||||
|
||||
// list all of the resources that really define Countries;Languages
|
||||
// this takes a long time to build the visible id list
|
||||
{
|
||||
Set xids = service.getVisibleIDs();
|
||||
StringBuffer buf = new StringBuffer("{");
|
||||
boolean notfirst = false;
|
||||
Iterator iter = xids.iterator();
|
||||
while (iter.hasNext()) {
|
||||
String xid = (String)iter.next();
|
||||
if (notfirst) {
|
||||
buf.append(", ");
|
||||
} else {
|
||||
notfirst = true;
|
||||
}
|
||||
buf.append(xid);
|
||||
}
|
||||
buf.append("}");
|
||||
logln(buf.toString());
|
||||
}
|
||||
|
||||
// get all the display names of these resources
|
||||
// this should be fast since the display names were cached.
|
||||
{
|
||||
Map names = service.getDisplayNames(LocaleUtility.getLocaleFromName("de_DE"));
|
||||
StringBuffer buf = new StringBuffer("{");
|
||||
Iterator iter = names.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Entry e = (Entry)iter.next();
|
||||
String name = (String)e.getKey();
|
||||
String id = (String)e.getValue();
|
||||
buf.append("\n " + name + " --> " + id);
|
||||
}
|
||||
buf.append("\n}");
|
||||
logln(buf.toString());
|
||||
}
|
||||
|
||||
service.registerFactory(new CalifornioLanguageFactory());
|
||||
// get all the display names of these resources
|
||||
{
|
||||
Map names = service.getDisplayNames(LocaleUtility.getLocaleFromName("en_US_CA_SURFER"));
|
||||
StringBuffer buf = new StringBuffer("{");
|
||||
Iterator iter = names.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Entry e = (Entry)iter.next();
|
||||
String name = (String)e.getKey();
|
||||
String id = (String)e.getValue();
|
||||
buf.append("\n " + name + " --> " + id);
|
||||
}
|
||||
buf.append("\n}");
|
||||
logln(buf.toString());
|
||||
}
|
||||
|
||||
// test notification
|
||||
// simple registration
|
||||
{
|
||||
ICULocaleService ls = new ICULocaleService();
|
||||
ServiceListener l1 = new ServiceListener() {
|
||||
private int n;
|
||||
public void serviceChanged(ICUService s) {
|
||||
logln("listener 1 report " + n++ + " service changed: " + s);
|
||||
}
|
||||
};
|
||||
ls.addListener(l1);
|
||||
ServiceListener l2 = new ServiceListener() {
|
||||
private int n;
|
||||
public void serviceChanged(ICUService s) {
|
||||
logln("listener 2 report " + n++ + " service changed: " + s);
|
||||
}
|
||||
};
|
||||
ls.addListener(l2);
|
||||
logln("registering foo... ");
|
||||
ls.registerObject("Foo", "en_FOO");
|
||||
logln("registering bar... ");
|
||||
ls.registerObject("Bar", "en_BAR");
|
||||
logln("getting foo...");
|
||||
logln((String)ls.get("en_FOO"));
|
||||
logln("removing listener 2...");
|
||||
ls.removeListener(l2);
|
||||
logln("registering baz...");
|
||||
ls.registerObject("Baz", "en_BAZ");
|
||||
logln("removing listener 1");
|
||||
ls.removeListener(l1);
|
||||
logln("registering burp...");
|
||||
ls.registerObject("Burp", "en_BURP");
|
||||
|
||||
// should only get one notification even if register multiple times
|
||||
logln("... trying multiple registration");
|
||||
ls.addListener(l1);
|
||||
ls.addListener(l1);
|
||||
ls.addListener(l1);
|
||||
ls.addListener(l2);
|
||||
ls.registerObject("Foo", "en_FOO");
|
||||
logln("... registered foo");
|
||||
|
||||
// since in a separate thread, we can callback and not deadlock
|
||||
ServiceListener l3 = new ServiceListener() {
|
||||
private int n;
|
||||
public void serviceChanged(ICUService s) {
|
||||
logln("listener 3 report " + n++ + " service changed...");
|
||||
if (s.get("en_BOINK") == null) { // don't recurse on ourselves!!!
|
||||
logln("registering boink...");
|
||||
s.registerObject("boink", "en_BOINK");
|
||||
}
|
||||
}
|
||||
};
|
||||
ls.addListener(l3);
|
||||
logln("registering boo...");
|
||||
ls.registerObject("Boo", "en_BOO");
|
||||
logln("...done");
|
||||
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class TestMultipleFactory extends MultipleKeyFactory {
|
||||
protected final String[] ids;
|
||||
protected final String factoryID;
|
||||
|
||||
public TestMultipleFactory(String[] ids) {
|
||||
this(ids, "");
|
||||
}
|
||||
|
||||
public TestMultipleFactory(String[] ids, String factoryID) {
|
||||
this.ids = (String[])ids.clone();
|
||||
|
||||
if (factoryID == null || factoryID.length() == 0) {
|
||||
this.factoryID = "";
|
||||
} else {
|
||||
this.factoryID = factoryID + ": ";
|
||||
}
|
||||
}
|
||||
|
||||
protected Object handleCreate(Key key) {
|
||||
for (int i = 0; i < ids.length; ++i) {
|
||||
if (key.currentID().equalsIgnoreCase(ids[i])) {
|
||||
return factoryID + key.canonicalID();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void handleUpdateVisibleIDs(Set result) {
|
||||
for (int i = 0; i < ids.length; ++i) {
|
||||
result.add(ids[i]);
|
||||
}
|
||||
}
|
||||
protected String handleGetDisplayName(String id, Locale locale) {
|
||||
return factoryID + LocaleUtility.getLocaleFromName(id).getDisplayName(locale);
|
||||
}
|
||||
}
|
||||
|
||||
static class TestHidingFactory implements ICUService.Factory {
|
||||
protected final String[] ids;
|
||||
protected final String factoryID;
|
||||
|
||||
public TestHidingFactory(String[] ids) {
|
||||
this(ids, "Hiding");
|
||||
}
|
||||
|
||||
public TestHidingFactory(String[] ids, String factoryID) {
|
||||
this.ids = (String[])ids.clone();
|
||||
|
||||
if (factoryID == null || factoryID.length() == 0) {
|
||||
this.factoryID = "";
|
||||
} else {
|
||||
this.factoryID = factoryID + ": ";
|
||||
}
|
||||
}
|
||||
|
||||
public Object create(Key key) {
|
||||
for (int i = 0; i < ids.length; ++i) {
|
||||
if (LocaleUtility.isFallbackOf(ids[i], key.currentID())) {
|
||||
return factoryID + key.canonicalID();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void updateVisibleIDs(Map result) {
|
||||
for (int i = 0; i < ids.length; ++i) {
|
||||
String id = ids[i];
|
||||
Iterator iter = result.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
if (LocaleUtility.isFallbackOf(id, (String)iter.next())) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
result.put(id, this);
|
||||
}
|
||||
}
|
||||
|
||||
public String getDisplayName(String id, Locale locale) {
|
||||
return factoryID + LocaleUtility.getLocaleFromName(id).getDisplayName(locale);
|
||||
}
|
||||
}
|
||||
|
||||
static class CalifornioLanguageFactory extends ICUResourceBundleFactory {
|
||||
CalifornioLanguageFactory() {
|
||||
super("Countries;Languages", true);
|
||||
}
|
||||
|
||||
private static String californio = "en_US_CA";
|
||||
private static String valley = californio + "_VALLEY";
|
||||
private static String surfer = californio + "_SURFER";
|
||||
private static String geek = californio + "_GEEK";
|
||||
|
||||
public void handleUpdateVisibleIDs(Set result) {
|
||||
super.handleUpdateVisibleIDs(result);
|
||||
|
||||
result.add(californio);
|
||||
result.add(valley);
|
||||
result.add(surfer);
|
||||
result.add(geek);
|
||||
}
|
||||
|
||||
protected String handleGetDisplayName(String id, Locale locale) {
|
||||
String prefix = "";
|
||||
String suffix = "";
|
||||
String ls = locale.toString();
|
||||
if (LocaleUtility.isFallbackOf(californio, ls)) {
|
||||
if (ls.equalsIgnoreCase(valley)) {
|
||||
prefix = "Like, you know, it's so totally ";
|
||||
} else if (ls.equalsIgnoreCase(surfer)) {
|
||||
prefix = "Dude, its ";
|
||||
} else if (ls.equalsIgnoreCase(geek)) {
|
||||
prefix = "I'd estimate it's approximately ";
|
||||
} else {
|
||||
prefix = "Huh? Maybe ";
|
||||
}
|
||||
}
|
||||
if (LocaleUtility.isFallbackOf(californio, id)) {
|
||||
if (id.equalsIgnoreCase(valley)) {
|
||||
suffix = "like the Valley, you know? Let's go to the mall!";
|
||||
} else if (id.equalsIgnoreCase(surfer)) {
|
||||
suffix = "time to hit those gnarly waves, Dude!!!";
|
||||
} else if (id.equalsIgnoreCase(geek)) {
|
||||
suffix = "all systems go. T-Minus 9, 8, 7...";
|
||||
} else {
|
||||
suffix = "No Habla Englais";
|
||||
}
|
||||
} else {
|
||||
suffix = super.handleGetDisplayName(id, locale);
|
||||
}
|
||||
|
||||
return prefix + suffix;
|
||||
}
|
||||
}
|
||||
}
|
198
icu4j/src/com/ibm/icu/dev/test/util/ICUServiceTestSample.java
Normal file
198
icu4j/src/com/ibm/icu/dev/test/util/ICUServiceTestSample.java
Normal file
|
@ -0,0 +1,198 @@
|
|||
package com.ibm.icu.dev.test.util;
|
||||
|
||||
import com.ibm.icu.impl.ICULocaleService;
|
||||
import com.ibm.icu.impl.ICUService;
|
||||
import com.ibm.icu.impl.LocaleUtility;
|
||||
import java.util.EventListener;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
public class ICUServiceTestSample {
|
||||
static public void main(String[] args) {
|
||||
HelloServiceClient client = new HelloServiceClient();
|
||||
|
||||
Thread t = new HelloUpdateThread();
|
||||
t.start();
|
||||
try {
|
||||
t.join();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
}
|
||||
System.out.println("done");
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that displays the current names in the Hello service.
|
||||
* Each time the service changes, it redisplays the names.
|
||||
*/
|
||||
static class HelloServiceClient implements HelloService.HelloServiceListener {
|
||||
|
||||
HelloServiceClient() {
|
||||
HelloService.addListener(this);
|
||||
display();
|
||||
}
|
||||
|
||||
/**
|
||||
* This will be called in the notification thread of
|
||||
* ICUNotifier. ICUNotifier could spawn a (non-daemon) thread
|
||||
* for each listener, so that impolite listeners wouldn't hold
|
||||
* up notification, but right now it doesn't. Instead, all
|
||||
* notifications are delivered on the notification thread.
|
||||
* Since that's a daemon thread, a notification might not
|
||||
* complete before main terminates.
|
||||
*/
|
||||
public void helloServiceChanged() {
|
||||
display();
|
||||
}
|
||||
|
||||
private void display() {
|
||||
Map names = HelloService.getDisplayNames(Locale.US);
|
||||
System.out.println("displaying " + names.size() + " names.");
|
||||
Iterator iter = names.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Entry entry = (Entry)iter.next();
|
||||
String displayName = (String)entry.getKey();
|
||||
HelloService service = HelloService.get((String)entry.getValue());
|
||||
System.out.println(displayName + " says " + service.hello());
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
System.out.println("----");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A thread to update the service.
|
||||
*/
|
||||
static class HelloUpdateThread extends Thread {
|
||||
String[][] updates = {
|
||||
{ "Hey", "en_US_INFORMAL" },
|
||||
{ "Hallo", "de_DE_INFORMAL" },
|
||||
{ "Yo!", "en_US_CALIFORNIA_INFORMAL" },
|
||||
{ "Chi Fanle Ma?", "zh__INFORMAL" },
|
||||
{ "Munch munch... Burger?", "en" },
|
||||
{ "Sniff", "fr" },
|
||||
{ "TongZhi! MaoZeDong SiXiang Wan Sui!", "zh_CN" },
|
||||
{ "Bier? Ja!", "de" },
|
||||
};
|
||||
public void run() {
|
||||
for (int i = 0; i < updates.length; ++i) {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
}
|
||||
HelloService.register(updates[i][0], LocaleUtility.getLocaleFromName(updates[i][1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An example service that wraps an ICU service in order to export custom API and
|
||||
* notification. The service just implements 'hello'.
|
||||
*/
|
||||
static final class HelloService {
|
||||
private static ICUService registry;
|
||||
private String name;
|
||||
|
||||
private HelloService(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The hello service...
|
||||
*/
|
||||
public String hello() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return super.toString() + ": " + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deferred init.
|
||||
*/
|
||||
private static ICUService registry() {
|
||||
if (registry == null) {
|
||||
initRegistry();
|
||||
}
|
||||
return registry;
|
||||
}
|
||||
|
||||
private static void initRegistry() {
|
||||
registry = new ICULocaleService() {
|
||||
protected boolean acceptsListener(EventListener l) {
|
||||
return true; // we already verify in our wrapper APIs
|
||||
}
|
||||
protected void notifyListener(EventListener l) {
|
||||
((HelloServiceListener)l).helloServiceChanged();
|
||||
}
|
||||
};
|
||||
|
||||
// initialize
|
||||
doRegister("Hello", "en");
|
||||
doRegister("Bonjour", "fr");
|
||||
doRegister("Ni Hao", "zh_CN");
|
||||
doRegister("Guten Tag", "de");
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom listener for changes to this service. We don't need to
|
||||
* point to the service since it is defined by this class and not
|
||||
* an object.
|
||||
*/
|
||||
public static interface HelloServiceListener extends EventListener {
|
||||
public void helloServiceChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-safe notification for this service.
|
||||
*/
|
||||
public static void addListener(HelloServiceListener l) {
|
||||
registry().addListener(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-safe notification for this service.
|
||||
*/
|
||||
public static void removeListener(HelloServiceListener l) {
|
||||
registry().removeListener(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-safe access to the service.
|
||||
*/
|
||||
public static HelloService get(String id) {
|
||||
return (HelloService)registry().get(id);
|
||||
}
|
||||
|
||||
public static Set getVisibleIDs() {
|
||||
return registry().getVisibleIDs();
|
||||
}
|
||||
|
||||
public static Map getDisplayNames(Locale locale) {
|
||||
return registry().getDisplayNames(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new hello string for this locale.
|
||||
*/
|
||||
public static void register(String helloString, Locale locale) {
|
||||
if (helloString == null || locale == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
doRegister(helloString, LocaleUtility.canonicalLocaleString(locale.toString()));
|
||||
}
|
||||
|
||||
private static void doRegister(String hello, String id) {
|
||||
registry().registerObject(new HelloService(hello), id);
|
||||
}
|
||||
}
|
||||
}
|
443
icu4j/src/com/ibm/icu/dev/test/util/ICUServiceThreadTest.java
Normal file
443
icu4j/src/com/ibm/icu/dev/test/util/ICUServiceThreadTest.java
Normal file
|
@ -0,0 +1,443 @@
|
|||
package com.ibm.icu.dev.test.util;
|
||||
|
||||
import com.ibm.icu.dev.test.TestFmwk;
|
||||
import com.ibm.icu.dev.test.TestLog;
|
||||
import com.ibm.icu.impl.ICUService;
|
||||
import com.ibm.icu.impl.ICUService.Factory;
|
||||
import com.ibm.icu.impl.ICUService.SimpleFactory;
|
||||
import com.ibm.icu.impl.ICUService.Key;
|
||||
import com.ibm.icu.impl.LocaleUtility;
|
||||
import com.ibm.icu.impl.ICULocaleData;
|
||||
import com.ibm.icu.impl.ICULocaleService;
|
||||
import com.ibm.icu.impl.ICULocaleService.LocaleKey;
|
||||
import com.ibm.icu.impl.ICULocaleService.MultipleKeyFactory;
|
||||
import com.ibm.icu.impl.ICULocaleService.ICUResourceBundleFactory;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Random;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class ICUServiceThreadTest extends TestFmwk
|
||||
{
|
||||
public static void main(String[] args) throws Exception {
|
||||
ICUServiceThreadTest test = new ICUServiceThreadTest();
|
||||
test.run(args);
|
||||
|
||||
// get
|
||||
// getvisibleids
|
||||
// getdisplayname(locale)
|
||||
// factories
|
||||
|
||||
// registerFactory
|
||||
// unregisterFactory
|
||||
|
||||
// 1) concurrent access
|
||||
// 2) access while factories change
|
||||
// 3) iteration while factories change
|
||||
// 4) concurrent conflicting access
|
||||
}
|
||||
|
||||
private static final String[] countries = {
|
||||
"ab", "bc", "cd", "de", "ef", "fg", "gh", "ji", "ij", "jk"
|
||||
};
|
||||
private static final String[] languages = {
|
||||
"", "ZY", "YX", "XW", "WV", "VU", "UT", "TS", "SR", "RQ", "QP"
|
||||
};
|
||||
private static final String[] variants = {
|
||||
"", "", "", "GOLD", "SILVER", "BRONZE"
|
||||
};
|
||||
|
||||
private static class TestFactory extends SimpleFactory {
|
||||
TestFactory(String id) {
|
||||
super(LocaleUtility.getLocaleFromName(id), id, true);
|
||||
}
|
||||
|
||||
public String getDisplayName(String id, Locale locale) {
|
||||
return (visible && id.equals(this.id)) ? "(" + locale.toString() + ") " + id : null;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Factory_" + id;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Random r = new Random();
|
||||
|
||||
private static String getCLV() {
|
||||
String c = countries[r.nextInt(countries.length)];
|
||||
String l = languages[r.nextInt(languages.length)];
|
||||
String v = variants[r.nextInt(variants.length)];
|
||||
return new Locale(c, l, v).toString();
|
||||
}
|
||||
|
||||
private static boolean WAIT = true;
|
||||
private static boolean GO = false;
|
||||
private static long TIME = 5000;
|
||||
|
||||
public static void runThreads() {
|
||||
runThreads(TIME);
|
||||
}
|
||||
|
||||
public static void runThreads(long time) {
|
||||
try {
|
||||
GO = true;
|
||||
WAIT = false;
|
||||
|
||||
Thread.sleep(time);
|
||||
|
||||
WAIT = true;
|
||||
GO = false;
|
||||
|
||||
Thread.sleep(300);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
static class TestThread extends Thread implements TestLog {
|
||||
private final String name;
|
||||
protected ICUService service;
|
||||
private final long delay;
|
||||
private final TestLog log;
|
||||
|
||||
public TestThread(String name, ICUService service, long delay, TestLog log) {
|
||||
this.name = name + " ";
|
||||
this.service = service;
|
||||
this.delay = delay;
|
||||
this.log = log;
|
||||
this.setDaemon(true);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (WAIT) {
|
||||
Thread.yield();
|
||||
}
|
||||
|
||||
try {
|
||||
while (GO) {
|
||||
iterate();
|
||||
if (delay > 0) {
|
||||
Thread.sleep(delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
protected void iterate() {
|
||||
}
|
||||
|
||||
public boolean logging() {
|
||||
return log != null;
|
||||
}
|
||||
|
||||
public void log(String msg) {
|
||||
if (logging()) {
|
||||
log.log(name + msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void logln(String msg) {
|
||||
if (logging()) {
|
||||
log.logln(name + msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void err(String msg) {
|
||||
if (logging()) {
|
||||
log.err(name + msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void errln(String msg) {
|
||||
if (logging()) {
|
||||
log.errln(name + msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class RegisterFactoryThread extends TestThread {
|
||||
RegisterFactoryThread(String name, ICUService service, long delay, TestLog log) {
|
||||
super("REG " + name, service, delay, log);
|
||||
}
|
||||
|
||||
protected void iterate() {
|
||||
Factory f = new TestFactory(getCLV());
|
||||
service.registerFactory(f);
|
||||
logln(f.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static class UnregisterFactoryThread extends TestThread {
|
||||
private Random r;
|
||||
List factories;
|
||||
|
||||
UnregisterFactoryThread(String name, ICUService service, long delay, TestLog log) {
|
||||
super("UNREG " + name, service, delay, log);
|
||||
|
||||
r = new Random();
|
||||
factories = service.factories();
|
||||
}
|
||||
|
||||
public void iterate() {
|
||||
int s = factories.size();
|
||||
if (s == 0) {
|
||||
factories = service.factories();
|
||||
} else {
|
||||
int n = r.nextInt(s);
|
||||
Factory f = (Factory)factories.remove(n);
|
||||
boolean success = service.unregisterFactory(f);
|
||||
if (logging()) logln("factory: " + f + (success ? " succeeded." : " *** failed."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class UnregisterFactoryListThread extends TestThread {
|
||||
Factory[] factories;
|
||||
int n;
|
||||
|
||||
UnregisterFactoryListThread(String name, ICUService service, long delay, Factory[] factories, TestLog log) {
|
||||
super("UNREG " + name, service, delay, log);
|
||||
|
||||
this.factories = factories;
|
||||
}
|
||||
|
||||
public void iterate() {
|
||||
if (n < factories.length) {
|
||||
Factory f = factories[n++];
|
||||
boolean success = service.unregisterFactory(f);
|
||||
if (logging()) logln("factory: " + f + (success ? " succeeded." : " *** failed."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class GetVisibleThread extends TestThread {
|
||||
GetVisibleThread(String name, ICUService service, long delay, TestLog log) {
|
||||
super("VIS " + name, service, delay, log);
|
||||
}
|
||||
|
||||
protected void iterate() {
|
||||
Set ids = service.getVisibleIDs();
|
||||
Iterator iter = ids.iterator();
|
||||
int n = 10;
|
||||
while (--n >= 0 && iter.hasNext()) {
|
||||
String id = (String)iter.next();
|
||||
Object result = service.get(id);
|
||||
logln("iter: " + n + " id: " + id + " result: " + result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class GetDisplayThread extends TestThread {
|
||||
Locale locale;
|
||||
|
||||
GetDisplayThread(String name, ICUService service, long delay, Locale locale, TestLog log) {
|
||||
super("DIS " + name, service, delay, log);
|
||||
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
protected void iterate() {
|
||||
Map names = service.getDisplayNames(locale);
|
||||
Iterator iter = names.entrySet().iterator();
|
||||
int n = 10;
|
||||
while (--n >= 0 && iter.hasNext()) {
|
||||
Entry e = (Entry)iter.next();
|
||||
String dname = (String)e.getKey();
|
||||
String id = (String)e.getValue();
|
||||
Object result = service.get(id);
|
||||
if (logging()) logln(" iter: " + n +
|
||||
" dname: " + dname +
|
||||
" id: " + id +
|
||||
" result: " + result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class GetThread extends TestThread {
|
||||
private String[] actualID;
|
||||
|
||||
GetThread(String name, ICUService service, long delay, TestLog log) {
|
||||
super("GET " + name, service, delay, log);
|
||||
|
||||
actualID = new String[1];
|
||||
}
|
||||
|
||||
protected void iterate() {
|
||||
String id = getCLV();
|
||||
Object o = service.get(id, actualID);
|
||||
if (logging() && o != null) {
|
||||
logln(" id: " + id + " actual: " + actualID[0] + " result: " + o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class GetListThread extends TestThread {
|
||||
private final String[] list;
|
||||
private int n;
|
||||
|
||||
GetListThread(String name, ICUService service, long delay, String[] list, TestLog log) {
|
||||
super("GETL " + name, service, delay, log);
|
||||
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
protected void iterate() {
|
||||
if (--n < 0) {
|
||||
n = list.length - 1;
|
||||
}
|
||||
String id = list[n];
|
||||
Object o = service.get(id);
|
||||
if (logging()) {
|
||||
logln(" id: " + id + " result: " + o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return a collection of unique factories, might be fewer than requested
|
||||
Collection getFactoryCollection(int requested) {
|
||||
Set locales = new HashSet();
|
||||
for (int i = 0; i < requested; ++i) {
|
||||
locales.add(getCLV());
|
||||
}
|
||||
List factories = new ArrayList(locales.size());
|
||||
Iterator iter = locales.iterator();
|
||||
while (iter.hasNext()) {
|
||||
factories.add(new TestFactory((String)iter.next()));
|
||||
}
|
||||
return factories;
|
||||
}
|
||||
|
||||
void registerFactories(ICUService service, Collection c) {
|
||||
Iterator iter = c.iterator();
|
||||
while (iter.hasNext()) {
|
||||
service.registerFactory((Factory)iter.next());
|
||||
}
|
||||
}
|
||||
|
||||
ICUService stableService() {
|
||||
if (stableService == null) {
|
||||
stableService = new ICULocaleService();
|
||||
registerFactories(stableService, getFactoryCollection(50));
|
||||
}
|
||||
return stableService;
|
||||
}
|
||||
private ICUService stableService;
|
||||
|
||||
// run multiple get on a stable service
|
||||
public void Test00_ConcurrentGet() {
|
||||
for(int i = 0; i < 10; ++i) {
|
||||
new GetThread("[" + Integer.toString(i) + "]", stableService(), 0, this).start();
|
||||
}
|
||||
runThreads();
|
||||
logln(stableService.stats());
|
||||
}
|
||||
|
||||
// run multiple getVisibleID on a stable service
|
||||
public void Test01_ConcurrentGetVisible() {
|
||||
for(int i = 0; i < 10; ++i) {
|
||||
new GetVisibleThread("[" + Integer.toString(i) + "]", stableService(), 0, this).start();
|
||||
}
|
||||
runThreads();
|
||||
logln(stableService.stats());
|
||||
}
|
||||
|
||||
// run multiple getDisplayName on a stable service
|
||||
public void Test02_ConcurrentGetDisplay() {
|
||||
String[] localeNames = {
|
||||
"en", "es", "de", "fr", "zh", "it", "no", "sv"
|
||||
};
|
||||
for(int i = 0; i < localeNames.length; ++i) {
|
||||
String locale = localeNames[i];
|
||||
new GetDisplayThread("[" + locale + "]",
|
||||
stableService(),
|
||||
0,
|
||||
LocaleUtility.getLocaleFromName(locale),
|
||||
this).start();
|
||||
}
|
||||
runThreads();
|
||||
logln(stableService.stats());
|
||||
}
|
||||
|
||||
// run register/unregister on a service
|
||||
public void Test03_ConcurrentRegUnreg() {
|
||||
ICUService service = new ICULocaleService();
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
new RegisterFactoryThread("[" + i + "]", service, 0, this).start();
|
||||
}
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
new UnregisterFactoryThread("[" + i + "]", service, 0, this).start();
|
||||
}
|
||||
runThreads();
|
||||
logln(service.stats());
|
||||
}
|
||||
|
||||
public void Test04_WitheringService() {
|
||||
ICUService service = new ICULocaleService();
|
||||
|
||||
Collection fc = getFactoryCollection(50);
|
||||
registerFactories(service, fc);
|
||||
|
||||
Factory[] factories = (Factory[])fc.toArray(new Factory[fc.size()]);
|
||||
Comparator comp = new Comparator() {
|
||||
public int compare(Object lhs, Object rhs) {
|
||||
return lhs.toString().compareTo(rhs.toString());
|
||||
}
|
||||
};
|
||||
Arrays.sort(factories, comp);
|
||||
|
||||
new GetThread("", service, 0, this).start();
|
||||
new UnregisterFactoryListThread("", service, 3, factories, this).start();
|
||||
|
||||
runThreads(2000);
|
||||
logln(service.stats());
|
||||
}
|
||||
|
||||
// "all hell breaks loose"
|
||||
// one register and one unregister thread, delay 500ms
|
||||
// two display threads with different locales, delay 500ms;
|
||||
// one visible id thread, delay 50ms
|
||||
// fifteen get threads, delay 0
|
||||
// run for ten seconds
|
||||
public void Test05_ConcurrentEverything() {
|
||||
ICUService service = new ICULocaleService();
|
||||
|
||||
new RegisterFactoryThread("", service, 500, this).start();
|
||||
|
||||
for(int i = 0; i < 15; ++i) {
|
||||
new GetThread("[" + Integer.toString(i) + "]", service, 0, this).start();
|
||||
}
|
||||
|
||||
new GetVisibleThread("", service, 50, this).start();
|
||||
|
||||
String[] localeNames = {
|
||||
"en", "de"
|
||||
};
|
||||
for(int i = 0; i < localeNames.length; ++i) {
|
||||
String locale = localeNames[i];
|
||||
new GetDisplayThread("[" + locale + "]",
|
||||
stableService(),
|
||||
500,
|
||||
LocaleUtility.getLocaleFromName(locale),
|
||||
this).start();
|
||||
}
|
||||
|
||||
new UnregisterFactoryThread("", service, 500, this).start();
|
||||
|
||||
// yoweee!!!
|
||||
runThreads(10000);
|
||||
logln(service.stats());
|
||||
}
|
||||
}
|
364
icu4j/src/com/ibm/icu/impl/ICULocaleService.java
Normal file
364
icu4j/src/com/ibm/icu/impl/ICULocaleService.java
Normal file
|
@ -0,0 +1,364 @@
|
|||
package com.ibm.icu.impl;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
|
||||
import java.util.Enumeration;
|
||||
|
||||
public class ICULocaleService extends ICUService {
|
||||
private String fallbackLocale;
|
||||
|
||||
/**
|
||||
* Construct an ICULocaleService with a fallback locale string based on the current
|
||||
* default locale at the time of construction.
|
||||
*/
|
||||
public ICULocaleService() {
|
||||
fallbackLocale = LocaleUtility.canonicalLocaleString(Locale.getDefault().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an ICULocaleService with the provided fallback locale string.
|
||||
*/
|
||||
public ICULocaleService(String fallbackLocale) {
|
||||
this.fallbackLocale = fallbackLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* A subclass of Key that implements a locale fallback mechanism.
|
||||
* The first locale to search for is the locale provided by the
|
||||
* client, and the fallback locale to search for is the default
|
||||
* locale. This is instantiated by ICULocaleService.</p>
|
||||
*
|
||||
* <p>Canonicalization adjusts the locale string so that the
|
||||
* section before the first understore is in lower case, and the rest
|
||||
* is in upper case, with no trailing underscores.
|
||||
*/
|
||||
public static class LocaleKey extends ICUService.Key {
|
||||
private String primaryID;
|
||||
private String fallbackID;
|
||||
private String currentID;
|
||||
|
||||
public static LocaleKey create(String primaryID, String fallbackID) {
|
||||
String canonicalPrimaryID = LocaleUtility.canonicalLocaleString(primaryID);
|
||||
String canonicalFallbackID = LocaleUtility.canonicalLocaleString(fallbackID);
|
||||
return new LocaleKey(primaryID, canonicalPrimaryID, canonicalFallbackID);
|
||||
}
|
||||
|
||||
public static LocaleKey createWithCanonicalFallback(String primaryID, String canonicalFallbackID) {
|
||||
String canonicalPrimaryID = LocaleUtility.canonicalLocaleString(primaryID);
|
||||
return new LocaleKey(primaryID, canonicalPrimaryID, canonicalFallbackID);
|
||||
}
|
||||
|
||||
public static LocaleKey createWithCanonical(String canonicalPrimaryID, String canonicalFallbackID) {
|
||||
return new LocaleKey(canonicalPrimaryID, canonicalPrimaryID, canonicalFallbackID);
|
||||
}
|
||||
|
||||
/**
|
||||
* PrimaryID is the user's requested locale string in
|
||||
* canonical form, fallbackID is the default locale's string
|
||||
* in canonical form.
|
||||
*/
|
||||
protected LocaleKey(String primaryID, String canonicalPrimaryID, String canonicalFallbackID) {
|
||||
super(primaryID);
|
||||
|
||||
if (canonicalPrimaryID == null) {
|
||||
this.primaryID = "";
|
||||
} else {
|
||||
this.primaryID = canonicalPrimaryID;
|
||||
}
|
||||
if (this.primaryID == "") {
|
||||
this.fallbackID = null;
|
||||
} else {
|
||||
if (canonicalFallbackID == null || this.primaryID.equals(canonicalFallbackID)) {
|
||||
this.fallbackID = "";
|
||||
} else {
|
||||
this.fallbackID = canonicalFallbackID;
|
||||
}
|
||||
}
|
||||
|
||||
this.currentID = this.primaryID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the (canonical) original ID.
|
||||
*/
|
||||
public String canonicalID() {
|
||||
return primaryID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the (canonical) current ID.
|
||||
*/
|
||||
public String currentID() {
|
||||
return currentID;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the key has a fallback, modify the key and return true,
|
||||
* otherwise return false.</p>
|
||||
*
|
||||
* <p>First falls back through the primary ID, then through
|
||||
* the fallbackID. The final fallback is the empty string,
|
||||
* unless the primary id was the empty string, in which case
|
||||
* there is no fallback.
|
||||
*/
|
||||
public boolean fallback() {
|
||||
String current = currentID();
|
||||
int x = current.lastIndexOf('_');
|
||||
if (x != -1) {
|
||||
currentID = current.substring(0, x);
|
||||
return true;
|
||||
}
|
||||
if (fallbackID != null) {
|
||||
currentID = fallbackID;
|
||||
fallbackID = fallbackID.length() == 0 ? null : "";
|
||||
return true;
|
||||
}
|
||||
currentID = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a factory that handles multiple keys, and records
|
||||
* information about the keys it handles or doesn't handle. This
|
||||
* allows it to quickly filter subsequent queries on keys it has
|
||||
* seen before. Subclasses implement handleCreate instead of
|
||||
* create. Before updateVisibleIDs is called, it keeps track of
|
||||
* keys that it doesn't handle. If its ids are visible, once
|
||||
* updateVisibleIDs is called, it builds a set of all the keys it
|
||||
* does handle and keeps track of the keys it does handle.
|
||||
*/
|
||||
public static abstract class MultipleKeyFactory implements ICUService.Factory {
|
||||
protected final boolean visible;
|
||||
private SoftReference cacheref;
|
||||
private boolean included;
|
||||
|
||||
public MultipleKeyFactory() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public MultipleKeyFactory(boolean visible) {
|
||||
this.visible = visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache of IDs. These are either the ids that we know we
|
||||
* don't understand, if included is false, or the entire set of ids
|
||||
* we do know we understand, if included is true. Note that if
|
||||
* the cache has been freed by gc, we reset the included flag, so
|
||||
* it must not be tested before this method is called.
|
||||
*/
|
||||
private HashSet getCache() {
|
||||
HashSet cache = null;
|
||||
if (cacheref != null) {
|
||||
cache = (HashSet)cacheref.get();
|
||||
}
|
||||
if (cache == null) {
|
||||
cache = new HashSet();
|
||||
cacheref = new SoftReference(cache);
|
||||
included = false;
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache of IDs we understand.
|
||||
*/
|
||||
private HashSet getIncludedCache() {
|
||||
HashSet cache = getCache();
|
||||
if (!included) {
|
||||
cache.clear();
|
||||
handleUpdateVisibleIDs(cache);
|
||||
included = true;
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
public final Object create(Key key) {
|
||||
Object result = null;
|
||||
String id = key.currentID();
|
||||
HashSet cache = getCache();
|
||||
if (cache.contains(id) == included) {
|
||||
result = handleCreate(key);
|
||||
if (!included && result == null) {
|
||||
cache.add(id);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public final void updateVisibleIDs(Map result) {
|
||||
if (visible) {
|
||||
Set cache = getIncludedCache();
|
||||
Iterator iter = cache.iterator();
|
||||
while (iter.hasNext()) {
|
||||
result.put((String)iter.next(), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final String getDisplayName(String id, Locale locale) {
|
||||
if (visible) {
|
||||
Set cache = getIncludedCache();
|
||||
if (cache.contains(id)) {
|
||||
return handleGetDisplayName(id, locale);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses implement this instead of create.
|
||||
*/
|
||||
protected abstract Object handleCreate(Key key);
|
||||
|
||||
/**
|
||||
* Subclasses implement this instead of updateVisibleIDs. Any
|
||||
* id known to and handled by this class should be added to
|
||||
* result.
|
||||
*/
|
||||
protected abstract void handleUpdateVisibleIDs(Set result);
|
||||
|
||||
/**
|
||||
* Subclasses implement this instead of getDisplayName.
|
||||
* Return the display name for the (visible) id in the
|
||||
* provided locale. The default implementation just returns
|
||||
* the id.
|
||||
*/
|
||||
protected String handleGetDisplayName(String id, Locale locale) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A factory that creates a service based on the ICU locale data.
|
||||
* Subclasses specify a prefix (default is LocaleElements), a
|
||||
* semicolon-separated list of required resources, and a visible flag.
|
||||
* This factory will search the ICU locale data for a bundle with
|
||||
* the exact prefix. Then it will test whether the required resources
|
||||
* are all in this exact bundle. If so, it instantiates the full
|
||||
* resource bundle, and hands it to createServiceFromResource, which
|
||||
* subclasses must implement. Otherwise it returns null.
|
||||
*/
|
||||
public static class ICUResourceBundleFactory extends MultipleKeyFactory {
|
||||
protected final String name;
|
||||
protected final String[] requiredContents;
|
||||
|
||||
/**
|
||||
* A service factory based on ICU resource data in the LocaleElements resources.
|
||||
*/
|
||||
public ICUResourceBundleFactory(String requiredContents, boolean visible) {
|
||||
this(ICULocaleData.LOCALE_ELEMENTS, requiredContents, visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* A service factory based on ICU resource data in resources
|
||||
* with the given name. If requiredContents is not null, all
|
||||
* listed resources must come directly from the same bundle.
|
||||
*/
|
||||
public ICUResourceBundleFactory(String name, String requiredContents, boolean visible) {
|
||||
super(visible);
|
||||
|
||||
this.name = name;
|
||||
if (requiredContents != null) {
|
||||
ArrayList list = new ArrayList();
|
||||
for (int i = 0, len = requiredContents.length();;) {
|
||||
while (i < len && requiredContents.charAt(i) == ';') {
|
||||
++i;
|
||||
}
|
||||
if (i == len) {
|
||||
break;
|
||||
}
|
||||
int j = requiredContents.indexOf(';', i);
|
||||
if (j == -1) {
|
||||
j = len;
|
||||
}
|
||||
list.add(requiredContents.substring(i, j));
|
||||
i = j;
|
||||
}
|
||||
this.requiredContents = (String[])list.toArray(new String[list.size()]);
|
||||
} else {
|
||||
this.requiredContents = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides parent handleCreate call. Parent will filter out keys that it
|
||||
* knows are not accepted by this factory before calling this method.
|
||||
*/
|
||||
protected Object handleCreate(Key key) {
|
||||
Locale loc = LocaleUtility.getLocaleFromName(key.currentID());
|
||||
if (acceptsLocale(loc)) {
|
||||
ResourceBundle bundle = ICULocaleData.getResourceBundle(name, loc); // full resource bundle tree lookup
|
||||
return createFromBundle(bundle, key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries all the available locales in ICU and adds the names
|
||||
* of those which it accepts to result. This is quite
|
||||
* time-consuming so we don't want to do it more than once if
|
||||
* we have to. This is only called if we are visible.
|
||||
*/
|
||||
protected void handleUpdateVisibleIDs(Set result) {
|
||||
Locale[] locales = ICULocaleData.getAvailableLocales(name);
|
||||
for (int i = 0; i < locales.length; ++i) {
|
||||
Locale locale = locales[i];
|
||||
if (acceptsLocale(locale)) {
|
||||
result.add(LocaleUtility.canonicalLocaleString(locale.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a localized name for the locale represented by id.
|
||||
*/
|
||||
protected String handleGetDisplayName(String id, Locale locale) {
|
||||
// use java's display name formatting for now
|
||||
return LocaleUtility.getLocaleFromName(id).getDisplayName(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* We only accept the locale if there is a bundle for this exact locale and if
|
||||
* all the required resources are directly in this bundle (none is from an
|
||||
* inherited bundle);
|
||||
*/
|
||||
protected boolean acceptsLocale(Locale loc) {
|
||||
|
||||
try {
|
||||
ResourceBundle bundle = ICULocaleData.loadResourceBundle(name, loc); // single resource bundle lookup
|
||||
if (requiredContents != null) {
|
||||
for (int i = 0; i < requiredContents.length; ++i) {
|
||||
if (bundle.getObject(requiredContents[i]) == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclassers implement this to create their service object based on the bundle and key.
|
||||
* The default implementation just returns the bundle.
|
||||
*/
|
||||
protected Object createFromBundle(ResourceBundle bundle, Key key) {
|
||||
return bundle;
|
||||
}
|
||||
}
|
||||
|
||||
protected Key createKey(String id) {
|
||||
return LocaleKey.createWithCanonicalFallback(id, fallbackLocale);
|
||||
}
|
||||
}
|
162
icu4j/src/com/ibm/icu/impl/ICUNotifier.java
Normal file
162
icu4j/src/com/ibm/icu/impl/ICUNotifier.java
Normal file
|
@ -0,0 +1,162 @@
|
|||
package com.ibm.icu.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EventListener;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>Abstract implementation of a notification facility. Clients add
|
||||
* EventListeners with addListener and remove them with removeListener.
|
||||
* Notifiers call notifyChanged when they wish to notify listeners.
|
||||
* This queues the listener list on the notification thread, which
|
||||
* eventually dequeues the list and calls notifyListener on each
|
||||
* listener in the list.</p>
|
||||
*
|
||||
* <p>Subclasses override acceptsListener and notifyListener
|
||||
* to add type-safe notification. AcceptsListener should return
|
||||
* true if the listener is of the appropriate type; ICUNotifier
|
||||
* itself will ensure the listener is non-null and that the
|
||||
* identical listener is not already registered with the Notifier.
|
||||
* NotifyListener should cast the listener to the appropriate
|
||||
* type and call the appropriate method on the listener.
|
||||
*/
|
||||
public abstract class ICUNotifier {
|
||||
private Object notifyLock = new Object();
|
||||
private NotifyThread notifyThread;
|
||||
private List listeners;
|
||||
|
||||
/**
|
||||
* Add a listener to be notified when notifyChanged is called.
|
||||
* The listener must not be null. AcceptsListener must return
|
||||
* true for the listener. Attempts to concurrently
|
||||
* register the identical listener more than once will be
|
||||
* silently ignored.
|
||||
*/
|
||||
public void addListener(EventListener l) {
|
||||
if (l == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
|
||||
if (acceptsListener(l)) {
|
||||
synchronized (notifyLock) {
|
||||
if (listeners == null) {
|
||||
listeners = new ArrayList(5);
|
||||
}
|
||||
// identity equality check
|
||||
Iterator iter = listeners.iterator();
|
||||
while (iter.hasNext()) {
|
||||
if (iter.next() == l) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
listeners.add(l);
|
||||
}
|
||||
} else {
|
||||
throw new InternalError("Listener invalid for this notifier.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop notifying this listener. The listener must
|
||||
* not be null. Attemps to remove a listener that is
|
||||
* not registered will be silently ignored.
|
||||
*/
|
||||
public void removeListener(EventListener l) {
|
||||
if (l == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
synchronized (notifyLock) {
|
||||
if (listeners != null) {
|
||||
// identity equality check
|
||||
Iterator iter = listeners.iterator();
|
||||
while (iter.hasNext()) {
|
||||
if (iter.next() == l) {
|
||||
iter.remove();
|
||||
if (listeners.size() == 0) {
|
||||
listeners = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a notification on the notification thread for the current
|
||||
* listeners. When the thread unqueues the notification, notifyListener
|
||||
* is called on each listener from the notification thread.
|
||||
*/
|
||||
public void notifyChanged() {
|
||||
if (listeners != null) {
|
||||
synchronized (notifyLock) {
|
||||
if (listeners != null) {
|
||||
if (notifyThread == null) {
|
||||
notifyThread = new NotifyThread();
|
||||
notifyThread.setDaemon(true);
|
||||
notifyThread.start();
|
||||
}
|
||||
notifyThread.queue(listeners.toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The notification thread.
|
||||
*/
|
||||
private class NotifyThread extends Thread {
|
||||
List queue;
|
||||
|
||||
/**
|
||||
* Queue the notification on the thread.
|
||||
*/
|
||||
public void queue(Object[] list) {
|
||||
synchronized (this) {
|
||||
if (queue == null) {
|
||||
queue = new LinkedList();
|
||||
}
|
||||
queue.add(list);
|
||||
notify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a notification to be queued, then notify all
|
||||
* listeners listed in the notification.
|
||||
*/
|
||||
public void run() {
|
||||
Object[] list;
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (this) {
|
||||
while (queue.isEmpty()) {
|
||||
wait();
|
||||
}
|
||||
list = (Object[])queue.remove(0);
|
||||
}
|
||||
|
||||
for (int i = 0; i < list.length; ++i) {
|
||||
notifyListener((EventListener)list[i]);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses implement this to return true if the listener is
|
||||
* of the appropriate type.
|
||||
*/
|
||||
protected abstract boolean acceptsListener(EventListener l);
|
||||
|
||||
/**
|
||||
* Subclasses implement this to notify the listener.
|
||||
*/
|
||||
protected abstract void notifyListener(EventListener l);
|
||||
}
|
254
icu4j/src/com/ibm/icu/impl/ICURWLock.java
Normal file
254
icu4j/src/com/ibm/icu/impl/ICURWLock.java
Normal file
|
@ -0,0 +1,254 @@
|
|||
package com.ibm.icu.impl;
|
||||
|
||||
// See Allan Holub's 1999 column in JavaWorld.
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* <p>A simple Reader/Writer lock. This assumes that there will
|
||||
* be little writing contention. It also doesn't allow
|
||||
* active readers to acquire and release a write lock, or
|
||||
* deal with priority inversion issues.</p>
|
||||
*
|
||||
* <p>Access to the lock should be enclosed in a try/finally block
|
||||
* in order to ensure that the lock is always released in case of
|
||||
* exceptions:<br><pre>
|
||||
* try {
|
||||
* lock.acquireRead();
|
||||
* // use service protected by the lock
|
||||
* }
|
||||
* finally {
|
||||
* lock.releaseRead();
|
||||
* }
|
||||
* </pre></p>
|
||||
*
|
||||
* <p>The lock provides utility methods getStats and clearStats
|
||||
* to return statistics on the use of the lock.</p>
|
||||
*/
|
||||
public class ICURWLock {
|
||||
private LinkedList ww; // list of waiting writers
|
||||
private int wwc; // waiting writers
|
||||
private int rc; // active readers, -1 if there's an active writer
|
||||
private int wrc; // waiting readers
|
||||
|
||||
private int stat_rc; // any read access granted
|
||||
private int stat_mrc; // multiple read access granted
|
||||
private int stat_wrc; // wait for read
|
||||
private int stat_wc; // write access granted
|
||||
private int stat_wwc; // wait for write
|
||||
|
||||
/**
|
||||
* Internal class used to gather statistics on the RWLock.
|
||||
*/
|
||||
public final static class Stats {
|
||||
/**
|
||||
* Number of times read access granted (read count).
|
||||
*/
|
||||
public final int _rc;
|
||||
|
||||
/**
|
||||
* Number of times concurrent read access granted (multiple read count).
|
||||
*/
|
||||
public final int _mrc;
|
||||
|
||||
/**
|
||||
* Number of times blocked for read (waiting reader count).
|
||||
*/
|
||||
public final int _wrc; // wait for read
|
||||
|
||||
/**
|
||||
* Number of times write access granted (writer count).
|
||||
*/
|
||||
public final int _wc;
|
||||
|
||||
/**
|
||||
* Number of times blocked for write (waiting writer count).
|
||||
*/
|
||||
public final int _wwc;
|
||||
|
||||
private Stats(ICURWLock lock) {
|
||||
this(lock.stat_rc, lock.stat_mrc, lock.stat_wrc, lock.stat_wc, lock.stat_wwc);
|
||||
}
|
||||
|
||||
private Stats(int rc, int mrc, int wrc, int wc, int wwc) {
|
||||
_rc = rc; _mrc = mrc; _wrc = wrc; _wc = wc; _wwc = wwc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string listing all the stats.
|
||||
*/
|
||||
public String toString() {
|
||||
return " rc: " + _rc +
|
||||
" mrc: " + _mrc +
|
||||
" wrc: " + _wrc +
|
||||
" wc: " + _wc +
|
||||
" wwc: " + _wwc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the stats.
|
||||
*/
|
||||
public void clearStats() {
|
||||
stat_rc = stat_mrc = stat_wrc = stat_wc = stat_wwc = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a snapshot of the current stats. This does not clear the stats.
|
||||
*/
|
||||
public Stats getStats() {
|
||||
return new Stats(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Acquire a read lock, blocking until a read lock is
|
||||
* available. Multiple readers can concurrently hold the read
|
||||
* lock.</p>
|
||||
*
|
||||
* <p>If there's a writer, or a waiting writer, increment the
|
||||
* waiting reader count and block on this. Otherwise
|
||||
* increment the active reader count and return. Caller must call
|
||||
* releaseRead when done (for example, in a finally block).</p>
|
||||
*/
|
||||
public synchronized void acquireRead() {
|
||||
if (rc >= 0 && wwc == 0) {
|
||||
++rc;
|
||||
++stat_rc;
|
||||
if (rc > 1) ++stat_mrc;
|
||||
} else {
|
||||
++wrc;
|
||||
++stat_wrc;
|
||||
try {
|
||||
wait();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Release a read lock and return. An error will be thrown
|
||||
* if a read lock is not currently held.</p>
|
||||
*
|
||||
* <p>If this is the last active reader, notify the oldest
|
||||
* waiting writer. Call when finished with work
|
||||
* controlled by acquireRead.</p>
|
||||
*/
|
||||
public synchronized void releaseRead() {
|
||||
if (rc > 0) {
|
||||
if (0 == --rc) {
|
||||
notifyWaitingWriter();
|
||||
}
|
||||
} else {
|
||||
throw new InternalError("no current reader to release");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Acquire the write lock, blocking until the write lock is
|
||||
* available. Only one writer can acquire the write lock, and
|
||||
* when held, no readers can acquire the read lock.</p>
|
||||
*
|
||||
* <p>If there are no readers and no waiting writers, mark as
|
||||
* having an active writer and return. Otherwise, add a lock to the
|
||||
* end of the waiting writer list, and block on it. Caller
|
||||
* must call releaseWrite when done (for example, in a finally
|
||||
* block).<p>
|
||||
*/
|
||||
public void acquireWrite() {
|
||||
// Do a quick check up top, to save us the lock allocation and
|
||||
// extra synch in the case where there is no contention for
|
||||
// writing. This is common in ICUService.
|
||||
|
||||
synchronized (this) {
|
||||
if (rc == 0 && wwc == 0) {
|
||||
rc = -1;
|
||||
++stat_wc;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We assume at this point that there is an active reader or a
|
||||
// waiting writer, so we don't recheck, though we could.
|
||||
|
||||
// Create a lock for this thread only, it will be the only
|
||||
// thread notified when the lock comes to the front of the
|
||||
// waiting writer list. We synchronize on the lock first, and
|
||||
// then this, because when we release the lock on this, the
|
||||
// lock will be available to the world. If another thread
|
||||
// removed and notified that lock before we synchronized and
|
||||
// waited on it, we'd miss the only notification, and we would
|
||||
// wait on it forever. So we synchronized on it before we
|
||||
// make it available, so that we're guaranteed to be waiting on
|
||||
// it before any notification can occur.
|
||||
|
||||
Object lock = new Object();
|
||||
synchronized (lock) {
|
||||
synchronized (this) {
|
||||
// again, we've assumed we don't have multiple waiting
|
||||
// writers, so we leave this null until we need it.
|
||||
// Once created, we keep it around.
|
||||
if (ww == null) {
|
||||
ww = new LinkedList();
|
||||
}
|
||||
ww.addLast(lock);
|
||||
++wwc;
|
||||
++stat_wwc;
|
||||
}
|
||||
try {
|
||||
lock.wait();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Release the write lock and return. An error will be thrown
|
||||
* if the write lock is not currently held.</p>
|
||||
*
|
||||
* <p>If there are waiting readers, make them all active and
|
||||
* notify all of them. Otherwise, notify the oldest waiting
|
||||
* writer, if any. Call when finished with work controlled by
|
||||
* acquireWrite.</p>
|
||||
*/
|
||||
public synchronized void releaseWrite() {
|
||||
if (rc < 0) {
|
||||
if (wrc > 0) {
|
||||
rc = wrc;
|
||||
wrc = 0;
|
||||
if (rc > 0) {
|
||||
stat_rc += rc;
|
||||
if (rc > 1) {
|
||||
stat_mrc += rc - 1;
|
||||
}
|
||||
}
|
||||
notifyAll();
|
||||
} else {
|
||||
rc = 0;
|
||||
notifyWaitingWriter();
|
||||
}
|
||||
} else {
|
||||
throw new InternalError("no current writer to release");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is a waiting writer thread, mark us as active for
|
||||
* writing, remove it from the list, and notify it.
|
||||
*/
|
||||
private void notifyWaitingWriter() {
|
||||
// only called within a block synchronized on this
|
||||
// we don't assume there is necessarily a waiting writer,
|
||||
// no no error if there isn't.
|
||||
if (wwc > 0) {
|
||||
rc = -1;
|
||||
Object lock = ww.removeFirst();
|
||||
--wwc;
|
||||
++stat_wc;
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
649
icu4j/src/com/ibm/icu/impl/ICUService.java
Normal file
649
icu4j/src/com/ibm/icu/impl/ICUService.java
Normal file
|
@ -0,0 +1,649 @@
|
|||
package com.ibm.icu.impl;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EventListener;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* <p>A Service provides access to service objects that implement a
|
||||
* particular service, e.g. transliterators. Users provide a String
|
||||
* id (for example, a locale string) to the service, and get back an
|
||||
* object for that id. Service objects can be any kind of object.
|
||||
* The service object is cached and returned for later queries, so
|
||||
* generally it should not be mutable, or the caller should clone the
|
||||
* object before modifying it.</p>
|
||||
*
|
||||
* <p>Services 'canonicalize' the query id and use the canonical id
|
||||
* to query for the service. The service also defines a mechanism
|
||||
* to 'fallback' the id multiple times. Clients can optionally
|
||||
* request the actual id that was matched by a query when they
|
||||
* use an id to retrieve a service object.</p>
|
||||
*
|
||||
* <p>Service objects are instantiated by Factory objects registered with
|
||||
* the service. The service queries each Factory in turn, from most recently
|
||||
* registered to earliest registered, until one returns a service object.
|
||||
* If none responds with a service object, a fallback id is generated,
|
||||
* and the process repeats until a service object is returned or until
|
||||
* the id has no further fallbacks.</p>
|
||||
*
|
||||
* <p>Factories can be dynamically registered and unregistered with the
|
||||
* service. When registered, a Factory is installed at the head of
|
||||
* the factory list, and so gets 'first crack' at any keys or fallback
|
||||
* keys. When unregistered, it is removed from the service and can no
|
||||
* longer be located through it. Service objects generated by this
|
||||
* factory and held by the client are unaffected.</p>
|
||||
*
|
||||
* <p>Internally, ICUService uses Keys to query factories and perform
|
||||
* fallback. The Key defines the canonical form of the id, and
|
||||
* implements the fallback strategy. Custom Keys can be defined that
|
||||
* parse complex IDs into components that Factories can more easily
|
||||
* use. The Key can cache the results of this parsing to save
|
||||
* repeated effort.</p>
|
||||
*
|
||||
* <p>ICUService implements ICUNotifier, so that clients can register
|
||||
* to to receive notification when factories are added or removed from
|
||||
* the service. ICUService provides a default EventListener subinterface,
|
||||
* ServiceListener, which can be registered with the service. When
|
||||
* the service changes, the ServiceListener's serviceChanged method
|
||||
* is called, with the service as the only argument.</p>
|
||||
*
|
||||
* <p>The ICUService API is both rich and generic, and it is expected
|
||||
* that most implementations will statically 'wrap' ICUService to
|
||||
* present a more appropriate API-- for example, to declare the type
|
||||
* of the objects returned from get, to limit the factories that can
|
||||
* be registered with the service, or to define their own listener
|
||||
* interface with a custom callback method. They might also customize
|
||||
* ICUService by overriding it, for example, to customize the Key and
|
||||
* fallback strategy. ICULocaleService is a customized service that
|
||||
* uses Locale names as ids and uses Keys that implement the standard
|
||||
* resource bundle fallback strategy.<p>
|
||||
*/
|
||||
public class ICUService extends ICUNotifier {
|
||||
/**
|
||||
* Access to factories is protected by a read-write lock. This is
|
||||
* to allow multiple threads to read concurrently, but keep
|
||||
* changes to the factory list atomic with respect to all readers.
|
||||
*/
|
||||
private final ICURWLock factoryLock = new ICURWLock();
|
||||
|
||||
/**
|
||||
* All the factories registered with this service.
|
||||
*/
|
||||
private final List factories = new ArrayList();
|
||||
|
||||
/**
|
||||
* Keys define how ids are canonicalized, and determine the
|
||||
* fallback strategy used when querying the factories. The default
|
||||
* key just takes as its canonicalID the id assigned to it when it
|
||||
* is constructed, and has no fallbacks.
|
||||
*/
|
||||
public static class Key {
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* Construct a key from an id.
|
||||
*/
|
||||
public Key(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the original ID.
|
||||
*/
|
||||
public final String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the canonical version of the original ID. This implementation
|
||||
* returns the original ID unchanged.
|
||||
*/
|
||||
public String canonicalID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the (canonical) current ID. This implementation returns the
|
||||
* canonical ID.
|
||||
*/
|
||||
public String currentID() {
|
||||
return canonicalID();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the key has a fallback, modify the key and return true,
|
||||
* otherwise return false. The current ID will change if there
|
||||
* is a fallback. No currentIDs should be repeated, and fallback
|
||||
* must eventually return false. This implmentation has no fallbacks
|
||||
* and always returns false.
|
||||
*/
|
||||
public boolean fallback() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factories generate the service objects maintained by the
|
||||
* service. A factory generates a service object from a key,
|
||||
* updates id->factory mappings, and returns the display name for
|
||||
* a supported id.
|
||||
*/
|
||||
public static interface Factory {
|
||||
/**
|
||||
* Create a service object from the key, if this factory
|
||||
* supports the key. Otherwise, return null.
|
||||
*/
|
||||
public Object create(Key key);
|
||||
|
||||
/**
|
||||
* Add IDs understood by this factory to the result map, with
|
||||
* this factory as the value. If this factory hides IDs
|
||||
* currently in result, it should remove or reset the mappings
|
||||
* for those IDs.
|
||||
*/
|
||||
public void updateVisibleIDs(Map result);
|
||||
|
||||
/**
|
||||
* Return the display name for this id in the provided locale.
|
||||
* If the id is not visible or not defined by the factory,
|
||||
* return null.
|
||||
*/
|
||||
public String getDisplayName(String id, Locale locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* A default implementation of factory. This provides default
|
||||
* implementations for subclasses, and implements a singleton
|
||||
* factory that matches a single id and returns a single
|
||||
* (possible deferred-initialized) instance. If visible is
|
||||
* true, updates the map passed to updateVisibleIDs with a
|
||||
* mapping from id to itself.
|
||||
*/
|
||||
public static class SimpleFactory implements Factory {
|
||||
protected Object instance;
|
||||
protected String id;
|
||||
protected boolean visible;
|
||||
|
||||
public SimpleFactory(Object instance, String id) {
|
||||
this(instance, id, true);
|
||||
}
|
||||
|
||||
public SimpleFactory(Object instance, String id, boolean visible) {
|
||||
if (instance == null || id == null) {
|
||||
throw new IllegalArgumentException("instance and id must each not be null");
|
||||
}
|
||||
this.instance = instance;
|
||||
this.id = id;
|
||||
this.visible = visible;
|
||||
}
|
||||
|
||||
public Object create(Key key) {
|
||||
if (id.equals(key.currentID())) {
|
||||
return instance;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void updateVisibleIDs(Map result) {
|
||||
if (visible) result.put(id, this);
|
||||
}
|
||||
|
||||
public String getDisplayName(String id, Locale locale) {
|
||||
return (visible && id.equals(this.id)) ? id : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience override for get(String, String[]).
|
||||
*/
|
||||
public Object get(String id) {
|
||||
return get(id, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Given an id, return a service object, and the actual id under
|
||||
* which it was found. If no service object matches this id,
|
||||
* return null.</p>
|
||||
*
|
||||
* <p>This tries each registered factory in order, and if none can
|
||||
* generate a service object for the key, repeats the process with
|
||||
* each fallback of the key until one returns a service object, or
|
||||
* the key has no fallback.</p>
|
||||
*/
|
||||
public Object get(String id, String[] actualIDReturn) {
|
||||
if (id == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
if (factories.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
CacheEntry result = null;
|
||||
Key key = createKey(id);
|
||||
if (key != null) {
|
||||
try {
|
||||
// The factory list can't be modified until we're done,
|
||||
// otherwise we might update the cache with an invalid result.
|
||||
// The cache has to stay in synch with the factory list.
|
||||
factoryLock.acquireRead();
|
||||
|
||||
Map cache = null;
|
||||
SoftReference cref = cacheref; // copy so we don't need to sync on this
|
||||
if (cref != null) {
|
||||
cache = (Map)cref.get();
|
||||
}
|
||||
if (cache == null) {
|
||||
// synchronized since additions and queries on the cache must be atomic
|
||||
// they can be interleaved, though
|
||||
cache = Collections.synchronizedMap(new HashMap());
|
||||
cref = new SoftReference(cache);
|
||||
}
|
||||
|
||||
String currentID = null;
|
||||
ArrayList cacheIDList = null;
|
||||
boolean putInCache = false;
|
||||
outer:
|
||||
do {
|
||||
currentID = key.currentID();
|
||||
|
||||
result = (CacheEntry)cache.get(currentID);
|
||||
if (result != null) {
|
||||
break outer;
|
||||
}
|
||||
|
||||
// first test of cache failed, so we'll have to update
|
||||
// the cache if we eventually succeed.
|
||||
putInCache = true;
|
||||
|
||||
Iterator fi = factories.iterator();
|
||||
while (fi.hasNext()) {
|
||||
Object service = ((Factory)fi.next()).create(key);
|
||||
if (service != null) {
|
||||
result = new CacheEntry(currentID, service);
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
|
||||
// prepare to load the cache with all additional ids that
|
||||
// will resolve to result, assuming we'll succeed. We
|
||||
// don't want to keep querying on an id that's going to
|
||||
// fallback to the one that succeeded, we want to hit the
|
||||
// cache the first time next goaround.
|
||||
if (cacheIDList == null) {
|
||||
cacheIDList = new ArrayList(5);
|
||||
}
|
||||
cacheIDList.add(currentID);
|
||||
|
||||
} while (key.fallback());
|
||||
|
||||
if (result != null) {
|
||||
if (putInCache) {
|
||||
cache.put(result.actualID, result);
|
||||
if (cacheIDList != null) {
|
||||
Iterator iter = cacheIDList.iterator();
|
||||
while (iter.hasNext()) {
|
||||
cache.put((String)iter.next(), result);
|
||||
}
|
||||
}
|
||||
// Atomic update. We held the read lock all this time
|
||||
// so we know our cache is consistent with the factory list.
|
||||
// We might stomp over a cache that some other thread
|
||||
// rebuilt, but that's the breaks. They're both good.
|
||||
cacheref = cref;
|
||||
}
|
||||
|
||||
if (actualIDReturn != null) {
|
||||
actualIDReturn[0] = result.actualID;
|
||||
}
|
||||
|
||||
return result.service;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
factoryLock.releaseRead();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
private SoftReference cacheref;
|
||||
|
||||
// Record the actual id for this service in the cache, so we can return it
|
||||
// even if we succeed later with a different id.
|
||||
private static final class CacheEntry {
|
||||
String actualID;
|
||||
Object service;
|
||||
CacheEntry(String actualID, Object service) {
|
||||
this.actualID = actualID;
|
||||
this.service = service;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a snapshot of the visible IDs for this service. This
|
||||
* set will not change as Factories are added or removed, but the
|
||||
* supported ids will, so there is no guarantee that all and only
|
||||
* the ids in the returned set are visible and supported by the
|
||||
* service in subsequent calls.
|
||||
*/
|
||||
public Set getVisibleIDs() {
|
||||
return getVisibleIDMap().keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map from visible ids to factories.
|
||||
*/
|
||||
private Map getVisibleIDMap() {
|
||||
Map idcache = null;
|
||||
SoftReference ref = idref;
|
||||
if (ref != null) {
|
||||
idcache = (Map)ref.get();
|
||||
}
|
||||
while (idcache == null) {
|
||||
synchronized (this) { // or idref-only lock?
|
||||
if (ref == idref || idref == null) {
|
||||
// no other thread updated idref before we got the lock, so
|
||||
// grab the factory list and update it ourselves
|
||||
try {
|
||||
factoryLock.acquireRead();
|
||||
|
||||
idcache = new TreeMap(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
ListIterator lIter = factories.listIterator(factories.size());
|
||||
while (lIter.hasPrevious()) {
|
||||
Factory f = (Factory)lIter.previous();
|
||||
f.updateVisibleIDs(idcache);
|
||||
}
|
||||
idcache = Collections.unmodifiableMap(idcache);
|
||||
idref = new SoftReference(idcache);
|
||||
}
|
||||
finally {
|
||||
factoryLock.releaseRead();
|
||||
}
|
||||
} else {
|
||||
// another thread updated idref, but gc may have stepped
|
||||
// in and undone its work, leaving idcache null. If so,
|
||||
// retry.
|
||||
ref = idref;
|
||||
idcache = (Map)ref.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return idcache;
|
||||
}
|
||||
private SoftReference idref;
|
||||
|
||||
/**
|
||||
* Convenience override for getDisplayName(String, Locale) that
|
||||
* uses the current default locale.
|
||||
*/
|
||||
public String getDisplayName(String id) {
|
||||
return getDisplayName(id, Locale.getDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a visible id, return the display name in the requested locale.
|
||||
* If there is no directly supported id corresponding to this id, return
|
||||
* null.
|
||||
*/
|
||||
public String getDisplayName(String id, Locale locale) {
|
||||
Map m = getVisibleIDMap();
|
||||
Factory f = (Factory)m.get(id);
|
||||
return f != null ? f.getDisplayName(id, locale) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience override of getDisplayNames(Locale) that uses the
|
||||
* current default Locale.
|
||||
*/
|
||||
public Map getDisplayNames() {
|
||||
return getDisplayNames(Locale.getDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a snapshot of the mapping from display names to visible
|
||||
* IDs for this service. This set will not change as factories
|
||||
* are added or removed, but the supported ids will, so there is
|
||||
* no guarantee that all and only the ids in the returned map will
|
||||
* be visible and supported by the service in subsequent calls,
|
||||
* nor is there any guarantee that the current display names match
|
||||
* those in the set.
|
||||
*/
|
||||
public Map getDisplayNames(Locale locale) {
|
||||
Map dncache = null;
|
||||
LocaleRef ref = dnref;
|
||||
if (ref != null) {
|
||||
dncache = ref.get(locale);
|
||||
}
|
||||
while (dncache == null) {
|
||||
synchronized (this) {
|
||||
if (ref == dnref || dnref == null) {
|
||||
dncache = new TreeMap(String.CASE_INSENSITIVE_ORDER);
|
||||
//dncache = new TreeMap(/* locale-specific collator */);
|
||||
|
||||
Map m = getVisibleIDMap();
|
||||
Iterator ei = m.entrySet().iterator();
|
||||
while (ei.hasNext()) {
|
||||
Entry e = (Entry)ei.next();
|
||||
String id = (String)e.getKey();
|
||||
Factory f = (Factory)e.getValue();
|
||||
dncache.put(f.getDisplayName(id, locale), id);
|
||||
}
|
||||
|
||||
dncache = Collections.unmodifiableMap(dncache);
|
||||
dnref = new LocaleRef(dncache, locale);
|
||||
} else {
|
||||
ref = dnref;
|
||||
dncache = ref.get(locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dncache;
|
||||
}
|
||||
private static class LocaleRef {
|
||||
Locale locale;
|
||||
SoftReference ref;
|
||||
|
||||
LocaleRef(Map dnCache, Locale locale) {
|
||||
this.locale = locale;
|
||||
this.ref = new SoftReference(dnCache);
|
||||
}
|
||||
|
||||
Map get(Locale locale) {
|
||||
if (this.locale.equals(locale)) {
|
||||
return (Map)ref.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private LocaleRef dnref;
|
||||
|
||||
/**
|
||||
* Return a snapshot of the currently registered factories. There
|
||||
* is no guarantee that the list will still match the current
|
||||
* factory list of the service subsequent to this call.
|
||||
*/
|
||||
public final List factories() {
|
||||
try {
|
||||
factoryLock.acquireRead();
|
||||
return new ArrayList(factories);
|
||||
}
|
||||
finally{
|
||||
factoryLock.releaseRead();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience override of registerObject(Object, String, boolean)
|
||||
* that defaults visible to true.
|
||||
*/
|
||||
public Factory registerObject(Object obj, String id) {
|
||||
return registerObject(obj, id, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an object with the provided id. The id will be
|
||||
* canonicalized. The canonicalized ID will be returned by
|
||||
* getVisibleIDs if visible is true.
|
||||
*/
|
||||
public Factory registerObject(Object obj, String id, boolean visible) {
|
||||
id = createKey(id).canonicalID();
|
||||
return registerFactory(new SimpleFactory(obj, id, visible));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a Factory. Returns the factory if the service accepts
|
||||
* the factory, otherwise returns null. The default implementation
|
||||
* accepts all factories.
|
||||
*/
|
||||
public final Factory registerFactory(Factory factory) {
|
||||
if (factory == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
try {
|
||||
factoryLock.acquireWrite();
|
||||
factories.add(0, factory);
|
||||
clearCaches();
|
||||
}
|
||||
finally {
|
||||
factoryLock.releaseWrite();
|
||||
}
|
||||
notifyChanged();
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a factory. The first matching registered factory will
|
||||
* be removed from the list. Returns true if a matching factory was
|
||||
* removed.
|
||||
*/
|
||||
public final boolean unregisterFactory(Factory factory) {
|
||||
if (factory == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
|
||||
boolean result = false;
|
||||
try {
|
||||
factoryLock.acquireWrite();
|
||||
if (factories.remove(factory)) {
|
||||
result = true;
|
||||
clearCaches();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
factoryLock.releaseWrite();
|
||||
}
|
||||
|
||||
if (result) {
|
||||
notifyChanged();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the service to the default factories. The factory
|
||||
* lock is acquired and then reInitializeFactories is called.
|
||||
*/
|
||||
public final void reset() {
|
||||
try {
|
||||
factoryLock.acquireWrite();
|
||||
reInitializeFactories();
|
||||
clearCaches();
|
||||
}
|
||||
finally {
|
||||
factoryLock.releaseWrite();
|
||||
}
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinitialize the factory list to its default state. By default
|
||||
* this clears the list. Subclasses can override to provide other
|
||||
* default initialization of the factory list. Subclasses must
|
||||
* not call this method directly, as it must only be called while
|
||||
* holding write access to the factory list.
|
||||
*/
|
||||
protected void reInitializeFactories() {
|
||||
factories.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a key from an id. This creates a Key instance.
|
||||
* Subclasses can override to define more useful keys appropriate
|
||||
* to the factories they accept.
|
||||
*/
|
||||
protected Key createKey(String id) {
|
||||
return new Key(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear caches maintained by this service. Subclasses can
|
||||
* override if they implement additional that need to be cleared
|
||||
* when the service changes. Subclasses should generally not call
|
||||
* this method directly, as it must only be called while
|
||||
* synchronized on this.
|
||||
*/
|
||||
protected void clearCaches() {
|
||||
// we don't synchronize on these because methods that use them
|
||||
// copy before use, and check for changes if they modify the
|
||||
// caches.
|
||||
cacheref = null;
|
||||
idref = null;
|
||||
dnref = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceListener is the listener that ICUService provides by default.
|
||||
* ICUService will notifiy this listener when factories are added to
|
||||
* or removed from the service. Subclasses can provide
|
||||
* different listener interfaces that extend EventListener, and modify
|
||||
* acceptsListener and notifyListener as appropriate.
|
||||
*/
|
||||
public static interface ServiceListener extends EventListener {
|
||||
public void serviceChanged(ICUService service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the listener is accepted; by default this
|
||||
* requires a ServiceListener. Subclasses can override to accept
|
||||
* different listeners.
|
||||
*/
|
||||
protected boolean acceptsListener(EventListener l) {
|
||||
return l instanceof ServiceListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the listener, which by default is a ServiceListener.
|
||||
* Subclasses can override to use a different listener.
|
||||
*/
|
||||
protected void notifyListener(EventListener l) {
|
||||
((ServiceListener)l).serviceChanged(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string describing the statistics for this service.
|
||||
* This also resets the statistics. Used for debugging purposes.
|
||||
*/
|
||||
public String stats() {
|
||||
String stats = factoryLock.getStats().toString();
|
||||
factoryLock.clearStats();
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
|
Loading…
Add table
Reference in a new issue