/* ********************************************************************** * Copyright (c) 2001-2010, International Business Machines * Corporation and others. All Rights Reserved. ********************************************************************** * Date Name Description * 08/19/2001 aliu Creation. ********************************************************************** */ package com.ibm.icu.text; import java.util.Enumeration; import java.util.Hashtable; import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.Vector; import com.ibm.icu.impl.ICUResourceBundle; import com.ibm.icu.impl.LocaleUtility; import com.ibm.icu.lang.UScript; import com.ibm.icu.text.RuleBasedTransliterator.Data; import com.ibm.icu.util.CaseInsensitiveString; import com.ibm.icu.util.UResourceBundle; class TransliteratorRegistry { // char constants private static final char LOCALE_SEP = '_'; // String constants private static final String NO_VARIANT = ""; // empty string private static final String ANY = "Any"; /** * Dynamic registry mapping full IDs to Entry objects. This * contains both public and internal entities. The visibility is * controlled by whether an entry is listed in availableIDs and * specDAG or not. * * Keys are CaseInsensitiveString objects. * Values are objects of class Class (subclass of Transliterator), * RuleBasedTransliterator.Data, Transliterator.Factory, or one * of the entry classes defined here (AliasEntry or ResourceEntry). */ private Hashtable registry; /** * DAG of visible IDs by spec. Hashtable: source => (Hashtable: * target => (Vector: variant)) The Vector of variants is never * empty. For a source-target with no variant, the special * variant NO_VARIANT (the empty string) is stored in slot zero of * the UVector. * * Keys are CaseInsensitiveString objects. * Values are Hashtable of (CaseInsensitiveString -> Vector of * CaseInsensitiveString) */ private Hashtable>> specDAG; /** * Vector of public full IDs (CaseInsensitiveString objects). */ private Vector availableIDs; //---------------------------------------------------------------------- // class Spec //---------------------------------------------------------------------- /** * A Spec is a string specifying either a source or a target. In more * general terms, it may also specify a variant, but we only use the * Spec class for sources and targets. * * A Spec may be a locale or a script. If it is a locale, it has a * fallback chain that goes xx_YY_ZZZ -> xx_YY -> xx -> ssss, where * ssss is the script mapping of xx_YY_ZZZ. The Spec API methods * hasFallback(), next(), and reset() iterate over this fallback * sequence. * * The Spec class canonicalizes itself, so the locale is put into * canonical form, or the script is transformed from an abbreviation * to a full name. */ static class Spec { private String top; // top spec private String spec; // current spec private String nextSpec; // next spec private String scriptName; // script name equivalent of top, if != top private boolean isSpecLocale; // TRUE if spec is a locale private boolean isNextLocale; // TRUE if nextSpec is a locale private ICUResourceBundle res; public Spec(String theSpec) { top = theSpec; spec = null; scriptName = null; try{ // Canonicalize script name. If top is a script name then // script != UScript.INVALID_CODE. int script = UScript.getCodeFromName(top); // Canonicalize script name -or- do locale->script mapping int[] s = UScript.getCode(top); if (s != null) { scriptName = UScript.getName(s[0]); // If the script name is the same as top then it's redundant if (scriptName.equalsIgnoreCase(top)) { scriptName = null; } } isSpecLocale = false; res = null; // If 'top' is not a script name, try a locale lookup if (script == UScript.INVALID_CODE) { Locale toploc = LocaleUtility.getLocaleFromName(top); res = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_TRANSLIT_BASE_NAME,toploc); // Make sure we got the bundle we wanted; otherwise, don't use it if (res!=null && LocaleUtility.isFallbackOf(res.getULocale().toString(), top)) { isSpecLocale = true; } } }catch(MissingResourceException e){ ///CLOVER:OFF // The constructor is called from multiple private methods // that protects an invalid scriptName scriptName = null; ///CLOVER:ON } // assert(spec != top); reset(); } public boolean hasFallback() { return nextSpec != null; } public void reset() { if (spec != top) { // [sic] pointer comparison spec = top; isSpecLocale = (res != null); setupNext(); } } private void setupNext() { isNextLocale = false; if (isSpecLocale) { nextSpec = spec; int i = nextSpec.lastIndexOf(LOCALE_SEP); // If i == 0 then we have _FOO, so we fall through // to the scriptName. if (i > 0) { nextSpec = spec.substring(0, i); isNextLocale = true; } else { nextSpec = scriptName; // scriptName may be null } } else { // Fallback to the script, which may be null if (nextSpec != scriptName) { nextSpec = scriptName; } else { nextSpec = null; } } } // Protocol: // for(String& s(spec.get()); // spec.hasFallback(); s(spec.next())) { ... public String next() { spec = nextSpec; isSpecLocale = isNextLocale; setupNext(); return spec; } public String get() { return spec; } public boolean isLocale() { return isSpecLocale; } /** * Return the ResourceBundle for this spec, at the current * level of iteration. The level of iteration goes from * aa_BB_CCC to aa_BB to aa. If the bundle does not * correspond to the current level of iteration, return null. * If isLocale() is false, always return null. */ public ResourceBundle getBundle() { if (res != null && res.getULocale().toString().equals(spec)) { return res; } return null; } public String getTop() { return top; } } //---------------------------------------------------------------------- // Entry classes //---------------------------------------------------------------------- static class ResourceEntry { public String resource; public String encoding; public int direction; public ResourceEntry(String n, String enc, int d) { resource = n; encoding = enc; direction = d; } } // An entry representing a rule in a locale resource bundle static class LocaleEntry { public String rule; public int direction; public LocaleEntry(String r, int d) { rule = r; direction = d; } } static class AliasEntry { public String alias; public AliasEntry(String a) { alias = a; } } static class CompoundRBTEntry { private String ID; private Vector idBlockVector; private Vector dataVector; private UnicodeSet compoundFilter; public CompoundRBTEntry(String theID, Vector theIDBlockVector, Vector theDataVector, UnicodeSet theCompoundFilter) { ID = theID; idBlockVector = theIDBlockVector; dataVector = theDataVector; compoundFilter = theCompoundFilter; } public Transliterator getInstance() { Vector transliterators = new Vector(); int passNumber = 1; int limit = Math.max(idBlockVector.size(), dataVector.size()); for (int i = 0; i < limit; i++) { if (i < idBlockVector.size()) { String idBlock = idBlockVector.get(i); if (idBlock.length() > 0) transliterators.add(Transliterator.getInstance(idBlock)); } if (i < dataVector.size()) { Data data = dataVector.get(i); transliterators.add(new RuleBasedTransliterator("%Pass" + passNumber++, data, null)); } } Transliterator t = new CompoundTransliterator(transliterators, passNumber - 1); t.setID(ID); if (compoundFilter != null) { t.setFilter(compoundFilter); } return t; } } //---------------------------------------------------------------------- // class TransliteratorRegistry: Basic public API //---------------------------------------------------------------------- public TransliteratorRegistry() { registry = new Hashtable(); specDAG = new Hashtable>>(); availableIDs = new Vector(); } /** * Given a simple ID (forward direction, no inline filter, not * compound) attempt to instantiate it from the registry. Return * 0 on failure. * * Return a non-empty aliasReturn value if the ID points to an alias. * We cannot instantiate it ourselves because the alias may contain * filters or compounds, which we do not understand. Caller should * make aliasReturn empty before calling. */ public Transliterator get(String ID, StringBuffer aliasReturn) { Object[] entry = find(ID); return (entry == null) ? null : instantiateEntry(ID, entry, aliasReturn); } /** * Register a class. This adds an entry to the * dynamic store, or replaces an existing entry. Any entry in the * underlying static locale resource store is masked. */ public void put(String ID, Class transliteratorSubclass, boolean visible) { registerEntry(ID, transliteratorSubclass, visible); } /** * Register an ID and a factory function pointer. This adds an * entry to the dynamic store, or replaces an existing entry. Any * entry in the underlying static locale resource store is masked. */ public void put(String ID, Transliterator.Factory factory, boolean visible) { registerEntry(ID, factory, visible); } /** * Register an ID and a resource name. This adds an entry to the * dynamic store, or replaces an existing entry. Any entry in the * underlying static locale resource store is masked. */ public void put(String ID, String resourceName, String encoding, int dir, boolean visible) { registerEntry(ID, new ResourceEntry(resourceName, encoding, dir), visible); } /** * Register an ID and an alias ID. This adds an entry to the * dynamic store, or replaces an existing entry. Any entry in the * underlying static locale resource store is masked. */ public void put(String ID, String alias, boolean visible) { registerEntry(ID, new AliasEntry(alias), visible); } /** * Register an ID and a Transliterator object. This adds an entry * to the dynamic store, or replaces an existing entry. Any entry * in the underlying static locale resource store is masked. */ public void put(String ID, Transliterator trans, boolean visible) { registerEntry(ID, trans, visible); } /** * Unregister an ID. This removes an entry from the dynamic store * if there is one. The static locale resource store is * unaffected. */ public void remove(String ID) { String[] stv = TransliteratorIDParser.IDtoSTV(ID); // Only need to do this if ID.indexOf('-') < 0 String id = TransliteratorIDParser.STVtoID(stv[0], stv[1], stv[2]); registry.remove(new CaseInsensitiveString(id)); removeSTV(stv[0], stv[1], stv[2]); availableIDs.removeElement(new CaseInsensitiveString(id)); } //---------------------------------------------------------------------- // class TransliteratorRegistry: Public ID and spec management //---------------------------------------------------------------------- /** * An internal class that adapts an enumeration over * CaseInsensitiveStrings to an enumeration over Strings. */ private static class IDEnumeration implements Enumeration { Enumeration en; public IDEnumeration(Enumeration e) { en = e; } public boolean hasMoreElements() { return en != null && en.hasMoreElements(); } public String nextElement() { return (en.nextElement()).getString(); } } /** * Returns an enumeration over the programmatic names of visible * registered transliterators. * * @return An Enumeration over String objects */ public Enumeration getAvailableIDs() { // Since the cache contains CaseInsensitiveString objects, but // the caller expects Strings, we have to use an intermediary. return new IDEnumeration(availableIDs.elements()); } /** * Returns an enumeration over all visible source names. * * @return An Enumeration over String objects */ public Enumeration getAvailableSources() { return new IDEnumeration(specDAG.keys()); } /** * Returns an enumeration over visible target names for the given * source. * * @return An Enumeration over String objects */ public Enumeration getAvailableTargets(String source) { CaseInsensitiveString cisrc = new CaseInsensitiveString(source); Hashtable> targets = specDAG.get(cisrc); if (targets == null) { return new IDEnumeration(null); } return new IDEnumeration(targets.keys()); } /** * Returns an enumeration over visible variant names for the given * source and target. * * @return An Enumeration over String objects */ public Enumeration getAvailableVariants(String source, String target) { CaseInsensitiveString cisrc = new CaseInsensitiveString(source); CaseInsensitiveString citrg = new CaseInsensitiveString(target); Hashtable> targets = specDAG.get(cisrc); if (targets == null) { return new IDEnumeration(null); } Vector variants = targets.get(citrg); if (variants == null) { return new IDEnumeration(null); } return new IDEnumeration(variants.elements()); } //---------------------------------------------------------------------- // class TransliteratorRegistry: internal //---------------------------------------------------------------------- /** * Convenience method. Calls 6-arg registerEntry(). */ private void registerEntry(String source, String target, String variant, Object entry, boolean visible) { String s = source; if (s.length() == 0) { s = ANY; } String ID = TransliteratorIDParser.STVtoID(source, target, variant); registerEntry(ID, s, target, variant, entry, visible); } /** * Convenience method. Calls 6-arg registerEntry(). */ private void registerEntry(String ID, Object entry, boolean visible) { String[] stv = TransliteratorIDParser.IDtoSTV(ID); // Only need to do this if ID.indexOf('-') < 0 String id = TransliteratorIDParser.STVtoID(stv[0], stv[1], stv[2]); registerEntry(id, stv[0], stv[1], stv[2], entry, visible); } /** * Register an entry object (adopted) with the given ID, source, * target, and variant strings. */ private void registerEntry(String ID, String source, String target, String variant, Object entry, boolean visible) { CaseInsensitiveString ciID = new CaseInsensitiveString(ID); Object[] arrayOfObj; // Store the entry within an array so it can be modified later if (entry instanceof Object[]) { arrayOfObj = (Object[])entry; } else { arrayOfObj = new Object[] { entry }; } registry.put(ciID, arrayOfObj); if (visible) { registerSTV(source, target, variant); if (!availableIDs.contains(ciID)) { availableIDs.addElement(ciID); } } else { removeSTV(source, target, variant); availableIDs.removeElement(ciID); } } /** * Register a source-target/variant in the specDAG. Variant may be * empty, but source and target must not be. If variant is empty then * the special variant NO_VARIANT is stored in slot zero of the * UVector of variants. */ private void registerSTV(String source, String target, String variant) { // assert(source.length() > 0); // assert(target.length() > 0); CaseInsensitiveString cisrc = new CaseInsensitiveString(source); CaseInsensitiveString citrg = new CaseInsensitiveString(target); CaseInsensitiveString civar = new CaseInsensitiveString(variant); Hashtable> targets = specDAG.get(cisrc); if (targets == null) { targets = new Hashtable>(); specDAG.put(cisrc, targets); } Vector variants = targets.get(citrg); if (variants == null) { variants = new Vector(); targets.put(citrg, variants); } // assert(NO_VARIANT == ""); // We add the variant string. If it is the special "no variant" // string, that is, the empty string, we add it at position zero. if (!variants.contains(civar)) { if (variant.length() > 0) { variants.addElement(civar); } else { variants.insertElementAt(civar, 0); } } } /** * Remove a source-target/variant from the specDAG. */ private void removeSTV(String source, String target, String variant) { // assert(source.length() > 0); // assert(target.length() > 0); CaseInsensitiveString cisrc = new CaseInsensitiveString(source); CaseInsensitiveString citrg = new CaseInsensitiveString(target); CaseInsensitiveString civar = new CaseInsensitiveString(variant); Hashtable> targets = specDAG.get(cisrc); if (targets == null) { return; // should never happen for valid s-t/v } Vector variants = targets.get(citrg); if (variants == null) { return; // should never happen for valid s-t/v } variants.removeElement(civar); if (variants.size() == 0) { targets.remove(citrg); // should delete variants if (targets.size() == 0) { specDAG.remove(cisrc); // should delete targets } } } private static final boolean DEBUG = false; /** * Attempt to find a source-target/variant in the dynamic registry * store. Return 0 on failure. */ private Object[] findInDynamicStore(Spec src, Spec trg, String variant) { String ID = TransliteratorIDParser.STVtoID(src.get(), trg.get(), variant); ///CLOVER:OFF if (DEBUG) { System.out.println("TransliteratorRegistry.findInDynamicStore:" + ID); } ///CLOVER:ON return registry.get(new CaseInsensitiveString(ID)); } /** * Attempt to find a source-target/variant in the static locale * resource store. Do not perform fallback. Return 0 on failure. * * On success, create a new entry object, register it in the dynamic * store, and return a pointer to it, but do not make it public -- * just because someone requested something, we do not expand the * available ID list (or spec DAG). */ private Object[] findInStaticStore(Spec src, Spec trg, String variant) { ///CLOVER:OFF if (DEBUG) { String ID = TransliteratorIDParser.STVtoID(src.get(), trg.get(), variant); System.out.println("TransliteratorRegistry.findInStaticStore:" + ID); } ///CLOVER:ON Object[] entry = null; if (src.isLocale()) { entry = findInBundle(src, trg, variant, Transliterator.FORWARD); } else if (trg.isLocale()) { entry = findInBundle(trg, src, variant, Transliterator.REVERSE); } // If we found an entry, store it in the Hashtable for next // time. if (entry != null) { registerEntry(src.getTop(), trg.getTop(), variant, entry, false); } return entry; } /** * Attempt to find an entry in a single resource bundle. This is * a one-sided lookup. findInStaticStore() performs up to two such * lookups, one for the source, and one for the target. * * Do not perform fallback. Return 0 on failure. * * On success, create a new Entry object, populate it, and return it. * The caller owns the returned object. */ private Object[] findInBundle(Spec specToOpen, Spec specToFind, String variant, int direction) { // assert(specToOpen.isLocale()); ResourceBundle res = specToOpen.getBundle(); if (res == null) { // This means that the bundle's locale does not match // the current level of iteration for the spec. return null; } for (int pass=0; pass<2; ++pass) { StringBuilder tag = new StringBuilder(); // First try either TransliteratorTo_xxx or // TransliterateFrom_xxx, then try the bidirectional // Transliterate_xxx. This precedence order is arbitrary // but must be consistent and documented. if (pass == 0) { tag.append(direction == Transliterator.FORWARD ? "TransliterateTo" : "TransliterateFrom"); } else { tag.append("Transliterate"); } tag.append(specToFind.get().toUpperCase()); try { // The Transliterate*_xxx resource is an array of // strings of the format { , , ... }. Each // is a variant name, and each is a rule. String[] subres = res.getStringArray(tag.toString()); // assert(subres != null); // assert(subres.length % 2 == 0); int i = 0; if (variant.length() != 0) { for (i=0; i