Allow @Editor.Path("") as a valid annotation to indicate that a sub-editor should be aliased to the object being edited by its parent object.
Patch by: bobv
Review by: rjrjr

Review at http://gwt-code-reviews.appspot.com/1205801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9401 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java b/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java
index 911d743..ee85f62 100644
--- a/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java
+++ b/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java
@@ -187,6 +187,9 @@
   public abstract HandlerRegistration subscribe();
 
   protected String appendPath(String path) {
+    if (path.length() == 0) {
+      return this.path;
+    }
     return appendPath(this.path, path);
   }
 
diff --git a/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java b/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java
index a33e03b..56579a6 100644
--- a/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java
+++ b/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java
@@ -174,9 +174,9 @@
               delegateFields.get(d));
         } else if (d.isLeafValueEditor()) {
           // if (can().access().without().npe()) { editor.subEditor.setValue() }
-          sw.println("if (%4$s) editor.%1$s.setValue(getObject()%2$s.%3$s());",
+          sw.println("if (%4$s) editor.%1$s.setValue(getObject()%2$s%3$s);",
               d.getSimpleExpression(), d.getBeanOwnerExpression(),
-              d.getGetterName(), d.getBeanOwnerGuard("getObject()"));
+              d.getGetterExpression(), d.getBeanOwnerGuard("getObject()"));
           // simpleEditor.put("some.path", editor.simpleEditor());
           sw.println("simpleEditors.put(\"%s\", editor.%s);",
               d.getDeclaredPath(), d.getSimpleExpression());
@@ -259,8 +259,8 @@
           // if (can().access().without().npe()) {
           sw.println("if (%s) {", d.getBeanOwnerGuard("getObject()"));
           // subDelegate.refresh(getObject().getFoo().getBar());
-          sw.indentln("%s.refresh(getObject()%s.%s());", delegateFields.get(d),
-              d.getBeanOwnerExpression(), d.getGetterName());
+          sw.indentln("%s.refresh(getObject()%s%s);", delegateFields.get(d),
+              d.getBeanOwnerExpression(), d.getGetterExpression());
           // } else { subDelegate.refresh(null); }
           sw.println("} else { %s.refresh(null); }", delegateFields.get(d));
           sw.outdent();
@@ -270,9 +270,9 @@
           sw.println("if (editor.%s != null) {", d.getSimpleExpression());
           sw.indent();
           // if (can().access().without().npe()) { editor.subEditor.setValue() }
-          sw.println("if (%4$s) editor.%1$s.setValue(getObject()%2$s.%3$s());",
+          sw.println("if (%4$s) editor.%1$s.setValue(getObject()%2$s%3$s);",
               d.getSimpleExpression(), d.getBeanOwnerExpression(),
-              d.getGetterName(), d.getBeanOwnerGuard("getObject()"));
+              d.getGetterExpression(), d.getBeanOwnerGuard("getObject()"));
           // else { editor.subEditor.setValue(null); }
           sw.println("else { editor.%s.setValue(null); }",
               d.getSimpleExpression());
diff --git a/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java b/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java
index 1a2200b..33465d9 100644
--- a/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java
+++ b/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java
@@ -55,9 +55,9 @@
       Map<EditorData, String> delegateFields) {
     // fooDelegate.initialize(appendPath("foo"), getObject().getFoo(),
     // editor.fooEditor);
-    sw.println("%s.initialize(appendPath(\"%s\"), getObject()%s.%s(),"
+    sw.println("%s.initialize(appendPath(\"%s\"), getObject()%s%s,"
         + " editor.%s, delegateMap);", delegateFields.get(d),
-        d.getPropertyName(), d.getBeanOwnerExpression(), d.getGetterName(),
+        d.getPropertyName(), d.getBeanOwnerExpression(), d.getGetterExpression(),
         d.getSimpleExpression());
   }
 }
diff --git a/user/src/com/google/gwt/editor/rebind/model/EditorAccess.java b/user/src/com/google/gwt/editor/rebind/model/EditorAccess.java
index 91aca95..9260db2 100644
--- a/user/src/com/google/gwt/editor/rebind/model/EditorAccess.java
+++ b/user/src/com/google/gwt/editor/rebind/model/EditorAccess.java
@@ -102,8 +102,8 @@
   }
 
   /**
-   * Returns <code> true if the editor accessed by this EditorAccess
-   * implements the IsEditor interface.
+   * Returns {@code true} if the editor accessed by this EditorAccess implements
+   * the IsEditor interface.
    */
   public boolean isEditor() {
     return isEditor;
diff --git a/user/src/com/google/gwt/editor/rebind/model/EditorData.java b/user/src/com/google/gwt/editor/rebind/model/EditorData.java
index 29dcb44..0bda120 100644
--- a/user/src/com/google/gwt/editor/rebind/model/EditorData.java
+++ b/user/src/com/google/gwt/editor/rebind/model/EditorData.java
@@ -85,8 +85,7 @@
       }
       try {
         data.editorExpression = (parent == null ? ""
-            : (parent.getExpression() + "."))
-            + access.getExpresson();
+            : (parent.getExpression() + ".")) + access.getExpresson();
         data.path = (parent == null ? "" : (parent.getPath() + "."))
             + access.getPath();
 
@@ -103,8 +102,8 @@
       }
     }
 
-    public Builder getterName(String value) {
-      data.getterName = value;
+    public Builder getterExpression(String value) {
+      data.getterExpression = value;
       return this;
     }
 
@@ -131,7 +130,7 @@
   private JClassType editedType;
   private JClassType editorType;
   private String editorExpression;
-  private String getterName;
+  private String getterExpression;
   private boolean isLeaf;
   private boolean isCompositeEditor;
   private boolean isDelegateRequired;
@@ -179,8 +178,12 @@
     return editorExpression;
   }
 
-  public String getGetterName() {
-    return getterName;
+  /**
+   * Returns an expression, relative to an instance of the parent object being
+   * edited, to retrieve the value to pass into the editor.
+   */
+  public String getGetterExpression() {
+    return getterExpression;
   }
 
   /**
diff --git a/user/src/com/google/gwt/editor/rebind/model/EditorModel.java b/user/src/com/google/gwt/editor/rebind/model/EditorModel.java
index 8577228..e11df64 100644
--- a/user/src/com/google/gwt/editor/rebind/model/EditorModel.java
+++ b/user/src/com/google/gwt/editor/rebind/model/EditorModel.java
@@ -455,6 +455,9 @@
 
     JClassType lookingAt = proxyType;
     part : for (int i = 0, j = parts.length; i < j; i++) {
+      if (parts[i].length() == 0) {
+        continue;
+      }
       String getterName = camelCase("get", parts[i]);
 
       for (JClassType search : lookingAt.getFlattenedSupertypeHierarchy()) {
@@ -494,7 +497,7 @@
     }
 
     int idx = interstitialGetters.lastIndexOf(".");
-    builder.beanOwnerExpression(idx == 0 ? "" : interstitialGetters.substring(
+    builder.beanOwnerExpression(idx <= 0 ? "" : interstitialGetters.substring(
         0, idx));
     if (parts.length > 1) {
       // Strip after last && since null is a valid value
@@ -502,8 +505,13 @@
           interstitialGuard.length());
       builder.beanOwnerGuard(interstitialGuard.substring(8));
     }
-    builder.getterName(interstitialGetters.substring(idx + 1,
-        interstitialGetters.length() - 2));
+    if (interstitialGetters.length() > 0) {
+      builder.getterExpression("."
+          + interstitialGetters.substring(idx + 1,
+              interstitialGetters.length() - 2) + "()");
+    } else {
+      builder.getterExpression("");
+    }
     builder.setterName(setterName);
   }
 
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
index e36214f..84e5f78 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
@@ -89,9 +89,9 @@
   protected void writeDelegateInitialization(SourceWriter sw, EditorData d,
       Map<EditorData, String> delegateFields) {
     sw.println("%s.initialize(eventBus, factory, "
-        + "appendPath(\"%s\"), getObject()%s.%s()," + " editor.%s,"
+        + "appendPath(\"%s\"), getObject()%s%s," + " editor.%s,"
         + " delegateMap, request);", delegateFields.get(d),
-        d.getPropertyName(), d.getBeanOwnerExpression(), d.getGetterName(),
-        d.getSimpleExpression());
+        d.getPropertyName(), d.getBeanOwnerExpression(),
+        d.getGetterExpression(), d.getSimpleExpression());
   }
 }
diff --git a/user/test/com/google/gwt/editor/EditorSuite.java b/user/test/com/google/gwt/editor/EditorSuite.java
index e69456e..a4d615c 100644
--- a/user/test/com/google/gwt/editor/EditorSuite.java
+++ b/user/test/com/google/gwt/editor/EditorSuite.java
@@ -15,8 +15,6 @@
  */
 package com.google.gwt.editor;
 
-import com.google.gwt.autobean.client.AutoBeanTest;
-import com.google.gwt.autobean.server.AutoBeanJreTest;
 import com.google.gwt.editor.client.EditorErrorTest;
 import com.google.gwt.editor.client.SimpleBeanEditorTest;
 import com.google.gwt.editor.client.adapters.ListEditorWrapperTest;
@@ -34,8 +32,6 @@
   public static Test suite() {
     GWTTestSuite suite = new GWTTestSuite(
         "Test suite for core Editor functions");
-    suite.addTestSuite(AutoBeanJreTest.class);
-    suite.addTestSuite(AutoBeanTest.class);
     suite.addTestSuite(DelegateMapTest.class);
     suite.addTestSuite(EditorModelTest.class);
     suite.addTestSuite(EditorErrorTest.class);
diff --git a/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java b/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
index 1f15edb..b2e55bd 100644
--- a/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
+++ b/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
@@ -93,6 +93,22 @@
       SimpleBeanEditorDriver<Person, PersonEditorWithAddressEditorView> {
   }
 
+  /**
+   * A test for assigning the object associated with an editor to an immediate
+   * child editors.
+   */
+  class PersonEditorWithAliasedSubEditors implements Editor<Person> {
+    @Path("")
+    PersonEditor e1 = new PersonEditor();
+
+    @Path("")
+    PersonEditor e2 = new PersonEditor();
+  }
+
+  interface PersonEditorWithAliasedSubEditorsDriver extends
+      SimpleBeanEditorDriver<Person, PersonEditorWithAliasedSubEditors> {
+  }
+
   class PersonEditorWithCoAddressEditorView implements Editor<Person> {
     AddressCoEditorView addressEditor = new AddressCoEditorView();
     SimpleEditor<String> name = SimpleEditor.of(UNINITIALIZED);
@@ -265,6 +281,29 @@
     assertEquals("David", person.manager.name);
   }
 
+  public void testAliasedEditors() {
+    PersonEditorWithAliasedSubEditors editor = new PersonEditorWithAliasedSubEditors();
+    PersonEditorWithAliasedSubEditorsDriver driver = GWT.create(PersonEditorWithAliasedSubEditorsDriver.class);
+    driver.initialize(editor);
+    driver.edit(person);
+
+    assertEquals("Alice", editor.e1.name.getValue());
+    assertEquals("Alice", editor.e2.name.getValue());
+
+    /*
+     * Expecting that aliased editors will be editing disjoint sets of
+     * properties, but we want to at least have a predictable behavior if two
+     * editors are assigned to the same property.
+     */
+    editor.e1.name.setValue("Should not see this");
+    driver.flush();
+    assertEquals("Alice", person.getName());
+
+    editor.e2.name.setValue("Should see this");
+    driver.flush();
+    assertEquals("Should see this", person.getName());
+  }
+
   /**
    * Test the use of the IsEditor interface that allows a view object to
    * encapsulate its Editor.
diff --git a/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java b/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
index d7164d4..564b02f 100644
--- a/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
+++ b/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
@@ -198,12 +198,12 @@
     assertFalse(fields[0].isDeclaredPathNested());
     assertEquals("", fields[0].getBeanOwnerExpression());
     assertEquals("true", fields[0].getBeanOwnerGuard("object"));
-    assertEquals("getName", fields[0].getGetterName());
+    assertEquals(".getName()", fields[0].getGetterExpression());
     assertEquals("address.street", fields[1].getPath());
     assertEquals(".getAddress()", fields[1].getBeanOwnerExpression());
     assertEquals("object.getAddress() != null",
         fields[1].getBeanOwnerGuard("object"));
-    assertEquals("getStreet", fields[1].getGetterName());
+    assertEquals(".getStreet()", fields[1].getGetterExpression());
     assertEquals("setStreet", fields[1].getSetterName());
     assertEquals("street", fields[1].getPropertyName());
     assertTrue(fields[1].isDeclaredPathNested());
@@ -404,6 +404,16 @@
     testLogger.assertCorrectLogEntries();
   }
 
+  /**
+   * Verify that {@code @Path("")} is valid.
+   */
+  public void testZeroLengthPath() throws UnableToCompleteException {
+    EditorModel m = new EditorModel(logger,
+        types.findType("t.PersonEditorWithAliasedSubEditorsDriver"), rfedType);
+    EditorData[] fields = m.getEditorData();
+    assertEquals(6, fields.length);
+  }
+
   private void checkPersonName(EditorData editorField) {
     assertNotNull(editorField);
     assertEquals(types.findType(SimpleEditor.class.getName()),
@@ -411,7 +421,7 @@
     assertTrue(editorField.isLeafValueEditor());
     assertFalse(editorField.isDelegateRequired());
     assertFalse(editorField.isValueAwareEditor());
-    assertEquals("getName", editorField.getGetterName());
+    assertEquals(".getName()", editorField.getGetterExpression());
     assertEquals("setName", editorField.getSetterName());
   }
 
@@ -425,7 +435,7 @@
     assertTrue(editorField.isLeafValueEditor());
     assertFalse(editorField.isDelegateRequired());
     assertFalse(editorField.isValueAwareEditor());
-    assertEquals("getReadonly", editorField.getGetterName());
+    assertEquals(".getReadonly()", editorField.getGetterExpression());
     assertNull(editorField.getSetterName());
   }
 
@@ -614,6 +624,31 @@
         code.append("}");
         return code;
       }
+    }, new MockJavaResource("t.PersonEditorWithAliasedSubEditors") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuilder code = new StringBuilder();
+        code.append("package t;\n");
+        code.append("import " + Editor.class.getName() + ";\n");
+        code.append("import " + SimpleEditor.class.getName() + ";\n");
+        code.append("class PersonEditorWithAliasedSubEditors implements Editor<PersonProxy> {\n");
+        code.append("@Path(\"\") PersonEditor e1;\n");
+        code.append("@Path(\"\") PersonEditor e2;\n");
+        code.append("}");
+        return code;
+      }
+    }, new MockJavaResource("t.PersonEditorWithAliasedSubEditorsDriver") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuilder code = new StringBuilder();
+        code.append("package t;\n");
+        code.append("import " + RequestFactoryEditorDriver.class.getName()
+            + ";\n");
+        code.append("interface PersonEditorWithAliasedSubEditorsDriver extends"
+            + " RequestFactoryEditorDriver<PersonProxy, t.PersonEditorWithAliasedSubEditors> {\n");
+        code.append("}");
+        return code;
+      }
     }, new MockJavaResource("t.PersonEditorUsingMethods") {
       @Override
       protected CharSequence getContent() {
@@ -774,7 +809,7 @@
     }};
 
     Set<Resource> toReturn = new HashSet<Resource>(Arrays.asList(javaFiles));
-    toReturn.addAll(Arrays.asList(new Resource[] {
+    toReturn.addAll(Arrays.asList(new Resource[]{
         new RealJavaResource(CompositeEditor.class),
         new RealJavaResource(Editor.class),
         new RealJavaResource(EditorError.class),