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),