property fall back value evaluation scheme - enable fall back bindings. Review at http://gwt-code-reviews.appspot.com/1369807 git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9824 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/DefaultSelectionProperty.java b/dev/core/src/com/google/gwt/core/ext/DefaultSelectionProperty.java index 7d7535b..dc4e804 100644 --- a/dev/core/src/com/google/gwt/core/ext/DefaultSelectionProperty.java +++ b/dev/core/src/com/google/gwt/core/ext/DefaultSelectionProperty.java
@@ -15,6 +15,9 @@ */ package com.google.gwt.core.ext; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.SortedSet; /** @@ -27,6 +30,7 @@ private final String fallbackValue; private final String name; private final SortedSet<String> possibleValues; + private final Map<String, ? extends List<? extends Set<String>>> fallbackValueMap; /** * Construct a selection property. @@ -40,6 +44,23 @@ */ public DefaultSelectionProperty(String currentValue, String fallbackValue, String name, SortedSet<String> possibleValues) { + this(currentValue, fallbackValue, name, possibleValues, null); + } + + /** + * 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 + * @param map the map propertyValue to fallback values + */ + public DefaultSelectionProperty(String currentValue, String fallbackValue, + String name, SortedSet<String> possibleValues, + Map<String, ? extends List<? extends Set<String>>> fallbackValueMap) { assert currentValue != null; assert fallbackValue != null; assert name != null; @@ -48,6 +69,7 @@ this.fallbackValue = fallbackValue; this.name = name; this.possibleValues = possibleValues; + this.fallbackValueMap = fallbackValueMap; } @Override @@ -76,6 +98,10 @@ return fallbackValue; } + public List<? extends Set<String>> getFallbackValues(String value) { + return (null != fallbackValueMap) ? fallbackValueMap.get(value) : null; + } + public String getName() { return name; }
diff --git a/dev/core/src/com/google/gwt/core/ext/SelectionProperty.java b/dev/core/src/com/google/gwt/core/ext/SelectionProperty.java index e781f76..e286d75 100644 --- a/dev/core/src/com/google/gwt/core/ext/SelectionProperty.java +++ b/dev/core/src/com/google/gwt/core/ext/SelectionProperty.java
@@ -16,6 +16,8 @@ package com.google.gwt.core.ext; +import java.util.List; +import java.util.Set; import java.util.SortedSet; /** @@ -47,6 +49,14 @@ String getFallbackValue(); /** + * Returns the list of fall back values for a given value. + * @param value the property value + * @return the fall back list of values by increasing order + * of preference. + */ + List<? extends Set<String>> getFallbackValues(String value); + + /** * Returns the possible values for the property in sorted order. * * @return a SortedSet of Strings containing the possible property values.
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 69e689c..36cb777 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java +++ b/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
@@ -28,11 +28,13 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import java.util.Map.Entry; import java.util.regex.Pattern; /** @@ -51,6 +53,8 @@ private PropertyProvider provider; private Class<? extends PropertyProviderGenerator> providerGenerator; private String fallback; + private HashMap<String,LinkedList<LinkedHashSet<String>>> fallbackValueMap; + private HashMap<String,LinkedList<String>> fallbackValues = new HashMap<String,LinkedList<String>>(); private final ConditionAll rootCondition = new ConditionAll(); { @@ -95,6 +99,20 @@ } /** + * Adds fall back value to given property name. + * @param value the property value. + * @param fallbackValue the fall back value for given property value. + */ + public void addFallbackValue(String value, String fallbackValue) { + LinkedList<String> values = fallbackValues.get(fallbackValue); + if (values == null) { + values = new LinkedList<String>(); + fallbackValues.put(fallbackValue, values); + } + values.addFirst(value); + } + + /** * Returns the set of allowed values in sorted order when a certain condition * is satisfied. */ @@ -145,6 +163,43 @@ return fallback; } + /** + * Returns the map of values to fall back values. the list of fall + * back values is in decreasing order of preference. + * @return map of property value to fall back values. + */ + public Map<String,? extends List<? extends Set<String>>> getFallbackValuesMap() { + if (fallbackValueMap == null) { + HashMap<String,LinkedList<LinkedHashSet<String>>> valuesMap = new HashMap<String,LinkedList<LinkedHashSet<String>>>(); + // compute closure of fall back values preserving order + for (Entry<String, LinkedList<String>> e : fallbackValues.entrySet()) { + String from = e.getKey(); + LinkedList<LinkedHashSet<String>> alternates = new LinkedList<LinkedHashSet<String>>(); + valuesMap.put(from, alternates); + LinkedList<String> childList = fallbackValues.get(from); + LinkedHashSet<String> children = new LinkedHashSet<String>(); + children.addAll(childList); + while (children != null && children.size() > 0) + { + alternates.add(children); + LinkedHashSet<String> newChildren = new LinkedHashSet<String>(); + for (String child : children) { + childList = fallbackValues.get(child); + if (null == childList) { + continue; + } + for (String val : childList) { + newChildren.add(val); + } + } + children = newChildren; + } + } + fallbackValueMap = valuesMap; + } + return fallbackValueMap; + } + public PropertyProvider getProvider() { return provider; }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java index a03daf9..bef8e5b 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java +++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java
@@ -23,6 +23,7 @@ import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.dev.util.collect.Sets; +import java.util.List; import java.util.Set; /** @@ -69,6 +70,31 @@ if (testValue.equals(value)) { return true; } else { + // no exact match was found, see if any fall back + // value would satisfy the condition + try { + SelectionProperty prop = propertyOracle.getSelectionProperty(logger, + propName); + List<? extends Set<String>> fallbackValues = prop.getFallbackValues(value); + if (fallbackValues != null && fallbackValues.size() > 0) { + logger.log(TreeLogger.DEBUG, "Property value '" + value + "'" + + " is the fallback of '" + fallbackValues.toString() + "'", null); + int cost = -1; + for (Set<String> values : fallbackValues) { + for (String fallbackValue : values) { + if (testValue.equals(fallbackValue)) { + query.setFallbackEvaluationCost(cost); + return false; + } + } + cost--; + } + } + } + catch (BadPropertyValueException e) { + // do nothing - currently, only selection + // properties support fall back values + } return false; } } catch (BadPropertyValueException e) {
diff --git a/dev/core/src/com/google/gwt/dev/cfg/DeferredBindingQuery.java b/dev/core/src/com/google/gwt/dev/cfg/DeferredBindingQuery.java index 503ca94..42b49d3 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/DeferredBindingQuery.java +++ b/dev/core/src/com/google/gwt/dev/cfg/DeferredBindingQuery.java
@@ -26,6 +26,7 @@ * against such a query. */ public class DeferredBindingQuery { + private int fallbackEvalCost = Integer.MAX_VALUE; private final Set<String> linkerNames; private final PropertyOracle propertyOracle; private final String testType; @@ -54,6 +55,10 @@ this.testType = testType; } + public int getFallbackEvaluationCost() { + return fallbackEvalCost; + } + public Set<String> getLinkerNames() { return linkerNames; } @@ -69,4 +74,8 @@ public TypeOracle getTypeOracle() { return typeOracle; } + + public void setFallbackEvaluationCost(int cost) { + fallbackEvalCost = cost; + } }
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 d5653e7..9d829ca 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java +++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -98,6 +98,9 @@ @SuppressWarnings("unused") // referenced reflectively protected final String __extend_property_2_values = null; + @SuppressWarnings("unused") // referenced reflectively ($ means optional) + protected final String __extend_property_3_fallback_value$ = null; + @SuppressWarnings("unused") // referenced reflectively protected final String __generate_with_1_class = null; @@ -441,11 +444,25 @@ @SuppressWarnings("unused") // called reflectively protected Schema __extend_property_begin(BindingProperty property, - PropertyValue[] values) { + PropertyValue[] values, PropertyFallbackValue fallbackValue) + throws UnableToCompleteException { for (int i = 0; i < values.length; i++) { property.addDefinedValue(property.getRootCondition(), values[i].token); } + // validate fallback-property value if present + if (fallbackValue != null) { + if (!property.isDefinedValue(fallbackValue.token)) { + logger.log(TreeLogger.ERROR, "The property " + property.name + + " fallback-value was not defined"); + throw new UnableToCompleteException(); + } + // add fallback map to the property + for (int i = 0; i < values.length; i++) { + property.addFallbackValue(values[i].token, fallbackValue.token); + } + } + // No children. return null; } @@ -1184,6 +1201,14 @@ } } + private static class PropertyFallbackValue { + public final String token; + + public PropertyFallbackValue(String token) { + this.token = token; + } + } + /** * Converts a comma-separated string into an array of property value tokens. */ @@ -1236,6 +1261,24 @@ } /** + * Converts a string into a property fallback value, validating it in the process. + */ + private final class PropertyFallbackValueAttrCvt extends AttributeConverter { + @Override + public Object convertToArg(Schema schema, int line, String elem, + String attr, String value) throws UnableToCompleteException { + String token = value.trim(); + if (Util.isValidJavaIdent(token)) { + return new PropertyFallbackValue(token); + } else { + Messages.PROPERTY_VALUE_INVALID.log(logger, token, null); + throw new UnableToCompleteException(); + } + } + } + + + /** * Converts a comma-separated string into an array of property value tokens. */ private final class PropertyValueGlobArrayAttrCvt extends AttributeConverter { @@ -1351,6 +1394,7 @@ private final PropertyNameAttrCvt propNameAttrCvt = new PropertyNameAttrCvt(); private final PropertyValueArrayAttrCvt propValueArrayAttrCvt = new PropertyValueArrayAttrCvt(); private final PropertyValueAttrCvt propValueAttrCvt = new PropertyValueAttrCvt(); + private final PropertyFallbackValueAttrCvt propFallbackValueAttrCvt = new PropertyFallbackValueAttrCvt(); private final PropertyValueGlobArrayAttrCvt propValueGlobArrayAttrCvt = new PropertyValueGlobArrayAttrCvt(); private final PropertyValueGlobAttrCvt propValueGlobAttrCvt = new PropertyValueGlobAttrCvt(); @@ -1371,6 +1415,7 @@ registerAttributeConverter(ConfigurationProperty.class, configurationPropAttrCvt); registerAttributeConverter(PropertyValue.class, propValueAttrCvt); + registerAttributeConverter(PropertyFallbackValue.class, propFallbackValueAttrCvt); registerAttributeConverter(PropertyValue[].class, propValueArrayAttrCvt); registerAttributeConverter(PropertyValueGlob.class, propValueGlobAttrCvt); registerAttributeConverter(PropertyValueGlob[].class,
diff --git a/dev/core/src/com/google/gwt/dev/cfg/Rule.java b/dev/core/src/com/google/gwt/dev/cfg/Rule.java index a24e1ca..7ebe9f0 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/Rule.java +++ b/dev/core/src/com/google/gwt/dev/cfg/Rule.java
@@ -24,19 +24,35 @@ * Abstract base class for various kinds of deferred binding rules. */ public abstract class Rule { - + + private int fallbackEvalCost = Integer.MAX_VALUE; private final ConditionAll rootCondition = new ConditionAll(); + /** + * Returns the cost of evaluation fallback binding values. + * when isApplicable() is true, this value is meaningless + * when isApplicable() is false, [1,Integer.MAX_VALUE-1] means + * a relative scale of cost, where cost c is better than cost c+k + * Integer.MAX_VALUE implies no match. + * @return cost of evaluating fallback values. + */ + public int getFallbackEvaluationCost() { + return fallbackEvalCost; + } + public ConditionAll getRootCondition() { return rootCondition; } - + public boolean isApplicable(TreeLogger logger, StandardGeneratorContext context, String typeName) throws UnableToCompleteException { - return rootCondition.isTrue(logger, new DeferredBindingQuery( + DeferredBindingQuery query = new DeferredBindingQuery( context.getPropertyOracle(), context.getActiveLinkerNames(), - context.getTypeOracle(), typeName)); + context.getTypeOracle(), typeName); + boolean result = rootCondition.isTrue(logger, query); + fallbackEvalCost = query.getFallbackEvaluationCost(); + return result; } public abstract RebindResult realize(TreeLogger logger,
diff --git a/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java b/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java index b7d2e7b..47906e4 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java +++ b/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java
@@ -22,6 +22,9 @@ import com.google.gwt.core.ext.TreeLogger; import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.TreeSet; /** @@ -152,7 +155,7 @@ possibleValues.add(v); } return new DefaultSelectionProperty(value, prop.getFallback(), name, - possibleValues); + possibleValues, (Map<String, List<Set<String>>>) prop.getFallbackValuesMap()); } }
diff --git a/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java b/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java index 819c468..494d549 100644 --- a/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java +++ b/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java
@@ -135,7 +135,8 @@ for (String v : cprop.getDefinedValues()) { possibleValues.add(v); } - return new DefaultSelectionProperty(value, fallback, name, possibleValues); + return new DefaultSelectionProperty(value, fallback, name, possibleValues, + cprop.getFallbackValuesMap()); } else { throw new BadPropertyValueException(propertyName); }
diff --git a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java index ee7e9f8..2daa472 100644 --- a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java +++ b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
@@ -117,6 +117,8 @@ return null; } + Rule minCostRuleSoFar = null; + for (Iterator<Rule> iter = rules.iterator(); iter.hasNext();) { Rule rule = iter.next(); @@ -132,7 +134,7 @@ if (!usedRules.contains(rule)) { usedRules.add(rule); Messages.TRACE_RULE_MATCHED.log(logger, null); - + return rule; } else { // We are skipping this rule because it has already been used @@ -141,11 +143,35 @@ } } else { Messages.TRACE_RULE_DID_NOT_MATCH.log(logger, null); + + // keep track of fallback partial matches + if (minCostRuleSoFar == null) { + minCostRuleSoFar = rule; + } + assert rule.getFallbackEvaluationCost() != 0; + // if we found a better match, keep that as the best candidate so far + if (rule.getFallbackEvaluationCost() <= minCostRuleSoFar.getFallbackEvaluationCost()) { + logger.log(TreeLogger.DEBUG, "Found better fallback match for " + rule); + minCostRuleSoFar = rule; + } } } + // if we reach this point, it means we did not find an exact match + // and we may have a partial match based on fall back values + assert minCostRuleSoFar != null; + if (minCostRuleSoFar.getFallbackEvaluationCost() < Integer.MAX_VALUE) { + logger.log(TreeLogger.WARN, "Could not find an exact match rule. Using 'closest' rule " + + minCostRuleSoFar + " based on fall back values. You may need to implement a specific " + + "binding in case the fall back behavior does not replace the missing binding"); + if (!usedRules.contains(minCostRuleSoFar)) { + usedRules.add(minCostRuleSoFar); + logger.log(TreeLogger.DEBUG, "No exact match was found, using closest match rule " + minCostRuleSoFar); + return minCostRuleSoFar; + } + } + // No matching rule for this type. - // return null; } @@ -161,14 +187,14 @@ if (!genCtx.isGeneratorResultCachingEnabled()) { return resultTypeName; } - + RebindStatus status = newResult.getResultStatus(); switch (status) { case USE_EXISTING: // in this case, no newly generated or cached types are needed break; - + case USE_ALL_NEW_WITH_NO_CACHING: /* * in this case, new artifacts have been generated, but no need to @@ -176,7 +202,7 @@ * advantage of caching). */ break; - + case USE_ALL_NEW: // use all new results, add a new cache entry cachedResult = new CachedRebindResult(newResult.getReturnedTypeName(), @@ -184,18 +210,18 @@ System.currentTimeMillis(), newResult.getClientDataMap()); rebindCachePut(rule, typeName, cachedResult); break; - + case USE_ALL_CACHED: // use all cached results assert (cachedResult != null); - + genCtx.commitArtifactsFromCache(logger); genCtx.addGeneratedUnitsFromCache(); - + // use cached type name resultTypeName = cachedResult.getReturnedTypeName(); break; - + case USE_PARTIAL_CACHED: /* * Add cached generated units marked for reuse to the context. @@ -203,7 +229,7 @@ * as GeneratedUnits. */ genCtx.addGeneratedUnitsMarkedForReuseFromCache(); - + /* * Create a new cache entry using the composite set of new and * reused cached results currently in genCtx. @@ -267,14 +293,14 @@ public void setRebindCache(RebindCache cache) { this.rebindCache = cache; } - + private CachedRebindResult rebindCacheGet(Rule rule, String typeName) { if (rebindCache != null) { return rebindCache.get(rule, typeName); } return null; } - + private void rebindCachePut(Rule rule, String typeName, CachedRebindResult result) { if (rebindCache != null) { rebindCache.put(rule, typeName, result);
diff --git a/dev/core/src/com/google/gwt/dev/util/xml/HandlerArgs.java b/dev/core/src/com/google/gwt/dev/util/xml/HandlerArgs.java index 9c564b3..b58614a 100644 --- a/dev/core/src/com/google/gwt/dev/util/xml/HandlerArgs.java +++ b/dev/core/src/com/google/gwt/dev/util/xml/HandlerArgs.java
@@ -63,6 +63,8 @@ AttributeConverter converter = schema.getAttributeConverter(handlerParams[i].getParamType()); return converter.convertToArg(schema, lineNumber, elemName, attrNames[i], value); + } else if (handlerParams[i].isOptional()) { + return null; } else { return new NullPointerException("Argument " + i + " was null"); } @@ -77,7 +79,7 @@ } public boolean isArgSet(int i) { - if (argValues[i] != null) { + if (argValues[i] != null || handlerParams[i].isOptional()) { return true; } else { return false;
diff --git a/dev/core/src/com/google/gwt/dev/util/xml/HandlerParam.java b/dev/core/src/com/google/gwt/dev/util/xml/HandlerParam.java index 6d4f539..a89418e 100644 --- a/dev/core/src/com/google/gwt/dev/util/xml/HandlerParam.java +++ b/dev/core/src/com/google/gwt/dev/util/xml/HandlerParam.java
@@ -81,11 +81,17 @@ private final Class<?> paramType; private final Field metaField; + + private final boolean isOptional; private final String normalizedAttrName; private HandlerParam(Class<?> paramType, Field metaField, String normalizedAttrName) { + this.isOptional = normalizedAttrName.endsWith("$"); + if (isOptional) { + normalizedAttrName = normalizedAttrName.substring(0, normalizedAttrName.length() - 1); + } this.paramType = paramType; this.metaField = metaField; this.normalizedAttrName = normalizedAttrName; @@ -116,4 +122,8 @@ public Class<?> getParamType() { return paramType; } + + public boolean isOptional() { + return isOptional; + } }
diff --git a/dev/core/test/com/google/gwt/dev/js/JsCoerceIntShiftTest.java b/dev/core/test/com/google/gwt/dev/js/JsCoerceIntShiftTest.java index 1ae331d..3a30878 100644 --- a/dev/core/test/com/google/gwt/dev/js/JsCoerceIntShiftTest.java +++ b/dev/core/test/com/google/gwt/dev/js/JsCoerceIntShiftTest.java
@@ -52,7 +52,8 @@ } else { SortedSet<String> valueSet = new TreeSet<String>(); valueSet.add(value); - return new DefaultSelectionProperty(value, value, value, valueSet); + return new DefaultSelectionProperty(value, value, value, valueSet, + null /* fallbackValueMap */); } }
diff --git a/user/src/com/google/gwt/user/UserAgent.gwt.xml b/user/src/com/google/gwt/user/UserAgent.gwt.xml index f60f650..261e194 100644 --- a/user/src/com/google/gwt/user/UserAgent.gwt.xml +++ b/user/src/com/google/gwt/user/UserAgent.gwt.xml
@@ -19,8 +19,11 @@ <module> <!-- Browser-sensitive code should use the 'user.agent' property --> - <define-property name="user.agent" values="ie6,ie8,gecko1_8,safari,opera"/> - + <define-property name="user.agent" values="ie6" /> + <extend-property name="user.agent" values="ie8" /> + <extend-property name="user.agent" values="gecko1_8" /> + <extend-property name="user.agent" values="safari" /> + <extend-property name="user.agent" values="opera" /> <property-provider name="user.agent" generator="com.google.gwt.user.rebind.UserAgentPropertyGenerator"/> <!-- Set to false to avoid runtime warnings for mismatched runtime and -->
diff --git a/user/src/com/google/gwt/user/rebind/UserAgentGenerator.java b/user/src/com/google/gwt/user/rebind/UserAgentGenerator.java index f0f3e8b..5f164d4 100644 --- a/user/src/com/google/gwt/user/rebind/UserAgentGenerator.java +++ b/user/src/com/google/gwt/user/rebind/UserAgentGenerator.java
@@ -80,10 +80,11 @@ } String userAgentValue; + SelectionProperty selectionProperty; try { - SelectionProperty property = propertyOracle.getSelectionProperty(logger, + selectionProperty = propertyOracle.getSelectionProperty(logger, PROPERTY_USER_AGENT); - userAgentValue = property.getCurrentValue(); + userAgentValue = selectionProperty.getCurrentValue(); } catch (BadPropertyValueException e) { logger.log(TreeLogger.ERROR, "Unable to find value for '" + PROPERTY_USER_AGENT + "'", e); @@ -113,7 +114,8 @@ sw.println(); sw.println("public native String getRuntimeValue() /*-{"); sw.indent(); - UserAgentPropertyGenerator.writeUserAgentPropertyJavaScript(sw); + UserAgentPropertyGenerator.writeUserAgentPropertyJavaScript(sw, + selectionProperty.getPossibleValues()); sw.outdent(); sw.println("}-*/;"); sw.println();
diff --git a/user/src/com/google/gwt/user/rebind/UserAgentPropertyGenerator.java b/user/src/com/google/gwt/user/rebind/UserAgentPropertyGenerator.java index 03c8df0..8c70403 100644 --- a/user/src/com/google/gwt/user/rebind/UserAgentPropertyGenerator.java +++ b/user/src/com/google/gwt/user/rebind/UserAgentPropertyGenerator.java
@@ -40,6 +40,51 @@ "ie6", "ie8", "gecko1_8", "safari", "opera"}); /** + * List of predicates to identify user agent. + * The order of evaluation is from top to bottom, i.e., the first matching + * predicate will have the associated ua token returned. + * ua is defined in an outer scope and is therefore visible in + * the predicate javascript fragment. + */ + private static UserAgentPropertyGeneratorPredicate[] predicates = + new UserAgentPropertyGeneratorPredicate[] { + + // opera + new UserAgentPropertyGeneratorPredicate("opera") + .getPredicateBlock() + .println("return (ua.indexOf('opera') != -1);") + .returns("'opera'"), + + // webkit family + new UserAgentPropertyGeneratorPredicate("safari") + .getPredicateBlock() + .println("return (ua.indexOf('webkit') != -1);") + .returns("'safari'"), + + // IE8 + new UserAgentPropertyGeneratorPredicate("ie8") + .getPredicateBlock() + .println("return (ua.indexOf('msie') != -1 && ($doc.documentMode >= 8));") + .returns("'ie8'"), + + // IE6 + new UserAgentPropertyGeneratorPredicate("ie6") + .getPredicateBlock() + .println("var result = /msie ([0-9]+)\\.([0-9]+)/.exec(ua);") + .println("if (result && result.length == 3)") + .indent() + .println("return (makeVersion(result) >= 6000);") + .outdent() + .returns("'ie6'"), + + // gecko family + new UserAgentPropertyGeneratorPredicate("gecko1_8") + .getPredicateBlock() + .println("return (ua.indexOf('gecko') != -1);") + .returns("'gecko1_8'"), + }; + + /** * Writes out the JavaScript function body for determining the value of the * <code>user.agent</code> selection property. This method is used to create * the selection script and by {@link UserAgentGenerator} to assert at runtime @@ -47,48 +92,29 @@ * <code>user.agent</code> values listed here should be kept in sync with * {@link #VALID_VALUES} and <code>UserAgent.gwt.xml</code>. */ - static void writeUserAgentPropertyJavaScript(SourceWriter body) { + static void writeUserAgentPropertyJavaScript(SourceWriter body, + SortedSet<String> possibleValues) { + + // write preamble body.println("var ua = navigator.userAgent.toLowerCase();"); body.println("var makeVersion = function(result) {"); body.indent(); body.println("return (parseInt(result[1]) * 1000) + parseInt(result[2]);"); body.outdent(); body.println("};"); - body.println("if (ua.indexOf('opera') != -1) {"); - body.indent(); - body.println("return 'opera';"); - body.outdent(); - body.println("} else if (ua.indexOf('webkit') != -1) {"); - body.indent(); - body.println("return 'safari';"); - body.outdent(); - body.println("} else if (ua.indexOf('msie') != -1) {"); - body.indent(); - body.println("if ($doc.documentMode >= 8) {"); - body.indent(); - body.println("return 'ie8';"); - body.outdent(); - body.println("} else {"); - body.indent(); - body.println("var result = /msie ([0-9]+)\\.([0-9]+)/.exec(ua);"); - body.println("if (result && result.length == 3) {"); - body.indent(); - body.println("var v = makeVersion(result);"); - body.println("if (v >= 6000) {"); - body.indent(); - body.println("return 'ie6';"); - body.outdent(); - body.println("}"); - body.outdent(); - body.println("}"); - body.outdent(); - body.println("}"); - body.outdent(); - body.println("} else if (ua.indexOf('gecko') != -1) {"); - body.indent(); - body.println("return 'gecko1_8';"); - body.outdent(); - body.println("}"); + + // write only selected user agents + for (int i = 0; i < predicates.length; i++) { + if (possibleValues.contains(predicates[i].getUserAgent())) { + body.println("if ((function() { "); + body.indent(); + body.print(predicates[i].toString()); + body.outdent(); + body.println("})()) return " + predicates[i].getReturnValue() + ";"); + } + } + + // default return body.println("return 'unknown';"); } @@ -105,14 +131,17 @@ + "\" value=\"false\"/> to suppress this warning message."); } } - + // make sure that the # of ua in VALID_VALUES + // is the same of predicates. maybe should iterate + // to make sure each one has a match. + assert predicates.length == VALID_VALUES.size(); StringSourceWriter body = new StringSourceWriter(); body.println("{"); body.indent(); - writeUserAgentPropertyJavaScript(body); + writeUserAgentPropertyJavaScript(body, possibleValues); body.outdent(); body.println("}"); return body.toString(); } -} +} \ No newline at end of file
diff --git a/user/src/com/google/gwt/user/rebind/UserAgentPropertyGeneratorPredicate.java b/user/src/com/google/gwt/user/rebind/UserAgentPropertyGeneratorPredicate.java new file mode 100644 index 0000000..66cb73d --- /dev/null +++ b/user/src/com/google/gwt/user/rebind/UserAgentPropertyGeneratorPredicate.java
@@ -0,0 +1,72 @@ +/* + * Copyright 2011 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.user.rebind; + +/** + * Represents a predicate expressed in Javascript + * returns the given user agent if predicate evaluates + * to true. + */ +public class UserAgentPropertyGeneratorPredicate { + + private final StringSourceWriter predicateWriter = new StringSourceWriter(); + private String userAgent; + private String returnValue; + + public UserAgentPropertyGeneratorPredicate(String userAgent) { + assert userAgent != null; + assert userAgent.length() > 0; + this.userAgent = userAgent; + } + + public UserAgentPropertyGeneratorPredicate getPredicateBlock() { + return this; + } + + public String getReturnValue() { + return returnValue; + } + + public String getUserAgent() { + return userAgent; + } + + public UserAgentPropertyGeneratorPredicate indent() { + predicateWriter.indent(); + return this; + } + + public UserAgentPropertyGeneratorPredicate outdent() { + predicateWriter.outdent(); + return this; + } + + public UserAgentPropertyGeneratorPredicate println(String s) { + predicateWriter.println(s); + return this; + } + + public UserAgentPropertyGeneratorPredicate returns(String s) { + returnValue = s; + return this; + } + + @Override + public String toString() { + return predicateWriter.toString(); + } +}