Resolves ROO-1508 by requiring CompositeEditors to return a canonical component Editor instance for path traversal.
Patch by: bobv
Review by: rjrjr

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8965 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/editor/client/CompositeEditor.java b/user/src/com/google/gwt/editor/client/CompositeEditor.java
index 11569de..95d48a6 100644
--- a/user/src/com/google/gwt/editor/client/CompositeEditor.java
+++ b/user/src/com/google/gwt/editor/client/CompositeEditor.java
@@ -82,6 +82,12 @@
   }
 
   /**
+   * Returns an canonical sub-editor instance that will be used by the driver
+   * for computing all edited paths.
+   */
+  E createEditorForTraversal();
+
+  /**
    * Used to implement {@link EditorDelegate#getPath()}.
    */
   String getPathElement(E subEditor);
diff --git a/user/src/com/google/gwt/editor/client/adapters/ListEditor.java b/user/src/com/google/gwt/editor/client/adapters/ListEditor.java
index 314be63..988aaa2 100644
--- a/user/src/com/google/gwt/editor/client/adapters/ListEditor.java
+++ b/user/src/com/google/gwt/editor/client/adapters/ListEditor.java
@@ -52,6 +52,12 @@
     this.editorSource = source;
   }
 
+  public E createEditorForTraversal() {
+    E toReturn = editorSource.create(0);
+    editorSource.dispose(toReturn);
+    return toReturn;
+  }
+
   public void flush() {
     list.flush();
   }
diff --git a/user/src/com/google/gwt/editor/client/adapters/OptionalFieldEditor.java b/user/src/com/google/gwt/editor/client/adapters/OptionalFieldEditor.java
index cac0a07..0373e16 100644
--- a/user/src/com/google/gwt/editor/client/adapters/OptionalFieldEditor.java
+++ b/user/src/com/google/gwt/editor/client/adapters/OptionalFieldEditor.java
@@ -63,6 +63,10 @@
     this.subEditor = subEditor;
   }
 
+  public E createEditorForTraversal() {
+    return subEditor;
+  }
+
   public void flush() {
     currentValue = chain.getValue(subEditor);
   }
diff --git a/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java b/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java
index 18f9f62..a33e03b 100644
--- a/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java
+++ b/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java
@@ -194,8 +194,8 @@
       for (EditorData d : data) {
         String mutableObjectExpression;
         if (d.getBeanOwnerExpression().length() > 0) {
-          mutableObjectExpression = mutableObjectExpression(d, String.format(
-              "(getObject()%s)", d.getBeanOwnerExpression()));
+          mutableObjectExpression = mutableObjectExpression(d,
+              String.format("(getObject()%s)", d.getBeanOwnerExpression()));
         } else {
           mutableObjectExpression = "getObject()";
         }
@@ -294,7 +294,8 @@
           editor.getQualifiedSourceName(), List.class.getName());
       sw.indent();
       for (EditorData d : data) {
-        if (d.isDelegateRequired() || d.isDeclaredPathNested()) {
+        if (d.isDelegateRequired() || d.isDeclaredPathNested()
+            || d.isCompositeEditor()) {
           // if (editor.subEditor != null) {
           sw.println("if (editor.%s != null) {", d.getSimpleExpression());
           sw.indent();
@@ -308,6 +309,15 @@
             sw.println("%s.traverseEditor(editor.%s, localPath, paths);",
                 getEditorDelegate(d), d.getSimpleExpression());
           }
+          if (d.isCompositeEditor()) {
+            /*
+             * composedDelegate.traverseEditor(editor.subEditor.
+             * createEditorForTraversal(), localPath, paths);
+             */
+            sw.println(
+                "%s.traverseEditor(editor.%s.createEditorForTraversal(), localPath, paths);",
+                getEditorDelegate(d.getComposedData()), d.getSimpleExpression());
+          }
           sw.outdent();
           sw.println("}");
         }
@@ -330,7 +340,7 @@
    * @param context
    * @param model
    * @param sw
-   *
+   * 
    * @throws UnableToCompleteException
    */
   protected void writeAdditionalContent(TreeLogger logger,
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 3dab1b4..8577228 100644
--- a/user/src/com/google/gwt/editor/rebind/model/EditorModel.java
+++ b/user/src/com/google/gwt/editor/rebind/model/EditorModel.java
@@ -60,7 +60,7 @@
         if (compositeEditorIntf.equals(parameterized.getBaseType())) {
           JClassType[] typeArgs = parameterized.getTypeArgs();
           assert typeArgs.length == 3;
-          return new JClassType[] {typeArgs[1], typeArgs[2]};
+          return new JClassType[]{typeArgs[1], typeArgs[2]};
         }
       }
     }
@@ -83,8 +83,8 @@
     if (parameterization != null) {
       return parameterization[0];
     }
-    logger.log(TreeLogger.ERROR, noEditorParameterizationMessage(editorIntf,
-        editorType));
+    logger.log(TreeLogger.ERROR,
+        noEditorParameterizationMessage(editorIntf, editorType));
     throw new UnableToCompleteException();
   }
 
@@ -102,8 +102,8 @@
     if (parameterization != null) {
       return parameterization[0];
     }
-    logger.log(TreeLogger.ERROR, noEditorParameterizationMessage(editorIntf,
-        editorType));
+    logger.log(TreeLogger.ERROR,
+        noEditorParameterizationMessage(editorIntf, editorType));
     throw new UnableToCompleteException();
   }
 
@@ -326,6 +326,10 @@
               && isEditorIntf.isAssignableFrom(editorType)) {
             // Ignore IsEditor.asEditor()
             continue;
+          } else if (access.getPath().equals("createEditorForTraversal")
+              && compositeEditorIntf.isAssignableFrom(editorType)) {
+            // Ignore CompositeEditor.createEditorForTraversal();
+            continue;
           }
           List<EditorData> data = createEditorData(access);
           accumulateEditorData(data, flatData, toReturn);
@@ -376,8 +380,8 @@
 
     // Are we looking at a view that implements IsEditor?
     if (access.isEditor()) {
-      EditorAccess subAccess = EditorAccess.via(access, calculateIsEditedType(
-          subLogger, access.getEditorType()));
+      EditorAccess subAccess = EditorAccess.via(access,
+          calculateIsEditedType(subLogger, access.getEditorType()));
       toReturn = createEditorData(subAccess);
 
       // If an object only implements IsEditor, return now
diff --git a/user/test/com/google/gwt/requestfactory/client/ui/EditorTest.java b/user/test/com/google/gwt/requestfactory/client/ui/EditorTest.java
index 6b1cc48..d9b71ae 100644
--- a/user/test/com/google/gwt/requestfactory/client/ui/EditorTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/ui/EditorTest.java
@@ -23,6 +23,8 @@
 import com.google.gwt.editor.client.EditorError;
 import com.google.gwt.editor.client.HasEditorDelegate;
 import com.google.gwt.editor.client.HasEditorErrors;
+import com.google.gwt.editor.client.adapters.EditorSource;
+import com.google.gwt.editor.client.adapters.ListEditor;
 import com.google.gwt.editor.client.adapters.SimpleEditor;
 import com.google.gwt.requestfactory.client.RequestFactoryEditorDriver;
 import com.google.gwt.requestfactory.client.RequestFactoryTestBase;
@@ -55,6 +57,10 @@
       RequestFactoryEditorDriver<SimpleFooProxy, SimpleFooEditor> {
   }
 
+  static class SimpleFooBarOnlyEditor implements Editor<SimpleFooProxy> {
+    SimpleBarEditor barField = new SimpleBarEditor();
+  }
+
   static class SimpleFooEditor implements HasEditorErrors<SimpleFooProxy> {
     /**
      * Test field-based access.
@@ -67,6 +73,13 @@
     @Path("barField.userName")
     final SimpleEditor<String> barName = SimpleEditor.of();
 
+    final ListEditor<SimpleFooProxy, SimpleFooBarOnlyEditor> selfOneToManyField = ListEditor.of(new EditorSource<SimpleFooBarOnlyEditor>() {
+      @Override
+      public SimpleFooBarOnlyEditor create(int index) {
+        return new SimpleFooBarOnlyEditor();
+      }
+    });
+
     private final SimpleBarEditor barEditor = new SimpleBarEditor();
 
     List<EditorError> errors;
@@ -108,32 +121,32 @@
     driver.initialize(req, editor);
 
     req.simpleFooRequest().findSimpleFooById(1L).with(driver.getPaths()).fire(
-    new Receiver<SimpleFooProxy>() {
-      @Override
-      public void onSuccess(SimpleFooProxy response) {
-    
-        SimpleFooRequest context = req.simpleFooRequest();
-        driver.edit(response, context);
-        context.persistAndReturnSelf().using(response).with(
-            driver.getPaths()).to(new Receiver<SimpleFooProxy>() {
+        new Receiver<SimpleFooProxy>() {
           @Override
           public void onSuccess(SimpleFooProxy response) {
-            assertEquals("EditorFooTest", response.getUserName());
-            assertEquals("EditorBarTest",
-                response.getBarField().getUserName());
-            finishTestAndReset();
+
+            SimpleFooRequest context = req.simpleFooRequest();
+            driver.edit(response, context);
+            context.persistAndReturnSelf().using(response).with(
+                driver.getPaths()).to(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                assertEquals("EditorFooTest", response.getUserName());
+                assertEquals("EditorBarTest",
+                    response.getBarField().getUserName());
+                finishTestAndReset();
+              }
+            });
+            assertEquals("GWT", editor.userName.getValue());
+            assertEquals("FOO", editor.barEditor().userName.getValue());
+            assertEquals("FOO", editor.barName.getValue());
+            editor.userName.setValue("EditorFooTest");
+            // When there are duplicate paths, last declared editor wins
+            editor.barEditor().userName.setValue("EditorBarTest");
+            editor.barName.setValue("ignored");
+            driver.flush().fire();
           }
         });
-        assertEquals("GWT", editor.userName.getValue());
-        assertEquals("FOO", editor.barEditor().userName.getValue());
-        assertEquals("FOO", editor.barName.getValue());
-        editor.userName.setValue("EditorFooTest");
-        // When there are duplicate paths, last declared editor wins
-        editor.barEditor().userName.setValue("EditorBarTest");
-        editor.barName.setValue("ignored");
-        driver.flush().fire();
-      }
-    });
   }
 
   public void testNoSubscription() {
@@ -141,10 +154,10 @@
 
     final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
     driver.initialize(req, editor);
-    
+
     /*
      * Confirm that it's always safe to call subscribe. The editor's delegate
-     * isn't set until edit is called, so edit nothing. 
+     * isn't set until edit is called, so edit nothing.
      */
     driver.edit(null, null);
     assertNull(editor.delegate.subscribe());
@@ -157,10 +170,11 @@
     final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
     driver.initialize(req, editor);
 
-    assertEquals(Arrays.asList("barField.userName", "barField"),
-        Arrays.asList(driver.getPaths()));
+    String[] paths = driver.getPaths();
+    assertEquals(Arrays.asList("barField.userName", "selfOneToManyField", 
+        "selfOneToManyField.barField", "barField"), Arrays.asList(paths));
 
-    req.simpleFooRequest().findSimpleFooById(1L).with(driver.getPaths()).fire(
+    req.simpleFooRequest().findSimpleFooById(1L).with(paths).fire(
         new Receiver<SimpleFooProxy>() {
           @Override
           public void onSuccess(SimpleFooProxy response) {
@@ -202,7 +216,7 @@
           }
         });
   }
-  
+
   public void testViolations() {
     delayTestFinish(TEST_TIMEOUT);
     final SimpleFooEditor editor = new SimpleFooEditor();