Revision: 6926 Author: [email protected] Date: Mon Nov 16 13:28:11 2009 Log: Fix thread safety issues in GwtLocaleFactoryImpl and LocaleUtils.
Patch by: jat Review by: amitmanjhi http://code.google.com/p/google-web-toolkit/source/detail?r=6926 Added: /trunk/dev/core/src/com/google/gwt/core/ext/DefaultConfigurationProperty.java /trunk/dev/core/src/com/google/gwt/core/ext/DefaultSelectionProperty.java Modified: /trunk/dev/core/src/com/google/gwt/core/ext/PropertyOracle.java /trunk/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java /trunk/dev/core/src/com/google/gwt/dev/shell/ModuleSpaceOOPHM.java /trunk/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java /trunk/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java /trunk/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java /trunk/user/src/com/google/gwt/i18n/rebind/LocaleUtils.java /trunk/user/src/com/google/gwt/i18n/server/GwtLocaleFactoryImpl.java /trunk/user/src/com/google/gwt/i18n/server/GwtLocaleImpl.java ======================================= --- /dev/null +++ /trunk/dev/core/src/com/google/gwt/core/ext/DefaultConfigurationProperty.java Mon Nov 16 13:28:11 2009 @@ -0,0 +1,81 @@ +/* + * 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.core.ext; + +import java.util.List; + +/** + * Default immutable implementation of ConfigurationProperty that receives its + * values in its constructor. + */ +public class DefaultConfigurationProperty implements ConfigurationProperty { + + private final String name; + private final List<String> values; + + /** + * Construct a configuration property. + * + * @param name the name of this property, must not be null + * @param values the list of possible values, must not be null and + * will be returned to callers, so a copy should be passed into this + * ctor if the caller will use this set later + */ + public DefaultConfigurationProperty(String name, List<String> values) { + assert name != null; + assert values != null; + this.name = name; + this.values = values; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + DefaultConfigurationProperty other = (DefaultConfigurationProperty) obj; + return name.equals(other.name) + && values.equals(other.values); + } + + public String getName() { + return name; + } + + public List<String> getValues() { + return values; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + name.hashCode(); + result = prime * result + values.hashCode(); + return result; + } + + @Override + public String toString() { + return "ConfigProp " + name + ": " + values.toString(); + } +} ======================================= --- /dev/null +++ /trunk/dev/core/src/com/google/gwt/core/ext/DefaultSelectionProperty.java Mon Nov 16 13:28:11 2009 @@ -0,0 +1,103 @@ +/* + * 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.core.ext; + +import java.util.SortedSet; + +/** + * Default immutable implementation of SelectionProperty that receives its + * values in its constructor. + */ +public class DefaultSelectionProperty implements SelectionProperty { + + private final String currentValue; + private final String fallbackValue; + private final String name; + private final SortedSet<String> possibleValues; + + /** + * Construct a selection property. + * + * @param currentValue current value of this property, must not be null + * @param fallbackValue the fallback value to use, must not be null + * @param name the name of this property, must not be null + * @param possibleValues the set of possible values, must not be null and + * will be returned to callers, so a copy should be passed into this + * ctor if the caller will use this set later + */ + public DefaultSelectionProperty(String currentValue, String fallbackValue, + String name, SortedSet<String> possibleValues) { + assert currentValue != null; + assert fallbackValue != null; + assert name != null; + assert possibleValues != null; + this.currentValue = currentValue; + this.fallbackValue = fallbackValue; + this.name = name; + this.possibleValues = possibleValues; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + DefaultSelectionProperty other = (DefaultSelectionProperty) obj; + return currentValue.equals(other.currentValue) + && fallbackValue.equals(other.fallbackValue) + && name.equals(other.name) + && possibleValues.equals(other.possibleValues); + } + + public String getCurrentValue() { + return currentValue; + } + + public String getFallbackValue() { + return fallbackValue; + } + + public String getName() { + return name; + } + + public SortedSet<String> getPossibleValues() { + return possibleValues; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + currentValue.hashCode(); + result = prime * result + fallbackValue.hashCode(); + result = prime * result + name.hashCode(); + result = prime * result + possibleValues.hashCode(); + return result; + } + + @Override + public String toString() { + return "SelectionProp " + name + ": " + currentValue + " of " + + possibleValues.toString() + ", fallback =" + fallbackValue; + } +} ======================================= --- /trunk/dev/core/src/com/google/gwt/core/ext/PropertyOracle.java Tue May 5 13:08:14 2009 +++ /trunk/dev/core/src/com/google/gwt/core/ext/PropertyOracle.java Mon Nov 16 13:28:11 2009 @@ -25,6 +25,11 @@ * <code>BadPropertyValueException</code> if the property is undefined. The * result of invoking this method with the same <code>propertyName</code> must * be stable. + * + * @param propertyName + * @return the configuration property instance (never null) + * @throws BadPropertyValueException if the property is unknown or not a + * configuration property */ ConfigurationProperty getConfigurationProperty(String propertyName) throws BadPropertyValueException; @@ -38,6 +43,8 @@ * @param logger the current logger * @param propertyName the name of the property * @return a value for the property + * @throws BadPropertyValueException if the property is unknown or not of the + * right type */ @Deprecated String getPropertyValue(TreeLogger logger, String propertyName) @@ -53,6 +60,8 @@ * @param logger the current logger * @param propertyName the name of the property * @return the possible values for the property + * @throws BadPropertyValueException if the property is unknown or not of the + * right type */ @Deprecated String[] getPropertyValueSet(TreeLogger logger, String propertyName) @@ -63,6 +72,11 @@ * <code>BadPropertyValueException</code> if the property is either undefined * or has a value that is unsupported. The result of invoking this method with * the same <code>propertyName</code> must be stable. + * @param logger + * @param propertyName + * @return the selection property instance (never null) + * @throws BadPropertyValueException if the property is unknown or not a + * selection property */ SelectionProperty getSelectionProperty(TreeLogger logger, String propertyName) throws BadPropertyValueException; ======================================= --- /trunk/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java Tue Jun 16 14:20:27 2009 +++ /trunk/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java Mon Nov 16 13:28:11 2009 @@ -16,12 +16,12 @@ package com.google.gwt.dev.cfg; import com.google.gwt.core.ext.BadPropertyValueException; +import com.google.gwt.core.ext.DefaultConfigurationProperty; +import com.google.gwt.core.ext.DefaultSelectionProperty; import com.google.gwt.core.ext.PropertyOracle; import com.google.gwt.core.ext.TreeLogger; import java.io.Serializable; -import java.util.List; -import java.util.SortedSet; import java.util.TreeSet; /** @@ -36,6 +36,13 @@ private final String[] orderedPropValues; + /** + * Create a property oracle that will return the supplied values. + * + * @param orderedProps array of binding properties + * @param orderedPropValues values of the above binding properties + * @param configProps array of config properties + */ public StaticPropertyOracle(BindingProperty[] orderedProps, String[] orderedPropValues, ConfigurationProperty[] configProps) { this.orderedProps = orderedProps; @@ -58,24 +65,23 @@ String propertyName) throws BadPropertyValueException { for (final ConfigurationProperty prop : configProps) { if (prop.getName().equals(propertyName)) { - return new com.google.gwt.core.ext.ConfigurationProperty() { - public String getName() { - return prop.getName(); - } - - public List<String> getValues() { - return prop.getValues(); - } - }; + return new DefaultConfigurationProperty(prop.getName(), + prop.getValues()); } } throw new BadPropertyValueException(propertyName); } + /** + * @return an array of binding properties. + */ public BindingProperty[] getOrderedProps() { return orderedProps; } + /** + * @return an array of binding property values. + */ public String[] getOrderedPropValues() { return orderedPropValues; } @@ -138,7 +144,6 @@ for (int i = 0; i < orderedProps.length; i++) { final BindingProperty prop = orderedProps[i]; final String name = prop.getName(); - final String fallback = prop.getFallback(); if (name.equals(propertyName)) { final String value = orderedPropValues[i]; String[] values = prop.getDefinedValues(); @@ -146,24 +151,8 @@ for (String v : values) { possibleValues.add(v); } - - return new com.google.gwt.core.ext.SelectionProperty() { - public String getCurrentValue() { - return value; - } - - public String getFallbackValue() { - return fallback; - } - - public String getName() { - return name; - } - - public SortedSet<String> getPossibleValues() { - return possibleValues; - } - }; + return new DefaultSelectionProperty(value, prop.getFallback(), name, + possibleValues); } } ======================================= --- /trunk/dev/core/src/com/google/gwt/dev/shell/ModuleSpaceOOPHM.java Fri Oct 16 20:54:44 2009 +++ /trunk/dev/core/src/com/google/gwt/dev/shell/ModuleSpaceOOPHM.java Mon Nov 16 13:28:11 2009 @@ -100,7 +100,7 @@ @Override protected JsValue doInvoke(String name, Object jthis, Class<?>[] types, Object[] args) throws Throwable { - TreeLogger branch = host.getLogger().branch(TreeLogger.DEBUG, + TreeLogger branch = host.getLogger().branch(TreeLogger.SPAM, "Invoke native method " + name, null); CompilingClassLoader isolatedClassLoader = getIsolatedClassLoader(); JsValueOOPHM jsthis = new JsValueOOPHM(); @@ -121,7 +121,7 @@ returnVal); branch.log(TreeLogger.SPAM, " returned " + returnVal); } catch (Throwable t) { - branch.log(TreeLogger.DEBUG, "exception thrown", t); + branch.log(TreeLogger.SPAM, "exception thrown", t); throw t; } return returnVal; ======================================= --- /trunk/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java Mon Aug 17 09:47:12 2009 +++ /trunk/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java Mon Nov 16 13:28:11 2009 @@ -16,6 +16,8 @@ package com.google.gwt.dev.shell; import com.google.gwt.core.ext.BadPropertyValueException; +import com.google.gwt.core.ext.DefaultConfigurationProperty; +import com.google.gwt.core.ext.DefaultSelectionProperty; import com.google.gwt.core.ext.PropertyOracle; import com.google.gwt.core.ext.SelectionProperty; import com.google.gwt.core.ext.TreeLogger; @@ -45,6 +47,12 @@ private final ModuleSpace space; + /** + * Create a property oracle that computes its properties from a module. + * + * @param props + * @param space + */ public ModuleSpacePropertyOracle(Properties props, ModuleSpace space) { this.space = space; this.props = props; @@ -57,15 +65,7 @@ final ConfigurationProperty cprop = (ConfigurationProperty) prop; final String name = cprop.getName(); final List<String> values = cprop.getValues(); - return new com.google.gwt.core.ext.ConfigurationProperty() { - public String getName() { - return name; - } - - public List<String> getValues() { - return values; - } - }; + return new DefaultConfigurationProperty(name, values); } else { throw new BadPropertyValueException(propertyName); } @@ -132,24 +132,8 @@ for (String v : cprop.getDefinedValues()) { possibleValues.add(v); } - return new com.google.gwt.core.ext.SelectionProperty() { - - public String getCurrentValue() { - return value; - } - - public String getFallbackValue() { - return fallback; - } - - public String getName() { - return name; - } - - public SortedSet<String> getPossibleValues() { - return possibleValues; - } - }; + return new DefaultSelectionProperty(value, fallback, name, + possibleValues); } else { throw new BadPropertyValueException(propertyName); } ======================================= --- /trunk/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java Tue Jun 23 20:13:51 2009 +++ /trunk/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java Mon Nov 16 13:28:11 2009 @@ -258,9 +258,9 @@ TypeOracle typeOracle = context.getTypeOracle(); PropertyOracle propertyOracle = context.getPropertyOracle(); - LocaleUtils.init(logger, propertyOracle); - GwtLocale locale = LocaleUtils.getCompileLocale(); - Set<GwtLocale> runtimeLocales = LocaleUtils.getRuntimeLocales(); + LocaleUtils localeUtils = LocaleUtils.getInstance(logger, propertyOracle); + GwtLocale locale = localeUtils.getCompileLocale(); + Set<GwtLocale> runtimeLocales = localeUtils.getRuntimeLocales(); JClassType targetClass; try { ======================================= --- /trunk/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java Mon May 18 11:47:32 2009 +++ /trunk/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java Mon Nov 16 13:28:11 2009 @@ -81,7 +81,8 @@ TypeOracle typeOracle = context.getTypeOracle(); // Get the current locale and interface type. PropertyOracle propertyOracle = context.getPropertyOracle(); - LocaleUtils.init(logger, propertyOracle); + LocaleUtils localeUtils = LocaleUtils.getInstance(logger, + propertyOracle); JClassType targetClass; try { @@ -90,12 +91,14 @@ logger.log(TreeLogger.ERROR, "No such type " + typeName, e); throw new UnableToCompleteException(); } - assert (LocaleInfoImpl.class.getName().equals(targetClass.getQualifiedSourceName())); + assert (LocaleInfoImpl.class.getName().equals( + targetClass.getQualifiedSourceName())); String packageName = targetClass.getPackage().getName(); String superClassName = targetClass.getName().replace('.', '_') + "_shared"; - Set<GwtLocale> localeSet = LocaleUtils.getAllLocales(); - GwtLocaleImpl[] allLocales = localeSet.toArray(new GwtLocaleImpl[localeSet.size()]); + Set<GwtLocale> localeSet = localeUtils.getAllLocales(); + GwtLocaleImpl[] allLocales = localeSet.toArray( + new GwtLocaleImpl[localeSet.size()]); // sort for deterministic output Arrays.sort(allLocales); PrintWriter pw = context.tryCreate(logger, packageName, superClassName); @@ -184,10 +187,10 @@ writer.println("}-*/;"); writer.commit(logger); } - GwtLocale locale = LocaleUtils.getCompileLocale(); + GwtLocale locale = localeUtils.getCompileLocale(); String className = targetClass.getName().replace('.', '_') + "_" + locale.getAsString(); - Set<GwtLocale> runtimeLocales = LocaleUtils.getRuntimeLocales(); + Set<GwtLocale> runtimeLocales = localeUtils.getRuntimeLocales(); if (!runtimeLocales.isEmpty()) { className += "_runtimeSelection"; } @@ -219,7 +222,6 @@ writer.println("public DateTimeConstants getDateTimeConstants() {"); LocalizableGenerator localizableGenerator = new LocalizableGenerator(); // Avoid warnings for trying to create the same type multiple times - @SuppressWarnings("hiding") GeneratorContext subContext = new CachedGeneratorContext(context); generateConstantsLookup(logger, subContext, writer, localizableGenerator, runtimeLocales, locale, ======================================= --- /trunk/user/src/com/google/gwt/i18n/rebind/LocaleUtils.java Thu Nov 12 12:14:16 2009 +++ /trunk/user/src/com/google/gwt/i18n/rebind/LocaleUtils.java Mon Nov 16 13:28:11 2009 @@ -25,8 +25,10 @@ import com.google.gwt.i18n.shared.GwtLocaleFactory; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.SortedSet; @@ -34,10 +36,58 @@ * Utility methods for dealing with locales. */ public class LocaleUtils { - // TODO(jat): rewrite to avoid statics + + /** + * A key for lookup of computed values in a cache. + */ + private static class CacheKey { + private final SelectionProperty localeProperty; + private final ConfigurationProperty runtimeLocaleProperty; + + /** + * Create a key for cache lookup. + * + * @param localeProperty "locale" property, must not be null + * @param runtimeLocaleProperty "runtime.locales" property, must not be null + */ + public CacheKey(SelectionProperty localeProperty, + ConfigurationProperty runtimeLocaleProperty) { + this.localeProperty = localeProperty; + this.runtimeLocaleProperty = runtimeLocaleProperty; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + CacheKey other = (CacheKey) obj; + return localeProperty.equals(other.localeProperty) + && runtimeLocaleProperty.equals(other.runtimeLocaleProperty); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + localeProperty.hashCode(); + result = prime * result + runtimeLocaleProperty.hashCode(); + return result; + } + } private static final GwtLocaleFactoryImpl factory = new GwtLocaleFactoryImpl(); + private static final Object cacheLock = new Object[0]; + + private static Map<CacheKey, LocaleUtils> cache; + /** * The token representing the locale property controlling Localization. */ @@ -48,20 +98,123 @@ */ private static final String PROP_RUNTIME_LOCALES = "runtime.locales"; - private static GwtLocale compileLocale; - - private static final Set<GwtLocale> allLocales = new HashSet<GwtLocale>(); - - private static final Set<GwtLocale> allCompileLocales = new HashSet<GwtLocale>(); - - private static final Set<GwtLocale> runtimeLocales = new HashSet<GwtLocale>(); - - public static synchronized void clear() { - allCompileLocales.clear(); - allLocales.clear(); - compileLocale = null; - factory.clear(); - runtimeLocales.clear(); + /** + * Clear any static state associated with LocaleUtils. + */ + public static synchronized void clear() { + factory.clear(); + synchronized (cacheLock) { + cache = null; + } + } + + /** + * Create a new LocaleUtils instance for the given PropertyOracle. Returned + * instances will be immutable and can be shared across threads. + * + * @param logger + * @param propertyOracle + * @return LocaleUtils instance + */ + public static LocaleUtils getInstance(TreeLogger logger, + PropertyOracle propertyOracle) { + try { + SelectionProperty localeProp + = propertyOracle.getSelectionProperty(logger, PROP_LOCALE); + ConfigurationProperty runtimeLocaleProp + = propertyOracle.getConfigurationProperty(PROP_RUNTIME_LOCALES); + CacheKey key = new CacheKey(localeProp, runtimeLocaleProp); + synchronized (cacheLock) { + if (cache == null) { + cache = new HashMap<CacheKey, LocaleUtils>(); + } + LocaleUtils localeUtils = cache.get(key); + if (localeUtils == null) { + localeUtils = createInstance(logger, localeProp, runtimeLocaleProp); + cache.put(key, localeUtils); + } + return localeUtils; + } + } catch (BadPropertyValueException e) { + // if we don't have locale properties defined, just return a basic one + logger.log(TreeLogger.WARN, + "Unable to get locale properties, using defaults", e); + GwtLocale defaultLocale = factory.fromString("default"); + Set<GwtLocale> allLocales = new HashSet<GwtLocale>(); + allLocales.add(defaultLocale); + return new LocaleUtils(defaultLocale, allLocales, allLocales, + Collections.<GwtLocale>emptySet()); + } + } + + /** + * Get a shared GwtLocale factory so instances are cached between all uses. + * + * @return singleton GwtLocaleFactory instance. + */ + public static synchronized GwtLocaleFactory getLocaleFactory() { + return factory; + } + + private static LocaleUtils createInstance(TreeLogger logger, + SelectionProperty localeProp, ConfigurationProperty prop) { + GwtLocale compileLocale = null; + Set<GwtLocale> allLocales = new HashSet<GwtLocale>(); + Set<GwtLocale> allCompileLocales = new HashSet<GwtLocale>(); + Set<GwtLocale> runtimeLocales = new HashSet<GwtLocale>(); + String localeName = localeProp.getCurrentValue(); + SortedSet<String> localeValues = localeProp.getPossibleValues(); + + GwtLocaleFactory factoryInstance = getLocaleFactory(); + GwtLocale newCompileLocale = factoryInstance.fromString(localeName); + compileLocale = newCompileLocale; + for (String localeValue : localeValues) { + allCompileLocales.add(factoryInstance.fromString(localeValue)); + } + allLocales.addAll(allCompileLocales); + + List<String> rtLocaleNames = prop.getValues(); + if (rtLocaleNames != null) { + for (String rtLocale : rtLocaleNames) { + GwtLocale locale = factoryInstance.fromString(rtLocale); + // TODO(jat): remove use of labels + existingLocales: + for (GwtLocale existing : allCompileLocales) { + for (GwtLocale alias : existing.getAliases()) { + if (!alias.isDefault() && locale.inheritsFrom(alias) + && locale.usesSameScript(alias)) { + allLocales.add(locale); + break existingLocales; + } + } + } + if (!compileLocale.isDefault() + && locale.inheritsFrom(compileLocale) + && locale.usesSameScript(compileLocale)) { + // TODO(jat): don't include runtime locales which also inherit + // from a more-specific compile locale than this one + runtimeLocales.add(locale); + } + } + } + return new LocaleUtils(compileLocale, allLocales, allCompileLocales, + runtimeLocales); + } + + private final Set<GwtLocale> allCompileLocales; + + private final Set<GwtLocale> allLocales; + + private final GwtLocale compileLocale; + + private final Set<GwtLocale> runtimeLocales; + + private LocaleUtils(GwtLocale compileLocale, Set<GwtLocale> allLocales, + Set<GwtLocale> allCompileLocales, Set<GwtLocale> runtimeLocales) { + this.compileLocale = compileLocale; + this.allLocales = Collections.unmodifiableSet(allLocales); + this.allCompileLocales = Collections.unmodifiableSet(allCompileLocales); + this.runtimeLocales = Collections.unmodifiableSet(runtimeLocales); } /** @@ -69,8 +222,8 @@ * * @return unmodifiable set of all compile-time locales */ - public static synchronized Set<GwtLocale> getAllCompileLocales() { - return Collections.unmodifiableSet(allCompileLocales); + public Set<GwtLocale> getAllCompileLocales() { + return allCompileLocales; } /** @@ -79,25 +232,16 @@ * * @return unmodifiable set of all locales */ - public static synchronized Set<GwtLocale> getAllLocales() { - return Collections.unmodifiableSet(allLocales); + public Set<GwtLocale> getAllLocales() { + return allLocales; } /** * @return the static compile-time locale for this permutation. */ - public static synchronized GwtLocale getCompileLocale() { + public GwtLocale getCompileLocale() { return compileLocale; } - - /** - * Get a shared GwtLocale factory so instances are cached between all uses. - * - * @return singleton GwtLocaleFactory instance. - */ - public static synchronized GwtLocaleFactory getLocaleFactory() { - return factory; - } /** * Returns a list of locales which are children of the current compile-time @@ -105,69 +249,7 @@ * * @return unmodifiable list of matching locales */ - public static synchronized Set<GwtLocale> getRuntimeLocales() { - return Collections.unmodifiableSet(runtimeLocales); - } - - /** - * Initialize from properties. Only needs to be called once, before any other - * calls. - * - * @param logger - * @param propertyOracle - */ - public static synchronized void init(TreeLogger logger, PropertyOracle propertyOracle) { - try { - SelectionProperty localeProp - = propertyOracle.getSelectionProperty(logger, PROP_LOCALE); - String localeName = localeProp.getCurrentValue(); - SortedSet<String> localeValues = localeProp.getPossibleValues(); - - GwtLocale newCompileLocale = factory.fromString(localeName); - if (newCompileLocale.equals(compileLocale)) { - return; - } - compileLocale = newCompileLocale; - allLocales.clear(); - allCompileLocales.clear(); - runtimeLocales.clear(); - for (String localeValue : localeValues) { - allCompileLocales.add(factory.fromString(localeValue)); - } - allLocales.addAll(allCompileLocales); - - ConfigurationProperty prop - = propertyOracle.getConfigurationProperty(PROP_RUNTIME_LOCALES); - List<String> rtLocaleNames = prop.getValues(); - if (rtLocaleNames != null) { - for (String rtLocale : rtLocaleNames) { - GwtLocale locale = factory.fromString(rtLocale); - // TODO(jat): remove use of labels - existingLocales: - for (GwtLocale existing : allCompileLocales) { - for (GwtLocale alias : existing.getAliases()) { - if (!alias.isDefault() && locale.inheritsFrom(alias) - && locale.usesSameScript(alias)) { - allLocales.add(locale); - break existingLocales; - } - } - } - if (!compileLocale.isDefault() - && locale.inheritsFrom(compileLocale) - && locale.usesSameScript(compileLocale)) { - // TODO(jat): don't include runtime locales which also inherit - // from a more-specific compile locale than this one - runtimeLocales.add(locale); - } - } - } - } catch (BadPropertyValueException e) { - logger.log(TreeLogger.TRACE, - "Unable to get locale properties, using defaults", e); - compileLocale = factory.fromString("default"); - allLocales.add(compileLocale); - return; - } + public Set<GwtLocale> getRuntimeLocales() { + return runtimeLocales; } } ======================================= --- /trunk/user/src/com/google/gwt/i18n/server/GwtLocaleFactoryImpl.java Wed Oct 28 09:10:53 2009 +++ /trunk/user/src/com/google/gwt/i18n/server/GwtLocaleFactoryImpl.java Mon Nov 16 13:28:11 2009 @@ -24,31 +24,38 @@ import java.util.Map; /** - * Creates server-side GwtLocale instances. + * Creates server-side GwtLocale instances. Thread-safe. */ public class GwtLocaleFactoryImpl implements GwtLocaleFactory { - // TODO(jat): remove code duplication here by combining these private static boolean isAlpha(String str, int min, int max) { - int len = str.length(); - if (len < min || len > max) { - return false; - } - for (int i = 0; i < len; ++i) { - if (!Character.isLetter(str.charAt(i))) { - return false; - } - } - return true; + 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 (!Character.isDigit(str.charAt(i))) { + if ((lettersNotDigits && !Character.isLetter(str.charAt(i))) + || (!lettersNotDigits && !Character.isDigit(str.charAt(i)))) { return false; } } @@ -63,13 +70,24 @@ str.substring(1).toLowerCase(Locale.ENGLISH); } + 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>(); - + 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() { - instanceCache.clear(); + synchronized (instanceCacheLock) { + instanceCache.clear(); + } } public GwtLocale fromComponents(String language, String script, @@ -100,10 +118,12 @@ } GwtLocaleImpl locale = new GwtLocaleImpl(this, language, region, script, variant); - if (instanceCache.containsKey(locale)) { - return instanceCache.get(locale); - } - instanceCache.put(locale, locale); + synchronized (instanceCacheLock) { + if (instanceCache.containsKey(locale)) { + return instanceCache.get(locale); + } + instanceCache.put(locale, locale); + } return locale; } @@ -185,5 +205,4 @@ public GwtLocale getDefault() { return fromComponents(null, null, null, null); } - -} +} ======================================= --- /trunk/user/src/com/google/gwt/i18n/server/GwtLocaleImpl.java Wed Oct 28 09:10:53 2009 +++ /trunk/user/src/com/google/gwt/i18n/server/GwtLocaleImpl.java Mon Nov 16 13:28:11 2009 @@ -40,9 +40,9 @@ * Maps deprecated language codes to the canonical code. Strings are always * in pairs, with the first being the canonical code and the second being * a deprecated code which maps to it. - * + * <p> * Source: http://www.loc.gov/standards/iso639-2/php/code_changes.php - * + * <p> * TODO: consider building maps if this list grows much. */ private static final String[] deprecatedLanguages = new String[] { @@ -62,9 +62,9 @@ * cannot be done automatically (such as cs -> rs/me) -- perhaps we could * have a way of flagging region codes which are no longer valid and allow * an appropriate warning message. - * + * <p> * Source: http://en.wikipedia.org/wiki/ISO_3166-1 - * + * <p> * TODO: consider building maps if this list grows much. */ private static final String[] deprecatedRegions = new String[] { @@ -233,9 +233,13 @@ private final String variant; - private ArrayList<GwtLocale> cachedSearchList; - - private ArrayList<GwtLocale> cachedAliases; + private final Object cacheLock = new Object[0]; + + // protected by cacheLock + private List<GwtLocale> cachedSearchList; + + // protected by cacheLock + private List<GwtLocale> cachedAliases; /** * Must only be called from a factory to preserve instance caching. @@ -280,40 +284,44 @@ public List<GwtLocale> getAliases() { // TODO(jat): more locale aliases? better way to encode them? - if (cachedAliases == null) { - cachedAliases = new ArrayList<GwtLocale>(); - GwtLocale canonicalForm = getCanonicalForm(); - Set<GwtLocale> seen = new HashSet<GwtLocale>(); - cachedAliases.add(canonicalForm); - ArrayList<GwtLocale> nextGroup = new ArrayList<GwtLocale>(); - nextGroup.add(this); - // Account for default script - String defaultScript = DefaultLanguageScripts.getDefaultScript(language); - if (defaultScript != null) { - if (script == null) { - nextGroup.add(factory.fromComponents(language, defaultScript, region, - variant)); - } else if (script.equals(defaultScript)) { - nextGroup.add(factory.fromComponents(language, null, region, variant)); - } - } - while (!nextGroup.isEmpty()) { - List<GwtLocale> thisGroup = nextGroup; - nextGroup = new ArrayList<GwtLocale>(); - for (GwtLocale locale : thisGroup) { - if (seen.contains(locale)) { - continue; - } - seen.add(locale); - if (!locale.equals(canonicalForm)) { - cachedAliases.add(locale); - } - addDeprecatedPairs(factory, locale, nextGroup); - addSpecialAliases(factory, locale, nextGroup); - } - } - } - return Collections.unmodifiableList(cachedAliases); + synchronized (cacheLock) { + if (cachedAliases == null) { + cachedAliases = new ArrayList<GwtLocale>(); + GwtLocale canonicalForm = getCanonicalForm(); + Set<GwtLocale> seen = new HashSet<GwtLocale>(); + cachedAliases.add(canonicalForm); + ArrayList<GwtLocale> nextGroup = new ArrayList<GwtLocale>(); + nextGroup.add(this); + // Account for default script + String defaultScript = DefaultLanguageScripts.getDefaultScript(language); + if (defaultScript != null) { + if (script == null) { + nextGroup.add(factory.fromComponents(language, defaultScript, + region, variant)); + } else if (script.equals(defaultScript)) { + nextGroup.add(factory.fromComponents(language, null, region, + variant)); + } + } + while (!nextGroup.isEmpty()) { + List<GwtLocale> thisGroup = nextGroup; + nextGroup = new ArrayList<GwtLocale>(); + for (GwtLocale locale : thisGroup) { + if (seen.contains(locale)) { + continue; + } + seen.add(locale); + if (!locale.equals(canonicalForm)) { + cachedAliases.add(locale); + } + addDeprecatedPairs(factory, locale, nextGroup); + addSpecialAliases(factory, locale, nextGroup); + } + } + cachedAliases = Collections.unmodifiableList(cachedAliases); + } + return cachedAliases; + } } public String getAsString() { @@ -408,42 +416,45 @@ public List<GwtLocale> getCompleteSearchList() { // TODO(jat): get zh_Hant to come before zh in search list for zh_TW - if (cachedSearchList == null) { - cachedSearchList = new ArrayList<GwtLocale>(); - Set<GwtLocale> seen = new HashSet<GwtLocale>(); - List<GwtLocale> thisGroup = new ArrayList<GwtLocale>(this.getAliases()); - seen.addAll(thisGroup); - GwtLocale defLocale = factory.getDefault(); - seen.add(defLocale); - while (!thisGroup.isEmpty()) { - cachedSearchList.addAll(thisGroup); - List<GwtLocale> nextGroup = new ArrayList<GwtLocale>(); - for (GwtLocale locale : thisGroup) { - List<GwtLocale> work = new ArrayList<GwtLocale>(locale.getAliases()); - work.removeAll(seen); - nextGroup.addAll(work); - seen.addAll(work); - work.clear(); - if (locale.getRegion() != null) { - addImmediateParentRegions(factory, locale, work); - } else if (locale.getVariant() != null) { - work.add(factory.fromComponents(locale.getLanguage(), - locale.getScript(), null, null)); - } else if (locale.getScript() != null) { - work.add(factory.fromComponents(locale.getLanguage(), null, null, - null)); - } - work.removeAll(seen); - nextGroup.addAll(work); - seen.addAll(work); - } - thisGroup = nextGroup; - } - if (!isDefault()) { - cachedSearchList.add(defLocale); - } - } - return cachedSearchList; + synchronized (cacheLock) { + if (cachedSearchList == null) { + cachedSearchList = new ArrayList<GwtLocale>(); + Set<GwtLocale> seen = new HashSet<GwtLocale>(); + List<GwtLocale> thisGroup = new ArrayList<GwtLocale>(this.getAliases()); + seen.addAll(thisGroup); + GwtLocale defLocale = factory.getDefault(); + seen.add(defLocale); + while (!thisGroup.isEmpty()) { + cachedSearchList.addAll(thisGroup); + List<GwtLocale> nextGroup = new ArrayList<GwtLocale>(); + for (GwtLocale locale : thisGroup) { + List<GwtLocale> work = new ArrayList<GwtLocale>(locale.getAliases()); + work.removeAll(seen); + nextGroup.addAll(work); + seen.addAll(work); + work.clear(); + if (locale.getRegion() != null) { + addImmediateParentRegions(factory, locale, work); + } else if (locale.getVariant() != null) { + work.add(factory.fromComponents(locale.getLanguage(), + locale.getScript(), null, null)); + } else if (locale.getScript() != null) { + work.add(factory.fromComponents(locale.getLanguage(), null, null, + null)); + } + work.removeAll(seen); + nextGroup.addAll(work); + seen.addAll(work); + } + thisGroup = nextGroup; + } + if (!isDefault()) { + cachedSearchList.add(defLocale); + } + cachedSearchList = Collections.unmodifiableList(cachedSearchList); + } + return cachedSearchList; + } } /** -- http://groups.google.com/group/Google-Web-Toolkit-Contributors
