Allow Editor framework to handle primitive types.
Issue 5864.
Patch by: bobv
Review by: rjrjr

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9607 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 0ae18b1..378cb4f 100644
--- a/user/src/com/google/gwt/editor/rebind/model/EditorModel.java
+++ b/user/src/com/google/gwt/editor/rebind/model/EditorModel.java
@@ -180,6 +180,8 @@
 
   private final TreeLogger logger;
 
+  private final TypeOracle oracle;
+
   private final EditorModel parentModel;
 
   private boolean poisoned;
@@ -212,7 +214,7 @@
       die(mustExtendMessage(driverType));
     }
 
-    TypeOracle oracle = intf.getOracle();
+    oracle = intf.getOracle();
     editorIntf = oracle.findType(Editor.class.getName()).isGenericType();
     assert editorIntf != null : "No Editor type";
     isEditorIntf = oracle.findType(IsEditor.class.getName()).isGenericType();
@@ -250,6 +252,7 @@
     this.editorSoFar = subEditor;
     this.isEditorIntf = parent.isEditorIntf;
     this.leafValueEditorIntf = parent.leafValueEditorIntf;
+    this.oracle = parent.oracle;
     this.parentModel = parent;
     this.proxyType = proxyType;
     this.typeData = parent.typeData;
@@ -467,6 +470,7 @@
       if (parts[i].length() == 0) {
         continue;
       }
+      boolean lastPart = i == j - 1;
       String getterName = camelCase("get", parts[i]);
 
       for (JClassType search : lookingAt.getFlattenedSupertypeHierarchy()) {
@@ -475,12 +479,19 @@
           for (JMethod maybeSetter : search.getOverloads(camelCase("set",
               parts[i]))) {
             if (maybeSetter.getReturnType().equals(JPrimitiveType.VOID)
-                && maybeSetter.getParameters().length == 1
-                && maybeSetter.getParameters()[0].getType().isClassOrInterface() != null
-                && maybeSetter.getParameters()[0].getType().isClassOrInterface().isAssignableFrom(
-                    propertyType)) {
-              setterName = maybeSetter.getName();
-              break;
+                && maybeSetter.getParameters().length == 1) {
+              JType setterParamType = maybeSetter.getParameters()[0].getType();
+              // Handle the case of setFoo(int) vs. Editor<Integer>
+              if (setterParamType.isPrimitive() != null) {
+                // Replace the int with Integer
+                setterParamType = oracle.findType(setterParamType.isPrimitive().getQualifiedBoxedSourceName());
+              }
+              boolean matches = setterParamType.isClassOrInterface().isAssignableFrom(
+                  propertyType);
+              if (matches) {
+                setterName = maybeSetter.getName();
+                break;
+              }
             }
           }
         }
@@ -489,7 +500,7 @@
         if (getter != null) {
           JType returnType = getter.getReturnType();
           lookingAt = returnType.isClassOrInterface();
-          if (lookingAt == null) {
+          if (!lastPart && lookingAt == null) {
             poison(foundPrimitiveMessage(returnType,
                 interstitialGetters.toString(), path));
             return;
diff --git a/user/test/com/google/gwt/editor/client/Person.java b/user/test/com/google/gwt/editor/client/Person.java
index e39c188..a77283b 100644
--- a/user/test/com/google/gwt/editor/client/Person.java
+++ b/user/test/com/google/gwt/editor/client/Person.java
@@ -19,14 +19,19 @@
  * Simple data object used by multiple tests.
  */
 public class Person {
-  String name;
   Address address;
   Person manager;
+  String name;
+  long localTime;
 
   public Address getAddress() {
     return address;
   }
 
+  public long getLocalTime() {
+    return localTime;
+  }
+
   public Person getManager() {
     return manager;
   }
@@ -39,6 +44,10 @@
     this.address = address;
   }
 
+  public void setLocalTime(long localTime) {
+    this.localTime = localTime;
+  }
+
   public void setManager(Person manager) {
     this.manager = manager;
   }
diff --git a/user/test/com/google/gwt/editor/client/PersonEditor.java b/user/test/com/google/gwt/editor/client/PersonEditor.java
index 61c8b0c..21a3970 100644
--- a/user/test/com/google/gwt/editor/client/PersonEditor.java
+++ b/user/test/com/google/gwt/editor/client/PersonEditor.java
@@ -22,6 +22,7 @@
  */
 class PersonEditor implements Editor<Person> {
   public AddressEditor addressEditor = new AddressEditor();
+  public SimpleEditor<Long> localTime = SimpleEditor.of(System.currentTimeMillis());
   public SimpleEditor<String> name = SimpleEditor.of(SimpleBeanEditorTest.UNINITIALIZED);
   @Path("manager.name")
   public SimpleEditor<String> managerName = SimpleEditor.of(SimpleBeanEditorTest.UNINITIALIZED);
diff --git a/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java b/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
index 0dec642..b5f121f 100644
--- a/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
+++ b/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
@@ -267,6 +267,8 @@
 
   static final String UNINITIALIZED = "uninitialized";
 
+  long now;
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.editor.Editor";
@@ -277,17 +279,20 @@
     PersonEditor editor = new PersonEditor();
     driver.initialize(editor);
     driver.edit(person);
+    assertEquals(now, editor.localTime.getValue().longValue());
     assertEquals("Alice", editor.name.getValue());
     assertEquals("City", editor.addressEditor.city.getValue());
     assertEquals("Street", editor.addressEditor.street.getValue());
     assertEquals("Bill", editor.managerName.getValue());
 
+    editor.localTime.setValue(now + 1);
     editor.name.setValue("Charles");
     editor.addressEditor.city.setValue("Wootville");
     editor.addressEditor.street.setValue("12345");
     editor.managerName.setValue("David");
 
     driver.flush();
+    assertEquals(now + 1, person.localTime);
     assertEquals("Charles", person.name);
     assertEquals("Wootville", person.address.city);
     assertEquals("12345", person.address.street);
@@ -354,7 +359,7 @@
     // Use the delegate to toggle the state
     editor.delegate.setDirty(true);
     assertTrue(driver.isDirty());
-    
+
     // Use the delegate to clear the state
     editor.delegate.setDirty(false);
     assertFalse(driver.isDirty());
@@ -370,23 +375,25 @@
 
   public void testDirtyWithOptionalEditor() {
     AddressEditor addressEditor = new AddressEditor();
-    PersonEditorWithOptionalAddressEditor editor = new PersonEditorWithOptionalAddressEditor(addressEditor);
+    PersonEditorWithOptionalAddressEditor editor = new PersonEditorWithOptionalAddressEditor(
+        addressEditor);
     PersonEditorWithOptionalAddressDriver driver = GWT.create(PersonEditorWithOptionalAddressDriver.class);
     driver.initialize(editor);
     driver.edit(person);
-    
+
     // Freshly-initialized should not be dirty
     assertFalse(driver.isDirty());
-    
+
     // Change the instance being edited
     Address a = new Address();
     editor.address.setValue(a);
     assertTrue(driver.isDirty());
-    
+
     // Check restoration works
     editor.address.setValue(personAddress);
     assertFalse(driver.isDirty());
   }
+
   /**
    * Test the use of the IsEditor interface that allows a view object to
    * encapsulate its Editor.
@@ -677,6 +684,7 @@
     person.address = personAddress;
     person.name = "Alice";
     person.manager = manager;
+    person.localTime = now = System.currentTimeMillis();
   }
 
   private <T extends Editor<Person>> void testLeafAddressEditor(
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 e6d3eb0..ad3afff 100644
--- a/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
+++ b/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.javac.CompilationState;
 import com.google.gwt.dev.javac.CompilationStateBuilder;
@@ -185,7 +186,7 @@
         types.findType("t.CompositeEditorDriver"), rfedType);
 
     EditorData[] data = m.getEditorData();
-    assertEquals(6, data.length);
+    assertEquals(7, data.length);
 
     String[] paths = new String[data.length];
     String[] expressions = new String[data.length];
@@ -194,17 +195,19 @@
       expressions[i] = data[i].getExpression();
     }
     assertEquals(Arrays.asList("address", "address.city", "address.street",
-        "person", "person.name", "person.readonly"), Arrays.asList(paths));
+        "person", "person.lastModified", "person.name", "person.readonly"),
+        Arrays.asList(paths));
     // address is a property, person is a method in CompositeEditor
     assertEquals(Arrays.asList("address", "address.city", "address.street",
-        "person()", "person().name", "person().readonly"),
-        Arrays.asList(expressions));
+        "person()", "person().lastModified", "person().name",
+        "person().readonly"), Arrays.asList(expressions));
     assertTrue(data[0].isDelegateRequired());
     assertFalse(data[0].isLeafValueEditor() || data[0].isValueAwareEditor());
     assertTrue(data[3].isDelegateRequired());
     assertFalse(data[3].isLeafValueEditor() || data[3].isValueAwareEditor());
-    checkPersonName(data[4]);
-    checkPersonReadonly(data[5]);
+    checkPersonLastModified(data[4]);
+    checkPersonName(data[5]);
+    checkPersonReadonly(data[6]);
   }
 
   public void testCyclicDriver() {
@@ -254,12 +257,14 @@
     EditorModel m = new EditorModel(logger,
         types.findType("t.PersonEditorDriver"), rfedType);
     EditorData[] fields = m.getEditorData();
-    assertEquals(2, fields.length);
+    assertEquals(3, fields.length);
 
+    // lastModified
+    checkPersonLastModified(fields[0]);
     // name
-    checkPersonName(fields[0]);
+    checkPersonName(fields[1]);
     // readonly
-    checkPersonReadonly(fields[1]);
+    checkPersonReadonly(fields[2]);
   }
 
   public void testFlatData() throws UnableToCompleteException {
@@ -275,9 +280,10 @@
     assertEquals("person", composite[1].getPropertyName());
 
     EditorData[] person = m.getEditorData(types.findType("t.PersonEditor"));
-    assertEquals(2, person.length);
-    assertEquals("name", person[0].getPropertyName());
-    assertEquals("readonly", person[1].getPropertyName());
+    assertEquals(3, person.length);
+    assertEquals("lastModified", person[0].getPropertyName());
+    assertEquals("name", person[1].getPropertyName());
+    assertEquals("readonly", person[2].getPropertyName());
 
     EditorData[] address = m.getEditorData(types.findType("t.AddressEditor"));
     assertEquals("city", address[0].getPropertyName());
@@ -401,6 +407,9 @@
     builder.expectError(
         EditorModel.tooManyInterfacesMessage(types.findType("t.TooManyInterfacesEditorDriver")),
         null);
+    builder.expectError(EditorModel.foundPrimitiveMessage(JPrimitiveType.LONG,
+        "", "lastModified.foo"), null);
+    builder.expectError(EditorModel.poisonedMessage(), null);
     UnitTestTreeLogger testLogger = builder.createLogger();
 
     try {
@@ -419,6 +428,13 @@
       fail("Should have thrown exception");
     } catch (UnableToCompleteException expected) {
     }
+    try {
+      new EditorModel(testLogger,
+          types.findType("t.PersonEditorWithBadPrimitiveAccessDriver"),
+          rfedType);
+      fail("Should have thrown exception");
+    } catch (UnableToCompleteException expected) {
+    }
     testLogger.assertCorrectLogEntries();
   }
 
@@ -447,7 +463,18 @@
     EditorModel m = new EditorModel(logger,
         types.findType("t.PersonEditorWithAliasedSubEditorsDriver"), rfedType);
     EditorData[] fields = m.getEditorData();
-    assertEquals(6, fields.length);
+    assertEquals(8, fields.length);
+  }
+
+  private void checkPersonLastModified(EditorData editorField) {
+    assertNotNull(editorField);
+    assertEquals(types.findType(SimpleEditor.class.getName()),
+        editorField.getEditorType().isParameterized().getBaseType());
+    assertTrue(editorField.isLeafValueEditor());
+    assertFalse(editorField.isDelegateRequired());
+    assertFalse(editorField.isValueAwareEditor());
+    assertEquals(".getLastModified()", editorField.getGetterExpression());
+    assertEquals("setLastModified", editorField.getSetterName());
   }
 
   private void checkPersonName(EditorData editorField) {
@@ -639,8 +666,10 @@
         code.append("interface PersonProxy extends EntityProxy {\n");
         code.append("AddressProxy getAddress();\n");
         code.append("String getName();\n");
-        code.append("void setName(String name);\n");
+        code.append("long getLastModified();\n");
         code.append("String getReadonly();\n");
+        code.append("void setName(String name);\n");
+        code.append("void setLastModified(long value);\n");
         code.append("}");
         return code;
       }
@@ -652,6 +681,7 @@
         code.append("import " + Editor.class.getName() + ";\n");
         code.append("import " + SimpleEditor.class.getName() + ";\n");
         code.append("class PersonEditor implements Editor<PersonProxy> {\n");
+        code.append("SimpleEditor<Long> lastModified;\n");
         code.append("public SimpleEditor<String> name;\n");
         code.append("SimpleEditor<String> readonly;\n");
         code.append("public static SimpleEditor ignoredStatic;\n");
@@ -685,6 +715,30 @@
         code.append("}");
         return code;
       }
+    }, new MockJavaResource("t.PersonEditorWithBadPrimitiveAccess") {
+      @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 PersonEditorWithBadPrimitiveAccess implements Editor<PersonProxy> {\n");
+        code.append("@Path(\"lastModified.foo\") SimpleEditor<String> bad;\n");
+        code.append("}");
+        return code;
+      }
+    }, new MockJavaResource("t.PersonEditorWithBadPrimitiveAccessDriver") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuilder code = new StringBuilder();
+        code.append("package t;\n");
+        code.append("import " + RequestFactoryEditorDriver.class.getName()
+            + ";\n");
+        code.append("interface PersonEditorWithBadPrimitiveAccessDriver extends"
+            + " RequestFactoryEditorDriver<PersonProxy, t.PersonEditorWithBadPrimitiveAccess> {\n");
+        code.append("}");
+        return code;
+      }
     }, new MockJavaResource("t.PersonEditorUsingMethods") {
       @Override
       protected CharSequence getContent() {