| /* |
| * Copyright 2009 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.i18n.server; |
| |
| import com.google.gwt.i18n.shared.GwtLocale; |
| import com.google.gwt.i18n.shared.GwtLocaleFactory; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| /** |
| * Creates server-side GwtLocale instances. Thread-safe. |
| */ |
| public class GwtLocaleFactoryImpl implements GwtLocaleFactory { |
| |
| private static boolean isAlpha(String str, int min, int max) { |
| return matches(str, min, max, true); |
| } |
| |
| private static boolean isDigit(String str, int min, int max) { |
| return matches(str, min, max, false); |
| } |
| |
| /** |
| * Check if the supplied string matches length and composition requirements. |
| * |
| * @param str string to check |
| * @param min minimum length |
| * @param max maximum length |
| * @param lettersNotDigits true if all characters should be letters, false if |
| * all characters should be digits |
| * @return true if the string is of a proper length and contains only the |
| * specified characters |
| */ |
| private static boolean matches(String str, int min, int max, |
| boolean lettersNotDigits) { |
| int len = str.length(); |
| if (len < min || len > max) { |
| return false; |
| } |
| for (int i = 0; i < len; ++i) { |
| if ((lettersNotDigits && !Character.isLetter(str.charAt(i))) |
| || (!lettersNotDigits && !Character.isDigit(str.charAt(i)))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static String titleCase(String str) { |
| if (str.length() < 2) { |
| return str.toUpperCase(Locale.ROOT); |
| } |
| return String.valueOf(Character.toTitleCase(str.charAt(0))) + |
| str.substring(1).toLowerCase(Locale.ROOT); |
| } |
| |
| private final Object instanceCacheLock = new Object[0]; |
| |
| // Locales are stored pointing at themselves. A new instance is created, |
| // which is pretty cheap, then looked up here. If it exists, the old |
| // one is used instead to preserved cached data structures. |
| private Map<GwtLocaleImpl, GwtLocaleImpl> instanceCache |
| = new HashMap<GwtLocaleImpl, GwtLocaleImpl>(); |
| |
| /** |
| * Clear an embedded cache of instances when they are no longer needed. |
| * <p> |
| * Note that GwtLocale instances constructed after this is called will not |
| * maintain identity with instances constructed before this call. |
| */ |
| public void clear() { |
| synchronized (instanceCacheLock) { |
| instanceCache.clear(); |
| } |
| } |
| |
| public GwtLocale fromComponents(String language, String script, |
| String region, String variant) { |
| if (language != null && language.length() == 0) { |
| language = null; |
| } |
| if (language != null) { |
| language = language.toLowerCase(Locale.ROOT); |
| } |
| if (script != null && script.length() == 0) { |
| script = null; |
| } |
| if (script != null) { |
| script = titleCase(script); |
| } |
| if (region != null && region.length() == 0) { |
| region = null; |
| } |
| if (region != null) { |
| region = region.toUpperCase(Locale.ROOT); |
| } |
| if (variant != null && variant.length() == 0) { |
| variant = null; |
| } |
| if (variant != null) { |
| variant = variant.toUpperCase(Locale.ROOT); |
| } |
| GwtLocaleImpl locale = new GwtLocaleImpl(this, language, region, script, |
| variant); |
| synchronized (instanceCacheLock) { |
| if (instanceCache.containsKey(locale)) { |
| return instanceCache.get(locale); |
| } |
| instanceCache.put(locale, locale); |
| } |
| return locale; |
| } |
| |
| /** |
| * @throws IllegalArgumentException if the supplied locale does not match |
| * BCP47 structural requirements. |
| */ |
| public GwtLocale fromString(String localeName) { |
| String language = null; |
| String script = null; |
| String region = null; |
| String variant = null; |
| if (localeName != null && !GwtLocale.DEFAULT_LOCALE.equals(localeName)) { |
| // split into component parts |
| ArrayList<String> localeParts = new ArrayList<String>(); |
| String[] parts = localeName.split("[-_]"); |
| for (int i = 0; i < parts.length; ++i) { |
| if (parts[i].length() == 1 && i + 1 < parts.length) { |
| localeParts.add(parts[i] + '-' + parts[++i]); |
| } else { |
| localeParts.add(parts[i]); |
| } |
| } |
| |
| // figure out the role of each part |
| int partIdx = 1; |
| int numParts = localeParts.size(); |
| if (numParts > 0) { |
| // Treat an initial private-use tag as the language tag |
| // Otherwise, language tags are 2-3 characters plus up to three |
| // 3-letter extensions, or 4-8 characters. |
| language = localeParts.get(0); |
| int len = language.length(); |
| // TODO: verify language tag length |
| // See if we have extended language tags |
| if ((len == 2 || len == 3) && partIdx < numParts) { |
| String part = localeParts.get(partIdx); |
| while (partIdx < numParts && partIdx < 4 |
| && isAlpha(part, 3, 3)) { |
| language += '-' + part; |
| if (++partIdx >= numParts) { |
| break; |
| } |
| part = localeParts.get(partIdx); |
| } |
| } |
| } |
| if (numParts > partIdx |
| && isAlpha(localeParts.get(partIdx), 4, 4)) { |
| // Scripts are exactly 4 letters |
| script = localeParts.get(partIdx++); |
| } |
| if (partIdx < numParts) { |
| // Regions may be 2 letters or 3 digits |
| String part = localeParts.get(partIdx); |
| if (isAlpha(part, 2, 2) || isDigit(part, 3, 3)) { |
| region = part; |
| ++partIdx; |
| } |
| } |
| if (partIdx < numParts) { |
| // Variants are 5-8 alphanum, or 4 alphanum if first is digit |
| String part = localeParts.get(partIdx); |
| int len = part.length(); |
| if ((len >= 5 && len <= 8) || (len == 4 |
| && Character.isDigit(part.charAt(0)))) { |
| variant = part; |
| ++partIdx; |
| } |
| } |
| if (partIdx < numParts) { |
| throw new IllegalArgumentException("Unrecognized locale format: " |
| + localeName); |
| } |
| } |
| return fromComponents(language, script, region, variant); |
| } |
| |
| public GwtLocale getDefault() { |
| return fromComponents(null, null, null, null); |
| } |
| } |