Allows isBoolean() properties to be handled correctly by AutoBeanCodex and RequestFactory.
Issue 5902.
Moves all method-to-property computations into BeanMethod and JBeanMethod utiliy enums.
Move server reflection implementation classes into server.impl package to clean up organization.
Patch by: bobv, t.broyer
Review by: rjrjr
Reported by: jasonhall, t.broyer

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9619 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
index fec733f..0adbd02 100644
--- a/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
+++ b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
@@ -16,10 +16,10 @@
 package com.google.gwt.autobean.rebind;
 
 import com.google.gwt.autobean.client.impl.AbstractAutoBeanFactory;
+import com.google.gwt.autobean.rebind.model.JBeanMethod;
 import com.google.gwt.autobean.rebind.model.AutoBeanFactoryMethod;
 import com.google.gwt.autobean.rebind.model.AutoBeanFactoryModel;
 import com.google.gwt.autobean.rebind.model.AutoBeanMethod;
-import com.google.gwt.autobean.rebind.model.AutoBeanMethod.Action;
 import com.google.gwt.autobean.rebind.model.AutoBeanType;
 import com.google.gwt.autobean.shared.AutoBean;
 import com.google.gwt.autobean.shared.AutoBeanFactory;
@@ -559,7 +559,7 @@
     sw.indent();
 
     for (AutoBeanMethod method : type.getMethods()) {
-      if (!method.getAction().equals(Action.GET)) {
+      if (!method.getAction().equals(JBeanMethod.GET)) {
         continue;
       }
 
@@ -567,7 +567,7 @@
       // If it's not a simple bean type, try to find a real setter method
       if (!type.isSimpleBean()) {
         for (AutoBeanMethod maybeSetter : type.getMethods()) {
-          if (maybeSetter.getAction().equals(Action.SET)
+          if (maybeSetter.getAction().equals(JBeanMethod.SET)
               && maybeSetter.getPropertyName().equals(method.getPropertyName())) {
             setter = maybeSetter;
             break;
diff --git a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
index 9268c26..b073335 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.autobean.rebind.model;
 
-import com.google.gwt.autobean.rebind.model.AutoBeanMethod.Action;
 import com.google.gwt.autobean.shared.AutoBean;
 import com.google.gwt.autobean.shared.AutoBeanFactory;
 import com.google.gwt.autobean.shared.AutoBeanFactory.Category;
@@ -152,7 +151,7 @@
               + " methods did not have static implementations:",
               beanType.getQualifiedSourceName());
           for (AutoBeanMethod missing : autoBeanType.getMethods()) {
-            if (missing.getAction().equals(Action.CALL)
+            if (missing.getAction().equals(JBeanMethod.CALL)
                 && missing.getStaticImpl() == null) {
               poison(missing.getMethod().getReadableDeclaration());
             }
@@ -235,9 +234,9 @@
       }
 
       // GET, SET, or CALL
-      Action action = Action.which(method);
+      JBeanMethod action = JBeanMethod.which(method);
       builder.setAction(action);
-      if (Action.CALL.equals(action)) {
+      if (JBeanMethod.CALL.equals(action)) {
         JMethod staticImpl = findStaticImpl(beanType, method);
         if (staticImpl == null && objectMethods.contains(method)) {
           // Don't complain about lack of implementation for Object methods
diff --git a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
index 0362aea..516ce3e 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
@@ -20,7 +20,6 @@
 import com.google.gwt.core.ext.typeinfo.JEnumConstant;
 import com.google.gwt.core.ext.typeinfo.JEnumType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
-import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.editor.rebind.model.ModelUtils;
@@ -34,108 +33,14 @@
  */
 public class AutoBeanMethod {
   /**
-   * Describes the type of method that was invoked.
-   */
-  public enum Action {
-    GET {
-      @Override
-      String inferName(JMethod method) {
-        if (JPrimitiveType.BOOLEAN.equals(method.getReturnType())) {
-          String name = method.getName();
-          if (name.startsWith("is") && name.length() > 2) {
-            name = Character.toLowerCase(name.charAt(2))
-                + (name.length() >= 4 ? name.substring(3) : "");
-            return name;
-          }
-        }
-        return super.inferName(method);
-      }
-
-      @Override
-      boolean matches(JMethod method) {
-        if (method.getParameters().length > 0) {
-          return false;
-        }
-        String name = method.getName();
-
-        // Allow boolean isFoo() or boolean hasFoo();
-        if (JPrimitiveType.BOOLEAN.equals(method.getReturnType())) {
-          if (name.startsWith("is") && name.length() > 2) {
-            return true;
-          }
-          if (name.startsWith("has") && name.length() > 3) {
-            return true;
-          }
-        }
-        if (name.startsWith("get") && name.length() > 3) {
-          return true;
-        }
-        return false;
-      }
-    },
-    SET {
-      @Override
-      boolean matches(JMethod method) {
-        if (!JPrimitiveType.VOID.equals(method.getReturnType())) {
-          return false;
-        }
-        if (method.getParameters().length != 1) {
-          return false;
-        }
-        String name = method.getName();
-        if (name.startsWith("set") && name.length() > 3) {
-          return true;
-        }
-        return false;
-      }
-    },
-    CALL {
-      /**
-       * Matches all leftover methods.
-       */
-      @Override
-      boolean matches(JMethod method) {
-        return true;
-      }
-    };
-
-    /**
-     * Determine which Action a method maps to.
-     */
-    public static Action which(JMethod method) {
-      for (Action action : Action.values()) {
-        if (action.matches(method)) {
-          return action;
-        }
-      }
-      throw new RuntimeException("CALL should have matched");
-    }
-
-    /**
-     * Infer the name of a property from the method.
-     */
-    String inferName(JMethod method) {
-      String name = method.getName();
-      name = Character.toLowerCase(name.charAt(3))
-          + (name.length() >= 5 ? name.substring(4) : "");
-      return name;
-    }
-
-    /**
-     * Returns {@code true} if the Action matches the method.
-     */
-    abstract boolean matches(JMethod method);
-  }
-
-  /**
    * Creates AutoBeanMethods.
    */
   public static class Builder {
     private AutoBeanMethod toReturn = new AutoBeanMethod();
 
     public AutoBeanMethod build() {
-      if (toReturn.action.equals(Action.GET)
-          || toReturn.action.equals(Action.SET)) {
+      if (toReturn.action.equals(JBeanMethod.GET)
+          || toReturn.action.equals(JBeanMethod.SET)) {
         PropertyName annotation = toReturn.method.getAnnotation(PropertyName.class);
         if (annotation != null) {
           toReturn.propertyName = annotation.value();
@@ -151,7 +56,7 @@
       }
     }
 
-    public void setAction(Action action) {
+    public void setAction(JBeanMethod action) {
       toReturn.action = action;
     }
 
@@ -227,7 +132,7 @@
     }
   }
 
-  private Action action;
+  private JBeanMethod action;
   private JClassType elementType;
   private Map<JEnumConstant, String> enumMap;
   private JClassType keyType;
@@ -241,7 +146,7 @@
   private AutoBeanMethod() {
   }
 
-  public Action getAction() {
+  public JBeanMethod getAction() {
     return action;
   }
 
diff --git a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java
index 1697a9f..5fd64e5 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.autobean.rebind.model;
 
-import com.google.gwt.autobean.rebind.model.AutoBeanMethod.Action;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
 
@@ -71,7 +70,7 @@
 
       toReturn.simpleBean = true;
       for (AutoBeanMethod method : methods) {
-        if (method.getAction().equals(Action.CALL)) {
+        if (method.getAction().equals(JBeanMethod.CALL)) {
           if (method.getStaticImpl() == null) {
             toReturn.simpleBean = false;
           } else {
diff --git a/user/src/com/google/gwt/autobean/rebind/model/JBeanMethod.java b/user/src/com/google/gwt/autobean/rebind/model/JBeanMethod.java
new file mode 100644
index 0000000..e403530
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/rebind/model/JBeanMethod.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2011 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.autobean.rebind.model;
+
+import static com.google.gwt.autobean.server.impl.BeanMethod.GET_PREFIX;
+import static com.google.gwt.autobean.server.impl.BeanMethod.HAS_PREFIX;
+import static com.google.gwt.autobean.server.impl.BeanMethod.IS_PREFIX;
+import static com.google.gwt.autobean.server.impl.BeanMethod.SET_PREFIX;
+
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+
+import java.beans.Introspector;
+
+/**
+ * Common utility code for matching {@link JMethod} and against bean-style
+ * accessor semantics.
+ * 
+ * @see com.google.gwt.autobean.server.impl.BeanMethod
+ */
+public enum JBeanMethod {
+  GET {
+    @Override
+    public String inferName(JMethod method) {
+      if (isBooleanProperty(method) && method.getName().startsWith(IS_PREFIX)) {
+        return Introspector.decapitalize(method.getName().substring(2));
+      }
+      return super.inferName(method);
+    }
+
+    @Override
+    public boolean matches(JMethod method) {
+      if (method.getParameters().length > 0) {
+        return false;
+      }
+
+      if (isBooleanProperty(method)) {
+        return true;
+      }
+
+      String name = method.getName();
+      if (name.startsWith(GET_PREFIX) && name.length() > 3) {
+        return true;
+      }
+      return false;
+    }
+
+    /**
+     * Returns {@code true} if the method matches {@code boolean isFoo()} or
+     * {@code boolean hasFoo()} property accessors.
+     */
+    private boolean isBooleanProperty(JMethod method) {
+      JType returnType = method.getReturnType();
+      if (JPrimitiveType.BOOLEAN.equals(returnType)
+          || method.getEnclosingType().getOracle().findType(
+              Boolean.class.getCanonicalName()).equals(returnType)) {
+        String name = method.getName();
+        if (name.startsWith(IS_PREFIX) && name.length() > 2) {
+          return true;
+        }
+        if (name.startsWith(HAS_PREFIX) && name.length() > 3) {
+          return true;
+        }
+      }
+      return false;
+    }
+  },
+  SET {
+    @Override
+    public boolean matches(JMethod method) {
+      if (!JPrimitiveType.VOID.equals(method.getReturnType())) {
+        return false;
+      }
+      if (method.getParameters().length != 1) {
+        return false;
+      }
+      String name = method.getName();
+      if (name.startsWith(SET_PREFIX) && name.length() > 3) {
+        return true;
+      }
+      return false;
+    }
+  },
+  CALL {
+    /**
+     * Matches all leftover methods.
+     */
+    @Override
+    public boolean matches(JMethod method) {
+      return true;
+    }
+  };
+
+  /**
+   * Determine which Action a method maps to.
+   */
+  public static JBeanMethod which(JMethod method) {
+    for (JBeanMethod action : JBeanMethod.values()) {
+      if (action.matches(method)) {
+        return action;
+      }
+    }
+    throw new RuntimeException("CALL should have matched");
+  }
+
+  /**
+   * Infer the name of a property from the method.
+   */
+  public String inferName(JMethod method) {
+    if (this == CALL) {
+      throw new UnsupportedOperationException(
+          "Cannot infer a property name for a CALL-type method");
+    }
+    return Introspector.decapitalize(method.getName().substring(3));
+  }
+
+  /**
+   * Returns {@code true} if the BeanLikeMethod matches the method.
+   */
+  public abstract boolean matches(JMethod method);
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java b/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
index c18d943..6ab47d1 100644
--- a/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
+++ b/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
@@ -15,14 +15,14 @@
  */
 package com.google.gwt.autobean.server;
 
+import com.google.gwt.autobean.server.impl.FactoryHandler;
+import com.google.gwt.autobean.server.impl.ProxyAutoBean;
 import com.google.gwt.autobean.shared.AutoBean;
 import com.google.gwt.autobean.shared.AutoBeanFactory;
 import com.google.gwt.autobean.shared.AutoBeanFactory.Category;
 import com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap;
 import com.google.gwt.autobean.shared.impl.EnumMap;
 
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Proxy;
 
 /**
  * Generates JVM-compatible implementations of AutoBeanFactory and AutoBean
@@ -59,7 +59,7 @@
       builder.setNoWrap(noWrap.value());
     }
 
-    return makeProxy(clazz, new FactoryHandler(builder.build()), EnumMap.class);
+    return ProxyAutoBean.makeProxy(clazz, new FactoryHandler(builder.build()), EnumMap.class);
   }
 
   /**
@@ -73,29 +73,4 @@
       Configuration configuration) {
     return new ProxyAutoBean<T>(EMPTY, clazz, configuration);
   }
-
-  /**
-   * Utility method to crete a new {@link Proxy} instance.
-   * 
-   * @param <T> the interface type to be implemented by the Proxy
-   * @param intf the Class representing the interface type
-   * @param handler the implementation of the interface
-   * @param extraInterfaces additional interface types the Proxy should
-   *          implement
-   * @return a Proxy instance
-   */
-  static <T> T makeProxy(Class<T> intf, InvocationHandler handler,
-      Class<?>... extraInterfaces) {
-    Class<?>[] intfs;
-    if (extraInterfaces == null) {
-      intfs = new Class<?>[] {intf};
-    } else {
-      intfs = new Class<?>[extraInterfaces.length + 1];
-      intfs[0] = intf;
-      System.arraycopy(extraInterfaces, 0, intfs, 1, extraInterfaces.length);
-    }
-
-    return intf.cast(Proxy.newProxyInstance(intf.getClassLoader(), intfs,
-        handler));
-  }
 }
diff --git a/user/src/com/google/gwt/autobean/server/BeanMethod.java b/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
similarity index 76%
rename from user/src/com/google/gwt/autobean/server/BeanMethod.java
rename to user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
index 24c2009..6cdf277 100644
--- a/user/src/com/google/gwt/autobean/server/BeanMethod.java
+++ b/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
@@ -13,23 +13,31 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.autobean.server;
+package com.google.gwt.autobean.server.impl;
 
-import com.google.gwt.autobean.server.impl.TypeUtils;
 import com.google.gwt.autobean.shared.AutoBean;
 
+import java.beans.Introspector;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 
 /**
  * Breakout of method types that an AutoBean shim interface can implement. The
  * order of the values of the enum is important.
+ * 
+ * @see com.google.gwt.autobean.rebind.model.JBeanMethod
  */
-enum BeanMethod {
+public enum BeanMethod {
   /**
    * Methods defined in Object.
    */
   OBJECT {
+
+    @Override
+    public String inferName(Method method) {
+      throw new UnsupportedOperationException();
+    }
+
     @Override
     Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args)
         throws Throwable {
@@ -49,15 +57,20 @@
    */
   GET {
     @Override
-    Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
-      String propertyName;
+    public String inferName(Method method) {
       String name = method.getName();
-      if (Boolean.TYPE.equals(method.getReturnType()) && name.startsWith("is")) {
-        propertyName = name.substring(2);
-      } else {
-        // A regular getter or a boolean hasFoo()
-        propertyName = name.substring(3);
+      if (name.startsWith(IS_PREFIX)) {
+        Class<?> returnType = method.getReturnType();
+        if (Boolean.TYPE.equals(returnType) || Boolean.class.equals(returnType)) {
+          return Introspector.decapitalize(name.substring(2));
+        }
       }
+      return super.inferName(method);
+    }
+
+    @Override
+    Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
+      String propertyName = inferName(method);
       Object toReturn = handler.getBean().getValues().get(propertyName);
       if (toReturn == null && method.getReturnType().isPrimitive()) {
         toReturn = TypeUtils.getDefaultPrimitiveValue(method.getReturnType());
@@ -74,13 +87,13 @@
       }
 
       String name = method.getName();
-      if (Boolean.TYPE.equals(returnType)) {
-        if (name.startsWith("is") && name.length() > 2
-            || name.startsWith("has") && name.length() > 3) {
+      if (Boolean.TYPE.equals(returnType) || Boolean.class.equals(returnType)) {
+        if (name.startsWith(IS_PREFIX) && name.length() > 2
+            || name.startsWith(HAS_PREFIX) && name.length() > 3) {
           return true;
         }
       }
-      return name.startsWith("get") && name.length() > 3;
+      return name.startsWith(GET_PREFIX) && name.length() > 3;
     }
   },
   /**
@@ -89,14 +102,14 @@
   SET {
     @Override
     Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
-      handler.getBean().getValues().put(method.getName().substring(3), args[0]);
+      handler.getBean().getValues().put(inferName(method), args[0]);
       return null;
     }
 
     @Override
     boolean matches(SimpleBeanHandler<?> handler, Method method) {
       String name = method.getName();
-      return name.startsWith("set") && name.length() > 3
+      return name.startsWith(SET_PREFIX) && name.length() > 3
           && method.getParameterTypes().length == 1
           && method.getReturnType().equals(Void.TYPE);
     }
@@ -106,6 +119,11 @@
    */
   CALL {
     @Override
+    public String inferName(Method method) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
     Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args)
         throws Throwable {
       if (args == null) {
@@ -131,6 +149,11 @@
     }
   };
 
+  public static final String GET_PREFIX = "get";
+  public static final String HAS_PREFIX = "has";
+  public static final String IS_PREFIX = "is";
+  public static final String SET_PREFIX = "set";
+
   private static final Object[] EMPTY_OBJECT = new Object[0];
 
   static Method findMethod(SimpleBeanHandler<?> handler, Method method) {
@@ -161,6 +184,17 @@
     return null;
   }
 
+  public String inferName(Method method) {
+    return Introspector.decapitalize(method.getName().substring(3));
+  }
+
+  /**
+   * Convenience method, not valid for {@link BeanMethod#CALL}.
+   */
+  public boolean matches(Method method) {
+    return matches(null, method);
+  }
+
   /**
    * Invoke the method.
    */
@@ -168,13 +202,6 @@
       Object[] args) throws Throwable;
 
   /**
-   * Convenience method, not valid for {@link BeanMethod#CALL}.
-   */
-  boolean matches(Method method) {
-    return matches(null, method);
-  }
-
-  /**
    * Determine if the method maches the given type.
    */
   abstract boolean matches(SimpleBeanHandler<?> handler, Method method);
diff --git a/user/src/com/google/gwt/autobean/server/BeanPropertyContext.java b/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
similarity index 75%
rename from user/src/com/google/gwt/autobean/server/BeanPropertyContext.java
rename to user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
index f0014b7..8b1a7a5 100644
--- a/user/src/com/google/gwt/autobean/server/BeanPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
@@ -13,9 +13,8 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.autobean.server;
+package com.google.gwt.autobean.server.impl;
 
-import com.google.gwt.autobean.server.impl.TypeUtils;
 
 import java.lang.reflect.Method;
 import java.util.Map;
@@ -30,7 +29,7 @@
 
   public BeanPropertyContext(ProxyAutoBean<?> bean, Method getter) {
     super(getter);
-    propertyName = getter.getName().substring(3);
+    propertyName = BeanMethod.GET.inferName(getter);
     map = bean.getPropertyMap();
   }
 
@@ -41,6 +40,9 @@
 
   @Override
   public void set(Object value) {
-    map.put(propertyName, TypeUtils.maybeAutobox(getType()).cast(value));
+    Class<?> maybeAutobox = TypeUtils.maybeAutobox(getType());
+    assert value == null || maybeAutobox.isInstance(value) : value.getClass().getCanonicalName()
+        + " is not assignable to " + maybeAutobox.getCanonicalName();
+    map.put(propertyName, maybeAutobox.cast(value));
   }
 }
diff --git a/user/src/com/google/gwt/autobean/server/FactoryHandler.java b/user/src/com/google/gwt/autobean/server/impl/FactoryHandler.java
similarity index 95%
rename from user/src/com/google/gwt/autobean/server/FactoryHandler.java
rename to user/src/com/google/gwt/autobean/server/impl/FactoryHandler.java
index c8090d1..4d21c6d 100644
--- a/user/src/com/google/gwt/autobean/server/FactoryHandler.java
+++ b/user/src/com/google/gwt/autobean/server/impl/FactoryHandler.java
@@ -13,8 +13,9 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.autobean.server;
+package com.google.gwt.autobean.server.impl;
 
+import com.google.gwt.autobean.server.Configuration;
 import com.google.gwt.autobean.shared.AutoBean.PropertyName;
 import com.google.gwt.autobean.shared.AutoBeanFactory;
 import com.google.gwt.autobean.shared.AutoBeanUtils;
@@ -27,7 +28,7 @@
 /**
  * Handles dispatches on AutoBeanFactory interfaces.
  */
-class FactoryHandler implements InvocationHandler {
+public class FactoryHandler implements InvocationHandler {
   private final Configuration configuration;
 
   /**
diff --git a/user/src/com/google/gwt/autobean/server/GetterPropertyContext.java b/user/src/com/google/gwt/autobean/server/impl/GetterPropertyContext.java
similarity index 79%
rename from user/src/com/google/gwt/autobean/server/GetterPropertyContext.java
rename to user/src/com/google/gwt/autobean/server/impl/GetterPropertyContext.java
index ed904ea..7e61307 100644
--- a/user/src/com/google/gwt/autobean/server/GetterPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/server/impl/GetterPropertyContext.java
@@ -13,7 +13,8 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.autobean.server;
+package com.google.gwt.autobean.server.impl;
+
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -30,12 +31,16 @@
     this.shim = bean.as();
 
     // Look for the setter method.
-    Method found;
-    try {
-      found = bean.getBeanType().getMethod(
-          "set" + getter.getName().substring(3), getter.getReturnType());
-    } catch (NoSuchMethodException expected) {
-      found = null;
+    Method found = null;
+    String name = BeanMethod.GET.inferName(getter);
+    for (Method m : getter.getDeclaringClass().getMethods()) {
+      if (BeanMethod.SET.matches(m)) {
+        if (BeanMethod.SET.inferName(m).equals(name)
+            && getter.getReturnType().isAssignableFrom(m.getParameterTypes()[0])) {
+          found = m;
+          break;
+        }
+      }
     }
     setter = found;
   }
diff --git a/user/src/com/google/gwt/autobean/server/MethodPropertyContext.java b/user/src/com/google/gwt/autobean/server/impl/MethodPropertyContext.java
similarity index 96%
rename from user/src/com/google/gwt/autobean/server/MethodPropertyContext.java
rename to user/src/com/google/gwt/autobean/server/impl/MethodPropertyContext.java
index 315e185..b88d44b 100644
--- a/user/src/com/google/gwt/autobean/server/MethodPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/server/impl/MethodPropertyContext.java
@@ -13,9 +13,8 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.autobean.server;
+package com.google.gwt.autobean.server.impl;
 
-import com.google.gwt.autobean.server.impl.TypeUtils;
 import com.google.gwt.autobean.shared.AutoBeanVisitor.CollectionPropertyContext;
 import com.google.gwt.autobean.shared.AutoBeanVisitor.MapPropertyContext;
 
diff --git a/user/src/com/google/gwt/autobean/server/ProxyAutoBean.java b/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
similarity index 86%
rename from user/src/com/google/gwt/autobean/server/ProxyAutoBean.java
rename to user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
index 0992adc..4c93baa 100644
--- a/user/src/com/google/gwt/autobean/server/ProxyAutoBean.java
+++ b/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
@@ -13,9 +13,9 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.autobean.server;
+package com.google.gwt.autobean.server.impl;
 
-import com.google.gwt.autobean.server.impl.TypeUtils;
+import com.google.gwt.autobean.server.Configuration;
 import com.google.gwt.autobean.shared.AutoBean;
 import com.google.gwt.autobean.shared.AutoBeanFactory;
 import com.google.gwt.autobean.shared.AutoBeanUtils;
@@ -23,6 +23,7 @@
 import com.google.gwt.autobean.shared.impl.AbstractAutoBean;
 import com.google.gwt.core.client.impl.WeakMapping;
 
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
@@ -38,7 +39,7 @@
  * 
  * @param <T> the type of interface being wrapped
  */
-class ProxyAutoBean<T> extends AbstractAutoBean<T> {
+public class ProxyAutoBean<T> extends AbstractAutoBean<T> {
   private static class Data {
     final List<Method> getters = new ArrayList<Method>();
     final List<String> getterNames = new ArrayList<String>();
@@ -51,6 +52,31 @@
 
   private static final Map<Class<?>, Data> cache = new WeakHashMap<Class<?>, Data>();
 
+  /**
+   * Utility method to crete a new {@link Proxy} instance.
+   * 
+   * @param <T> the interface type to be implemented by the Proxy
+   * @param intf the Class representing the interface type
+   * @param handler the implementation of the interface
+   * @param extraInterfaces additional interface types the Proxy should
+   *          implement
+   * @return a Proxy instance
+   */
+  public static <T> T makeProxy(Class<T> intf, InvocationHandler handler,
+      Class<?>... extraInterfaces) {
+    Class<?>[] intfs;
+    if (extraInterfaces == null) {
+      intfs = new Class<?>[] {intf};
+    } else {
+      intfs = new Class<?>[extraInterfaces.length + 1];
+      intfs[0] = intf;
+      System.arraycopy(extraInterfaces, 0, intfs, 1, extraInterfaces.length);
+    }
+
+    return intf.cast(Proxy.newProxyInstance(intf.getClassLoader(), intfs,
+        handler));
+  }
+
   private static Data calculateData(Class<?> beanType) {
     Data toReturn;
     synchronized (cache) {
@@ -66,9 +92,7 @@
             if (annotation != null) {
               name = annotation.value();
             } else {
-              name = method.getName();
-              name = Character.toLowerCase(name.charAt(3))
-                  + (name.length() >= 5 ? name.substring(4) : "");
+              name = BeanMethod.GET.inferName(method);
             }
             toReturn.getterNames.add(name);
 
@@ -93,6 +117,7 @@
   private final Class<T> beanType;
   private final Configuration configuration;
   private final Data data;
+
   private final T shim;
 
   // These constructors mirror the generated constructors.
@@ -171,8 +196,7 @@
 
   @Override
   protected T createSimplePeer() {
-    return AutoBeanFactoryMagic.makeProxy(beanType, new SimpleBeanHandler<T>(
-        this));
+    return ProxyAutoBean.makeProxy(beanType, new SimpleBeanHandler<T>(this));
   }
 
   /**
@@ -293,8 +317,8 @@
   }
 
   private T createShim() {
-    T toReturn = AutoBeanFactoryMagic.makeProxy(beanType, new ShimHandler<T>(
-        this, getWrapped()));
+    T toReturn = ProxyAutoBean.makeProxy(beanType, new ShimHandler<T>(this,
+        getWrapped()));
     WeakMapping.set(toReturn, AutoBean.class.getName(), this);
     return toReturn;
   }
diff --git a/user/src/com/google/gwt/autobean/server/ShimHandler.java b/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
similarity index 97%
rename from user/src/com/google/gwt/autobean/server/ShimHandler.java
rename to user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
index 94103ae..d2930d6 100644
--- a/user/src/com/google/gwt/autobean/server/ShimHandler.java
+++ b/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
@@ -13,9 +13,8 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.autobean.server;
+package com.google.gwt.autobean.server.impl;
 
-import com.google.gwt.autobean.server.impl.TypeUtils;
 import com.google.gwt.autobean.shared.AutoBean;
 import com.google.gwt.autobean.shared.AutoBeanUtils;
 
diff --git a/user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java b/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
similarity index 97%
rename from user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java
rename to user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
index 9f0dc3f..d25468d 100644
--- a/user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java
+++ b/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.autobean.server;
+package com.google.gwt.autobean.server.impl;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
index 2ea756b..1e3419b 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.requestfactory.rebind.model;
 
+import com.google.gwt.autobean.rebind.model.JBeanMethod;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
@@ -236,6 +237,7 @@
 
       // Look at the methods declared on the EntityProxy
       List<RequestMethod> requestMethods = new ArrayList<RequestMethod>();
+      Map<String, JMethod> duplicatePropertyGetters = new HashMap<String, JMethod>();
       for (JMethod method : entityProxyType.getInheritableMethods()) {
         if (method.getEnclosingType().equals(entityProxyInterface)) {
           // Ignore methods on EntityProxy
@@ -246,11 +248,18 @@
 
         JType transportedType;
         String name = method.getName();
-        if (name.startsWith("get") && method.getParameters().length == 0) {
-          // Getter
+        if (JBeanMethod.GET.matches(method)) {
           transportedType = method.getReturnType();
+          String propertyName = JBeanMethod.GET.inferName(method);
+          JMethod previouslySeen = duplicatePropertyGetters.get(propertyName);
+          if (previouslySeen == null) {
+            duplicatePropertyGetters.put(propertyName, method);
+          } else {
+            poison("Duplicate accessors for property %s: %s() and %s()",
+                propertyName, previouslySeen.getName(), method.getName());
+          }
 
-        } else if (name.startsWith("set") && method.getParameters().length == 1) {
+        } else if (JBeanMethod.SET.matches(method)) {
           transportedType = method.getParameters()[0].getType();
 
         } else if (name.equals("stableId")
diff --git a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
index 4f20c7b..a04226b 100644
--- a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.requestfactory.server;
 
+import com.google.gwt.autobean.server.impl.BeanMethod;
 import com.google.gwt.autobean.server.impl.TypeUtils;
 import com.google.gwt.autobean.shared.ValueCodex;
 import com.google.gwt.requestfactory.shared.BaseProxy;
@@ -65,9 +66,19 @@
     jsr303Validator = found;
   }
 
-  private static String capitalize(String name) {
-    return Character.toUpperCase(name.charAt(0))
-        + (name.length() >= 1 ? name.substring(1) : "");
+  /**
+   * Linear search, but we want to handle getFoo, isFoo, and hasFoo. The result
+   * of this method will be cached by the ServiceLayerCache.
+   */
+  private static Method getBeanMethod(BeanMethod methodType,
+      Class<?> domainType, String property) {
+    for (Method m : domainType.getMethods()) {
+      if (methodType.matches(m) && property.equals(methodType.inferName(m))) {
+        m.setAccessible(true);
+        return m;
+      }
+    }
+    return null;
   }
 
   @Override
@@ -96,6 +107,11 @@
   }
 
   @Override
+  public Method getGetter(Class<?> domainType, String property) {
+    return getBeanMethod(BeanMethod.GET, domainType, property);
+  }
+
+  @Override
   public Object getId(Object domainObject) {
     return getTop().getProperty(domainObject, "id");
   }
@@ -107,25 +123,19 @@
 
   @Override
   public Object getProperty(Object domainObject, String property) {
-    Throwable toReport;
     try {
-      Method method = domainObject.getClass().getMethod(
-          "get" + capitalize(property));
-      method.setAccessible(true);
-      Object value = method.invoke(domainObject);
+      Method getter = getTop().getGetter(domainObject.getClass(), property);
+      if (getter == null) {
+        die(null, "Could not determine getter for property %s on type %s",
+            property, domainObject.getClass().getCanonicalName());
+      }
+      Object value = getter.invoke(domainObject);
       return value;
-    } catch (SecurityException e) {
-      toReport = e;
-    } catch (NoSuchMethodException e) {
-      toReport = e;
-    } catch (IllegalArgumentException e) {
-      toReport = e;
     } catch (IllegalAccessException e) {
-      toReport = e;
+      return die(e, "Could not retrieve property %s", property);
     } catch (InvocationTargetException e) {
       return report(e);
     }
-    return die(toReport, "Could not retrieve property %s", property);
   }
 
   @Override
@@ -147,6 +157,11 @@
   }
 
   @Override
+  public Method getSetter(Class<?> domainType, String property) {
+    return getBeanMethod(BeanMethod.SET, domainType, property);
+  }
+
+  @Override
   public Object getVersion(Object domainObject) {
     return getTop().getProperty(domainObject, "version");
   }
@@ -210,28 +225,19 @@
   @Override
   public void setProperty(Object domainObject, String property,
       Class<?> expectedType, Object value) {
-    Method setter;
-    Throwable ex;
     try {
-      setter = domainObject.getClass().getMethod("set" + capitalize(property),
-          expectedType);
-      setter.setAccessible(true);
+      Method setter = getTop().getSetter(domainObject.getClass(), property);
+      if (setter == null) {
+        die(null, "Could not locate setter for property %s in type %s",
+            property, domainObject.getClass().getCanonicalName());
+      }
       setter.invoke(domainObject, value);
       return;
-    } catch (SecurityException e) {
-      ex = e;
-    } catch (NoSuchMethodException e) {
-      ex = e;
-    } catch (IllegalArgumentException e) {
-      ex = e;
     } catch (IllegalAccessException e) {
-      ex = e;
+      die(e, "Could not set property %s", property);
     } catch (InvocationTargetException e) {
       report(e);
-      return;
     }
-    die(ex, "Could not locate setter for property %s in type %s", property,
-        domainObject.getClass().getCanonicalName());
   }
 
   @Override
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
index 8cd93a4..1c4062a 100644
--- a/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
@@ -44,6 +44,12 @@
    */
 
   /**
+   * Provides a flag to disable the ServiceLayerCache for debugging purposes.
+   */
+  private static final boolean ENABLE_CACHE = Boolean.valueOf(System.getProperty(
+      "gwt.rf.ServiceLayerCache", "true"));
+
+  /**
    * Create a RequestFactory ServiceLayer that is optionally modified by the
    * given decorators.
    * 
@@ -54,7 +60,8 @@
   public static ServiceLayer create(ServiceLayerDecorator... decorators) {
     List<ServiceLayerDecorator> list = new ArrayList<ServiceLayerDecorator>();
     // Always hit the cache first
-    ServiceLayerCache cache = new ServiceLayerCache();
+    ServiceLayerDecorator cache = ENABLE_CACHE ? new ServiceLayerCache()
+        : new ServiceLayerDecorator();
     list.add(cache);
     // The the user-provided decorators
     if (decorators != null) {
@@ -121,6 +128,16 @@
       Method domainMethod);
 
   /**
+   * Determine the method to invoke when retrieving the given property.
+   * 
+   * @param domainType a domain entity type
+   * @param property the name of the property to be retrieved
+   * @return the Method that should be invoked to retrieve the property or
+   *         {@code null} if the method could not be located
+   */
+  public abstract Method getGetter(Class<?> domainType, String property);
+
+  /**
    * Return the persistent id for a domain object. May return {@code null} to
    * indicate that the domain object has not been persisted. The value returned
    * from this method must be a simple type (e.g. Integer, String) or a domain
@@ -162,6 +179,16 @@
   public abstract Type getRequestReturnType(Method contextMethod);
 
   /**
+   * Determine the method to invoke when setting the given property.
+   * 
+   * @param domainType a domain entity type
+   * @param property the name of the property to be set
+   * @return the Method that should be invoked to set the property or
+   *         {@code null} if the method could not be located
+   */
+  public abstract Method getSetter(Class<?> domainType, String property);
+
+  /**
    * May return {@code null} to indicate that the domain object has not been
    * persisted. The value returned from this method must be a simple type (e.g.
    * Integer, String) or a domain type for which a mapping to an EntityProxy or
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java
index 0c89f75..80102cc 100644
--- a/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java
@@ -44,8 +44,10 @@
 
   private static final Method createLocator;
   private static final Method createServiceInstance;
+  private static final Method getGetter;
   private static final Method getIdType;
   private static final Method getRequestReturnType;
+  private static final Method getSetter;
   private static final Method requiresServiceLocator;
   private static final Method resolveClass;
   private static final Method resolveClientType;
@@ -60,8 +62,10 @@
     createLocator = getMethod("createLocator", Class.class);
     createServiceInstance = getMethod("createServiceInstance", Method.class,
         Method.class);
+    getGetter = getMethod("getGetter", Class.class, String.class);
     getIdType = getMethod("getIdType", Class.class);
     getRequestReturnType = getMethod("getRequestReturnType", Method.class);
+    getSetter = getMethod("getSetter", Class.class, String.class);
     requiresServiceLocator = getMethod("requiresServiceLocator", Method.class,
         Method.class);
     resolveClass = getMethod("resolveClass", String.class);
@@ -114,6 +118,12 @@
   }
 
   @Override
+  public Method getGetter(Class<?> domainType, String property) {
+    return getOrCache(getGetter, new Pair<Class<?>, String>(domainType,
+        property), Method.class, domainType, property);
+  }
+
+  @Override
   public Class<?> getIdType(Class<?> domainType) {
     return getOrCache(getIdType, domainType, Class.class, domainType);
   }
@@ -125,6 +135,12 @@
   }
 
   @Override
+  public Method getSetter(Class<?> domainType, String property) {
+    return getOrCache(getSetter, new Pair<Class<?>, String>(domainType,
+        property), Method.class, domainType, property);
+  }
+
+  @Override
   public boolean requiresServiceLocator(Method contextMethod,
       Method domainMethod) {
     return getOrCache(requiresServiceLocator, new Pair<Method, Method>(
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
index c642d94..3167acb 100644
--- a/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
@@ -60,6 +60,11 @@
   }
 
   @Override
+  public Method getGetter(Class<?> domainType, String property) {
+    return getNext().getGetter(domainType, property);
+  }
+
+  @Override
   public Object getId(Object domainObject) {
     return getNext().getId(domainObject);
   }
@@ -80,6 +85,11 @@
   }
 
   @Override
+  public Method getSetter(Class<?> domainType, String property) {
+    return getNext().getSetter(domainType, property);
+  }
+
+  @Override
   public Object getVersion(Object domainObject) {
     return getNext().getVersion(domainObject);
   }
diff --git a/user/test/com/google/gwt/autobean/client/AutoBeanTest.java b/user/test/com/google/gwt/autobean/client/AutoBeanTest.java
index c9484a1..759b93c 100644
--- a/user/test/com/google/gwt/autobean/client/AutoBeanTest.java
+++ b/user/test/com/google/gwt/autobean/client/AutoBeanTest.java
@@ -104,9 +104,13 @@
   interface OtherIntf {
     Intf getIntf();
 
+    HasBoolean getHasBoolean();
+
     UnreferencedInFactory getUnreferenced();
 
     void setIntf(Intf intf);
+
+    void setHasBoolean(HasBoolean value);
   }
 
   static class RealIntf implements Intf {
@@ -362,17 +366,26 @@
   public void testTraversal() {
     final AutoBean<OtherIntf> other = factory.otherIntf();
     final AutoBean<Intf> intf = factory.intf();
+    final AutoBean<HasBoolean> hasBoolean = factory.hasBoolean();
     other.as().setIntf(intf.as());
+    other.as().setHasBoolean(hasBoolean.as());
     intf.as().setInt(42);
+    hasBoolean.as().setGet(true);
+    hasBoolean.as().setHas(true);
+    hasBoolean.as().setIs(true);
 
     class Checker extends AutoBeanVisitor {
-      boolean seenOther;
+      boolean seenHasBoolean;
       boolean seenIntf;
+      boolean seenOther;
 
       @Override
       public void endVisitReferenceProperty(String propertyName,
           AutoBean<?> value, PropertyContext ctx) {
-        if ("intf".equals(propertyName)) {
+        if ("hasBoolean".equals(propertyName)) {
+          assertSame(hasBoolean, value);
+          assertEquals(HasBoolean.class, ctx.getType());
+        } else if ("intf".equals(propertyName)) {
           assertSame(intf, value);
           assertEquals(Intf.class, ctx.getType());
         } else if ("unreferenced".equals(propertyName)) {
@@ -392,6 +405,10 @@
         } else if ("string".equals(propertyName)) {
           assertNull(value);
           assertEquals(String.class, ctx.getType());
+        } else if ("get".equals(propertyName) || "has".equals(propertyName)
+            || "is".equals(propertyName)) {
+          assertEquals(boolean.class, ctx.getType());
+          assertTrue((Boolean) value);
         } else {
           fail("Unknown value property " + propertyName);
         }
@@ -399,10 +416,12 @@
 
       @Override
       public boolean visit(AutoBean<?> bean, Context ctx) {
-        if (bean == other) {
-          seenOther = true;
+        if (bean == hasBoolean) {
+          seenHasBoolean = true;
         } else if (bean == intf) {
           seenIntf = true;
+        } else if (bean == other) {
+          seenOther = true;
         } else {
           fail("Unknown AutoBean");
         }
@@ -410,8 +429,9 @@
       }
 
       void check() {
-        assertTrue(seenOther);
+        assertTrue(seenHasBoolean);
         assertTrue(seenIntf);
+        assertTrue(seenOther);
       }
     }
     Checker c = new Checker();
diff --git a/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java b/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java
index 348dfe3..798bdce 100644
--- a/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java
+++ b/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java
@@ -147,8 +147,16 @@
 
     String getString();
 
+    Boolean hasOtherBoolean();
+
+    boolean isBoolean();
+
+    void setBoolean(boolean b);
+
     void setInt(int i);
 
+    void setOtherBoolean(Boolean b);
+
     void setString(String s);
   }
 
@@ -265,11 +273,15 @@
   public void testSimple() {
     AutoBean<Simple> bean = f.simple();
     Simple simple = bean.as();
+    simple.setBoolean(true);
     simple.setInt(42);
+    simple.setOtherBoolean(true);
     simple.setString("Hello World!");
 
     AutoBean<Simple> decodedBean = checkEncode(bean);
     assertTrue(AutoBeanUtils.diff(bean, decodedBean).isEmpty());
+    assertTrue(decodedBean.as().isBoolean());
+    assertTrue(decodedBean.as().hasOtherBoolean());
 
     AutoBean<HasSimple> bean2 = f.hasSimple();
     bean2.as().setSimple(simple);
diff --git a/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java b/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
index ce68390..14f14fb 100644
--- a/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
+++ b/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
@@ -140,6 +140,11 @@
         "Invalid Request parameterization java.lang.Iterable");
   }
 
+  public void testDuplicateBooleanGetters() {
+    testModelWithMethodDecl("Request<t.ProxyWithRepeatedGetters> method();",
+        "Duplicate accessors for property foo: getFoo() and isFoo()");
+  }
+
   public void testMissingProxyFor() {
     testModelWithMethodDeclArgs("Request<TestProxy> okMethodProxy();",
         TestContextImpl.class.getName(), null,
@@ -233,6 +238,22 @@
         code.append("}");
         return code;
       }
+    }, new MockJavaResource("t.ProxyWithRepeatedGetters") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuilder code = new StringBuilder();
+        code.append("package t;\n");
+        code.append("import " + ProxyFor.class.getName() + ";\n");
+        code.append("import " + EntityProxy.class.getName() + ";\n");
+        if (proxyClass != null) {
+          code.append("@ProxyFor(" + proxyClass + ".class)");
+        }
+        code.append("interface ProxyWithRepeatedGetters extends EntityProxy {\n");
+        code.append("  boolean getFoo();");
+        code.append("  boolean isFoo();");
+        code.append("}");
+        return code;
+      }
     }, new MockJavaResource("java.util.List") {
         // Tests a Driver interface that extends more than RFED
       @Override
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
index dad490c..d02ce15 100644
--- a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
@@ -129,6 +129,8 @@
    * objects with a null version property.
    */
   public static SimpleFoo getSimpleFooWithNullVersion() {
+    System.err.println("The following exception about an entity with a null"
+        + " version is expected");
     SimpleFoo foo = new SimpleFoo();
     foo.setVersion(null);
     return foo;
diff --git a/user/test/com/google/gwt/requestfactory/shared/BoxesAndPrimitivesTest.java b/user/test/com/google/gwt/requestfactory/shared/BoxesAndPrimitivesTest.java
index ddcea1d..99fe784 100644
--- a/user/test/com/google/gwt/requestfactory/shared/BoxesAndPrimitivesTest.java
+++ b/user/test/com/google/gwt/requestfactory/shared/BoxesAndPrimitivesTest.java
@@ -51,6 +51,22 @@
       return 0;
     }
 
+    public boolean hasHas() {
+      return EXPECTED_BOOL;
+    }
+
+    public Boolean hasHasBoxed() {
+      return EXPECTED_BOOL_BOXED;
+    }
+
+    public boolean isIs() {
+      return EXPECTED_BOOL;
+    }
+
+    public Boolean isIsBoxed() {
+      return EXPECTED_BOOL_BOXED;
+    }
+
     public void setBoxed(Integer value) {
       assertEquals(EXPECTED_BOXED, value);
     }
@@ -111,6 +127,14 @@
 
     int getPrimitive();
 
+    boolean hasHas();
+
+    Boolean hasHasBoxed();
+
+    boolean isIs();
+
+    Boolean isIsBoxed();
+
     void setBoxed(Integer value);
 
     void setPrimitive(int value);
@@ -125,6 +149,8 @@
 
   private static final int EXPECTED = 42;
   private static final Integer EXPECTED_BOXED = Integer.valueOf(EXPECTED);
+  private static final boolean EXPECTED_BOOL = true;
+  private static final Boolean EXPECTED_BOOL_BOXED = Boolean.TRUE;
   private static final int TEST_DELAY = 5000;
 
   private Factory factory;
@@ -163,6 +189,10 @@
       public void onSuccess(Proxy response) {
         assertEquals(EXPECTED_BOXED, response.getBoxed());
         assertEquals(EXPECTED, response.getPrimitive());
+        assertEquals(EXPECTED_BOOL, response.isIs());
+        assertEquals(EXPECTED_BOOL_BOXED, response.isIsBoxed());
+        assertEquals(EXPECTED_BOOL, response.hasHas());
+        assertEquals(EXPECTED_BOOL_BOXED, response.hasHasBoxed());
       }
     });
     // Boxed service argument