Add support for mapping ConstraintViolation objects into SimpleBeanEditor.
Patch by: bobv
Review by: rjrjr

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9562 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/build.xml b/build.xml
index 7bc3744..0254f39 100755
--- a/build.xml
+++ b/build.xml
@@ -184,6 +184,9 @@
       <arg file="${gwt.apicheck.config}"/>
       <arg value="-logLevel"/>
       <arg value="ERROR"/>
+      <!-- Needed for checking types that include validation APIs -->
+      <arg value="-validationSourceJars" />
+      <arg path="${gwt.tools.lib}/javax/validation/validation-api-1.0.0.GA-sources.jar" />
     </java>
   </target>
 
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiCompatibilityChecker.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiCompatibilityChecker.java
index 185ff22..61c3b5b 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiCompatibilityChecker.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiCompatibilityChecker.java
@@ -67,19 +67,19 @@
  * <p>
  * An {@code ApiContainer} object is a list of {@link ApiPackage} objects.
  * {@code ApiPackage} objects themselves are list of {@link ApiClass} objects.
- * {@code ApiClass} objects contain list of {@code ApiConstructor}, {@code
- * ApiMethod}, and {@code JField} objects.
+ * {@code ApiClass} objects contain list of {@code ApiConstructor},
+ * {@code ApiMethod}, and {@code JField} objects.
  * </p>
  * 
  * <p>
  * Each {@code ApiDiffGenerator} object computes the list of intersecting and
- * missing {@link ApiPackageDiffGenerator} objects. Each {@code
- * ApiPackageDiffGenerator} object in turn computes the list of intersecting and
- * missing {@link ApiClassDiffGenerator} objects. Each {@code
- * ApiClassDiffGenerator} object in turn computes the list of intersecting and
- * missing API members. The members are represented by {@link ApiConstructor}
- * for constructors, {@link ApiMethod} for methods, and {@link ApiField} for
- * fields.
+ * missing {@link ApiPackageDiffGenerator} objects. Each
+ * {@code ApiPackageDiffGenerator} object in turn computes the list of
+ * intersecting and missing {@link ApiClassDiffGenerator} objects. Each
+ * {@code ApiClassDiffGenerator} object in turn computes the list of
+ * intersecting and missing API members. The members are represented by
+ * {@link ApiConstructor} for constructors, {@link ApiMethod} for methods, and
+ * {@link ApiField} for fields.
  * </p>
  * 
  * <p>
@@ -176,65 +176,6 @@
   }
 
   /**
-   * Abstract internal class that specifies a set of {@link CompilationUnit}.
-   */
-  private abstract static class Resources {
-    protected final TreeLogger logger;
-
-    Resources(TreeLogger logger) {
-      this.logger = logger;
-    }
-
-    public abstract Set<Resource> getResources() throws NotFoundException,
-        IOException, UnableToCompleteException;
-
-    // TODO (amitmanjhi): remove this code. use TypeOracle functionality
-    // instead.
-    protected String extractPackageName(Reader reader) throws IOException {
-      BufferedReader br = new BufferedReader(reader);
-      String str = null;
-      while ((str = br.readLine()) != null) {
-        if (str.indexOf("package") != 0) {
-          continue;
-        }
-        String parts[] = str.split("[\b\t\n\r ]+");
-        if ((parts.length == 2) && parts[0].equals("package")) {
-          return parts[1].substring(0, parts[1].length() - 1); // the ; char
-        }
-      }
-      return null;
-    }
-
-    protected File getFileFromName(String tag, String pathName)
-        throws FileNotFoundException {
-      File file = new File(pathName);
-      if (!file.exists()) {
-        throw new FileNotFoundException(tag + "file " + pathName + " not found");
-      }
-      return file;
-    }
-
-    // TODO (amitmanjhi): remove this code. use TypeOracle functionality
-    // instead.
-    protected boolean isValidPackage(String packageName, String filePath) {
-      logger.log(TreeLogger.SPAM, "packageName = " + packageName
-          + ", filePath = " + filePath, null);
-      if (packageName == null) {
-        return false;
-      }
-      int lastSlashPosition = filePath.lastIndexOf("/");
-      if (lastSlashPosition == -1) {
-        return false;
-      }
-      String dirPath = filePath.substring(0, lastSlashPosition);
-      String packageNameAsPath = packageName.replace('.', '/');
-      logger.log(TreeLogger.SPAM, "packageNameAsPath " + packageNameAsPath
-          + ", dirPath = " + dirPath, null);
-      return dirPath.endsWith(packageNameAsPath);
-    }
-  }
-
-  /**
    * Class that specifies a set of {@link CompilationUnit} read from jar files.
    */
   private static class JarFileResources extends Resources {
@@ -349,6 +290,65 @@
   }
 
   /**
+   * Abstract internal class that specifies a set of {@link CompilationUnit}.
+   */
+  private abstract static class Resources {
+    protected final TreeLogger logger;
+
+    Resources(TreeLogger logger) {
+      this.logger = logger;
+    }
+
+    public abstract Set<Resource> getResources() throws NotFoundException,
+        IOException, UnableToCompleteException;
+
+    // TODO (amitmanjhi): remove this code. use TypeOracle functionality
+    // instead.
+    protected String extractPackageName(Reader reader) throws IOException {
+      BufferedReader br = new BufferedReader(reader);
+      String str = null;
+      while ((str = br.readLine()) != null) {
+        if (str.indexOf("package") != 0) {
+          continue;
+        }
+        String parts[] = str.split("[\b\t\n\r ]+");
+        if ((parts.length == 2) && parts[0].equals("package")) {
+          return parts[1].substring(0, parts[1].length() - 1); // the ; char
+        }
+      }
+      return null;
+    }
+
+    protected File getFileFromName(String tag, String pathName)
+        throws FileNotFoundException {
+      File file = new File(pathName);
+      if (!file.exists()) {
+        throw new FileNotFoundException(tag + "file " + pathName + " not found");
+      }
+      return file;
+    }
+
+    // TODO (amitmanjhi): remove this code. use TypeOracle functionality
+    // instead.
+    protected boolean isValidPackage(String packageName, String filePath) {
+      logger.log(TreeLogger.SPAM, "packageName = " + packageName
+          + ", filePath = " + filePath, null);
+      if (packageName == null) {
+        return false;
+      }
+      int lastSlashPosition = filePath.lastIndexOf("/");
+      if (lastSlashPosition == -1) {
+        return false;
+      }
+      String dirPath = filePath.substring(0, lastSlashPosition);
+      String packageNameAsPath = packageName.replace('.', '/');
+      logger.log(TreeLogger.SPAM, "packageNameAsPath " + packageNameAsPath
+          + ", dirPath = " + dirPath, null);
+      return dirPath.endsWith(packageNameAsPath);
+    }
+  }
+
+  /**
    * Class that specifies a set of {@link CompilationUnit} read from the
    * file-system.
    */
@@ -438,8 +438,9 @@
                 fileName.length() - 5);
             String pkgName = extractPackageName(new FileReader(file));
             if (pkgName == null) {
-              logger.log(TreeLogger.WARN, "Not adding file = "
-                  + file.toString() + ", because packageName = null", null);
+              logger.log(TreeLogger.WARN,
+                  "Not adding file = " + file.toString()
+                      + ", because packageName = null", null);
             } else {
               if (isValidPackage(pkgName,
                   sourcePathEntry.toURI().toURL().toString())) {
@@ -520,10 +521,12 @@
 
       AbstractTreeLogger logger = new PrintWriterTreeLogger();
       logger.setMaxDetail(checker.type);
-      logger.log(TreeLogger.INFO, "gwtDevJar = " + checker.gwtDevJar
-          + ", userJar = " + checker.gwtUserJar + ", refjars = "
-          + Arrays.toString(checker.refJars) + ", logLevel = " + checker.type
-          + ", printAllApi = " + checker.printAllApi, null);
+      logger.log(
+          TreeLogger.INFO,
+          "gwtDevJar = " + checker.gwtDevJar + ", userJar = "
+              + checker.gwtUserJar + ", refjars = "
+              + Arrays.toString(checker.refJars) + ", logLevel = "
+              + checker.type + ", printAllApi = " + checker.printAllApi, null);
 
       Set<String> excludedPackages = checker.getSetOfExcludedPackages(checker.configProperties);
       if (PROCESS_NEW_API) {
@@ -532,6 +535,7 @@
             checker.configProperties.getProperty("dirRoot_new"),
             checker.getConfigPropertyAsSet("sourceFiles_new"),
             checker.getConfigPropertyAsSet("excludedFiles_new"), logger).getResources());
+        resources.addAll(checker.getJavaxValidationCompilationUnits(logger));
         resources.addAll(checker.getGwtCompilationUnits(logger));
         newApi = new ApiContainer(
             checker.configProperties.getProperty("name_new"), resources,
@@ -552,6 +556,7 @@
               checker.getConfigPropertyAsSet("sourceFiles_old"),
               checker.getConfigPropertyAsSet("excludedFiles_old"), logger).getResources());
         }
+        resources.addAll(checker.getJavaxValidationCompilationUnits(logger));
         resources.addAll(checker.getGwtCompilationUnits(logger));
         existingApi = new ApiContainer(
             checker.configProperties.getProperty("name_old"), resources,
@@ -631,7 +636,7 @@
   }
 
   private Properties configProperties;
-
+  private JarFile[] extraSourceJars;
   private JarFile gwtDevJar;
   private JarFile gwtUserJar;
 
@@ -817,6 +822,35 @@
         return configProperties != null && whiteList != null;
       }
     });
+
+    registerHandler(new ArgHandlerString() {
+
+      @Override
+      public String getPurpose() {
+        return "The location of the javax.validation sources";
+      }
+
+      @Override
+      public String getTag() {
+        return "-validationSourceJars";
+      }
+
+      @Override
+      public String[] getTagArgs() {
+        return new String[] {"jar1.jar:jar2.jar"};
+      }
+
+      @Override
+      public boolean setString(String str) {
+        boolean success = true;
+        String[] parts = str.split(System.getProperty("path.separator"));
+        extraSourceJars = new JarFile[parts.length];
+        for (int i = 0, j = parts.length; i < j; i++) {
+          extraSourceJars[i] = getJarFromString(parts[i]);
+        }
+        return success;
+      }
+    });
   }
 
   @Override
@@ -941,6 +975,25 @@
     return resources;
   }
 
+  /**
+   * This is a hack to make the ApiChecker able to find the javax.validation
+   * sources, which we include through an external jar file.
+   */
+  private Set<Resource> getJavaxValidationCompilationUnits(TreeLogger logger)
+      throws UnableToCompleteException, NotFoundException, IOException {
+    Set<Resource> resources = new HashSet<Resource>();
+    if (extraSourceJars != null) {
+      Resources extra = new JarFileResources(extraSourceJars,
+          Collections.singleton(""), new HashSet<String>(Arrays.asList(
+              "javax/validation/Validation.java",
+              "javax/validation/constraints/Pattern.java")), logger);
+      Set<Resource> loaded = extra.getResources();
+      System.out.println("Found " + loaded.size() + " new resources");
+      resources.addAll(loaded);
+    }
+    return resources;
+  }
+
   /*
    * excludedPackages is used in only one place: to determine whether some class
    * is an api class or not
diff --git a/user/src/com/google/gwt/editor/Editor.gwt.xml b/user/src/com/google/gwt/editor/Editor.gwt.xml
index 1fd667d..f5146c6 100644
--- a/user/src/com/google/gwt/editor/Editor.gwt.xml
+++ b/user/src/com/google/gwt/editor/Editor.gwt.xml
@@ -14,6 +14,7 @@
 <!-- Editor framework support -->
 <module>
   <inherits name="com.google.gwt.core.Core" />
+  <inherits name="com.google.gwt.validation.Validation" />
 
   <source path="client"/>
   <source path="shared"/>
diff --git a/user/src/com/google/gwt/editor/client/SimpleBeanEditorDriver.java b/user/src/com/google/gwt/editor/client/SimpleBeanEditorDriver.java
index 7ea8245..5b5168a 100644
--- a/user/src/com/google/gwt/editor/client/SimpleBeanEditorDriver.java
+++ b/user/src/com/google/gwt/editor/client/SimpleBeanEditorDriver.java
@@ -17,6 +17,8 @@
 
 import java.util.List;
 
+import javax.validation.ConstraintViolation;
+
 /**
  * Automates editing of simple bean-like objects. The {@link EditorDelegate}
  * provided from this driver has a no-op implementation of
@@ -34,6 +36,9 @@
  * instance.flush();
  * }
  * </pre>
+ * <p>
+ * Note that this interface is intended to be implemented by generated code and
+ * is subject to API expansion in the future.
  * 
  * @param <T> the type being edited
  * @param <E> the Editor for the type
@@ -77,4 +82,17 @@
    * @param editor the Editor to populate
    */
   void initialize(E editor);
+
+  /**
+   * Show {@link ConstraintViolation ConstraintViolations} generated through a
+   * {@link javax.validation.Validator Validator}. The violations will be
+   * converted into {@link EditorError} objects whose
+   * {@link EditorError#getUserData() getUserData()} method can be used to
+   * access the original ConstraintViolation object.
+   * 
+   * @param violations an Iterable over {@link ConstraintViolation} instances
+   * @return <code>true</code> if there were any unconsumed EditorErrors which
+   *         can be retrieved from {@link #getErrors()}
+   */
+  boolean setConstraintViolations(Iterable<ConstraintViolation<?>> violations);
 }
diff --git a/user/src/com/google/gwt/editor/client/impl/AbstractSimpleBeanEditorDriver.java b/user/src/com/google/gwt/editor/client/impl/AbstractSimpleBeanEditorDriver.java
index 5bfc2f4..55330b8 100644
--- a/user/src/com/google/gwt/editor/client/impl/AbstractSimpleBeanEditorDriver.java
+++ b/user/src/com/google/gwt/editor/client/impl/AbstractSimpleBeanEditorDriver.java
@@ -22,6 +22,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.validation.ConstraintViolation;
+
 /**
  * A base implementation class for generated SimpleBeanEditorDriver
  * implementations.
@@ -65,6 +67,19 @@
     this.editor = editor;
   }
 
+  public boolean setConstraintViolations(
+      final Iterable<ConstraintViolation<?>> violations) {
+    checkDelegate();
+    SimpleViolation.pushViolations(
+        SimpleViolation.iterableFromConstrantViolations(violations),
+        delegateMap);
+
+    // Flush the errors, which will take care of co-editor chains.
+    errors = new ArrayList<EditorError>();
+    delegate.flushErrors(errors);
+    return hasErrors();
+  }
+
   protected abstract SimpleBeanEditorDelegate<T, E> createDelegate();
 
   /**
diff --git a/user/src/com/google/gwt/editor/client/impl/SimpleViolation.java b/user/src/com/google/gwt/editor/client/impl/SimpleViolation.java
new file mode 100644
index 0000000..b297bac
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/impl/SimpleViolation.java
@@ -0,0 +1,162 @@
+/*
+ * 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.editor.client.impl;
+
+import java.util.Iterator;
+import java.util.List;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * Abstraction of a ConstraintViolation or a RequestFactory Violation object.
+ * Also contains a factory method to create SimpleViolation instances from
+ * {@link ConstraintViolation} objects.
+ */
+public abstract class SimpleViolation {
+  /**
+   * Provides a source of SimpleViolation objects based on ConstraintViolations.
+   * This is re-used by the RequestFactoryEditorDriver implementation, which
+   * does not share a type hierarchy with the SimpleBeanEditorDriver.
+   */
+  static class ConstraintViolationIterable implements Iterable<SimpleViolation> {
+
+    private final Iterable<ConstraintViolation<?>> violations;
+
+    public ConstraintViolationIterable(
+        Iterable<ConstraintViolation<?>> violations) {
+      this.violations = violations;
+    }
+
+    public Iterator<SimpleViolation> iterator() {
+      // Use a fresh source iterator each time
+      final Iterator<ConstraintViolation<?>> source = violations.iterator();
+      return new Iterator<SimpleViolation>() {
+        public boolean hasNext() {
+          return source.hasNext();
+        }
+
+        public SimpleViolation next() {
+          return new SimpleViolationAdapter(source.next());
+        }
+
+        public void remove() {
+          source.remove();
+        }
+      };
+    }
+  }
+
+  /**
+   * Adapts the ConstraintViolation interface to the SimpleViolation interface.
+   */
+  static class SimpleViolationAdapter extends SimpleViolation {
+    private final ConstraintViolation<?> v;
+
+    public SimpleViolationAdapter(ConstraintViolation<?> v) {
+      this.v = v;
+    }
+
+    public Object getKey() {
+      return v.getLeafBean();
+    }
+
+    public String getMessage() {
+      return v.getMessage();
+    }
+
+    public String getPath() {
+      /*
+       * TODO(bobv,nchalko): Determine the correct way to extract this
+       * information from the ConstraintViolation.
+       */
+      return v.getPropertyPath().toString();
+    }
+
+    public Object getUserDataObject() {
+      return v;
+    }
+  }
+
+  public static Iterable<SimpleViolation> iterableFromConstrantViolations(
+      Iterable<ConstraintViolation<?>> violations) {
+    return new ConstraintViolationIterable(violations);
+  }
+
+  /**
+   * Maps an abstract representation of a violation into the appropriate
+   * EditorDelegate.
+   */
+  public static void pushViolations(Iterable<SimpleViolation> violations,
+      DelegateMap delegateMap) {
+    // For each violation
+    for (SimpleViolation error : violations) {
+      Object key = error.getKey();
+      List<AbstractEditorDelegate<?, ?>> delegateList = delegateMap.get(key);
+      if (delegateList != null) {
+
+        // For each delegate editing some record...
+        for (AbstractEditorDelegate<?, ?> baseDelegate : delegateList) {
+
+          // compute its base path in the hierarchy...
+          String basePath = baseDelegate.getPath();
+
+          // and the absolute path of the leaf editor receiving the error.
+          String absolutePath = (basePath.length() > 0 ? basePath + "." : "")
+              + error.getPath();
+
+          // Find the leaf editor's delegate.
+          List<AbstractEditorDelegate<?, ?>> leafDelegates = delegateMap.getPath(absolutePath);
+          if (leafDelegates != null) {
+            // Only attach the error to the first delegate in a co-editor chain.
+            leafDelegates.get(0).recordError(error.getMessage(), null,
+                error.getUserDataObject());
+          } else {
+            // No EditorDelegate to attach it to, stick it on the base.
+            baseDelegate.recordError(error.getMessage(), null,
+                error.getUserDataObject(), error.getPath());
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Typically constructed via factory methods.
+   */
+  protected SimpleViolation() {
+  }
+
+  /**
+   * Return the object that the violation is about.
+   */
+  public abstract Object getKey();
+
+  /**
+   * Return a user-facing message describing the violation.
+   */
+  public abstract String getMessage();
+
+  /**
+   * Return a dotted path describing the property.
+   */
+  public abstract String getPath();
+
+  /**
+   * An object that should be available from
+   * {@link com.google.gwt.editor.client.EditorError#getUserData()}.
+   */
+  public abstract Object getUserDataObject();
+}
diff --git a/user/src/com/google/gwt/editor/client/testing/MockSimpleBeanEditorDriver.java b/user/src/com/google/gwt/editor/client/testing/MockSimpleBeanEditorDriver.java
index a36670a..01bb3f1 100644
--- a/user/src/com/google/gwt/editor/client/testing/MockSimpleBeanEditorDriver.java
+++ b/user/src/com/google/gwt/editor/client/testing/MockSimpleBeanEditorDriver.java
@@ -22,6 +22,8 @@
 import java.util.Collections;
 import java.util.List;
 
+import javax.validation.ConstraintViolation;
+
 /**
  * A no-op implementation of {@link SimpleBeanEditorDriver} that records its
  * inputs.
@@ -84,4 +86,12 @@
   public void initialize(E editor) {
     this.editor = editor;
   }
+
+  /**
+   * A no-op method that always returns false.
+   */
+  public boolean setConstraintViolations(
+      Iterable<ConstraintViolation<?>> violations) {
+    return false;
+  }
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
index 1afa4e8..cf20de9 100644
--- a/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
@@ -24,6 +24,8 @@
 
 import java.util.List;
 
+import javax.validation.ConstraintViolation;
+
 /**
  * The interface that links RequestFactory and the Editor framework together.
  * Used for configuration and lifecycle control. Expected that this will be
@@ -42,17 +44,18 @@
  * instance.flush().fire(new Receiver {...});
  * }
  * </pre>
- *
- * <p> See {@code com.google.gwt.requestfactory.client.testing.MockRequestFactoryEditorDriver}
+ * 
+ * <p>
  * 
  * @param <P> the type of Proxy being edited
  * @param <E> the type of Editor that will edit the Record
+ * @see {@link com.google.gwt.requestfactory.client.testing.MockRequestFactoryEditorDriver}
  */
 public interface RequestFactoryEditorDriver<P, E extends Editor<? super P>> {
   /**
    * Start driving the Editor and its sub-editors with data for display-only
    * mode.
-   *
+   * 
    * @param proxy a Proxy of type P
    */
   void display(P proxy);
@@ -60,8 +63,8 @@
   /**
    * Start driving the Editor and its sub-editors with data. A
    * {@link RequestContext} is required to provide context for the changes to
-   * the proxy (see {@link RequestContext#edit}. Note that this driver will
-   * not fire the request.
+   * the proxy (see {@link RequestContext#edit}. Note that this driver will not
+   * fire the request.
    * 
    * @param proxy the proxy to be edited
    * @param request the request context that will accumulate edits and is
@@ -83,21 +86,21 @@
   /**
    * Returns any unconsumed {@link EditorError EditorErrors} from the last call
    * to {@link #flush()}.
-   *
+   * 
    * @return a List of {@link EditorError} instances
    */
   List<EditorError> getErrors();
 
   /**
    * Returns a new array containing the request paths.
-   *
+   * 
    * @return an array of Strings
    */
   String[] getPaths();
 
   /**
    * Indicates if the last call to {@link #flush()} resulted in any errors.
-   *
+   * 
    * @return {@code} true if errors are present
    */
   boolean hasErrors();
@@ -105,7 +108,7 @@
   /**
    * Overload of {@link #initialize(RequestFactory, Editor)} to allow a modified
    * {@link EventBus} to be monitored for subscription services.
-   *
+   * 
    * @param eventBus the {@link EventBus}
    * @param requestFactory a {@link RequestFactory} instance
    * @param editor an {@link Editor} of type E
@@ -118,7 +121,7 @@
   /**
    * Initializes a driver with the editor it will run, and a RequestFactory to
    * use for subscription services.
-   *
+   * 
    * @param requestFactory a {@link RequestFactory} instance
    * @param editor an {@link Editor} of type E
    * 
@@ -130,21 +133,33 @@
    * Initializes a driver that will not be able to support subscriptions. Calls
    * to {@link com.google.gwt.editor.client.EditorDelegate#subscribe()} will do
    * nothing.
-   *
+   * 
    * @param editor an {@link Editor} of type E
    */
   void initialize(E editor);
 
   /**
+   * Show {@link ConstraintViolation ConstraintViolations} generated through a
+   * JSR 303 Validator. The violations will be converted into
+   * {@link EditorError} objects whose {@link EditorError#getUserData()
+   * getUserData()} method can be used to access the original
+   * ConstraintViolation object.
+   * 
+   * @param violations an Iterable over {@link ConstraintViolation} instances
+   * @return <code>true</code> if there were any unconsumed EditorErrors which
+   *         can be retrieved from {@link #getErrors()}
+   */
+  boolean setConstraintViolations(Iterable<ConstraintViolation<?>> violations);
+
+  /**
    * Show Violations returned from an attempt to submit a request. The
    * violations will be converted into {@link EditorError} objects whose
    * {@link EditorError#getUserData() getUserData()} method can be used to
    * access the original Violation object.
-   *
-   * @param errors a Iterable over {@link Violation} instances
    * 
+   * @param violations an Iterable over {@link Violation} instances
    * @return <code>true</code> if there were any unconsumed EditorErrors which
    *         can be retrieved from {@link #getErrors()}
    */
-  boolean setViolations(Iterable<Violation> errors);
+  boolean setViolations(Iterable<Violation> violations);
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
index 2e22c51..416c984 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
@@ -17,8 +17,8 @@
 
 import com.google.gwt.editor.client.Editor;
 import com.google.gwt.editor.client.EditorError;
-import com.google.gwt.editor.client.impl.AbstractEditorDelegate;
 import com.google.gwt.editor.client.impl.DelegateMap;
+import com.google.gwt.editor.client.impl.SimpleViolation;
 import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.requestfactory.client.RequestFactoryEditorDriver;
 import com.google.gwt.requestfactory.shared.EntityProxy;
@@ -28,8 +28,11 @@
 import com.google.gwt.requestfactory.shared.Violation;
 
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
+import javax.validation.ConstraintViolation;
+
 /**
  * Contains utility methods for top-level driver implementations.
  * 
@@ -40,6 +43,66 @@
     implements RequestFactoryEditorDriver<R, E> {
 
   /**
+   * Adapts a RequestFactory Violation object to the SimpleViolation interface.
+   */
+  static class SimpleViolationAdapter extends SimpleViolation {
+    private final Violation v;
+
+    /**
+     * @param source
+     */
+    private SimpleViolationAdapter(Violation v) {
+      this.v = v;
+    }
+
+    public Object getKey() {
+      return v.getOriginalProxy();
+    }
+
+    public String getMessage() {
+      return v.getMessage();
+    }
+
+    public String getPath() {
+      return v.getPath();
+    }
+
+    public Object getUserDataObject() {
+      return v;
+    }
+  }
+
+  /**
+   * Provides a source of SimpleViolation objects based on RequestFactory's
+   * simplified Violation interface.
+   */
+  static class ViolationIterable implements Iterable<SimpleViolation> {
+
+    private final Iterable<Violation> violations;
+
+    public ViolationIterable(Iterable<Violation> violations) {
+      this.violations = violations;
+    }
+
+    public Iterator<SimpleViolation> iterator() {
+      final Iterator<Violation> source = violations.iterator();
+      return new Iterator<SimpleViolation>() {
+        public boolean hasNext() {
+          return source.hasNext();
+        }
+
+        public SimpleViolation next() {
+          return new SimpleViolationAdapter(source.next());
+        }
+
+        public void remove() {
+          source.remove();
+        }
+      };
+    }
+  }
+
+  /**
    * Since the ValueProxy is being mutated in-place, we need a way to stabilize
    * its hashcode for future equality checks.
    */
@@ -130,51 +193,13 @@
     initialize(requestFactory.getEventBus(), requestFactory, editor);
   }
 
+  public boolean setConstraintViolations(
+      Iterable<ConstraintViolation<?>> violations) {
+    return doSetViolations(SimpleViolation.iterableFromConstrantViolations(violations));
+  }
+
   public boolean setViolations(Iterable<Violation> violations) {
-    checkDelegate();
-
-    // For each violation
-    for (Violation error : violations) {
-
-      /*
-       * Find the delegates that are attached to the object. Use getRaw() here
-       * since the violation doesn't include an EntityProxy reference
-       */
-      Object key = error.getInvalidProxy();
-      // Object key = error.getOriginalProxy();
-      // if (key == null) {
-      // }
-      List<AbstractEditorDelegate<?, ?>> delegateList = delegateMap.get(key);
-      if (delegateList != null) {
-
-        // For each delegate editing some record...
-        for (AbstractEditorDelegate<?, ?> baseDelegate : delegateList) {
-
-          // compute its base path in the hierarchy...
-          String basePath = baseDelegate.getPath();
-
-          // and the absolute path of the leaf editor receiving the error.
-          String absolutePath = (basePath.length() > 0 ? basePath + "." : "")
-              + error.getPath();
-
-          // Find the leaf editor's delegate.
-          List<AbstractEditorDelegate<?, ?>> leafDelegates = delegateMap.getPath(absolutePath);
-          if (leafDelegates != null) {
-            // Only attach the error to the first delegate in a co-editor chain.
-            leafDelegates.get(0).recordError(error.getMessage(), null, error);
-          } else {
-            // No EditorDelegate to attach it to, stick it on the base.
-            baseDelegate.recordError(error.getMessage(), null, error,
-                error.getPath());
-          }
-        }
-      }
-    }
-
-    // Flush the errors, which will take care of co-editor chains.
-    errors = new ArrayList<EditorError>();
-    delegate.flushErrors(errors);
-    return !errors.isEmpty();
+    return doSetViolations(new ViolationIterable(violations));
   }
 
   protected abstract RequestFactoryEditorDelegate<R, E> createDelegate();
@@ -211,4 +236,14 @@
 
     traverseEditors(paths);
   }
+
+  private boolean doSetViolations(Iterable<SimpleViolation> violations) {
+    checkDelegate();
+    SimpleViolation.pushViolations(violations, delegateMap);
+
+    // Flush the errors, which will take care of co-editor chains.
+    errors = new ArrayList<EditorError>();
+    delegate.flushErrors(errors);
+    return hasErrors();
+  }
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
index 66e4675..e7bf389 100644
--- a/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
@@ -26,6 +26,8 @@
 import java.util.Collections;
 import java.util.List;
 
+import javax.validation.ConstraintViolation;
+
 /**
  * A no-op implementation of {@link RequestFactoryEditorDriver} that records its
  * inputs.
@@ -142,6 +144,14 @@
   /**
    * A no-op method that always returns false.
    */
+  public boolean setConstraintViolations(
+      Iterable<ConstraintViolation<?>> violations) {
+    return false;
+  }
+
+  /**
+   * A no-op method that always returns false.
+   */
   public boolean setViolations(Iterable<Violation> errors) {
     return false;
   }
diff --git a/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java b/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
index 25e82f3..e6d3eb0 100644
--- a/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
+++ b/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
@@ -54,6 +54,8 @@
 import java.util.HashSet;
 import java.util.Set;
 
+import javax.validation.ConstraintViolation;
+
 /**
  * Test case for {@link EditorModel} that uses mock CompilationStates.
  */
@@ -881,8 +883,10 @@
     Set<Resource> toReturn = new HashSet<Resource>(Arrays.asList(javaFiles));
     toReturn.addAll(Arrays.asList(new Resource[] {
         new RealJavaResource(CompositeEditor.class),
+        new EmptyMockJavaResource(ConstraintViolation.class),
         new RealJavaResource(Editor.class),
         new RealJavaResource(EditorError.class),
+        new EmptyMockJavaResource(EntityProxy.class),
         new EmptyMockJavaResource(EventBus.class),
         new EmptyMockJavaResource(HasEditorDelegate.class),
         new EmptyMockJavaResource(HasEditorErrors.class),
@@ -890,7 +894,6 @@
         new RealJavaResource(IsEditor.class),
         new EmptyMockJavaResource(Iterable.class),
         new RealJavaResource(LeafValueEditor.class),
-        new EmptyMockJavaResource(EntityProxy.class),
         new EmptyMockJavaResource(RequestFactory.class),
         new RealJavaResource(RequestFactoryEditorDriver.class),
         new EmptyMockJavaResource(Request.class),