blob: 7b232fdbea77397ab9604dc33027e541a5a8ef29 [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 2009, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.impl.locale;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.util.InvalidLocaleException;
public final class LocaleExtension {
private String _canonical;
private transient TreeMap/*<String,String>*/ _extensions;
private transient String _privuse;
private transient TreeMap/*<String,String>*/ _keywords;
private static final LocaleObjectPool/*<String,LocaleExtension>*/ EXTENSIONPOOL =
new LocaleObjectPool/*<String,LocaleExtension>*/();
public static final LocaleExtension EMPTY_EXTENSION = new LocaleExtension("");
private static final String LOCALESEP = "_";
private static final String LOCALESINGLETON = "u";
private static final String PRIVUSE = "x";
private static final int MINLEN = 3; // minium length of string representation "x_?"
private LocaleExtension(String canonical) {
_canonical = canonical;
}
public static LocaleExtension get(String extstr) throws InvalidLocaleException {
if (extstr == null || extstr.length() == 0) {
return EMPTY_EXTENSION;
}
// Convert to lower case
extstr = AsciiUtil.toLowerString(extstr);
LocaleExtension singleton = (LocaleExtension)EXTENSIONPOOL.get(extstr);
if (singleton == null) {
LocaleExtension locext = getCanonical(extstr);
// Try to get from the pool with the canonicalized string
singleton = (LocaleExtension)EXTENSIONPOOL.get(locext.toString());
if (singleton == null) {
singleton = (LocaleExtension)EXTENSIONPOOL.register(locext.toString(), locext);
}
}
return singleton;
}
/*
* This method is package local and used by LocaleBuilder.
* LocaleBuilder stores singleton extensions including locale
* keywords in a Map structure, so we do not need to parse plain
* extension string again.
*/
static LocaleExtension get(TreeMap/*<String,String>*/ extensions, String privuse) {
boolean hasExtensions = (extensions != null && extensions.size() > 0);
boolean hasPrivuse = (privuse != null && privuse.length() > 0);
if (!hasExtensions && !hasPrivuse) {
return EMPTY_EXTENSION;
}
StringBuffer buf = new StringBuffer();
if (hasExtensions) {
mapToLocaleExtensionString(extensions, buf);
}
if (hasPrivuse) {
// prepend x_
privuse = PRIVUSE + LOCALESEP + privuse;
if (buf.length() > 0) {
buf.append(LOCALESEP);
}
buf.append(privuse);
}
String extstr = buf.toString();
// Check if the same LocaleExtension is available in the pool
LocaleExtension singleton = (LocaleExtension)EXTENSIONPOOL.get(extstr);
if (singleton == null) {
// create a new one
singleton = new LocaleExtension(extstr.intern());
singleton._extensions = extensions;
singleton._privuse = privuse;
if (singleton._extensions != null) {
String kwdstr = (String)singleton._extensions.get(LOCALESINGLETON);
if (kwdstr != null) {
singleton._keywords = parseKeywordSubtags(kwdstr, LOCALESEP);
}
}
singleton = (LocaleExtension)EXTENSIONPOOL.register(singleton._canonical, singleton);
}
return singleton;
}
private static LocaleExtension getCanonical(String extstr) throws InvalidLocaleException {
if (extstr == null || extstr.length() == 0) {
return EMPTY_EXTENSION;
}
if (extstr.length() < MINLEN) {
throw new InvalidLocaleException("Locale extension string '" + extstr + "' is too short.");
}
extstr = AsciiUtil.toLowerString(extstr);
TreeMap/*<String,String>*/ extensions = null;
TreeMap/*<String,String>*/ keywords = null;
String ext = null; // extensions part
String prv = null; // private use part
if (extstr.charAt(0) == PRIVUSE.charAt(0)) {
if (extstr.charAt(1) == LOCALESEP.charAt(0)) {
prv = extstr.substring(2).intern();
} else {
throw new InvalidLocaleException("Locale extension string '" + extstr
+ "' must start with a singleton segment.");
}
} else {
int idx = extstr.indexOf(LOCALESEP + PRIVUSE + LOCALESEP);
if (idx == -1) {
ext = extstr;
} else {
ext = extstr.substring(0, idx);
prv = extstr.substring(idx + 3).intern();
}
}
if (ext != null) {
//String[] subtags = ext.split(LOCALESEP);
String[] subtags = Utility.split(ext, LOCALESEP.charAt(0));
String letter = subtags[0];
if (letter.length() != 1) {
throw new InvalidLocaleException("Locale extension string '" + extstr
+ "' must start with a singleton segment.");
}
extensions = new TreeMap/*<String,String>*/();
StringBuffer buf = new StringBuffer();
boolean inLocaleKeywords = false;
String kwkey = null;
for (int i = 1; i < subtags.length; i++) {
if (subtags[i].length() == 0) {
throw new InvalidLocaleException("Locale extension string '" + extstr
+ "' contains an empty segment.");
}
if (subtags[i].length() == 1) {
// next extension singleton
if (extensions.containsKey(subtags[i])) {
throw new InvalidLocaleException("Locale extension string '" + extstr
+ "' contains multiple extensions: " + subtags[i]);
}
// write out the previous extension
if (inLocaleKeywords) {
if (kwkey != null) {
throw new InvalidLocaleException("Locale extension string '" + extstr
+ "' contains a malformed locale keywords: " + kwkey);
}
// creating a single string including locale keyword key/type pairs
mapToLocaleExtensionString(keywords, buf);
inLocaleKeywords = false;
}
if (buf.length() == 0) {
throw new InvalidLocaleException("Locale extension string '" + extstr
+ "' contains an empty extension value.");
}
extensions.put(letter.intern(), buf.toString().intern());
// preparation for next extension
if (subtags[i].equals(LOCALESINGLETON)) {
keywords = new TreeMap/*<String,String>*/();
inLocaleKeywords = true;
}
letter = subtags[i];
buf.setLength(0);
continue;
}
if (inLocaleKeywords) {
if (kwkey == null) {
kwkey = subtags[i];
} else {
keywords.put(kwkey.intern(), subtags[i].intern());
kwkey = null;
}
} else {
// append an extension subtag
if (buf.length() > 0) {
buf.append(LOCALESEP);
}
buf.append(subtags[i]);
}
}
// process the last extension
if (inLocaleKeywords) {
if (kwkey != null) {
throw new InvalidLocaleException("Locale extension string '" + extstr
+ "' contains a malformed locale keywords: " + kwkey);
}
// creating a single string including locale keyword key/type pairs
mapToLocaleExtensionString(keywords, buf);
}
if (buf.length() == 0) {
throw new InvalidLocaleException("Locale extension string '" + extstr
+ "' contains an empty extension value.");
}
extensions.put(letter.intern(), buf.toString().intern());
}
// Reconstruct a locale extension string
StringBuffer canonicalbuf = new StringBuffer();
if (extensions != null) {
Set/*<Map.Entry<String,String>>*/ entries = extensions.entrySet();
//for (Map.Entry<String,String> entry : entries) {
Iterator itr = entries.iterator();
while (itr.hasNext()) {
Map.Entry entry = (Map.Entry)itr.next();
if (canonicalbuf.length() > 0) {
canonicalbuf.append(LOCALESEP);
}
canonicalbuf.append(entry.getKey());
canonicalbuf.append(LOCALESEP);
canonicalbuf.append(entry.getValue());
}
}
if (prv != null) {
if (canonicalbuf.length() > 0) {
canonicalbuf.append(LOCALESEP);
}
canonicalbuf.append(PRIVUSE);
canonicalbuf.append(LOCALESEP);
canonicalbuf.append(prv);
}
// Finally, create an instance of LocaleExtension
LocaleExtension le = new LocaleExtension(canonicalbuf.toString().intern());
le._extensions = extensions;
le._keywords = keywords;
le._privuse = prv;
return le;
}
public String toString() {
return _canonical;
}
public int hashCode() {
return _canonical.hashCode();
}
public String getPrivateUse() {
return _privuse;
}
public Set/*<String>*/ getLocaleKeywordKeys() {
if (_keywords != null) {
return _keywords.keySet();
}
return null;
}
public boolean containsLocaleKeywordKey(String key) {
if (_keywords != null) {
return _keywords.containsKey(key);
}
return false;
}
public String getLocaleKeywordType(String key) {
if (_keywords != null) {
return (String)_keywords.get(key);
}
return null;
}
public Set/*<String>*/ getExtensionKeys() {
if (_extensions != null) {
return _extensions.keySet();
}
return null;
}
public boolean containsExtensionKey(String key) {
if (_extensions != null) {
return _extensions.containsKey(key);
}
return false;
}
public String getExtensionValue(String key) {
if (_extensions != null) {
return (String)_extensions.get(key);
}
return null;
}
static String mapToLocaleExtensionString(Map/*<String,String>*/ map, StringBuffer buf) {
Set/*<Map.Entry<String,String>>*/ entries = map.entrySet();
Iterator itr = entries.iterator();
//for (Map.Entry<String,String> entry : entries) {
while (itr.hasNext()) {
Map.Entry entry = (Map.Entry)itr.next();
if (buf.length() > 0) {
buf.append(LOCALESEP);
}
buf.append(entry.getKey());
buf.append(LOCALESEP);
buf.append(entry.getValue());
}
return buf.toString();
}
static TreeMap/*<String,String>*/ parseKeywordSubtags(String text, String delim) {
if (text == null || text.length() == 0) {
return null;
}
//String[] subtags = AsciiUtil.toLowerString(text).split(delim);
String[] subtags = Utility.split(AsciiUtil.toLowerString(text), delim.charAt(0));
if ((subtags.length % 2) != 0) {
// number of keyword subtags must be even
return null;
}
TreeMap/*<String,String>*/ keywords = new TreeMap/*<String,String>*/();
int idx = 0;
while (idx < subtags.length) {
String key = subtags[idx++];
String type = subtags[idx++];
if ((keywords.put(key.intern(), type.intern())) != null) {
return null;
}
}
return keywords;
}
}