Add property provider generators.
Patch by: jat
Review by: unnurg
Review at http://gwt-code-reviews.appspot.com/1063801
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9281 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/PropertyProviderGenerator.java b/dev/core/src/com/google/gwt/core/ext/linker/PropertyProviderGenerator.java
new file mode 100644
index 0000000..fd8f1d3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/PropertyProviderGenerator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 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.linker;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+
+import java.util.SortedSet;
+
+/**
+ * An interface for generating a property provider JS implementation, rather
+ * than having it defined in a module file.
+ *
+ * Use it like this:
+ * <pre>
+ * <property-provider name="foo" generator="org.example.FooGenerator"/>
+ * </pre>
+ * A default implementation in JS can be included inside the property-provider
+ * tag as usual, and will be used if the generator returns {@code null}.
+ */
+public interface PropertyProviderGenerator {
+
+ /**
+ * Generate a property provider.
+ *
+ * @param logger TreeLogger
+ * @param possibleValues the possible values of this property
+ * @param fallback the fallback value for this property, or null
+ * @param configProperties the configuration properties for this module
+ * @return the JS source of the property provider (the complete body of a JS
+ * function taking no arguments, including open/close braces), or null to
+ * use the default implementation in the property-provider tag
+ * @throws UnableToCompleteException after logging the message if processing
+ * is unable to continue
+ */
+ String generate(TreeLogger logger, SortedSet<String> possibleValues,
+ String fallback, SortedSet<ConfigurationProperty> configProperties)
+ throws UnableToCompleteException;
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/SelectionProperty.java b/dev/core/src/com/google/gwt/core/ext/linker/SelectionProperty.java
index 8940edd..6f27aba 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/SelectionProperty.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/SelectionProperty.java
@@ -15,6 +15,9 @@
*/
package com.google.gwt.core.ext.linker;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+
import java.util.SortedSet;
/**
@@ -27,6 +30,12 @@
* used in generators.
*/
public interface SelectionProperty {
+
+ /**
+ * Returns the fallback value or an empty string if not defined.
+ */
+ String getFallbackValue();
+
/**
* Returns the name of the deferred binding property.
*/
@@ -40,8 +49,15 @@
/**
* Returns a raw function body that provides the runtime value to be used for
* a deferred binding property.
+ *
+ * @param logger logger to use for any warnings/errors
+ * @param configProperties set of configuration properties
+ * @throws UnableToCompleteException if execution cannot continue, after
+ * having logged a message
*/
- String getPropertyProvider();
+ String getPropertyProvider(TreeLogger logger,
+ SortedSet<ConfigurationProperty> configProperties)
+ throws UnableToCompleteException;
/**
* Returns <code>true</code> if the value of the SelectionProperty is always
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/PermutationsUtil.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/PermutationsUtil.java
index f69cc48..c0367fa 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/PermutationsUtil.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/PermutationsUtil.java
@@ -18,6 +18,7 @@
import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.SelectionProperty;
import com.google.gwt.dev.util.StringKey;
@@ -72,9 +73,16 @@
/**
* Uses the internal map to insert JS to select a permutation into the
* selection script.
+ *
+ * @param selectionScript
+ * @param logger
+ * @param context
+ * @return the modified selectionScript buffer
+ * @throws UnableToCompleteException
*/
public StringBuffer addPermutationsJs(StringBuffer selectionScript,
- TreeLogger logger, LinkerContext context) {
+ TreeLogger logger, LinkerContext context)
+ throws UnableToCompleteException {
int startPos;
PropertiesUtil.addPropertiesJs(selectionScript, logger, context);
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java
index 3ee5355..e645566 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java
@@ -18,21 +18,27 @@
import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ConfigurationProperty;
import com.google.gwt.core.ext.linker.SelectionProperty;
+import java.util.SortedSet;
+
/**
* A utility class to fill in the properties javascript in linker templates.
*/
public class PropertiesUtil {
public static StringBuffer addPropertiesJs(StringBuffer selectionScript,
- TreeLogger logger, LinkerContext context) {
+ TreeLogger logger, LinkerContext context)
+ throws UnableToCompleteException {
int startPos;
// Add property providers
startPos = selectionScript.indexOf("// __PROPERTIES_END__");
if (startPos != -1) {
for (SelectionProperty p : context.getProperties()) {
- String text = generatePropertyProvider(p);
+ String text = generatePropertyProvider(logger, p,
+ context.getConfigurationProperties());
selectionScript.insert(startPos, text);
startPos += text.length();
}
@@ -40,12 +46,14 @@
return selectionScript;
}
- private static String generatePropertyProvider(SelectionProperty prop) {
+ private static String generatePropertyProvider(TreeLogger logger,
+ SelectionProperty prop, SortedSet<ConfigurationProperty> configProps)
+ throws UnableToCompleteException {
StringBuffer toReturn = new StringBuffer();
if (prop.tryGetValue() == null && !prop.isDerived()) {
toReturn.append("providers['" + prop.getName() + "'] = function()");
- toReturn.append(prop.getPropertyProvider());
+ toReturn.append(prop.getPropertyProvider(logger, configProps));
toReturn.append(";");
toReturn.append("values['" + prop.getName() + "'] = {");
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java
index 92b8670..f50ae60 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java
@@ -15,6 +15,10 @@
*/
package com.google.gwt.core.ext.linker.impl;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ConfigurationProperty;
+import com.google.gwt.core.ext.linker.PropertyProviderGenerator;
import com.google.gwt.core.ext.linker.SelectionProperty;
import com.google.gwt.dev.cfg.BindingProperty;
@@ -31,22 +35,29 @@
private static final String FALLBACK_TOKEN = "/*-FALLBACK-*/";
private final String activeValue;
+ private final String fallback;
private final boolean isDerived;
private final String name;
private final String provider;
+ private final Class<? extends PropertyProviderGenerator> providerGenerator;
private final SortedSet<String> values;
public StandardSelectionProperty(BindingProperty p) {
activeValue = p.getConstrainedValue();
isDerived = p.isDerived();
name = p.getName();
- String fallback = p.getFallback();
+ fallback = p.getFallback();
+ providerGenerator = p.getProviderGenerator();
provider = p.getProvider() == null ? null
: p.getProvider().getBody().replace(FALLBACK_TOKEN, fallback);
values = Collections.unmodifiableSortedSet(new TreeSet<String>(
Arrays.asList(p.getDefinedValues())));
}
+ public String getFallbackValue() {
+ return fallback;
+ }
+
public String getName() {
return name;
}
@@ -55,8 +66,27 @@
return values;
}
- public String getPropertyProvider() {
- return provider;
+ public String getPropertyProvider(TreeLogger logger,
+ SortedSet<ConfigurationProperty> configProperties)
+ throws UnableToCompleteException {
+ String generatorResult = null;
+ if (providerGenerator != null) {
+ Throwable caught = null;
+ try {
+ PropertyProviderGenerator gen = providerGenerator.newInstance();
+ generatorResult = gen.generate(logger, values, fallback,
+ configProperties);
+ } catch (InstantiationException e) {
+ caught = e;
+ } catch (IllegalAccessException e) {
+ caught = e;
+ }
+ if (caught != null) {
+ logger.log(TreeLogger.WARN, "Failed to execute property provider "
+ + "generator '" + providerGenerator + "'", caught);
+ }
+ }
+ return generatorResult != null ? generatorResult : provider;
}
public boolean isDerived() {
diff --git a/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java b/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
index 5bea795..69e689c 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.dev.cfg;
+import com.google.gwt.core.ext.linker.PropertyProviderGenerator;
import com.google.gwt.dev.util.collect.IdentityHashSet;
import com.google.gwt.dev.util.collect.Lists;
import com.google.gwt.dev.util.collect.Sets;
@@ -48,6 +49,7 @@
private final Map<Condition, SortedSet<String>> conditionalValues = new LinkedHashMap<Condition, SortedSet<String>>();
private final SortedSet<String> definedValues = new TreeSet<String>();
private PropertyProvider provider;
+ private Class<? extends PropertyProviderGenerator> providerGenerator;
private String fallback;
private final ConditionAll rootCondition = new ConditionAll();
@@ -134,6 +136,11 @@
return definedValues.toArray(new String[definedValues.size()]);
}
+ /**
+ * Returns the fallback value for this property, or the empty string if none.
+ *
+ * @return the fallback value
+ */
public String getFallback() {
return fallback;
}
@@ -142,6 +149,13 @@
return provider;
}
+ /**
+ * @return the the provider generator class, or null if none.
+ */
+ public Class<? extends PropertyProviderGenerator> getProviderGenerator() {
+ return providerGenerator;
+ }
+
public Set<String> getRequiredProperties() {
Set<String> toReturn = Sets.create();
for (Condition cond : conditionalValues.keySet()) {
@@ -168,8 +182,7 @@
/**
* Returns <code>true</code> if the value was previously provided to
- * {@link #addDefinedValue(String)} since the last time {@link #clearValues()}
- * was called.
+ * {@link #addDefinedValue(Condition,String)}.
*/
public boolean isDefinedValue(String value) {
return definedValues.contains(value);
@@ -194,7 +207,7 @@
* the currently-defined values.
*
* @throws IllegalArgumentException if any of the provided values were not
- * provided to {@link #addDefinedValue(String)}.
+ * provided to {@link #addDefinedValue(Condition,String)}.
*/
public void setAllowedValues(Condition condition, String... values) {
SortedSet<String> temp = new TreeSet<String>(Arrays.asList(values));
@@ -228,6 +241,15 @@
}
/**
+ * Set a provider generator for this property.
+ *
+ * @param generator
+ */
+ public void setProviderGenerator(Class<? extends PropertyProviderGenerator> generator) {
+ providerGenerator = generator;
+ }
+
+ /**
* Create a minimal number of equivalence sets, expanding any glob patterns.
*/
void normalizeCollapsedValues() {
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
index fa76101..d5653e7 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -20,6 +20,7 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.LinkerOrder;
+import com.google.gwt.core.ext.linker.PropertyProviderGenerator;
import com.google.gwt.dev.js.JsParser;
import com.google.gwt.dev.js.JsParserException;
import com.google.gwt.dev.js.ast.JsExprStmt;
@@ -49,100 +50,151 @@
private final class BodySchema extends Schema {
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __add_linker_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __clear_configuration_property_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __collapse_all_properties_1_value = "true";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __collapse_property_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __collapse_property_2_values = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __define_configuration_property_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __define_configuration_property_2_is_multi_valued = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __define_linker_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __define_linker_2_class = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __define_property_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __define_property_2_values = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __entry_point_1_class = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __extend_configuration_property_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __extend_configuration_property_2_value = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __extend_property_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __extend_property_2_values = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __generate_with_1_class = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __inherits_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __property_provider_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
+ protected final String __property_provider_2_generator = "";
+
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __public_1_path = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __public_2_includes = "";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __public_3_excludes = "";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __public_4_skips = "";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __public_5_defaultexcludes = "yes";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __public_6_casesensitive = "true";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __replace_with_1_class = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __script_1_src = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __servlet_1_path = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __servlet_2_class = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __set_configuration_property_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __set_configuration_property_2_value = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __set_property_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __set_property_2_value = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __set_property_fallback_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __set_property_fallback_2_value = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __source_1_path = "";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __source_2_includes = "";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __source_3_excludes = "";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __source_4_skips = "";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __source_5_defaultexcludes = "yes";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __source_6_casesensitive = "true";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __stylesheet_1_src = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __super_source_1_path = "";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __super_source_2_includes = "";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __super_source_3_excludes = "";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __super_source_4_skips = "";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __super_source_5_defaultexcludes = "yes";
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __super_source_6_casesensitive = "true";
/**
@@ -153,6 +205,7 @@
private Schema fChild;
+ @SuppressWarnings("unused") // called reflectively
protected Schema __add_linker_begin(LinkerName name)
throws UnableToCompleteException {
if (moduleDef.getLinker(name.name) == null) {
@@ -163,6 +216,7 @@
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __clear_configuration_property_begin(PropertyName name)
throws UnableToCompleteException {
// Don't allow configuration properties with the same name as a
@@ -189,11 +243,13 @@
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __collapse_all_properties_begin(boolean collapse) {
moduleDef.setCollapseAllProperties(collapse);
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __collapse_property_begin(PropertyName name,
PropertyValueGlob[] values) throws UnableToCompleteException {
Property prop = moduleDef.getProperties().find(name.token);
@@ -235,6 +291,7 @@
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __define_configuration_property_begin(PropertyName name,
String is_multi_valued) throws UnableToCompleteException {
boolean isMultiValued = toPrimitiveBoolean(is_multi_valued);
@@ -303,6 +360,7 @@
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __define_linker_begin(LinkerName name,
Class<? extends Linker> linker) throws UnableToCompleteException {
if (!Linker.class.isAssignableFrom(linker)) {
@@ -319,6 +377,7 @@
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __define_property_begin(PropertyName name,
PropertyValue[] values) throws UnableToCompleteException {
@@ -351,11 +410,13 @@
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __entry_point_begin(String className) {
moduleDef.addEntryPointTypeName(className);
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __extend_configuration_property_begin(PropertyName name,
String value) throws UnableToCompleteException {
@@ -378,6 +439,7 @@
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __extend_property_begin(BindingProperty property,
PropertyValue[] values) {
for (int i = 0; i < values.length; i++) {
@@ -388,12 +450,14 @@
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __fail_begin() {
RuleFail rule = new RuleFail();
moduleDef.getRules().prepend(rule);
return new FullConditionSchema(rule.getRootCondition());
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __generate_with_begin(Class<? extends Generator> generator)
throws UnableToCompleteException {
if (!Generator.class.isAssignableFrom(generator)) {
@@ -406,6 +470,7 @@
return new FullConditionSchema(rule.getRootCondition());
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __inherits_begin(String name)
throws UnableToCompleteException {
TreeLogger branch = logger.branch(TreeLogger.TRACE,
@@ -414,36 +479,49 @@
return null;
}
- @SuppressWarnings("unused")
- protected Schema __property_provider_begin(BindingProperty property) {
- return fChild = new ScriptSchema();
+ @SuppressWarnings("unused") // called reflectively
+ protected Schema __property_provider_begin(BindingProperty property,
+ Class<? extends PropertyProviderGenerator> generator) {
+ fChild = new ScriptSchema();
+ return fChild;
}
- protected void __property_provider_end(BindingProperty property)
+ @SuppressWarnings("unused") // called reflectively
+ protected void __property_provider_end(BindingProperty property,
+ Class<? extends PropertyProviderGenerator> generator)
throws UnableToCompleteException {
- ScriptSchema childSchema = ((ScriptSchema) fChild);
- String script = childSchema.getScript();
- if (script == null) {
- // This is a problem.
- //
- logger.log(TreeLogger.ERROR,
- "Property providers must specify a JavaScript body", null);
+ if (generator != null
+ && !PropertyProviderGenerator.class.isAssignableFrom(generator)) {
+ logger.log(TreeLogger.ERROR, "A property provider generator must "
+ + "extend " + PropertyProviderGenerator.class.getName(), null);
throw new UnableToCompleteException();
}
-
- int lineNumber = childSchema.getStartLineNumber();
- JsFunction fn = parseJsBlock(lineNumber, script);
-
- property.setProvider(new PropertyProvider(fn.getBody().toSource()));
+ ScriptSchema childSchema = ((ScriptSchema) fChild);
+ String script = childSchema.getScript();
+ property.setProviderGenerator(generator);
+ if (script == null) {
+ if (generator == null) {
+ // This is a problem.
+ //
+ logger.log(TreeLogger.ERROR, "Property providers must specify a "
+ + "JavaScript body or a provider generator", null);
+ throw new UnableToCompleteException();
+ }
+ } else {
+ int lineNumber = childSchema.getStartLineNumber();
+ JsFunction fn = parseJsBlock(lineNumber, script);
+ property.setProvider(new PropertyProvider(fn.getBody().toSource()));
+ }
}
- @SuppressWarnings("unused")
+ @SuppressWarnings("unused") // called reflectively
protected Schema __public_begin(String path, String includes,
String excludes, String skips, String defaultExcludes,
String caseSensitive) {
return fChild = new IncludeExcludeSchema();
}
+ @SuppressWarnings("unused") // called reflectively
protected void __public_end(String path, String includes, String excludes,
String skips, String defaultExcludes, String caseSensitive) {
IncludeExcludeSchema childSchema = ((IncludeExcludeSchema) fChild);
@@ -468,6 +546,7 @@
skipList, doDefaultExcludes, doCaseSensitive);
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __replace_with_begin(String className) {
RuleReplaceWith rule = new RuleReplaceWith(className);
moduleDef.getRules().prepend(rule);
@@ -478,11 +557,12 @@
* @param src a partial or full url to a script file to inject
* @return <code>null</code> since there can be no children
*/
- @SuppressWarnings("unused")
+ @SuppressWarnings("unused") // called reflectively
protected Schema __script_begin(String src) {
return fChild = new ScriptSchema();
}
+ @SuppressWarnings("unused") // called reflectively
protected void __script_end(String src) {
ScriptSchema childSchema = (ScriptSchema) fChild;
String js = childSchema.getScript();
@@ -495,6 +575,7 @@
moduleDef.getScripts().append(new Script(src));
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __servlet_begin(String path, String servletClass)
throws UnableToCompleteException {
@@ -511,6 +592,7 @@
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __set_configuration_property_begin(PropertyName name,
String value) throws UnableToCompleteException {
@@ -547,13 +629,14 @@
return null;
}
- @SuppressWarnings("unused")
+ @SuppressWarnings("unused") // called reflectively
protected Schema __set_property_begin(BindingProperty prop,
PropertyValue[] value) throws UnableToCompleteException {
bindingPropertyCondition = new ConditionAll();
return new PropertyConditionSchema(bindingPropertyCondition);
}
+ @SuppressWarnings("unused") // called reflectively
protected void __set_property_end(BindingProperty prop,
PropertyValue[] value) throws UnableToCompleteException {
boolean error = false;
@@ -577,6 +660,7 @@
prop.setAllowedValues(bindingPropertyCondition, stringValues);
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __set_property_fallback_begin(BindingProperty prop,
PropertyValue value) throws UnableToCompleteException {
boolean error = true;
@@ -600,13 +684,14 @@
* Indicates which subdirectories contain translatable source without
* necessarily adding a sourcepath entry.
*/
- @SuppressWarnings("unused")
+ @SuppressWarnings("unused") // called reflectively
protected Schema __source_begin(String path, String includes,
String excludes, String skips, String defaultExcludes,
String caseSensitive) {
return fChild = new IncludeExcludeSchema();
}
+ @SuppressWarnings("unused") // called reflectively
protected void __source_end(String path, String includes, String excludes,
String skips, String defaultExcludes, String caseSensitive) {
addSourcePackage(path, includes, excludes, skips, defaultExcludes,
@@ -617,6 +702,7 @@
* @param src a partial or full url to a stylesheet file to inject
* @return <code>null</code> since there can be no children
*/
+ @SuppressWarnings("unused") // called reflectively
protected Schema __stylesheet_begin(String src) {
moduleDef.getStyles().append(src);
return null;
@@ -626,13 +712,14 @@
* Like adding a translatable source package, but such that it uses the
* module's package itself as its sourcepath root entry.
*/
- @SuppressWarnings("unused")
+ @SuppressWarnings("unused") // called reflectively
protected Schema __super_source_begin(String path, String includes,
String excludes, String skips, String defaultExcludes,
String caseSensitive) {
return fChild = new IncludeExcludeSchema();
}
+ @SuppressWarnings("unused") // called reflectively
protected void __super_source_end(String path, String includes,
String excludes, String skips, String defaultExcludes,
String caseSensitive) {
@@ -773,6 +860,10 @@
@Override
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
+ if (value.length() == 0) {
+ // handle optional class names
+ return null;
+ }
try {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return cl.loadClass(value);
@@ -788,14 +879,17 @@
*/
private final class FullConditionSchema extends PropertyConditionSchema {
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __when_type_assignable_1_class = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __when_type_is_1_class = null;
public FullConditionSchema(CompoundCondition parentCondition) {
super(parentCondition);
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __when_type_assignable_begin(String className) {
Condition cond = new ConditionWhenTypeAssignableTo(className);
parentCondition.getConditions().add(cond);
@@ -804,6 +898,7 @@
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __when_type_is_begin(String className) {
Condition cond = new ConditionWhenTypeIs(className);
parentCondition.getConditions().add(cond);
@@ -820,10 +915,13 @@
private static final class IncludeExcludeSchema extends Schema {
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __exclude_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __include_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __skip_1_name = null;
/**
@@ -853,21 +951,24 @@
return skips;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __exclude_begin(String name) {
excludes.add(name);
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __include_begin(String name) {
includes.add(name);
return null;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __skip_begin(String name) {
skips.add(name);
return null;
}
-}
+ }
private static class LinkerName {
public final String name;
@@ -882,6 +983,7 @@
*/
private final class LinkerNameAttrCvt extends AttributeConverter {
+ @Override
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
// Ensure the value is a valid Java identifier
@@ -912,6 +1014,7 @@
*/
private final class NullableNameAttrCvt extends AttributeConverter {
+ @Override
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
if (value == null || value.length() == 0) {
@@ -945,6 +1048,7 @@
this.concreteType = concreteType;
}
+ @Override
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
// Find the named property.
@@ -974,10 +1078,13 @@
* A limited number of conditional predicates based only on properties.
*/
private class PropertyConditionSchema extends Schema {
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __when_linker_added_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __when_property_is_1_name = null;
+ @SuppressWarnings("unused") // referenced reflectively
protected final String __when_property_is_2_value = null;
protected final CompoundCondition parentCondition;
@@ -986,24 +1093,28 @@
this.parentCondition = parentCondition;
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __all_begin() {
CompoundCondition cond = new ConditionAll();
parentCondition.getConditions().add(cond);
return subSchema(cond);
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __any_begin() {
CompoundCondition cond = new ConditionAny();
parentCondition.getConditions().add(cond);
return subSchema(cond);
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __none_begin() {
CompoundCondition cond = new ConditionNone();
parentCondition.getConditions().add(cond);
return subSchema(cond);
}
+ @SuppressWarnings("unused") // called reflectively
protected Schema __when_linker_added_begin(LinkerName linkerName) {
Condition cond = new ConditionWhenLinkerAdded(linkerName.name);
parentCondition.getConditions().add(cond);
@@ -1017,6 +1128,7 @@
* module writers. It prevents them from trying to create property providers
* for unknown properties.
*/
+ @SuppressWarnings("unused") // called reflectively
protected Schema __when_property_is_begin(BindingProperty prop,
PropertyValue value) {
Condition cond = new ConditionWhenPropertyIs(prop.getName(), value.token);
@@ -1044,6 +1156,7 @@
*/
private final class PropertyNameAttrCvt extends AttributeConverter {
+ @Override
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
// Ensure each part of the name is valid.
@@ -1075,6 +1188,7 @@
* Converts a comma-separated string into an array of property value tokens.
*/
private final class PropertyValueArrayAttrCvt extends AttributeConverter {
+ @Override
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
String[] tokens = value.split(",");
@@ -1095,6 +1209,7 @@
* Converts a string into a property value, validating it in the process.
*/
private final class PropertyValueAttrCvt extends AttributeConverter {
+ @Override
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
@@ -1124,6 +1239,7 @@
* Converts a comma-separated string into an array of property value tokens.
*/
private final class PropertyValueGlobArrayAttrCvt extends AttributeConverter {
+ @Override
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
String[] tokens = value.split(",");
@@ -1144,6 +1260,7 @@
* Converts a string into a property value glob, validating it in the process.
*/
private final class PropertyValueGlobAttrCvt extends AttributeConverter {
+ @Override
public Object convertToArg(Schema schema, int line, String elem,
String attr, String value) throws UnableToCompleteException {
@@ -1169,6 +1286,7 @@
public ScriptSchema() {
}
+ @SuppressWarnings("unused") // called reflectively
public void __text(String text) {
if (script == null) {
script = new StringBuffer();
diff --git a/dev/core/test/com/google/gwt/core/ext/linker/impl/StandardSelectionPropertyTest.java b/dev/core/test/com/google/gwt/core/ext/linker/impl/StandardSelectionPropertyTest.java
index 19a4cb9..1c646fa 100644
--- a/dev/core/test/com/google/gwt/core/ext/linker/impl/StandardSelectionPropertyTest.java
+++ b/dev/core/test/com/google/gwt/core/ext/linker/impl/StandardSelectionPropertyTest.java
@@ -15,16 +15,37 @@
*/
package com.google.gwt.core.ext.linker.impl;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ConfigurationProperty;
+import com.google.gwt.core.ext.linker.PropertyProviderGenerator;
import com.google.gwt.dev.cfg.BindingProperty;
import com.google.gwt.dev.cfg.PropertyProvider;
+import com.google.gwt.dev.shell.FailErrorLogger;
import junit.framework.TestCase;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
/**
* Tests for {@link StandardSelectionProperty}.
*/
public class StandardSelectionPropertyTest extends TestCase {
+ /**
+ * Test property provider generator.
+ */
+ public static class MyProviderGenerator
+ implements PropertyProviderGenerator {
+
+ public String generate(TreeLogger logger, SortedSet<String> possibleValues,
+ String fallback, SortedSet<ConfigurationProperty> configProperties)
+ throws UnableToCompleteException {
+ return "good " + fallback;
+ }
+ }
+
private static final String FBV = "FBV";
private static final String PROVIDER_MULTIFALLBACK =
@@ -36,30 +57,49 @@
private static final String PROVIDER_NOFALLBACK = "provider text without fallback";
- public void testNoFallback() {
+ private static final TreeLogger logger = new FailErrorLogger();
+
+ private static final SortedSet<ConfigurationProperty> configProperties = new TreeSet<ConfigurationProperty>();
+
+ public void testNoFallback() throws UnableToCompleteException {
BindingProperty bp = new BindingProperty("doesNotUseFallback");
PropertyProvider provider = new PropertyProvider(PROVIDER_NOFALLBACK);
bp.setProvider(provider);
StandardSelectionProperty property = new StandardSelectionProperty(bp);
- assertEquals(PROVIDER_NOFALLBACK, property.getPropertyProvider());
+ assertEquals(PROVIDER_NOFALLBACK, property.getPropertyProvider(logger,
+ configProperties));
provider = new PropertyProvider(PROVIDER_MULTIFALLBACK);
bp.setProvider(provider);
property = new StandardSelectionProperty(bp);
- assertEquals(PROVIDER_MULTIFALLBACK_EMPTY, property.getPropertyProvider());
+ assertEquals(PROVIDER_MULTIFALLBACK_EMPTY, property.getPropertyProvider(
+ logger, configProperties));
}
- public void testWithFallback() {
+ public void testPropertyProviderGenerator() throws UnableToCompleteException {
+ BindingProperty bp = new BindingProperty("providerGenerator");
+ bp.setFallback(FBV);
+ PropertyProvider provider = new PropertyProvider("bad");
+ bp.setProvider(provider);
+ bp.setProviderGenerator(MyProviderGenerator.class);
+ StandardSelectionProperty property = new StandardSelectionProperty(bp);
+ assertEquals("good " + FBV, property.getPropertyProvider(logger,
+ configProperties));
+ }
+
+ public void testWithFallback() throws UnableToCompleteException {
BindingProperty bp = new BindingProperty("doesUseFallback");
bp.setFallback(FBV);
PropertyProvider provider = new PropertyProvider(PROVIDER_NOFALLBACK);
bp.setProvider(provider);
StandardSelectionProperty property = new StandardSelectionProperty(bp);
- assertEquals(PROVIDER_NOFALLBACK, property.getPropertyProvider());
+ assertEquals(PROVIDER_NOFALLBACK, property.getPropertyProvider(logger,
+ configProperties));
provider = new PropertyProvider(PROVIDER_MULTIFALLBACK);
bp.setProvider(provider);
property = new StandardSelectionProperty(bp);
- assertEquals(PROVIDER_MULTIFALLBACK_FBV, property.getPropertyProvider());
+ assertEquals(PROVIDER_MULTIFALLBACK_FBV, property.getPropertyProvider(
+ logger, configProperties));
}
}
diff --git a/distro-source/core/src/gwt-module.dtd b/distro-source/core/src/gwt-module.dtd
index fd59093..10c6614 100644
--- a/distro-source/core/src/gwt-module.dtd
+++ b/distro-source/core/src/gwt-module.dtd
@@ -32,28 +32,28 @@
<!ELEMENT source (include | exclude)*>
<!ATTLIST source
path CDATA #REQUIRED
- includes CDATA #IMPLIED
- excludes CDATA #IMPLIED
- defaultexcludes (yes | no) "yes"
- casesensitive (true | false) "true"
+ includes CDATA #IMPLIED
+ excludes CDATA #IMPLIED
+ defaultexcludes (yes | no) "yes"
+ casesensitive (true | false) "true"
>
<!-- Specify the public resource path, relative to the classpath location of the module descriptor -->
<!ELEMENT public (include | exclude)*>
<!ATTLIST public
path CDATA #REQUIRED
- includes CDATA #IMPLIED
- excludes CDATA #IMPLIED
- defaultexcludes (yes | no) "yes"
- casesensitive (true | false) "true"
+ includes CDATA #IMPLIED
+ excludes CDATA #IMPLIED
+ defaultexcludes (yes | no) "yes"
+ casesensitive (true | false) "true"
>
<!-- Specify a source path that rebases subpackages into the root namespace -->
<!ELEMENT super-source (include | exclude)*>
<!ATTLIST super-source
path CDATA #REQUIRED
- includes CDATA #IMPLIED
- excludes CDATA #IMPLIED
- defaultexcludes (yes | no) "yes"
- casesensitive (true | false) "true"
+ includes CDATA #IMPLIED
+ excludes CDATA #IMPLIED
+ defaultexcludes (yes | no) "yes"
+ casesensitive (true | false) "true"
>
<!ELEMENT include EMPTY>
<!ATTLIST include
@@ -160,6 +160,7 @@
<!ELEMENT property-provider (#PCDATA)>
<!ATTLIST property-provider
name CDATA #REQUIRED
+ generator CDATA #IMPLIED
>
<!-- All possible predicates -->
<!ENTITY % predicates "when-property-is | when-type-assignable | when-type-is | all | any | none">
diff --git a/tools/api-checker/config/gwt20_21userApi.conf b/tools/api-checker/config/gwt20_21userApi.conf
index b79ca67..216ac23 100644
--- a/tools/api-checker/config/gwt20_21userApi.conf
+++ b/tools/api-checker/config/gwt20_21userApi.conf
@@ -10,7 +10,6 @@
:com/google/gwt/benchmarks/BenchmarkShell.java\
:com/google/gwt/benchmarks/client/Benchmark.java\
:com/google/gwt/core/ext/**\
-:com/google/gwt/core/linker/**\
:com/google/gwt/dev/*.java\
:com/google/gwt/dev/asm/**\
:com/google/gwt/dev/cfg/**\
@@ -28,9 +27,9 @@
:com/google/gwt/resources/css/**\
:com/google/gwt/resources/ext/**\
:com/google/gwt/resources/rg/**\
-:com/google/gwt/user/linker/**\
:com/google/gwt/util/**\
:com/google/gwt/soyc/**\
+:**/linker/**\
:**/rebind/**\
:**/remote/**\
:**/server/**\
@@ -42,7 +41,6 @@
:com/google/gwt/regexp/shared/**\
:com/google/gwt/rpc/client/impl/ClientWriterFactory.java\
:com/google/gwt/rpc/client/impl/EscapeUtil.java\
-:com/google/gwt/rpc/linker/*.java\
:com/google/gwt/uibinder/attributeparsers/*.java\
:com/google/gwt/uibinder/elementparsers/*.java\
:com/google/gwt/uibinder/testing/*.java\
@@ -64,6 +62,7 @@
excludedFiles_new user/src/com/google/gwt/benchmarks/BenchmarkReport.java\
:user/src/com/google/gwt/benchmarks/BenchmarkShell.java\
:user/src/com/google/gwt/benchmarks/client/Benchmark.java\
+:**/linker/**\
:**/rebind/**\
:**/server/**\
:**/tools/**\
@@ -74,17 +73,14 @@
:user/src/com/google/gwt/junit/client/GWTTestCase.java\
:user/src/com/google/gwt/junit/client/impl/GWTRunner.java\
:user/src/com/google/gwt/junit/remote\
-:user/src/com/google/gwt/precompress/linker\
:user/src/com/google/gwt/resources/css\
:user/src/com/google/gwt/resources/ext\
:user/src/com/google/gwt/resources/rg\
:user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java\
:user/src/com/google/gwt/rpc/client/impl/ClientWriterFactory.java\
:user/src/com/google/gwt/rpc/client/impl/EscapeUtil.java\
-:user/src/com/google/gwt/rpc/linker\
:user/src/com/google/gwt/safehtml/shared/SafeHtmlHostedModeUtils.java\
:user/src/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java\
-:user/src/com/google/gwt/user/linker\
:user/src/com/google/gwt/uibinder/attributeparsers\
:user/src/com/google/gwt/uibinder/elementparsers\
:user/src/com/google/gwt/uibinder/testing\
diff --git a/user/src/com/google/gwt/i18n/I18N.gwt.xml b/user/src/com/google/gwt/i18n/I18N.gwt.xml
index 00d90ca..a22c58f 100644
--- a/user/src/com/google/gwt/i18n/I18N.gwt.xml
+++ b/user/src/com/google/gwt/i18n/I18N.gwt.xml
@@ -23,60 +23,56 @@
<!-- 'default' is always defined. -->
<define-property name="locale" values="default" />
- <property-provider name="locale">
- <![CDATA[
- try {
- var locale;
- var defaultLocale = "/*-FALLBACK-*/" || 'default';
+ <!--
+ - Configuration property defining the query parameter to use for the locale.
+ - Valid values are any legal URL query parameter name.
+ -->
+ <define-configuration-property name="locale.queryparam"
+ is-multi-valued="false"/>
+ <set-configuration-property name="locale.queryparam" value="locale"/>
- // Look for the locale as a url argument
- if (locale == null) {
- var args = location.search;
- var startLang = args.indexOf("locale=");
- if (startLang >= 0) {
- var language = args.substring(startLang);
- var begin = language.indexOf("=") + 1;
- var end = language.indexOf("&");
- if (end == -1) {
- end = language.length;
- }
- locale = language.substring(begin, end);
- }
- }
+ <!--
+ - Configuration property defining the cookie to use for the locale.
+ - Valid values are any legal cookie name.
+ -->
+ <define-configuration-property name="locale.cookie" is-multi-valued="false"/>
+ <set-configuration-property name="locale.cookie" value=""/>
- if (locale == null) {
- // Look for the locale on the web page
- locale = __gwt_getMetaProperty("locale")
- }
+ <!--
+ - Configuration property controlling whether to use user agent info for
+ - the user's locale.
+ - Valid values are (case insensitive): y/yes/n/no/true/false/on/off (others
+ - are treated as no).
+ -->
+ <define-configuration-property name="locale.useragent"
+ is-multi-valued="false"/>
+ <set-configuration-property name="locale.useragent" value="N"/>
- if (locale == null) {
- // Look for an override computed by other means in the selection script
- locale = $wnd['__gwt_Locale'];
- } else {
- $wnd['__gwt_Locale'] = locale || defaultLocale;
- }
+ <!--
+ - Configuration controlling whether to look for locale information in meta
+ - tags embedded by the server.
+ - Valid values are (case insensitive): y/yes/n/no/true/false/on/off (others
+ - are treated as no).
+ -->
+ <define-configuration-property name="locale.usemeta"
+ is-multi-valued="false"/>
+ <set-configuration-property name="locale.usemeta" value="Y"/>
- if (locale == null) {
- return defaultLocale;
- }
+ <!--
+ - Configuration property defining the order to search for a locale.
+ - Valid values are comma-separated lists of the following values:
+ - * queryparam
+ - * meta
+ - * cookie
+ - * useragent
+ -->
+ <define-configuration-property name="locale.searchorder"
+ is-multi-valued="false"/>
+ <set-configuration-property name="locale.searchorder"
+ value="queryparam,cookie,meta,useragent"/>
- while (!__gwt_isKnownPropertyValue("locale", locale)) {
- var lastIndex = locale.lastIndexOf("_");
- if (lastIndex == -1) {
- locale = defaultLocale;
- break;
- } else {
- locale = locale.substring(0,lastIndex);
- }
- }
-
- return locale;
- } catch(e){
- alert("Unexpected exception in locale detection, using default: " + e);
- return "default";
- }
- ]]>
- </property-provider>
+ <property-provider name="locale"
+ generator="com.google.gwt.i18n.linker.LocalePropertyProviderGenerator"/>
<generate-with class="com.google.gwt.i18n.rebind.LocalizableGenerator">
<when-type-assignable class="com.google.gwt.i18n.client.Localizable" />
@@ -99,7 +95,6 @@
reference the compile-time locale set in the "locale" property.
-->
<define-configuration-property name="runtime.locales" is-multi-valued="true"/>
- <set-configuration-property name="runtime.locales" value=""/>
<!--
A "real" locale to be served by default (i.e. if the browser either
diff --git a/user/src/com/google/gwt/i18n/client/LocaleInfo.java b/user/src/com/google/gwt/i18n/client/LocaleInfo.java
index ff188cf..6505de8 100644
--- a/user/src/com/google/gwt/i18n/client/LocaleInfo.java
+++ b/user/src/com/google/gwt/i18n/client/LocaleInfo.java
@@ -69,6 +69,16 @@
}
/**
+ * Returns the name of the cookie used by GWT to get the locale, or
+ * null if this app was compiled to not use a cookie.
+ *
+ * @return cookie name or null
+ */
+ public static String getLocaleCookieName() {
+ return instance.infoImpl.getLocaleCookieName();
+ }
+
+ /**
* Returns the display name of the requested locale in its native locale, if
* possible. If no native localization is available, the English name will
* be returned, or as a last resort just the locale name will be returned. If
@@ -86,6 +96,16 @@
*/
return instance.infoImpl.getLocaleNativeDisplayName(localeName);
}
+
+ /**
+ * Returns the name of the query parameter used by GWT to get the locale, or
+ * null if this app was compiled to not use a query parameter.
+ *
+ * @return query parameter name or null
+ */
+ public static String getLocaleQueryParamName() {
+ return instance.infoImpl.getLocaleQueryParamName();
+ }
/**
* Returns true if any locale supported by this build of the app is RTL.
diff --git a/user/src/com/google/gwt/i18n/client/impl/LocaleInfoImpl.java b/user/src/com/google/gwt/i18n/client/impl/LocaleInfoImpl.java
index 92d5ee8..ce83e1b 100644
--- a/user/src/com/google/gwt/i18n/client/impl/LocaleInfoImpl.java
+++ b/user/src/com/google/gwt/i18n/client/impl/LocaleInfoImpl.java
@@ -62,6 +62,13 @@
}
/**
+ * @return the cookie name used for the GWT locale, or null if none.
+ */
+ public String getLocaleCookieName() {
+ return null;
+ }
+
+ /**
* Returns the current locale name, such as "default, "en_US", etc.
*/
public String getLocaleName() {
@@ -82,6 +89,13 @@
}
/**
+ * @return the query parameter name used for the GWT locale, or null if none.
+ */
+ public String getLocaleQueryParamName() {
+ return null;
+ }
+
+ /**
* @return an implementation of {@link LocalizedNames} for this locale.
*/
public LocalizedNames getLocalizedNames() {
diff --git a/user/src/com/google/gwt/i18n/linker/LocalePropertyProviderGenerator.java b/user/src/com/google/gwt/i18n/linker/LocalePropertyProviderGenerator.java
new file mode 100644
index 0000000..ecbf23f
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/linker/LocalePropertyProviderGenerator.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2010 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.linker;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ConfigurationProperty;
+import com.google.gwt.core.ext.linker.PropertyProviderGenerator;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.google.gwt.user.rebind.StringSourceWriter;
+
+import java.util.SortedSet;
+import java.util.regex.Pattern;
+
+/**
+ * Generates a property provider implementation for the "locale" property.
+ */
+public class LocalePropertyProviderGenerator implements PropertyProviderGenerator {
+
+ public static final String LOCALE_QUERYPARAM = "locale.queryparam";
+
+ public static final String LOCALE_COOKIE = "locale.cookie";
+
+ public static final String LOCALE_SEARCHORDER = "locale.searchorder";
+
+ public static final String LOCALE_USEMETA = "locale.usemeta";
+
+ public static final String LOCALE_USERAGENT = "locale.useragent";
+
+ protected static final Pattern COOKIE_PATTERN = Pattern.compile("^[A-Za-z][A-Za-z0-9_]*$");
+
+ protected static final Pattern QUERYPARAM_PATTERN = Pattern.compile("^[A-Za-z][A-Za-z0-9_]*$");
+
+ /**
+ * Return true when the supplied value represents a true/yes/on value.
+ *
+ * @param value
+ * @return true if the string represents true/yes/on
+ */
+ protected static boolean isTrue(String value) {
+ return value != null && ("yes".equalsIgnoreCase(value)
+ || "y".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value)
+ || "on".equalsIgnoreCase(value));
+ }
+
+ public String generate(TreeLogger logger, SortedSet<String> possibleValues,
+ String fallback, SortedSet<ConfigurationProperty> configProperties)
+ throws UnableToCompleteException {
+ // get relevant config property values
+ String localeQueryParam = null;
+ String localeCookie = null;
+ boolean localeUserAgent = false;
+ boolean localeUseMeta = false;
+ String localeSearchOrder = "queryparam,cookie,meta,useragent";
+ for (ConfigurationProperty configProp : configProperties) {
+ String name = configProp.getName();
+ if (LOCALE_QUERYPARAM.equals(name)) {
+ localeQueryParam = configProp.getValues().get(0);
+ if (localeQueryParam != null && localeQueryParam.length() != 0
+ && !validateQueryParam(localeQueryParam)) {
+ logger.log(TreeLogger.WARN, "Ignoring invalid value of '"
+ + localeQueryParam + "' from '" + LOCALE_QUERYPARAM
+ + "', not a valid query parameter name");
+ localeQueryParam = null;
+ }
+ } else if (LOCALE_COOKIE.equals(name)) {
+ localeCookie = configProp.getValues().get(0);
+ if (localeCookie != null && localeCookie.length() != 0
+ && !validateCookieName(localeCookie)) {
+ logger.log(TreeLogger.WARN, "Ignoring invalid value of '"
+ + localeCookie + "' from '" + LOCALE_COOKIE
+ + "', not a valid cookie name");
+ localeCookie = null;
+ }
+ } else if (LOCALE_USEMETA.equals(name)) {
+ localeUseMeta = isTrue(configProp.getValues().get(0));
+ } else if (LOCALE_USERAGENT.equals(name)) {
+ localeUserAgent = isTrue(configProp.getValues().get(0));
+ } else if (LOCALE_SEARCHORDER.equals(name)) {
+ localeSearchOrder = configProp.getValues().get(0);
+ }
+ }
+ // provide a default for the search order
+ localeSearchOrder = localeSearchOrder.trim();
+ if (localeSearchOrder == null || localeSearchOrder.length() == 0) {
+ localeSearchOrder = "queryparam,cookie,meta,useragent";
+ }
+
+ if (fallback == null) {
+ // TODO(jat): define this in a common place
+ fallback = "default";
+ }
+
+ // build property provider body
+ StringSourceWriter body = new StringSourceWriter();
+ body.println("{");
+ body.indent();
+ body.println("var locale = null;");
+ body.println("var rtlocale = '" + fallback + "';");
+ body.println("try {");
+ for (String method : localeSearchOrder.split(",")) {
+ if ("queryparam".equals(method)) {
+ if (localeQueryParam != null && localeQueryParam.length() > 0) {
+ body.println("if (!locale) {");
+ body.indent();
+ generateQueryParamLookup(logger, body, localeQueryParam);
+ body.outdent();
+ body.println("}");
+ }
+ } else if ("cookie".equals(method)) {
+ if (localeCookie != null && localeCookie.length() > 0) {
+ body.println("if (!locale) {");
+ body.indent();
+ generateCookieLookup(logger, body, localeCookie);
+ body.outdent();
+ body.println("}");
+ }
+ } else if ("meta".equals(method)) {
+ if (localeUseMeta) {
+ body.println("if (!locale) {");
+ body.indent();
+ generateMetaLookup(logger, body);
+ body.outdent();
+ body.println("}");
+ }
+ } else if ("useragent".equals(method)) {
+ if (localeUserAgent) {
+ body.println("if (!locale) {");
+ body.indent();
+ generateUserAgentLookup(logger, body);
+ body.outdent();
+ body.println("}");
+ }
+ } else {
+ logger.log(TreeLogger.WARN, "Ignoring unknown locale lookup method \""
+ + method + "\"");
+ body.println("// ignoring invalid lookup method '" + method + "'");
+ }
+ }
+ body.println("if (!locale) {");
+ body.indent();
+ body.println("locale = $wnd['__gwt_Locale'];");
+ body.outdent();
+ body.println("}");
+ body.println("if (locale) {");
+ body.indent();
+ body.println("rtlocale = locale;");
+ body.outdent();
+ body.println("}");
+ generateInheritanceLookup(logger, body);
+ body.outdent();
+ body.println("} catch (e) {");
+ body.indent();
+ body.println("alert(\"Unexpected exception in locale detection, using "
+ + "default: \" + e);\n");
+ body.outdent();
+ body.println("}");
+ body.println("$wnd['__gwt_Locale'] = rtlocale;");
+ body.println("return locale || \"" + fallback + "\";");
+ body.outdent();
+ body.println("}");
+ return body.toString();
+ }
+
+ /**
+ * Generate JS code that looks up the locale value from a cookie.
+ *
+ * @param logger logger to use
+ * @param body
+ * @param cookieName
+ * @throws UnableToCompleteException
+ */
+ protected void generateCookieLookup(TreeLogger logger, SourceWriter body,
+ String cookieName) throws UnableToCompleteException {
+ body.println("var cookies = $doc.cookie;");
+ body.println("var idx = cookies.indexOf(\"" + cookieName + "=\");");
+ body.println("if (idx >= 0) {");
+ body.indent();
+ body.println("var end = cookies.indexOf(';', idx);");
+ body.println("if (end < 0) {");
+ body.indent();
+ body.println("end = cookies.length();");
+ body.outdent();
+ body.println("}");
+ body.println("locale = cookies.substring(idx + " + (cookieName.length() + 1)
+ + ", end);");
+ body.outdent();
+ body.println("}");
+ }
+
+ /**
+ * Generate JS code that takes the value of the "locale" variable and finds
+ * parent locales until the value is a supported locale or the default locale.
+ *
+ * @param logger logger to use
+ * @param body
+ * @throws UnableToCompleteException
+ */
+ protected void generateInheritanceLookup(TreeLogger logger, SourceWriter body)
+ throws UnableToCompleteException {
+ body.println("while (locale && !__gwt_isKnownPropertyValue(\"locale\", locale)) {");
+ body.indent();
+ body.println("var lastIndex = locale.lastIndexOf(\"_\");");
+ body.println("if (lastIndex < 0) {");
+ body.indent();
+ body.println("locale = null;");
+ body.println("break;");
+ body.outdent();
+ body.println("}");
+ body.println("locale = locale.substring(0, lastIndex);");
+ body.outdent();
+ body.println("}");
+ }
+
+ /**
+ * Generate JS code to fetch the locale from a meta property.
+ *
+ * @param logger logger to use
+ * @param body
+ * @throws UnableToCompleteException
+ */
+ protected void generateMetaLookup(TreeLogger logger, SourceWriter body)
+ throws UnableToCompleteException {
+ // TODO(jat): do we want to allow customizing the meta property name?
+ body.println("locale = __gwt_getMetaProperty(\"locale\");");
+ }
+
+ /**
+ * Generate JS code to get the locale from a query parameter.
+ *
+ * @param logger logger to use
+ * @param body where to append JS output
+ * @param queryParam the query parameter to use
+ * @throws UnableToCompleteException
+ */
+ protected void generateQueryParamLookup(TreeLogger logger, SourceWriter body,
+ String queryParam) throws UnableToCompleteException {
+ body.println("var queryParam = location.search;");
+ body.println("var qpStart = queryParam.indexOf(\"" + queryParam + "=\");");
+ body.println("if (qpStart >= 0) {");
+ body.indent();
+ body.println("var value = queryParam.substring(qpStart + "
+ + (queryParam.length() + 1) + ");");
+ body.println("var end = queryParam.indexOf(\"&\", qpStart);");
+ body.println("if (end < 0) {");
+ body.indent();
+ body.println("end = queryParam.length();");
+ body.outdent();
+ body.println("}");
+ body.println("locale = queryParam.substring(qpStart + "
+ + (queryParam.length() + 1) + ", end);");
+ body.outdent();
+ body.println("}");
+ }
+
+ /**
+ * Generate JS code to fetch the locale from the user agent's compile-time
+ * locale.
+ *
+ * @param logger logger to use
+ * @param body
+ * @throws UnableToCompleteException
+ */
+ protected void generateUserAgentLookup(TreeLogger logger, SourceWriter body)
+ throws UnableToCompleteException {
+ body.println("var language = navigator.browserLanguage ? "
+ + "navigator.browserLanguage : navigator.language;");
+ body.println("if (language) {");
+ body.indent();
+ body.println("locale = language.replace(/-/g, \"_\");");
+ body.outdent();
+ body.println();
+ }
+
+ /**
+ * Validate that a name is a valid cookie name.
+ *
+ * @param cookieName
+ * @return true if cookieName is an acceptable cookie name
+ */
+ protected boolean validateCookieName(String cookieName) {
+ return COOKIE_PATTERN.matcher(cookieName).matches();
+ }
+
+ /**
+ * Validate that a value is a valid query parameter name.
+ *
+ * @param queryParam
+ * @return true if queryParam is a valid query parameter name.
+ */
+ protected boolean validateQueryParam(String queryParam) {
+ return QUERYPARAM_PATTERN.matcher(queryParam).matches();
+ }
+}
diff --git a/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java b/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java
index a899a96..f681eb7 100644
--- a/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java
+++ b/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java
@@ -17,6 +17,8 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.core.ext.ConfigurationProperty;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.PropertyOracle;
@@ -26,6 +28,7 @@
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.i18n.client.impl.LocaleInfoImpl;
+import com.google.gwt.i18n.linker.LocalePropertyProviderGenerator;
import com.google.gwt.i18n.server.GwtLocaleImpl;
import com.google.gwt.i18n.shared.GwtLocale;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
@@ -83,6 +86,24 @@
}
/**
+ * Generates source representing a string constant that might be null (and
+ * empty strings are treated as null as well).
+ *
+ * @param value
+ * @return source representation of value
+ */
+ private static String possiblyNullStringConstant(String value) {
+ if (value == null || value.length() == 0) {
+ return "null";
+ }
+ return "\"" + quoteQuotes(value) + "\"";
+ }
+
+ private static String quoteQuotes(String val) {
+ return val.replace("\"", "\\\"");
+ }
+
+ /**
* Generate an implementation for the given type.
*
* @param logger error logger
@@ -99,6 +120,25 @@
PropertyOracle propertyOracle = context.getPropertyOracle();
LocaleUtils localeUtils = LocaleUtils.getInstance(logger, propertyOracle,
context);
+ String cookieName = null;
+ String queryParamName = null;
+ ConfigurationProperty prop;
+ try {
+ prop = propertyOracle.getConfigurationProperty(
+ LocalePropertyProviderGenerator.LOCALE_COOKIE);
+ cookieName = prop.getValues().get(0);
+ } catch (BadPropertyValueException e) {
+ } catch (IndexOutOfBoundsException e) {
+ // ignore, leaving the value as null
+ }
+ try {
+ prop = propertyOracle.getConfigurationProperty(
+ LocalePropertyProviderGenerator.LOCALE_QUERYPARAM);
+ queryParamName = prop.getValues().get(0);
+ } catch (BadPropertyValueException e) {
+ } catch (IndexOutOfBoundsException e) {
+ // ignore, leaving the value as null
+ }
JClassType targetClass;
try {
@@ -177,6 +217,12 @@
writer.println("}");
writer.println();
writer.println("@Override");
+ writer.println("public String getLocaleCookieName() {");
+ writer.println(" return " + possiblyNullStringConstant(cookieName)
+ + ";");
+ writer.println("}");
+ writer.println();
+ writer.println("@Override");
writer.println("public String getLocaleNativeDisplayName(String localeName) {");
writer.println(" if (GWT.isScript()) {");
writer.println(" if (nativeDisplayNamesNative == null) {");
@@ -210,6 +256,12 @@
writer.println("}");
writer.println();
writer.println("@Override");
+ writer.println("public String getLocaleQueryParamName() {");
+ writer.println(" return " + possiblyNullStringConstant(queryParamName)
+ + ";");
+ writer.println("}");
+ writer.println();
+ writer.println("@Override");
writer.println("public boolean hasAnyRTL() {");
writer.println(" return " + hasAnyRtl + ";");
writer.println("}");
@@ -379,8 +431,4 @@
}
locales.add(locale);
}
-
- private String quoteQuotes(String val) {
- return val.replace("\"", "\\\"");
- }
}
diff --git a/user/test/com/google/gwt/i18n/I18NTest_ar.gwt.xml b/user/test/com/google/gwt/i18n/I18NTest_ar.gwt.xml
index e00f9d8..cfc54ae 100644
--- a/user/test/com/google/gwt/i18n/I18NTest_ar.gwt.xml
+++ b/user/test/com/google/gwt/i18n/I18NTest_ar.gwt.xml
@@ -20,4 +20,7 @@
<source path="client"/>
<extend-property name="locale" values="ar"/>
<set-property name = "locale" value = "ar"/>
+
+ <set-configuration-property name="locale.cookie" value="LOCALE"/>
+ <set-configuration-property name="locale.queryparam" value="arlocale"/>
</module>
diff --git a/user/test/com/google/gwt/i18n/client/LocaleInfoTest.java b/user/test/com/google/gwt/i18n/client/LocaleInfoTest.java
index 52aa6db..c4bae15 100644
--- a/user/test/com/google/gwt/i18n/client/LocaleInfoTest.java
+++ b/user/test/com/google/gwt/i18n/client/LocaleInfoTest.java
@@ -38,6 +38,17 @@
assertEquals("piglatin_UK_WINDOWS", locale);
}
+ public void testLocaleCookieName() {
+ String cookieName = LocaleInfo.getLocaleCookieName();
+ assertNull("Default locale cooke name should be null", cookieName);
+ }
+
+ public void testLocaleQueryParam() {
+ String queryParam = LocaleInfo.getLocaleQueryParamName();
+ assertEquals("Default locale query param should be 'locale'", "locale",
+ queryParam);
+ }
+
public void testNativeDisplayNames() {
// en isn't in the property set for this module so should return null
String displayName = LocaleInfo.getLocaleNativeDisplayName("en");
diff --git a/user/test/com/google/gwt/i18n/client/LocaleInfo_ar_Test.java b/user/test/com/google/gwt/i18n/client/LocaleInfo_ar_Test.java
index 6acd948..3b2a15e 100644
--- a/user/test/com/google/gwt/i18n/client/LocaleInfo_ar_Test.java
+++ b/user/test/com/google/gwt/i18n/client/LocaleInfo_ar_Test.java
@@ -30,11 +30,6 @@
return "com.google.gwt.i18n.I18NTest_ar";
}
- public void testCurrentLocale() {
- String locale = LocaleInfo.getCurrentLocale().getLocaleName();
- assertEquals("ar", locale);
- }
-
public void testAvailableLocales() {
String[] locales = LocaleInfo.getAvailableLocaleNames();
ArrayList<String> localeList = new ArrayList<String>();
@@ -43,6 +38,23 @@
assertTrue(localeList.contains("default"));
}
+ public void testCurrentLocale() {
+ String locale = LocaleInfo.getCurrentLocale().getLocaleName();
+ assertEquals("ar", locale);
+ }
+
+ public void testLocaleCookieName() {
+ String cookieName = LocaleInfo.getLocaleCookieName();
+ assertEquals("I18N/ar locale cooke name should be 'LOCALE'", "LOCALE",
+ cookieName);
+ }
+
+ public void testLocaleQueryParam() {
+ String queryParam = LocaleInfo.getLocaleQueryParamName();
+ assertEquals("I18N/ar locale query param should be 'arlocale'", "arlocale",
+ queryParam);
+ }
+
public void testNativeDisplayNames() {
// verify ar is known
String displayName = LocaleInfo.getLocaleNativeDisplayName("ar");