Add @SkipInterfaceValidation to RequestFactory to selectively bypass matching
interface methods to domain methods.
http://gwt-code-reviews.appspot.com/1338807
Patch by: t.broyer
Review by: bobv


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10022 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/EntityProxyModel.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/EntityProxyModel.java
index 139aef0..87c2eac 100644
--- a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/EntityProxyModel.java
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/EntityProxyModel.java
@@ -44,10 +44,6 @@
       return toReturn;
     }
 
-    public void setProxyFor(Class<?> value) {
-      toReturn.proxyFor = value;
-    }
-
     public void setQualifiedBinaryName(String qualifiedBinaryName) {
       toReturn.qualifiedBinaryName = qualifiedBinaryName;
     }
@@ -74,7 +70,6 @@
     ENTITY, VALUE
   }
 
-  private Class<?> proxyFor;
   private String qualifiedBinaryName;
   private String qualifiedSourceName;
   private List<RequestMethod> requestMethods;
@@ -92,10 +87,6 @@
     visitor.endVisit(this);
   }
 
-  public Class<?> getProxyFor() {
-    return proxyFor;
-  }
-
   public String getQualifiedBinaryName() {
     return qualifiedBinaryName;
   }
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
index b6841ab..5e3dfe3 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -39,6 +39,7 @@
 import com.google.web.bindery.requestfactory.shared.RequestFactory;
 import com.google.web.bindery.requestfactory.shared.Service;
 import com.google.web.bindery.requestfactory.shared.ServiceName;
+import com.google.web.bindery.requestfactory.shared.SkipInterfaceValidation;
 import com.google.web.bindery.requestfactory.shared.ValueProxy;
 
 import java.io.IOException;
@@ -416,17 +417,27 @@
       if ("<clinit>".equals(name) || "<init>".equals(name)) {
         return null;
       }
-      RFMethod method = new RFMethod(name, desc);
+      final RFMethod method = new RFMethod(name, desc);
       method.setDeclaredStatic((access & Opcodes.ACC_STATIC) != 0);
       method.setDeclaredSignature(signature);
       methods.add(method);
-      return null;
+
+      return new EmptyVisitor() {
+        @Override
+        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+          if (desc.equals(Type.getDescriptor(SkipInterfaceValidation.class))) {
+            method.setValidationSkipped(true);
+          }
+          return null;
+        }
+      };
     }
   }
 
   private static class RFMethod extends Method {
     private boolean isDeclaredStatic;
     private String signature;
+    private boolean isValidationSkipped;
 
     public RFMethod(String name, String desc) {
       super(name, desc);
@@ -440,6 +451,10 @@
       return isDeclaredStatic;
     }
 
+    public boolean isValidationSkipped() {
+      return isValidationSkipped;
+    }
+
     public void setDeclaredSignature(String signature) {
       this.signature = signature;
     }
@@ -448,6 +463,10 @@
       isDeclaredStatic = value;
     }
 
+    public void setValidationSkipped(boolean isValidationSkipped) {
+      this.isValidationSkipped = isValidationSkipped;
+    }
+
     @Override
     public String toString() {
       return (isDeclaredStatic ? "static " : "") + super.toString();
@@ -727,7 +746,7 @@
    * annotation</li>
    * <li>The domain object has getId() and getVersion() methods</li>
    * <li>All property methods in the EntityProxy can be mapped onto an
-   * equivalent domain method</li>
+   * equivalent domain method (unless validation is skipped for the method)</li>
    * <li>All referenced proxy types are valid</li>
    * </ul>
    * 
@@ -778,7 +797,7 @@
    * <li><code>binaryName</code> has a {@link Service} or {@link ServiceName}
    * annotation</li>
    * <li>All service methods in the RequestContext can be mapped onto an
-   * equivalent domain method</li>
+   * equivalent domain method (unless validation is skipped for the method)</li>
    * <li>All referenced EntityProxy types are valid</li>
    * </ul>
    * 
@@ -886,7 +905,7 @@
    * <li><code>binaryName</code> has a {@link ProxyFor} or {@link ProxyForName}
    * annotation</li>
    * <li>All property methods in the EntityProxy can be mapped onto an
-   * equivalent domain method</li>
+   * equivalent domain method (unless validation is skipped for the method)</li>
    * <li>All referenced proxy types are valid</li>
    * </ul>
    * 
@@ -985,7 +1004,7 @@
         returnType, method.getArgumentTypes()));
 
     RFMethod found = findCompatibleServiceMethod(logger, domainServiceType,
-        searchFor);
+        searchFor, !method.isValidationSkipped());
 
     if (found != null) {
       boolean isInstance = isAssignable(logger, instanceRequestIntf,
@@ -1069,11 +1088,12 @@
    * domain object.
    */
   private void checkPropertyMethod(ErrorContext logger,
-      Method clientPropertyMethod, Type domainType) {
+      RFMethod clientPropertyMethod, Type domainType) {
     logger = logger.setMethod(clientPropertyMethod);
 
     findCompatiblePropertyMethod(logger, domainType,
-        createDomainMethod(logger, clientPropertyMethod));
+        createDomainMethod(logger, clientPropertyMethod),
+        !clientPropertyMethod.isValidationSkipped());
   }
 
   private void checkUnresolvedKeyTypes(ErrorContext logger) {
@@ -1207,8 +1227,8 @@
    * hierarchy that is assignment-compatible with the given Method.
    */
   private RFMethod findCompatiblePropertyMethod(final ErrorContext logger,
-      Type domainType, Method searchFor) {
-    return findCompatibleMethod(logger, domainType, searchFor, true, false,
+      Type domainType, Method searchFor, boolean mustFind) {
+    return findCompatibleMethod(logger, domainType, searchFor, mustFind, false,
         false);
   }
 
@@ -1217,8 +1237,8 @@
    * hierarchy that is assignment-compatible with the given Method.
    */
   private RFMethod findCompatibleServiceMethod(final ErrorContext logger,
-      Type domainType, Method searchFor) {
-    return findCompatibleMethod(logger, domainType, searchFor, true, false,
+      Type domainType, Method searchFor, boolean mustFind) {
+    return findCompatibleMethod(logger, domainType, searchFor, mustFind, false,
         true);
   }
 
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java b/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java
new file mode 100644
index 0000000..ad6575f
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java
@@ -0,0 +1,35 @@
+/*
+ * 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.web.bindery.requestfactory.shared;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation on methods of {@link RequestContext}, {@link EntityProxy}, or
+ * {@link ValueProxy} interfaces so that the
+ * {@link com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator
+ * RequestFactoryInterfaceValidator} doesn't enforce the presence of a
+ * corresponding method on the domain type.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface SkipInterfaceValidation {
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
index 98fa37d..698b2b4 100644
--- a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -27,6 +27,7 @@
 import com.google.web.bindery.requestfactory.shared.Service;
 import com.google.web.bindery.requestfactory.shared.ServiceName;
 import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+import com.google.web.bindery.requestfactory.shared.SkipInterfaceValidation;
 import com.google.web.bindery.requestfactory.shared.ValueProxy;
 import com.google.web.bindery.requestfactory.shared.impl.FindRequest;
 
@@ -247,6 +248,28 @@
     Request<Integer> doesNotExist(int a);
   }
 
+  @Service(Domain.class)
+  interface SkipValidationContext extends RequestContext {
+    @SkipInterfaceValidation
+    Request<Integer> doesNotExist(int a);
+
+    @SkipInterfaceValidation
+    Request<Long> foo(int a);
+  }
+
+  @Service(Domain.class)
+  interface SkipValidationProxy extends ValueProxy {
+    @SkipInterfaceValidation
+    boolean doesNotExist();
+  }
+
+  @Service(Domain.class)
+  interface SkipValidationChecksReferredProxies extends ValueProxy {
+    @SkipInterfaceValidation
+    // still validates other proxies
+    DomainProxyMissingAnnotation getDomainProxyMissingAnnotation();
+  }
+
   @ProxyFor(Domain.class)
   @ProxyForName("Domain")
   @Service(Domain.class)
@@ -421,6 +444,21 @@
     assertTrue(v.isPoisoned());
   }
 
+  public void testSkipValidationChecksReferredProxies() {
+    v.validateValueProxy(SkipValidationChecksReferredProxies.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  public void testSkipValidationContext() {
+    v.validateRequestContext(SkipValidationContext.class.getName());
+    assertFalse(v.isPoisoned());
+  }
+
+  public void testSkipValidationProxy() {
+    v.validateValueProxy(SkipValidationProxy.class.getName());
+    assertFalse(v.isPoisoned());
+  }
+
   /**
    * Perform a full test of the RequestFactory used for most tests.
    */