It's not reasonable at this time to enforce the rule that a
configuration property be defined before it is set.  This patch
loosens the restriction so that multiple compatible definitions
(including definitions implied by <set-configuration-property>) can
co-exist.  A detailed warning is logged indicating the definition
sites, and the fact that the behavior may eventually be disallowed.

Review by: spoon



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5334 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
index 5cc8bb9..a9bcbd6 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
@@ -238,8 +238,8 @@
     Reader r = null;
     try {
       r = Util.createReader(logger, moduleURL);
-      ModuleDefSchema schema = new ModuleDefSchema(logger, this, moduleURL,
-          moduleDir, moduleDef);
+      ModuleDefSchema schema = new ModuleDefSchema(logger, this, moduleName,
+          moduleURL, moduleDir, moduleDef);
       ReflectiveParser.parse(logger, schema, r);
     } catch (Throwable e) {
       logger.log(TreeLogger.ERROR, "Unexpected error while processing XML", e);
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 63c40da..f41cc8b 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -32,6 +32,8 @@
 import com.google.gwt.dev.util.xml.AttributeConverter;
 import com.google.gwt.dev.util.xml.Schema;
 
+import com.sun.tools.example.debug.expr.Token;
+
 import java.io.IOException;
 import java.io.StringReader;
 import java.net.URL;
@@ -46,6 +48,7 @@
  * Configures a module definition object using XML.
  */
 public class ModuleDefSchema extends Schema {
+  
   private final class BodySchema extends Schema {
 
     protected final String __add_linker_1_name = null;
@@ -139,7 +142,7 @@
       moduleDef.addLinker(name.name);
       return null;
     }
-
+    
     protected Schema __clear_configuration_property_begin(PropertyName name)
         throws UnableToCompleteException {
       // Don't allow configuration properties with the same name as a
@@ -177,11 +180,47 @@
         // Create the property
         existingProperty = moduleDef.getProperties().createConfiguration(
             name.token, isMultiValued);
+        if (!propertyDefinitions.containsKey(name.token)) {
+          propertyDefinitions.put(name.token, moduleName);
+        }
+      } else if (existingProperty instanceof ConfigurationProperty) {
+        // Allow redefinition only if the 'is-multi-valued' setting is identical
+        // The previous definition may have been explicit, via
+        // <define-configuration-property>, or implicit, via
+        // <set-configuration-property>.  In the latter case, the
+        // value of the 'is-multi-valued' attribute was taken as false.
+        String originalDefinition = propertyDefinitions.get(name.token);
+        if (((ConfigurationProperty) existingProperty).allowsMultipleValues()
+            != isMultiValued) {
+          if (originalDefinition != null) {
+            logger.log(TreeLogger.ERROR, "The configuration property named "
+                + name.token
+                + " is already defined with a different 'is-multi-valued' setting");
+          } else {
+            logger.log(TreeLogger.ERROR, "The configuration property named "
+                + name.token
+                + " is already defined implicitly by 'set-configuration-property'"
+                + " in " + propertySettings.get(name.token)
+                + " with 'is-multi-valued' set to 'false'");
+          }
+          throw new UnableToCompleteException();
+        } else {
+          if (originalDefinition != null) {
+            logger.log(TreeLogger.WARN,
+                "Ignoring identical definition of the configuration property named "
+                + name.token + " (originally defined in "
+                + originalDefinition
+                + ", redefined in " + moduleName + ")");
+          } else {
+            logger.log(TreeLogger.WARN,
+                "Definition of already set configuration property named "
+                + name.token + " in " + moduleName
+                + " (set in " + propertySettings.get(name.token)
+                + ").  This may be disallowed in the future.");
+          }
+        }
       } else {
-        if (existingProperty instanceof ConfigurationProperty) {
-          logger.log(TreeLogger.ERROR, "The configuration property named "
-              + name.token + " may not be redefined.");
-        } else if (existingProperty instanceof BindingProperty) {
+        if (existingProperty instanceof BindingProperty) {
           logger.log(TreeLogger.ERROR, "The property " + name.token
               + " is already defined as a deferred-binding property");
         } else {
@@ -411,6 +450,16 @@
         // compatibility but don't allow multiple values.
         existingProperty = moduleDef.getProperties().createConfiguration(
             name.token, false);
+        if (!propertySettings.containsKey(name.token)) {
+          propertySettings.put(name.token, moduleName);
+        }
+
+        logger.log(TreeLogger.WARN,
+            "Setting configuration property named "
+            + name.token + " in "
+            + moduleName
+            + " that has not been previously defined."
+            + "  This may be disallowed in the future.");
       } else if (!(existingProperty instanceof ConfigurationProperty)) {
         if (existingProperty instanceof BindingProperty) {
           logger.log(TreeLogger.ERROR, "The property " + name.token
@@ -1001,6 +1050,20 @@
     }
   }
 
+  /**
+   * Map of property names to the modules that defined them explicitly using
+   * <define-configuration-property>, used to generate warning messages.
+   */
+  private static final HashMap<String,String> propertyDefinitions
+      = new HashMap<String,String>();
+ 
+  /**
+   * Map of property names to the modules that defined them implicitly using
+   * <set-configuration-property>, used to generate warning messages.
+   */
+  private static final HashMap<String,String> propertySettings
+      = new HashMap<String,String>();
+
   private static final Map<String, Object> singletonsByName = new HashMap<String, Object>();
 
   private static void addPrefix(String[] strings, String prefix) {
@@ -1016,9 +1079,8 @@
   private static boolean toPrimitiveBoolean(String s) {
     return "yes".equalsIgnoreCase(s) || "true".equalsIgnoreCase(s);
   }
-
+  
   protected final String __module_1_rename_to = "";
-
   private final PropertyAttrCvt bindingPropAttrCvt = new PropertyAttrCvt(
       BindingProperty.class);
   private final BodySchema bodySchema;
@@ -1034,6 +1096,7 @@
   private final ModuleDefLoader loader;
   private final TreeLogger logger;
   private final ModuleDef moduleDef;
+  private final String moduleName;
   private final String modulePackageAsPath;
   private final URL moduleURL;
   private final NullableNameAttrCvt nullableNameAttrCvt = new NullableNameAttrCvt();
@@ -1042,9 +1105,10 @@
   private final PropertyValueAttrCvt propValueAttrCvt = new PropertyValueAttrCvt();
 
   public ModuleDefSchema(TreeLogger logger, ModuleDefLoader loader,
-      URL moduleURL, String modulePackageAsPath, ModuleDef toConfigure) {
+      String moduleName, URL moduleURL, String modulePackageAsPath, ModuleDef toConfigure) {
     this.logger = logger;
     this.loader = loader;
+    this.moduleName = moduleName;
     this.moduleURL = moduleURL;
     this.modulePackageAsPath = modulePackageAsPath;
     assert (modulePackageAsPath.endsWith("/") || modulePackageAsPath.equals(""));