Handle validation of cascaded Arrays, Iterables and Maps.
[JSR 303 TCK Result] 62 of 258 (24.03%) Pass with 27 Failures and 5 Errors.
Review at http://gwt-code-reviews.appspot.com/1282801
Review by: rchandia@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9528 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/validation/src/com/google/gwt/sample/validation/shared/Person.java b/samples/validation/src/com/google/gwt/sample/validation/shared/Person.java
index 607bb11..ee882b8 100644
--- a/samples/validation/src/com/google/gwt/sample/validation/shared/Person.java
+++ b/samples/validation/src/com/google/gwt/sample/validation/shared/Person.java
@@ -17,6 +17,8 @@
import com.google.gwt.user.client.rpc.IsSerializable;
+import java.util.Map;
+
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
@@ -31,6 +33,9 @@
@Valid
private Address address;
+ @Valid
+ private Map<String, Address> otherAddresses;
+
@NotNull
@Size(min = 4, message = "{custom.name.size.message}")
private String name;
diff --git a/user/src/com/google/gwt/validation/client/impl/GwtValidationContext.java b/user/src/com/google/gwt/validation/client/impl/GwtValidationContext.java
index 38d6ccd..0ff46f5 100644
--- a/user/src/com/google/gwt/validation/client/impl/GwtValidationContext.java
+++ b/user/src/com/google/gwt/validation/client/impl/GwtValidationContext.java
@@ -52,7 +52,33 @@
public GwtValidationContext<T> append(String name) {
GwtValidationContext<T> temp = new GwtValidationContext<T>(rootBean,
beanDescriptor, messageInterpolator);
- temp.path = temp.path.append(name);
+ temp.path = path.append(name);
+ return temp;
+ }
+
+ /**
+ * Append a indexed node to the path.
+ *
+ * @param name
+ * @return the new GwtValidationContext.
+ */
+ public GwtValidationContext<T> appendIndex(String name, int index) {
+ GwtValidationContext<T> temp = new GwtValidationContext<T>(rootBean,
+ beanDescriptor, messageInterpolator);
+ temp.path = path.appendIndex(name, index);
+ return temp;
+ }
+
+ /**
+ * Append a keyed node to the path.
+ *
+ * @param name
+ * @return the new GwtValidationContext.
+ */
+ public GwtValidationContext<T> appendKey(String name, String key) {
+ GwtValidationContext<T> temp = new GwtValidationContext<T>(rootBean,
+ beanDescriptor, messageInterpolator);
+ temp.path = path.appendKey(name, key);
return temp;
}
diff --git a/user/src/com/google/gwt/validation/client/impl/PathImpl.java b/user/src/com/google/gwt/validation/client/impl/PathImpl.java
index 8591073..f481564 100644
--- a/user/src/com/google/gwt/validation/client/impl/PathImpl.java
+++ b/user/src/com/google/gwt/validation/client/impl/PathImpl.java
@@ -40,7 +40,9 @@
}
private PathImpl(PathImpl originalPath, Node node) {
- nodes.addAll(originalPath.nodes);
+ if (!originalPath.isRoot()) {
+ nodes.addAll(originalPath.nodes);
+ }
nodes.add(node);
}
@@ -111,4 +113,8 @@
}
return sb.toString();
}
+
+ private boolean isRoot() {
+ return nodes.size() == 1 && nodes.get(0) == NodeImpl.ROOT_NODE;
+ }
}
diff --git a/user/src/com/google/gwt/validation/rebind/AbstractCreator.java b/user/src/com/google/gwt/validation/rebind/AbstractCreator.java
index 1fb9e02..3e02b60 100644
--- a/user/src/com/google/gwt/validation/rebind/AbstractCreator.java
+++ b/user/src/com/google/gwt/validation/rebind/AbstractCreator.java
@@ -20,6 +20,7 @@
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JPackage;
+import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.user.rebind.AbstractSourceCreator;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
@@ -68,6 +69,11 @@
return BeanHelper.createBeanHelper(clazz, logger, context);
}
+ protected BeanHelper createBeanHelper(JType jType)
+ throws UnableToCompleteException {
+ return BeanHelper.createBeanHelper(jType, logger, context);
+ }
+
protected final String getPackage() {
JPackage serviceIntfPkg = validatorType.getPackage();
String packageName = serviceIntfPkg == null ? "" : serviceIntfPkg.getName();
@@ -85,7 +91,7 @@
sw.println(" = ");
sw.indent();
sw.indent();
-
+
// GWT.create(MyBeanValidator.class);
sw.println("GWT.create(" + bean.getFullyQualifiedValidatorName()
+ ".class);");
diff --git a/user/src/com/google/gwt/validation/rebind/BeanHelper.java b/user/src/com/google/gwt/validation/rebind/BeanHelper.java
index e3c6d84..f87a6e9 100644
--- a/user/src/com/google/gwt/validation/rebind/BeanHelper.java
+++ b/user/src/com/google/gwt/validation/rebind/BeanHelper.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -17,7 +17,9 @@
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.validation.client.impl.GwtSpecificValidator;
@@ -41,7 +43,7 @@
// stash the map in a ThreadLocal, since each GWT module lives in its own
// thread in DevMode
- private static final ThreadLocal<Map<JClassType, BeanHelper>> threadLocalHelperMap =
+ private static final ThreadLocal<Map<JClassType, BeanHelper>> threadLocalHelperMap =
new ThreadLocal<Map<JClassType, BeanHelper>>() {
@Override
protected synchronized Map<JClassType, BeanHelper> initialValue() {
@@ -63,6 +65,18 @@
return helper;
}
+ protected static BeanHelper createBeanHelper(JType jType, TreeLogger logger,
+ GeneratorContext context) throws UnableToCompleteException {
+ try {
+ Class<?> clazz = Class.forName(jType.getQualifiedSourceName());
+ return createBeanHelper(clazz, logger, context);
+ } catch (ClassNotFoundException e) {
+ logger.log(TreeLogger.ERROR, "Unable to create BeanHelper for " + jType,
+ e);
+ throw new UnableToCompleteException();
+ }
+ }
+
protected static boolean isClassConstrained(Class<?> clazz) {
return serverSideValidor.getConstraintsForClass(clazz).isBeanConstrained();
}
diff --git a/user/src/com/google/gwt/validation/rebind/GwtSpecificValidatorCreator.java b/user/src/com/google/gwt/validation/rebind/GwtSpecificValidatorCreator.java
index 6e2c00c..409e546 100644
--- a/user/src/com/google/gwt/validation/rebind/GwtSpecificValidatorCreator.java
+++ b/user/src/com/google/gwt/validation/rebind/GwtSpecificValidatorCreator.java
@@ -22,9 +22,11 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
@@ -50,11 +52,13 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintViolation;
import javax.validation.Payload;
+import javax.validation.Valid;
import javax.validation.ValidationException;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstraintDescriptor;
@@ -198,14 +202,6 @@
this.beanHelper = beanHelper;
}
- public JType getElementType(PropertyDescriptor p, boolean useField) {
- if (useField) {
- return beanType.findField(p.getPropertyName()).getType();
- } else {
- return beanType.findMethod(this.asGetter(p), NO_ARGS).getReturnType();
- }
- }
-
@Override
protected void compose(ClassSourceFileComposerFactory composerFactory) {
addImports(composerFactory, GWT.class, GwtBeanDescriptor.class,
@@ -280,24 +276,51 @@
}
private Annotation getAnnotation(PropertyDescriptor p, boolean useField,
- ConstraintDescriptor<?> constraint) {
- Class<? extends Annotation> expectedAnnotaionClass =
- ((Annotation) constraint.getAnnotation()).annotationType();
+ Class<? extends Annotation> expectedAnnotationClass) {
Annotation annotation = null;
if (useField) {
JField field = beanType.findField(p.getPropertyName());
if (field.getEnclosingType().equals(beanType)) {
- annotation = field.getAnnotation(expectedAnnotaionClass);
+ annotation = field.getAnnotation(expectedAnnotationClass);
}
} else {
JMethod method = beanType.findMethod(asGetter(p), NO_ARGS);
if (method.getEnclosingType().equals(beanType)) {
- annotation = method.getAnnotation(expectedAnnotaionClass);
+ annotation = method.getAnnotation(expectedAnnotationClass);
}
}
return annotation;
}
+ private Annotation getAnnotation(PropertyDescriptor p, boolean useField,
+ ConstraintDescriptor<?> constraint) {
+ Class<? extends Annotation> expectedAnnotationClass =
+ ((Annotation) constraint.getAnnotation()).annotationType();
+ return getAnnotation(p, useField, expectedAnnotationClass);
+ }
+
+
+ private JType getAssociationType(PropertyDescriptor p, boolean useField) {
+ JType type = getElementType(p, useField);
+ JArrayType jArray = type.isArray();
+ if (jArray != null) {
+ // TODO(nchalko) check stuff
+ return jArray.getComponentType();
+ }
+ JParameterizedType pType = type.isParameterized();
+ JClassType[] typeArgs = pType.getTypeArgs();
+ // it is either a Iterable or a Map use the last type arg.
+ return typeArgs[typeArgs.length - 1];
+ }
+
+ private JType getElementType(PropertyDescriptor p, boolean useField) {
+ if (useField) {
+ return beanType.findField(p.getPropertyName()).getType();
+ } else {
+ return beanType.findMethod(this.asGetter(p), NO_ARGS).getReturnType();
+ }
+ }
+
/**
* @param elementType
* @return
@@ -328,6 +351,10 @@
}
}
+ private boolean hasValid(PropertyDescriptor p, boolean useField) {
+ return getAnnotation(p, useField, Valid.class) != null;
+ }
+
private boolean isIterableOrMap(Class<?> elementClass) {
// TODO(nchalko) handle iterables everywhere this is called.
return elementClass.isArray()
@@ -514,8 +541,10 @@
count++; // index starts at zero.
}
writePropertyDescriptor(sw, p);
- if (p.isCascaded() && !isIterableOrMap(p.getElementClass())) {
- beansToValidate.add(createBeanHelper(p.getElementClass()));
+ if (p.isCascaded()) {
+ beansToValidate.add(isIterableOrMap(p.getElementClass())
+ ? createBeanHelper(getAssociationType(p, true))
+ : createBeanHelper(p.getElementClass()));
}
}
@@ -699,7 +728,8 @@
sw.outdent();
}
- private void writePropertyValidators(SourceWriter sw) {
+ private void writePropertyValidators(SourceWriter sw)
+ throws UnableToCompleteException {
for (PropertyDescriptor p :
beanHelper.getBeanDescriptor().getConstrainedProperties()) {
if (hasField(p)) {
@@ -779,8 +809,6 @@
writeValidateInheritance(sw, clazz, Stage.OBJECT, null);
// all reachable and cascadable associations
-
- beanType.getFields();
}
// return violations;
sw.println("return violations;");
@@ -801,9 +829,9 @@
} else {
// TODO(nchalko) handle constraint.isReportAsSingleViolation()
- // validate(context, violations, object, value, new MyValidator(),
+ // validate(myContext, violations, object, value, new MyValidator(),
// constraintDescriptor, groups);
- sw.print("validate(context, violations, object, value, ");
+ sw.print("validate(myContext, violations, object, value, ");
sw.print("new "); // new one each time because validators are not
// thread safe
// TODO(nchalko) use ConstraintValidatorFactory
@@ -904,6 +932,99 @@
}
}
+ private void writeValidateIterable(SourceWriter sw, PropertyDescriptor p,
+ JType associationType, BeanHelper helper) {
+ // int i = 0;
+ sw.println("int i = 0;");
+
+ // for (BeanType instance : value) {
+ sw.print("for(");
+ sw.print(associationType.getQualifiedSourceName());
+ sw.println(" instance : value) {");
+ sw.indent();
+
+ // if(instance != null ) {
+ sw.println(" if(instance != null ) {");
+ sw.indent();
+
+ // violations.addAll(
+ sw.println("violations.addAll(");
+ sw.indent();
+ sw.indent();
+
+ // myGwtValidator.validate(
+ sw.print(helper.getValidatorInstanceName());
+ sw.println(".validate(");
+ sw.indent();
+ sw.indent();
+
+ // context.appendIndex("myProperty",i++),
+ sw.print("context.appendIndex(\"");
+ sw.print(p.getPropertyName());
+ sw.println("\",i++),");
+
+ // instance, groups));
+ sw.println("instance, groups));");
+ sw.outdent();
+ sw.outdent();
+ sw.outdent();
+ sw.outdent();
+
+ // }
+ sw.outdent();
+ sw.println("}");
+
+ // }
+ sw.outdent();
+ sw.println("}");
+ }
+
+ private void writeValidateMap(SourceWriter sw, PropertyDescriptor p,
+ JType associationType, BeanHelper helper) {
+ // for (Entry<?, Type> entry : value.entrySet()) {
+ sw.print("for(");
+ sw.print(Entry.class.getCanonicalName());
+ sw.print("<?, ");
+ sw.print(associationType.getQualifiedSourceName());
+ sw.println("> entry : value.entrySet()) {");
+ sw.indent();
+
+ // if(entry.getValue() != null ) {
+ sw.println(" if(entry.getValue() != null ) {");
+ sw.indent();
+
+ // violations.addAll(
+ sw.println("violations.addAll(");
+ sw.indent();
+ sw.indent();
+
+ // myGwtValidator.validate(
+ sw.print(helper.getValidatorInstanceName());
+ sw.println(".validate(");
+ sw.indent();
+ sw.indent();
+
+ // context.appendKey("myProperty",String.valueOf(entry.getKey())),
+ sw.print("context.appendKey(\"");
+ sw.print(p.getPropertyName());
+ sw.println("\",String.valueOf(entry.getKey())),");
+
+ // entry.getValue(), groups));
+ sw.println("entry.getValue(), groups));");
+ sw.outdent();
+ sw.outdent();
+ sw.outdent();
+ sw.outdent();
+
+ // }
+ sw.outdent();
+ sw.println("}");
+
+ // }
+ sw.outdent();
+ sw.println("}");
+ }
+
private void writeValidateProperty(SourceWriter sw) {
// public <T> Set<ConstraintViolation<T>> validate(
sw.println("public <T> Set<ConstraintViolation<T>> validateProperty(");
@@ -1023,7 +1144,7 @@
}
private void writeValidatePropertyMethod(SourceWriter sw,
- PropertyDescriptor p, boolean useField) {
+ PropertyDescriptor p, boolean useField) throws UnableToCompleteException {
Class<?> elementClass = p.getElementClass();
JType elementType = getElementType(p, useField);
@@ -1035,37 +1156,55 @@
sw.print(validateMethodGetterName(p));
}
sw.println("(");
- // GwtValidationContext<T> context, Set<ConstraintViolation<T>> violations,
- // BeanType object, <Type> value, Class<?>... groups) {
sw.indent();
sw.indent();
- sw.println("GwtValidationContext<T> context,");
- sw.println("Set<ConstraintViolation<T>> violations,");
+
+ // final GwtValidationContext<T> context,
+ sw.println("final GwtValidationContext<T> context,");
+
+ // final Set<ConstraintViolation<T>> violations,
+ sw.println("final Set<ConstraintViolation<T>> violations,");
+
+ // final BeanType object, <Type> value,
sw.println(beanHelper.getTypeCanonicalName() + " object,");
- sw.print(elementType.getQualifiedSourceName());
+
+ // Class<?>... groups) {
+ sw.print("final ");
+ sw.print(elementType.getParameterizedQualifiedSourceName());
sw.println(" value,");
sw.println("Class<?>... groups) {");
sw.outdent();
- // TODO(nchalko) handle the other path types.
// context = context.append("myProperty");
- sw.print("context = context.append(\"");
+ sw.print("final GwtValidationContext<T> myContext = context.append(\"");
sw.print(p.getPropertyName());
sw.println("\");");
// TODO(nchalko) move this out of here to the Validate method
- if (p.isCascaded() && !isIterableOrMap(elementClass)) {
-
- BeanHelper helper = createBeanHelper(elementClass);
+ if (p.isCascaded()) {
// if(value != null) {
sw.println("if(value != null) {");
sw.indent();
- // violations.addAll(myGwtValidator.validate(context, value, groups));
- sw.print("violations.addAll(");
- sw.print(helper.getValidatorInstanceName());
- sw.println(".validate(context, value, groups));");
+ if (isIterableOrMap(elementClass)) {
+ if (hasValid(p, useField)) {
+ JType associationType = getAssociationType(p, useField);
+ BeanHelper helper = createBeanHelper(associationType);
+ // TODO(nchalko) assume iterable for now
+ if (Map.class.isAssignableFrom(elementClass)) {
+ writeValidateMap(sw, p, associationType, helper);
+ } else {
+ writeValidateIterable(sw, p, associationType, helper);
+ }
+ }
+ } else {
+ BeanHelper helper = createBeanHelper(elementClass);
+ // violations.addAll(myGwtValidator.validate(context, value, groups));
+ sw.print("violations.addAll(");
+ sw.print(helper.getValidatorInstanceName());
+ sw.println(".validate(myContext, value, groups));");
+ }
// }
sw.outdent();
diff --git a/user/test/org/hibernate/jsr303/tck/tests/validation/ValidatePropertyGwtTest.java b/user/test/org/hibernate/jsr303/tck/tests/validation/ValidatePropertyGwtTest.java
index 535d08b..39fa78d 100644
--- a/user/test/org/hibernate/jsr303/tck/tests/validation/ValidatePropertyGwtTest.java
+++ b/user/test/org/hibernate/jsr303/tck/tests/validation/ValidatePropertyGwtTest.java
@@ -62,6 +62,7 @@
delegate.testValidatePropertyWithNullProperty();
}
+ @Failing(issue = 5804)
public void testValidIsNotHonoredValidateProperty() {
delegate.testValidIsNotHonoredValidateProperty();
}