Restores various unit tests of the UiBinder code generator.

Review by fabbott


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6305 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/eclipse/user/.classpath b/eclipse/user/.classpath
index 37be02a..1b04495 100644
--- a/eclipse/user/.classpath
+++ b/eclipse/user/.classpath
@@ -24,6 +24,10 @@
 	<classpathentry kind="var" path="GWT_TOOLS/lib/xerces/xerces-2_9_1/xml-apis.jar" />
 	<classpathentry kind="var" path="GWT_TOOLS/lib/w3c/sac/sac-1.3.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/w3c/flute/flute-1.3.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/cglib/cglib-2.2.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/easymock/easymock.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/easymock/easymockclassextension.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/objectweb/asm-3.1.jar"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/gwt-dev-windows"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/user/build.xml b/user/build.xml
index 4db7d74..ab92084 100755
--- a/user/build.xml
+++ b/user/build.xml
@@ -34,6 +34,10 @@
     <pathelement location="${gwt.build}/out/dev/core/bin-test" />
     <pathelement location="test-super" />
     <pathelement location="test_i18n_${gwt.i18n.test.InnerClassChar}" />
+       <pathelement location="${gwt.tools.lib}/cglib/cglib-2.2.jar"/>
+       <pathelement location="${gwt.tools.lib}/easymock/easymock.jar"/>
+       <pathelement location="${gwt.tools.lib}/easymock/easymockclassextension.jar"/>
+       <pathelement location="${gwt.tools.lib}/objectweb/asm-3.1.jar"/>
   </path>
 
   <!-- Platform shouldn't matter here, just picking one -->
@@ -75,6 +79,10 @@
         <pathelement location="${gwt.tools.lib}/tomcat/servlet-api-2.5.jar" />
         <pathelement location="${gwt.tools.lib}/junit/junit-3.8.1.jar" />
         <pathelement location="${gwt.tools.lib}/selenium/selenium-java-client-driver.jar" />
+       <pathelement location="${gwt.tools.lib}/cglib/cglib-2.2.jar"/>
+       <pathelement location="${gwt.tools.lib}/easymock/easymock.jar"/>
+       <pathelement location="${gwt.tools.lib}/easymock/easymockclassextension.jar"/>
+       <pathelement location="${gwt.tools.lib}/objectweb/asm-3.1.jar"/>
         <pathelement location="${gwt.dev.jar}" />
       </classpath>
     </gwt.javac>
diff --git a/user/src/com/google/gwt/uibinder/rebind/MortalLogger.java b/user/src/com/google/gwt/uibinder/rebind/MortalLogger.java
index 80964ad..d0c77d2 100644
--- a/user/src/com/google/gwt/uibinder/rebind/MortalLogger.java
+++ b/user/src/com/google/gwt/uibinder/rebind/MortalLogger.java
@@ -26,7 +26,7 @@
 public class MortalLogger {
   private final TreeLogger logger;
 
-  MortalLogger(TreeLogger logger) {
+  public MortalLogger(TreeLogger logger) {
     this.logger = logger;
   }
 
diff --git a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
new file mode 100644
index 0000000..20b033b
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2009 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.uibinder;
+
+import com.google.gwt.uibinder.rebind.GwtResourceEntityResolverTest;
+import com.google.gwt.uibinder.rebind.HandlerEvaluatorTest;
+import com.google.gwt.uibinder.rebind.TokenatorTest;
+import com.google.gwt.uibinder.rebind.XMLElementTest;
+import com.google.gwt.uibinder.rebind.model.OwnerClassTest;
+import com.google.gwt.uibinder.rebind.model.OwnerFieldClassTest;
+import com.google.gwt.uibinder.rebind.model.OwnerFieldTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Suite of UiBinder tests that require the JRE
+ */
+public class UiBinderJreSuite {
+  public static Test suite() {
+    TestSuite suite = new TestSuite("UiBinder tests that require the JRE");
+
+    // rebind
+    suite.addTestSuite(GwtResourceEntityResolverTest.class);
+    suite.addTestSuite(HandlerEvaluatorTest.class);
+    suite.addTestSuite(TokenatorTest.class);
+    suite.addTestSuite(XMLElementTest.class);
+
+    // model
+    suite.addTestSuite(OwnerClassTest.class);
+    suite.addTestSuite(OwnerFieldClassTest.class);
+    suite.addTestSuite(OwnerFieldTest.class);
+
+    return suite;
+  }
+
+  private UiBinderJreSuite() {
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/HandlerEvaluatorTest.java b/user/test/com/google/gwt/uibinder/rebind/HandlerEvaluatorTest.java
new file mode 100644
index 0000000..f8680be
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/HandlerEvaluatorTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2009 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.uibinder.rebind;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.shell.log.TreeItemLogger;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.uibinder.rebind.model.OwnerClass;
+
+import junit.framework.TestCase;
+
+import org.easymock.classextension.EasyMock;
+import org.easymock.classextension.IMocksControl;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Tests the HandlerEvaluator.
+ *
+ */
+public class HandlerEvaluatorTest extends TestCase {
+
+  HandlerEvaluator evaluator;
+
+  // Defines the mock control.
+  private IMocksControl mockControl;
+
+  private OwnerClass ownerType;
+  private MortalLogger logger;
+  private TypeOracle oracle;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    logger = new MortalLogger(new TreeItemLogger());
+
+    // Creates all needed mocks.
+    mockControl = EasyMock.createControl();
+    ownerType = mockControl.createMock(OwnerClass.class);
+    oracle = mockControl.createMock(TypeOracle.class);
+
+    // TODO(hermes): sucks I know!!!! This class shouldn't be using EasyMock
+    // but for now that's the easiest way of creating new instances of
+    // TypeOracle, TreeLogger, etc. Again, I must check a better way of
+    // injecting TypeOracle, TreeLogger and JClassType.
+
+    JClassType handlerRegistrationJClass = mockControl.createMock(JClassType.class);
+    EasyMock.expect(oracle.findType(HandlerRegistration.class.getName())).andReturn(
+        handlerRegistrationJClass);
+
+    JClassType eventHandlerJClass = mockControl.createMock(JClassType.class);
+    EasyMock.expect(oracle.findType(EventHandler.class.getName())).andReturn(
+        eventHandlerJClass);
+
+    mockControl.replay();
+    evaluator = new HandlerEvaluator(ownerType, logger, oracle);
+    mockControl.verify();
+    mockControl.reset();
+  }
+
+  public void testWriteAddHandler() throws Exception {
+    StringWriter sw = new StringWriter();
+    evaluator.writeAddHandler(new IndentedWriter(new PrintWriter(sw)),
+        "handler1", "addClickHandler", "label1");
+
+    assertEquals("label1.addClickHandler(handler1);", sw.toString().trim());
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/JClassTypeAdapter.java b/user/test/com/google/gwt/uibinder/rebind/JClassTypeAdapter.java
new file mode 100644
index 0000000..4d73cdb
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/JClassTypeAdapter.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright 2009 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.uibinder.rebind;
+
+import com.google.gwt.core.ext.typeinfo.HasAnnotations;
+import com.google.gwt.core.ext.typeinfo.HasTypeParameters;
+import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JConstructor;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.isA;
+
+import org.easymock.IAnswer;
+import org.easymock.classextension.EasyMock;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericDeclaration;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Creates stub adapters for GWT reflection using EasyMock.
+ * This takes in a real java reflection class and returns a JClassType which
+ * forwards all its method calls to the equivalent java reflection methods.
+ * <p>
+ * Most reflections are done lazily (only when the method is actually called),
+ * specially those which potentially require mocking a new class / method /
+ * field / parameter / constructor / etc.
+ * <p>
+ * The support for the typeinfo API is still incomplete, and in fact will
+ * always be in some way since Java doesn't support all the reflections that
+ * GWT's typeinfo does.
+ * <p>
+ * TODO: With a bit of generalization, this class should make its way
+ * to core/src/com/google/gwt/junit
+ *
+ * To make it public we need to...
+ * <ol>
+ * <li>implement the missing parts and TODOs (e.g. generics)
+ * <li>add tests to it (even though it's a testing utility, it does need
+ * tests, specially for cases like inner and anonymous classes, generic
+ * parameters, etc.)
+ * <li>decide what to do with the parts of JType reflection that java doesn't
+ * have an equivalent for - e.g. parameter names. This may involve making a
+ * slightly more complex API to inject those values.
+ * </ol>
+ */
+public class JClassTypeAdapter {
+
+  private final Map<Class<?>, JClassType> adaptedClasses =
+    new HashMap<Class<?>, JClassType>();
+  private final List<Object> allMocks = new ArrayList<Object>();
+
+  public void verifyAll() {
+    EasyMock.verify(allMocks.toArray());
+  }
+
+  /**
+   * Creates a mock GWT class type for the given Java class.
+   *
+   * @param clazz the java class
+   * @return the gwt class
+   */
+  public JClassType adaptJavaClass(final Class<?> clazz) {
+    if (clazz.isPrimitive()) {
+      throw new RuntimeException(
+          "Only classes can be passed to adaptJavaClass");
+    }
+
+    // First try the cache (also avoids infinite recursion if a type references
+    // itself).
+    JClassType type = adaptedClasses.get(clazz);
+    if (type != null) {
+      return type;
+    }
+
+    // Create and put in the cache
+    type = createMock(JClassType.class);
+    final JClassType finalType = type;
+    adaptedClasses.put(clazz, type);
+
+    // Adds behaviour for annotations and generics
+    addAnnotationBehaviour(clazz, type);
+
+    // TODO(rdamazio): Add generics behaviour
+
+    // Add behaviour for getting methods
+    expect(type.getMethods()).andStubAnswer(new IAnswer<JMethod[]>() {
+      public JMethod[] answer() throws Throwable {
+        // TODO(rdamazio): Check behaviour for parent methods
+        Method[] realMethods = clazz.getDeclaredMethods();
+        JMethod[] methods = new JMethod[realMethods.length];
+        for (int i = 0; i < realMethods.length; i++) {
+          methods[i] = adaptMethod(realMethods[i], finalType);
+        }
+        return methods;
+      }
+    });
+
+    // Add behaviour for getting constructors
+    expect(type.getConstructors()).andStubAnswer(new IAnswer<JConstructor[]>() {
+      public JConstructor[] answer() throws Throwable {
+        Constructor<?>[] realConstructors = clazz.getDeclaredConstructors();
+        JConstructor[] constructors = new JConstructor[realConstructors.length];
+        for (int i = 0; i < realConstructors.length; i++) {
+          constructors[i] = adaptConstructor(realConstructors[i], finalType);
+        }
+        return constructors;
+      }
+    });
+
+    // Add behaviour for getting fields
+    expect(type.getFields()).andStubAnswer(new IAnswer<JField[]>() {
+      public JField[] answer() throws Throwable {
+        Field[] realFields = clazz.getDeclaredFields();
+        JField[] fields = new JField[realFields.length];
+        for (int i = 0; i < realFields.length; i++) {
+          fields[i] = adaptField(realFields[i], finalType);
+        }
+        return fields;
+      }
+    });
+
+    // Add behaviour for getting names
+    expect(type.getName()).andStubReturn(clazz.getName());
+    expect(type.getQualifiedSourceName()).andStubReturn(
+        clazz.getCanonicalName());
+    expect(type.getSimpleSourceName()).andStubReturn(clazz.getSimpleName());
+
+    // Add modifier behaviour
+    int modifiers = clazz.getModifiers();
+    expect(type.isAbstract()).andStubReturn(Modifier.isAbstract(modifiers));
+    expect(type.isFinal()).andStubReturn(Modifier.isFinal(modifiers));
+    expect(type.isPublic()).andStubReturn(Modifier.isPublic(modifiers));
+    expect(type.isProtected()).andStubReturn(Modifier.isProtected(modifiers));
+    expect(type.isPrivate()).andStubReturn(Modifier.isPrivate(modifiers));
+
+    // Add conversion behaviours
+    expect(type.isArray()).andStubReturn(null);
+    expect(type.isEnum()).andStubReturn(null);
+    expect(type.isPrimitive()).andStubReturn(null);
+    expect(type.isClassOrInterface()).andStubReturn(type);
+    if (clazz.isInterface()) {
+      expect(type.isClass()).andStubReturn(null);
+      expect(type.isInterface()).andStubReturn(type);
+    } else {
+      expect(type.isClass()).andStubReturn(type);
+      expect(type.isInterface()).andStubReturn(null);
+    }
+    expect(type.getEnclosingType()).andStubAnswer(new IAnswer<JClassType>() {
+      public JClassType answer() throws Throwable {
+        Class<?> enclosingClass = clazz.getEnclosingClass();
+        if (enclosingClass == null) {
+          return null;
+        }
+
+        return adaptJavaClass(enclosingClass);
+      }
+    });
+    expect(type.getSuperclass()).andStubAnswer(new IAnswer<JClassType>() {
+      public JClassType answer() throws Throwable {
+        Class<?> superclass = clazz.getSuperclass();
+        if (superclass == null) {
+          return null;
+        }
+
+        return adaptJavaClass(superclass);
+      }
+    });
+
+    // TODO(rdamazio): Mock out other methods as needed
+    // TODO(rdamazio): Figure out what to do with reflections that GWT allows
+    //                 but Java doesn't
+
+    EasyMock.replay(type);
+    return type;
+  }
+
+  /**
+   * Creates a mock GWT field for the given Java field.
+   *
+   * @param realField the java field
+   * @param enclosingType the GWT enclosing type
+   * @return the GWT field
+   */
+  public JField adaptField(final Field realField, JClassType enclosingType) {
+    JField field = createMock(JField.class);
+
+    addAnnotationBehaviour(realField, field);
+
+    expect(field.getType()).andStubAnswer(new IAnswer<JType>() {
+      public JType answer() throws Throwable {
+        return adaptType(realField.getType());
+      }
+    });
+
+    expect(field.getEnclosingType()).andStubReturn(enclosingType);
+    expect(field.getName()).andStubReturn(realField.getName());
+
+    EasyMock.replay(field);
+    return field;
+  }
+
+  /**
+   * Creates a mock GWT constructor for the given java constructor.
+   *
+   * @param realConstructor the java constructor
+   * @param enclosingType the type to which the constructor belongs
+   * @return the GWT constructor
+   */
+  private JConstructor adaptConstructor(final Constructor<?> realConstructor,
+      JClassType enclosingType) {
+    final JConstructor constructor = createMock(JConstructor.class);
+
+    addCommonAbstractMethodBehaviour(realConstructor, constructor,
+        enclosingType);
+    addAnnotationBehaviour(realConstructor, constructor);
+
+    // Parameters
+    expect(constructor.getParameters()).andStubAnswer(
+        new IAnswer<JParameter[]>() {
+          public JParameter[] answer() throws Throwable {
+            return adaptParameters(realConstructor.getParameterTypes(),
+                realConstructor.getParameterAnnotations(), constructor);
+          }
+        });
+
+    // Thrown exceptions
+    expect(constructor.getThrows()).andStubAnswer(
+        new IAnswer<JType[]>() {
+          public JType[] answer() throws Throwable {
+            Class<?>[] realThrows = realConstructor.getExceptionTypes();
+            JType[] gwtThrows = new JType[realThrows.length];
+            for (int i = 0; i < realThrows.length; i++) {
+              gwtThrows[i] = adaptType(realThrows[i]);
+            }
+            return gwtThrows;
+          }
+        });
+
+    EasyMock.replay(constructor);
+    return constructor;
+  }
+
+  /**
+   * Creates a mock GWT method for the given java method.
+   *
+   * @param realMethod the java method
+   * @param enclosingType the type to which the method belongs
+   * @return the GWT method
+   */
+  private JMethod adaptMethod(final Method realMethod,
+      JClassType enclosingType) {
+    // TODO(rdamazio): ensure a single instance per method per class
+    final JMethod method = createMock(JMethod.class);
+
+    addCommonAbstractMethodBehaviour(realMethod, method, enclosingType);
+    addAnnotationBehaviour(realMethod, method);
+    addGenericsBehaviour(realMethod, method);
+
+    expect(method.isStatic()).andStubReturn(
+        Modifier.isStatic(realMethod.getModifiers()));
+
+    // Return type
+    expect(method.getReturnType()).andStubAnswer(new IAnswer<JType>() {
+      public JType answer() throws Throwable {
+        return adaptType(realMethod.getReturnType());
+      }
+    });
+
+    // Parameters
+    expect(method.getParameters()).andStubAnswer(new IAnswer<JParameter[]>() {
+      public JParameter[] answer() throws Throwable {
+        return adaptParameters(realMethod.getParameterTypes(),
+            realMethod.getParameterAnnotations(), method);
+      }
+    });
+
+    // Thrown exceptions
+    expect(method.getThrows()).andStubAnswer(new IAnswer<JType[]>() {
+      public JType[] answer() throws Throwable {
+        Class<?>[] realThrows = realMethod.getExceptionTypes();
+        JType[] gwtThrows = new JType[realThrows.length];
+        for (int i = 0; i < realThrows.length; i++) {
+          gwtThrows[i] = adaptType(realThrows[i]);
+        }
+        return gwtThrows;
+      }
+    });
+
+    EasyMock.replay(method);
+    return method;
+  }
+
+  /**
+   * Creates an array of mock GWT parameters for the given array of java
+   * parameters.
+   *
+   * @param parameterTypes the types of the parameters
+   * @param parameterAnnotations the list of annotations for each parameter
+   * @param method the method or constructor to which the parameters belong
+   * @return an array of GWT parameters
+   */
+  @SuppressWarnings("unchecked")
+  protected JParameter[] adaptParameters(Class<?>[] parameterTypes,
+      Annotation[][] parameterAnnotations, JAbstractMethod method) {
+    JParameter[] parameters = new JParameter[parameterTypes.length];
+    for (int i = 0; i < parameterTypes.length; i++) {
+      final Class<?> realParameterType = parameterTypes[i];
+      JParameter parameter = createMock(JParameter.class);
+      parameters[i] = parameter;
+
+      // TODO(rdamazio): getName() has no plain java equivalent.
+      //                 Perhaps compiling with -g:vars ?
+
+      expect(parameter.getEnclosingMethod()).andStubReturn(method);
+      expect(parameter.getType()).andStubAnswer(new IAnswer<JType>() {
+        public JType answer() throws Throwable {
+          return adaptType(realParameterType);
+        }
+      });
+
+      // Add annotation behaviour
+      final Annotation[] annotations = parameterAnnotations[i];
+
+      expect(parameter.isAnnotationPresent(isA(Class.class))).andStubAnswer(
+          new IAnswer<Boolean>() {
+            public Boolean answer() throws Throwable {
+              Class<? extends Annotation> annotationClass =
+                  (Class<? extends Annotation>)
+                  EasyMock.getCurrentArguments()[0];
+              for (Annotation annotation : annotations) {
+                if (annotation.equals(annotationClass)) {
+                  return true;
+                }
+              }
+              return false;
+            }
+          });
+
+      expect(parameter.getAnnotation(isA(Class.class))).andStubAnswer(
+          new IAnswer<Annotation>() {
+            public Annotation answer() throws Throwable {
+              Class<? extends Annotation> annotationClass =
+                  (Class<? extends Annotation>)
+                  EasyMock.getCurrentArguments()[0];
+              for (Annotation annotation : annotations) {
+                if (annotation.equals(annotationClass)) {
+                  return annotation;
+                }
+              }
+              return null;
+            }
+          });
+
+      EasyMock.replay(parameter);
+    }
+
+    return parameters;
+  }
+
+  /**
+   * Creates a GWT mock type for the given java type.
+   * The type can be a class or a primitive type.
+   *
+   * @param type the java type
+   * @return the GWT type
+   */
+  private JType adaptType(Class<?> type) {
+    if (!type.isPrimitive()) {
+      return adaptJavaClass(type);
+    } else {
+      return adaptPrimitiveType(type);
+    }
+  }
+
+  /**
+   * Returns the GWT primitive type for the given java primitive type.
+   *
+   * @param type the java primitive type
+   * @return the GWT primitive equivalent
+   */
+  private JType adaptPrimitiveType(Class<?> type) {
+    if (boolean.class.equals(type)) { return JPrimitiveType.BOOLEAN; }
+    if (int.class.equals(type)) { return JPrimitiveType.INT; }
+    if (char.class.equals(type)) { return JPrimitiveType.CHAR; }
+    if (byte.class.equals(type)) { return JPrimitiveType.BYTE; }
+    if (long.class.equals(type)) { return JPrimitiveType.LONG; }
+    if (short.class.equals(type)) { return JPrimitiveType.SHORT; }
+    if (float.class.equals(type)) { return JPrimitiveType.FLOAT; }
+    if (double.class.equals(type)) { return JPrimitiveType.DOUBLE; }
+    if (void.class.equals(type)) { return JPrimitiveType.VOID; }
+
+    throw new IllegalArgumentException(
+        "Invalid primitive type: " + type.getName());
+  }
+
+  /**
+   * Adds expectations common to all method types (methods and constructors).
+   *
+   * @param realMember the java method
+   * @param member the mock GWT method
+   * @param enclosingType the type to which the method belongs
+   */
+  private void addCommonAbstractMethodBehaviour(Member realMember,
+      JAbstractMethod member, JClassType enclosingType) {
+    // Attributes
+    int modifiers = realMember.getModifiers();
+    expect(member.isPublic()).andStubReturn(Modifier.isPublic(modifiers));
+    expect(member.isProtected()).andStubReturn(Modifier.isProtected(modifiers));
+    expect(member.isPrivate()).andStubReturn(Modifier.isPrivate(modifiers));
+    expect(member.getName()).andStubReturn(realMember.getName());
+    expect(member.getEnclosingType()).andStubReturn(enclosingType);
+  }
+
+  /**
+   * Adds expectations for getting annotations from elements (methods, classes,
+   * parameters, etc.).
+   *
+   * @param realElement the java element which contains annotations
+   * @param element the mock GWT element which contains annotations
+   */
+  @SuppressWarnings("unchecked")
+  private void addAnnotationBehaviour(final AnnotatedElement realElement,
+      final HasAnnotations element) {
+    expect(element.isAnnotationPresent(isA(Class.class))).andStubAnswer(
+        new IAnswer<Boolean>() {
+          public Boolean answer() throws Throwable {
+            Class<? extends Annotation> annotationClass =
+                (Class<? extends Annotation>) EasyMock.getCurrentArguments()[0];
+            return realElement.isAnnotationPresent(annotationClass);
+          }
+        });
+
+    expect(element.getAnnotation(isA(Class.class))).andStubAnswer(
+        new IAnswer<Annotation>() {
+          public Annotation answer() throws Throwable {
+            Class<? extends Annotation> annotationClass =
+                (Class<? extends Annotation>) EasyMock.getCurrentArguments()[0];
+            return realElement.getAnnotation(annotationClass);
+          }
+        });
+  }
+
+  /**
+   * Adds expectations for getting generics types.
+   *
+   * @param realGeneric the java generic declaration
+   * @param generic the mock GWT generic declaration
+   */
+  private void addGenericsBehaviour(final GenericDeclaration realGeneric,
+      final HasTypeParameters generic) {
+    // TODO(rdamazio): Implement when necessary
+  }
+
+  /**
+   * Creates a mock of the given class and adds it to the {@link #allMocks}
+   * member list.
+   *
+   * @param <T> the type of the mock
+   * @param clazz the class of the mock
+   * @return the mock
+   */
+  private <T> T createMock(Class<T> clazz) {
+    T mock = EasyMock.createMock(clazz);
+    allMocks.add(mock);
+    return mock;
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/model/OwnerClassTest.java b/user/test/com/google/gwt/uibinder/rebind/model/OwnerClassTest.java
new file mode 100644
index 0000000..24cc2e6
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/model/OwnerClassTest.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2009 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.uibinder.rebind.model;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.dev.shell.log.TreeItemLogger;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.uibinder.client.UiFactory;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.uibinder.rebind.JClassTypeAdapter;
+import com.google.gwt.uibinder.rebind.MortalLogger;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Label;
+
+import junit.framework.TestCase;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for the owner class descriptor.
+ */
+public class OwnerClassTest extends TestCase {
+
+  private JClassTypeAdapter gwtTypeAdapter;
+  private MortalLogger logger;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    logger = new MortalLogger(new TreeItemLogger());
+    gwtTypeAdapter = new JClassTypeAdapter();
+  }
+
+  /**
+   * Empty uibinder class for sanity checking.
+   */
+  private static class EmptyOwnerClass { }
+
+  public void testOwnerClass_empty() throws Exception {
+    JClassType ownerType = gwtTypeAdapter.adaptJavaClass(EmptyOwnerClass.class);
+    JClassType labelType = gwtTypeAdapter.adaptJavaClass(Label.class);
+    OwnerClass ownerClass = new OwnerClass(ownerType, logger);
+
+    assertNull(ownerClass.getUiFactoryMethod(labelType));
+    assertNull(ownerClass.getUiField("fieldName"));
+    assertNull(ownerClass.getUiFieldForType(labelType));
+    assertTrue(ownerClass.getUiFields().isEmpty());
+    assertTrue(ownerClass.getUiHandlers().isEmpty());
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  /**
+   * Uibinder class for testing of {@link UiFactory}.
+   */
+  private static class UiFactoryClass {
+    @UiFactory
+    Label createLabel() {
+      throw new UnsupportedOperationException("Should never be called");
+    }
+  }
+
+  public void testOwnerClass_uiFactory() throws Exception {
+    JClassType ownerType = gwtTypeAdapter.adaptJavaClass(UiFactoryClass.class);
+    JClassType labelType = gwtTypeAdapter.adaptJavaClass(Label.class);
+    OwnerClass ownerClass = new OwnerClass(ownerType, logger);
+
+    JMethod uiFactoryMethod = ownerClass.getUiFactoryMethod(labelType);
+    assertNotNull(uiFactoryMethod);
+    assertEquals("createLabel", uiFactoryMethod.getName());
+    assertEquals(labelType, uiFactoryMethod.getReturnType());
+    JParameter[] parameters = uiFactoryMethod.getParameters();
+    assertNotNull(parameters);
+    assertEquals(0, parameters.length);
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  /**
+   * Uibinder class for testing bad usage of {@link UiFactory}.
+   */
+  private static class BadUiFactoryClass {
+    @UiFactory
+    int thisShouldntWork() {
+      throw new UnsupportedOperationException("Should never be called");
+    }
+  }
+
+  public void testOwnerClass_uiFactoryBadType() {
+    JClassType ownerType = gwtTypeAdapter.adaptJavaClass(BadUiFactoryClass.class);
+    try {
+      new OwnerClass(ownerType, logger);
+      fail("Expected exception not thrown.");
+    } catch (UnableToCompleteException utce) {
+      // Expected
+    }
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  /**
+   * Uibinder class for testing bad usage of {@link UiFactory}.
+   */
+  private static class DuplicateUiFactoryClass {
+    @UiFactory
+    Label labelFactory1() {
+      throw new UnsupportedOperationException("Should never be called");
+    }
+
+    @UiFactory
+    Label labelFactory2() {
+      throw new UnsupportedOperationException("Should never be called");
+    }
+  }
+
+  public void testOwnerClass_uiFactoryDuplicateType() {
+    JClassType ownerType = gwtTypeAdapter.adaptJavaClass(DuplicateUiFactoryClass.class);
+    try {
+      new OwnerClass(ownerType, logger);
+      fail("Expected exception not thrown.");
+    } catch (UnableToCompleteException utce) {
+      // Expected
+    }
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  /**
+   * Uibinder class for testing of {@link UiField}.
+   */
+  private static class UiFieldsClass {
+    @UiField
+    Label label1;
+
+    @UiField(provided = true)
+    Button button1;
+  }
+
+  public void testOwnerClass_uiFields() throws Exception {
+    JClassType ownerType = gwtTypeAdapter.adaptJavaClass(UiFieldsClass.class);
+    JClassType labelType = gwtTypeAdapter.adaptJavaClass(Label.class);
+    JClassType buttonType = gwtTypeAdapter.adaptJavaClass(Button.class);
+    OwnerClass ownerClass = new OwnerClass(ownerType, logger);
+
+    OwnerField labelField = ownerClass.getUiField("label1");
+    OwnerField labelField2 = ownerClass.getUiFieldForType(labelType);
+    assertNotNull(labelField);
+    assertNotNull(labelField2);
+    assertEquals(labelField, labelField2);
+    assertFalse(labelField.isProvided());
+    assertEquals(labelType, labelField.getType().getRawType());
+    assertEquals("label1", labelField.getName());
+
+    OwnerField buttonField = ownerClass.getUiField("button1");
+    OwnerField buttonField2 = ownerClass.getUiFieldForType(buttonType);
+    assertNotNull(buttonField);
+    assertNotNull(buttonField2);
+    assertEquals(buttonField, buttonField2);
+    assertTrue(buttonField.isProvided());
+    assertEquals(buttonType, buttonField.getType().getRawType());
+    assertEquals("button1", buttonField.getName());
+
+    Collection<OwnerField> uiFields = ownerClass.getUiFields();
+    Set<OwnerField> uiFieldSet = new HashSet<OwnerField>(uiFields);
+    Set<OwnerField> expectedFieldSet = new HashSet<OwnerField>();
+    expectedFieldSet.add(labelField);
+    expectedFieldSet.add(buttonField);
+    assertEquals(expectedFieldSet, uiFieldSet);
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  /**
+   * Uibinder class for testing bad usage of {@link UiField}.
+   */
+  private static class BadUiFieldsClass {
+    @UiField
+    int thisShouldntWork;
+  }
+
+  public void testOwnerClass_uiFieldsBadType() {
+    JClassType ownerType = gwtTypeAdapter.adaptJavaClass(BadUiFieldsClass.class);
+    try {
+      new OwnerClass(ownerType, logger);
+      fail("Expected exception not thrown.");
+    } catch (UnableToCompleteException utce) {
+      // Expected
+    }
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  /**
+   * Uibinder class for testing of {@link UiHandler}.
+   */
+  private static class UiHandlersClass {
+    @UiHandler("myField")
+    void onMyFieldClicked(ClickEvent ev) {
+      throw new UnsupportedOperationException("Should never be called");
+    }
+
+    @UiHandler({"myField", "myOtherField"})
+    void onMouseOver(MouseOverEvent ev) {
+      throw new UnsupportedOperationException("Should never be called");
+    }
+  }
+
+  public void testOwnerClass_uiHandlers() throws Exception {
+    JClassType ownerType = gwtTypeAdapter.adaptJavaClass(UiHandlersClass.class);
+    OwnerClass ownerClass = new OwnerClass(ownerType, logger);
+
+    // Assert the two expected handlers are there
+    List<JMethod> uiHandlers = ownerClass.getUiHandlers();
+    assertEquals(2, uiHandlers.size());
+    JMethod clickMethod = null,
+        mouseOverMethod = null;
+
+    // Don't care about ordering
+    for (JMethod method : uiHandlers) {
+      if (method.getName().equals("onMyFieldClicked")) {
+        clickMethod = method;
+      } else if (method.getName().equals("onMouseOver")) {
+        mouseOverMethod = method;
+      }
+    }
+
+    assertNotNull(clickMethod);
+    assertNotNull(mouseOverMethod);
+
+    // Check the click handler
+    JClassType clickEventType = gwtTypeAdapter.adaptJavaClass(ClickEvent.class);
+    JParameter[] clickParams = clickMethod.getParameters();
+    assertEquals(1, clickParams.length);
+    assertEquals(clickEventType, clickParams[0].getType());
+    assertTrue(clickMethod.isAnnotationPresent(UiHandler.class));
+    UiHandler clickAnnotation = clickMethod.getAnnotation(UiHandler.class);
+    String[] clickFields = clickAnnotation.value();
+    assertEquals(1, clickFields.length);
+    assertEquals("myField", clickFields[0]);
+
+    // Check the mouse over handler
+    JClassType mouseOverEventType = gwtTypeAdapter.adaptJavaClass(MouseOverEvent.class);
+    JParameter[] mouseOverParams = mouseOverMethod.getParameters();
+    assertEquals(1, mouseOverParams.length);
+    assertEquals(mouseOverEventType, mouseOverParams[0].getType());
+    assertTrue(mouseOverMethod.isAnnotationPresent(UiHandler.class));
+    UiHandler mouseOverAnnotation = mouseOverMethod.getAnnotation(UiHandler.class);
+    String[] mouseOverFields = mouseOverAnnotation.value();
+    assertEquals(2, mouseOverFields.length);
+    assertEquals("myField", mouseOverFields[0]);
+    assertEquals("myOtherField", mouseOverFields[1]);
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  /**
+   * Parent class for testing inheritance of owner classes.
+   */
+  private static class ParentUiBinderClass {
+    @UiField
+    Label label1;
+
+    @UiFactory
+    Label createLabel() {
+      throw new UnsupportedOperationException("Should never be called");
+    }
+
+    @UiHandler("label1")
+    void onLabelMouseOver(MouseOverEvent ev) {
+      throw new UnsupportedOperationException("Should never be called");
+    }
+  }
+
+  /**
+   * Child class for testing inheritance of owner classes.
+   */
+  private static class ChildUiBinderClass extends ParentUiBinderClass {
+    @UiField(provided = true)
+    Button button1;
+
+    @UiFactory
+    Button createButton() {
+      throw new UnsupportedOperationException("Should never be called");
+    }
+
+    @UiHandler("button1")
+    void onButtonClicked(ClickEvent ev) {
+      throw new UnsupportedOperationException("Should never be called");
+    }
+  }
+
+  public void testOwnerClass_withParent() throws Exception {
+    JClassType ownerType =
+        gwtTypeAdapter.adaptJavaClass(ChildUiBinderClass.class);
+    JClassType labelType = gwtTypeAdapter.adaptJavaClass(Label.class);
+    JClassType buttonType = gwtTypeAdapter.adaptJavaClass(Button.class);
+    OwnerClass ownerClass = new OwnerClass(ownerType, logger);
+
+    // Test fields
+    OwnerField labelField = ownerClass.getUiField("label1");
+    OwnerField labelField2 = ownerClass.getUiFieldForType(labelType);
+    assertNotNull(labelField);
+    assertNotNull(labelField2);
+    assertEquals(labelField, labelField2);
+    assertFalse(labelField.isProvided());
+    assertEquals(labelType, labelField.getType().getRawType());
+    assertEquals("label1", labelField.getName());
+
+    OwnerField buttonField = ownerClass.getUiField("button1");
+    OwnerField buttonField2 = ownerClass.getUiFieldForType(buttonType);
+    assertNotNull(buttonField);
+    assertNotNull(buttonField2);
+    assertEquals(buttonField, buttonField2);
+    assertTrue(buttonField.isProvided());
+    assertEquals(buttonType, buttonField.getType().getRawType());
+    assertEquals("button1", buttonField.getName());
+
+    Collection<OwnerField> uiFields = ownerClass.getUiFields();
+    Set<OwnerField> uiFieldSet = new HashSet<OwnerField>(uiFields);
+    Set<OwnerField> expectedFieldSet = new HashSet<OwnerField>();
+    expectedFieldSet.add(labelField);
+    expectedFieldSet.add(buttonField);
+    assertEquals(expectedFieldSet, uiFieldSet);
+
+    // Test factories
+    JMethod labelFactoryMethod = ownerClass.getUiFactoryMethod(labelType);
+    assertNotNull(labelFactoryMethod);
+    assertEquals("createLabel", labelFactoryMethod.getName());
+    assertEquals(labelType, labelFactoryMethod.getReturnType());
+    JParameter[] labelParams = labelFactoryMethod.getParameters();
+    assertNotNull(labelParams);
+    assertEquals(0, labelParams.length);
+
+    JMethod buttonFactoryMethod = ownerClass.getUiFactoryMethod(labelType);
+    assertNotNull(buttonFactoryMethod);
+    assertEquals("createLabel", buttonFactoryMethod.getName());
+    assertEquals(labelType, buttonFactoryMethod.getReturnType());
+    JParameter[] buttonParams = buttonFactoryMethod.getParameters();
+    assertNotNull(buttonParams);
+    assertEquals(0, buttonParams.length);
+
+    // Test handlers
+    List<JMethod> uiHandlers = ownerClass.getUiHandlers();
+    assertEquals(2, uiHandlers.size());
+    JMethod clickMethod = null,
+        mouseOverMethod = null;
+
+    for (JMethod method : uiHandlers) {
+      if (method.getName().equals("onButtonClicked")) {
+        clickMethod = method;
+      } else if (method.getName().equals("onLabelMouseOver")) {
+        mouseOverMethod = method;
+      }
+    }
+
+    assertNotNull(clickMethod);
+    assertNotNull(mouseOverMethod);
+
+    JClassType clickEventType = gwtTypeAdapter.adaptJavaClass(ClickEvent.class);
+    JParameter[] clickParams = clickMethod.getParameters();
+    assertEquals(1, clickParams.length);
+    assertEquals(clickEventType, clickParams[0].getType());
+    assertTrue(clickMethod.isAnnotationPresent(UiHandler.class));
+    UiHandler clickAnnotation = clickMethod.getAnnotation(UiHandler.class);
+    String[] clickFields = clickAnnotation.value();
+    assertEquals(1, clickFields.length);
+    assertEquals("button1", clickFields[0]);
+
+    JClassType mouseOverEventType = gwtTypeAdapter.adaptJavaClass(MouseOverEvent.class);
+    JParameter[] mouseOverParams = mouseOverMethod.getParameters();
+    assertEquals(1, mouseOverParams.length);
+    assertEquals(mouseOverEventType, mouseOverParams[0].getType());
+    assertTrue(mouseOverMethod.isAnnotationPresent(UiHandler.class));
+    UiHandler mouseOverAnnotation = mouseOverMethod.getAnnotation(UiHandler.class);
+    String[] mouseOverFields = mouseOverAnnotation.value();
+    assertEquals(1, mouseOverFields.length);
+    assertEquals("label1", mouseOverFields[0]);
+
+    gwtTypeAdapter.verifyAll();
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldClassTest.java b/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldClassTest.java
new file mode 100644
index 0000000..8de43d2
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldClassTest.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2009 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.uibinder.rebind.model;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JConstructor;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.dev.shell.log.TreeItemLogger;
+import com.google.gwt.uibinder.client.UiConstructor;
+import com.google.gwt.uibinder.rebind.JClassTypeAdapter;
+import com.google.gwt.uibinder.rebind.MortalLogger;
+import com.google.gwt.user.client.ui.Label;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for descriptors of potential owner field classes.
+ */
+public class OwnerFieldClassTest extends TestCase {
+
+  private JClassTypeAdapter gwtTypeAdapter;
+  private MortalLogger logger;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    logger = new MortalLogger(new TreeItemLogger());
+    gwtTypeAdapter = new JClassTypeAdapter();
+  }
+
+  public void testOwnerFieldClass() throws Exception {
+    // Get the JType for a Label
+    JClassType labelType = gwtTypeAdapter.adaptJavaClass(Label.class);
+
+    // Now get its field class model
+    OwnerFieldClass fieldClass = OwnerFieldClass.getFieldClass(labelType, logger);
+
+    // Check the class model properties
+    assertEquals(labelType, fieldClass.getRawType());
+    assertNull(fieldClass.getUiConstructor());
+
+    JMethod setter = fieldClass.getSetter("visible");
+    assertMethod(setter, "setVisible", JPrimitiveType.BOOLEAN);
+
+    // Check that the same instance of the model is returned if asked again
+    assertSame(fieldClass, OwnerFieldClass.getFieldClass(labelType, logger));
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  /**
+   * Class with lots of setters for testing.
+   */
+  private static class SettersTestClass {
+    // No ambiguity in these setters
+    public void setBlaBla(int x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    public void setBlaBle(String x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    // To be used in subclass test
+    public void setBlaBla2(int x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    public void setBlaBle2(String x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    public void setBli2(int x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    public void setBli2(double x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    // Ambiguous, String parameter should win
+    public void setBle(int y) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    public void setBle(String y) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    // Ambiguous with no winner
+    public void setBli(int y) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    public void setBli(double y) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    // Not considered setters
+    void setNothing(int x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    public int setNothing2(String x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    public void notASetter(String x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    public static void setStatic(String x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+  }
+
+  public void testOwnerFieldClass_setters() throws Exception {
+    JClassType settersType =
+        gwtTypeAdapter.adaptJavaClass(SettersTestClass.class);
+    JClassType stringType = gwtTypeAdapter.adaptJavaClass(String.class);
+    OwnerFieldClass settersClass = OwnerFieldClass.getFieldClass(settersType, logger);
+    assertEquals(settersType, settersClass.getRawType());
+    assertNull(settersClass.getUiConstructor());
+
+    JMethod blaBlaSetter = settersClass.getSetter("blaBla");
+    assertMethod(blaBlaSetter, "setBlaBla", JPrimitiveType.INT);
+    JMethod blaBleSetter = settersClass.getSetter("blaBle");
+    assertMethod(blaBleSetter, "setBlaBle", stringType);
+
+    assertNull(settersClass.getSetter("nothing"));
+    assertNull(settersClass.getSetter("nothing2"));
+    assertNull(settersClass.getSetter("notASetter"));
+    assertNull(settersClass.getSetter("aSetter"));
+    assertNull(settersClass.getSetter("static"));
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  public void testOwnerFieldClass_ambiguousSetters() throws Exception {
+    JClassType settersType =
+        gwtTypeAdapter.adaptJavaClass(SettersTestClass.class);
+    JClassType stringType = gwtTypeAdapter.adaptJavaClass(String.class);
+    OwnerFieldClass settersClass = OwnerFieldClass.getFieldClass(settersType, logger);
+    assertEquals(settersType, settersClass.getRawType());
+
+    JMethod bleSetter = settersClass.getSetter("ble");
+    assertMethod(bleSetter, "setBle", stringType);
+
+    try {
+      settersClass.getSetter("bli");
+      fail("Expected exception not thrown");
+    } catch (UnableToCompleteException utce) {
+      // Expected
+    }
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  /**
+   * Class with overridden setters for testing.
+   */
+  private static class OverriddenSettersTestClass extends SettersTestClass {
+    // Simple override of parent method
+    @Override
+    public void setBlaBla(int x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    // setBlaBle is not overridden
+
+    // Subclass adds ambiguity, String from this class wins
+    public void setBlaBla2(String x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    // Subclass adds ambiguity, String from superclass wins
+    public void setBlaBle2(int x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    // setBle had settled ambiguity, this shouldn't change it
+    public void setBle(double x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    // setBli2 ambiguous in superclass only
+
+    // setBlo us ambiguous here only
+    public void setBlo(int x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    public void setBlo(double x) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    // Solves superclass ambiguity
+    public void setBli(String y) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+  }
+
+  public void testOwnerFieldClass_overriddenSetters() throws Exception {
+    JClassType settersType =
+        gwtTypeAdapter.adaptJavaClass(OverriddenSettersTestClass.class);
+    JClassType stringType = gwtTypeAdapter.adaptJavaClass(String.class);
+    OwnerFieldClass settersClass = OwnerFieldClass.getFieldClass(settersType, logger);
+    assertEquals(settersType, settersClass.getRawType());
+
+    // setBlaBla is not ambiguous, though overridden
+    JMethod blaBlaSetter = settersClass.getSetter("blaBla");
+    assertMethod(blaBlaSetter, "setBlaBla", JPrimitiveType.INT);
+
+    // setBlaBle is not overridden, works from superclass
+    JMethod blaBleSetter = settersClass.getSetter("blaBle");
+    assertMethod(blaBleSetter, "setBlaBle", stringType);
+
+    // setBlaBla2 is not ambiguous, subclass String wins
+    JMethod blaBla2Setter = settersClass.getSetter("blaBla2");
+    assertMethod(blaBla2Setter, "setBlaBla2", stringType);
+
+    // setBlaBle2 is not ambiguous, superclass String wins
+    JMethod blaBle2Setter = settersClass.getSetter("blaBle2");
+    assertMethod(blaBle2Setter, "setBlaBle2", stringType);
+
+    // setBle is disambiguated and overridden
+    JMethod bleSetter = settersClass.getSetter("ble");
+    assertMethod(bleSetter, "setBle", stringType);
+
+    // setBli was ambiguous in the superclass, subclass String settles it
+    JMethod bliSetter = settersClass.getSetter("bli");
+    assertMethod(bliSetter, "setBli", stringType);
+
+    // setBli2 is ambiguous in the superclass
+    try {
+      settersClass.getSetter("bli2");
+      fail("Expected exception not thrown");
+    } catch (UnableToCompleteException utce) {
+      // Expected
+    }
+
+    // setBlo is ambiguous in the subclass
+    try {
+      settersClass.getSetter("blo");
+      fail("Expected exception not thrown");
+    } catch (UnableToCompleteException utce) {
+      // Expected
+    }
+
+    // Ignored superclass setters are still ignored
+    assertNull(settersClass.getSetter("nothing"));
+    assertNull(settersClass.getSetter("nothing2"));
+    assertNull(settersClass.getSetter("notASetter"));
+    assertNull(settersClass.getSetter("aSetter"));
+    assertNull(settersClass.getSetter("static"));
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  /**
+   * Class with a {@link UiConstructor}-annotated constructor.
+   */
+  private static class UiConstructorClass {
+    @UiConstructor
+    public UiConstructorClass(boolean visible) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+  }
+
+  public void testOwnerFieldClass_withUiConstructor() throws Exception {
+    JClassType constructorsType =
+        gwtTypeAdapter.adaptJavaClass(UiConstructorClass.class);
+    OwnerFieldClass constructorsClass =
+        OwnerFieldClass.getFieldClass(constructorsType, logger);
+    assertEquals(constructorsType, constructorsClass.getRawType());
+
+    JConstructor constructor = constructorsClass.getUiConstructor();
+    assertNotNull(constructor);
+    assertEquals(constructorsType, constructor.getEnclosingType());
+
+    JParameter[] parameters = constructor.getParameters();
+    assertEquals(1, parameters.length);
+    assertEquals(JPrimitiveType.BOOLEAN, parameters[0].getType());
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  /**
+   * Class with (disallowed) multiple constructors annotated with
+   * {@link UiConstructor}.
+   */
+  private static class MultiUiConstructorsClass {
+    @UiConstructor
+    public MultiUiConstructorsClass(boolean visible) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    @UiConstructor
+    public MultiUiConstructorsClass(String size) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+  }
+
+  public void testOwnerFieldClass_withMultipleUiConstructors()
+      throws Exception {
+    JClassType constructorsType =
+        gwtTypeAdapter.adaptJavaClass(MultiUiConstructorsClass.class);
+
+    try {
+      OwnerFieldClass.getFieldClass(constructorsType, logger);
+      fail("Expected exception not thrown");
+    } catch (UnableToCompleteException utce) {
+      // Expected
+    }
+  }
+
+  /**
+   * Asserts that the given method has the proper name and parameters.
+   *
+   * @param method the actual method
+   * @param methodName the expected method name
+   * @param parameterTypes the expected parameter types
+   */
+  private void assertMethod(JMethod method, String methodName,
+      JType... parameterTypes) {
+    assertNotNull(method);
+    assertEquals(methodName, method.getName());
+    JParameter[] parameters = method.getParameters();
+    assertEquals(parameterTypes.length, parameters.length);
+    for (int i = 0; i < parameters.length; i++) {
+      assertEquals("Parameter " + i + " of method " + methodName
+          + " mismatch. Expected" + parameterTypes[i].getSimpleSourceName()
+          + "; actual: " + parameters[i].getType().getSimpleSourceName(),
+          parameterTypes[i], parameters[i].getType());
+    }
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldTest.java b/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldTest.java
new file mode 100644
index 0000000..b1d5a86
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2009 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.uibinder.rebind.model;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.dev.shell.log.TreeItemLogger;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.rebind.JClassTypeAdapter;
+import com.google.gwt.uibinder.rebind.MortalLogger;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Label;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the owner field descriptor.
+ */
+public class OwnerFieldTest extends TestCase {
+
+  private JClassTypeAdapter gwtTypeAdapter;
+  private JClassType ownerType;
+  private MortalLogger logger;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    logger = new MortalLogger(new TreeItemLogger());
+    gwtTypeAdapter = new JClassTypeAdapter();
+    ownerType = gwtTypeAdapter.adaptJavaClass(this.getClass());
+  }
+
+  // Fields for testing
+
+  @UiField
+  Label someField;
+
+  @UiField(provided = true)
+  Button providedField;
+
+  @UiField
+  int badTypeField;
+
+  Label nonAnnotatedField;
+
+  public void testOwnerField() throws Exception {
+    JClassType labelType = gwtTypeAdapter.adaptJavaClass(Label.class);
+    JClassType buttonType = gwtTypeAdapter.adaptJavaClass(Button.class);
+
+    JField someGwtField = gwtTypeAdapter.adaptField(
+        this.getClass().getDeclaredField("someField"), ownerType);
+    OwnerField someOwnerField = new OwnerField(someGwtField, logger);
+    assertEquals("someField", someOwnerField.getName());
+    assertEquals(labelType, someOwnerField.getType().getRawType());
+    assertFalse(someOwnerField.isProvided());
+
+    JField providedGwtField = gwtTypeAdapter.adaptField(
+        this.getClass().getDeclaredField("providedField"), ownerType);
+    OwnerField providedOwnerField = new OwnerField(providedGwtField, logger);
+    assertEquals("providedField", providedOwnerField.getName());
+    assertEquals(buttonType, providedOwnerField.getType().getRawType());
+    assertTrue(providedOwnerField.isProvided());
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  public void testOwnerField_badFieldType() throws Exception {
+    JField someGwtField = gwtTypeAdapter.adaptField(
+        this.getClass().getDeclaredField("badTypeField"), ownerType);
+    try {
+      new OwnerField(someGwtField, logger);
+      fail("Expected exception not thrown.");
+    } catch (UnableToCompleteException utce) {
+      // Expected
+    }
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  public void testOwnerField_missingAnnotation() throws Exception {
+    JField someGwtField = gwtTypeAdapter.adaptField(
+        this.getClass().getDeclaredField("nonAnnotatedField"), ownerType);
+    try {
+      new OwnerField(someGwtField, logger);
+      fail("Expected exception not thrown.");
+    } catch (UnableToCompleteException utce) {
+      // Expected
+    }
+
+    gwtTypeAdapter.verifyAll();
+  }
+}