Java tutorial
/* * Copyright 2010 Google Inc. Copyright 2016 Manfred Tremmel * * 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 de.knightsoftnet.validators.rebind; import de.knightsoftnet.validators.client.impl.AbstractGwtSpecificValidator; import de.knightsoftnet.validators.client.impl.ConstraintDescriptorImpl; import de.knightsoftnet.validators.client.impl.ConstraintOrigin; import de.knightsoftnet.validators.client.impl.Group; import de.knightsoftnet.validators.client.impl.GroupChain; import de.knightsoftnet.validators.client.impl.GroupChainGenerator; import de.knightsoftnet.validators.client.impl.GwtBeanDescriptor; import de.knightsoftnet.validators.client.impl.GwtBeanDescriptorImpl; import de.knightsoftnet.validators.client.impl.GwtValidationContext; import de.knightsoftnet.validators.client.impl.PropertyDescriptorImpl; import de.knightsoftnet.validators.client.impl.metadata.BeanMetadata; import de.knightsoftnet.validators.client.impl.metadata.ValidationGroupsMetadata; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.UnsafeNativeLong; import com.google.gwt.core.ext.Generator; 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.JField; import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.core.ext.typeinfo.JPrimitiveType; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.dev.jjs.ast.JBooleanLiteral; import com.google.gwt.dev.jjs.ast.JCharLiteral; import com.google.gwt.dev.jjs.ast.JDoubleLiteral; import com.google.gwt.dev.jjs.ast.JFloatLiteral; import com.google.gwt.dev.jjs.ast.JIntLiteral; import com.google.gwt.dev.jjs.ast.JLongLiteral; import com.google.gwt.thirdparty.guava.common.base.Function; import com.google.gwt.thirdparty.guava.common.base.Functions; import com.google.gwt.thirdparty.guava.common.base.Joiner; import com.google.gwt.thirdparty.guava.common.base.Predicate; import com.google.gwt.thirdparty.guava.common.collect.ImmutableList; import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet; import com.google.gwt.thirdparty.guava.common.collect.Iterables; import com.google.gwt.thirdparty.guava.common.collect.Maps; import com.google.gwt.thirdparty.guava.common.collect.Ordering; import com.google.gwt.thirdparty.guava.common.collect.Sets; import com.google.gwt.thirdparty.guava.common.primitives.Primitives; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; import org.apache.commons.lang3.StringUtils; import org.hibernate.validator.internal.engine.path.PathImpl; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.validation.Constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintViolation; import javax.validation.GroupSequence; import javax.validation.Path.Node; import javax.validation.Payload; import javax.validation.UnexpectedTypeException; import javax.validation.Valid; import javax.validation.ValidationException; import javax.validation.groups.Default; import javax.validation.metadata.BeanDescriptor; import javax.validation.metadata.ConstraintDescriptor; import javax.validation.metadata.PropertyDescriptor; /** * Creates a {@link de.knightsoftnet.validators.client.impl.GwtSpecificValidator}. * <p> * This class is not thread safe. * </p> */ @SuppressWarnings("checkstyle:linelength") public final class GwtSpecificValidatorCreator extends AbstractCreator { private static enum Stage { OBJECT, PROPERTY, VALUE } static final JType[] NO_ARGS = new JType[] {}; private static final String DEFAULT_VIOLATION_VAR = "violations"; private static final Annotation[] NO_ANNOTATIONS = new Annotation[] {}; private static final Function<java.beans.PropertyDescriptor, String> PROPERTY_DESCRIPTOR_TO_NAME = new Function<java.beans.PropertyDescriptor, String>() { @Override public String apply(final java.beans.PropertyDescriptor pd) { return pd == null ? null : pd.getName(); } }; private static final Function<Object, String> TO_LITERAL = new Function<Object, String>() { @Override public String apply(final Object input) { return input == null ? null : asLiteral(input); } }; public static String asGetter(final PropertyDescriptor propertyDescriptor) { return "get" + StringUtils.capitalize(propertyDescriptor.getPropertyName()); } /** * Returns the literal value of an object that is suitable for inclusion in Java Source code. * * <p> * Supports all types that {@link Annotation} value can have. * </p> * * * @throws IllegalArgumentException if the type of the object does not have a java literal form. */ public static String asLiteral(final Object value) throws IllegalArgumentException { final Class<?> clazz = value.getClass(); if (clazz.isArray()) { final StringBuilder sb = new StringBuilder(); final Object[] array = (Object[]) value; sb.append("new " + clazz.getComponentType().getCanonicalName() + "[] {"); boolean first = true; for (final Object object : array) { if (first) { first = false; } else { sb.append(','); } sb.append(asLiteral(object)); } sb.append('}'); return sb.toString(); } if (value instanceof Boolean) { return JBooleanLiteral.get(((Boolean) value).booleanValue()).toSource(); } else if (value instanceof Byte) { return JIntLiteral.get(((Byte) value).byteValue()).toSource(); } else if (value instanceof Character) { return JCharLiteral.get(((Character) value).charValue()).toSource(); } else if (value instanceof Class<?>) { return ((Class<?>) (Class<?>) value).getCanonicalName() + ".class"; } else if (value instanceof Double) { return JDoubleLiteral.get(((Double) value).doubleValue()).toSource(); } else if (value instanceof Enum) { return value.getClass().getCanonicalName() + "." + ((Enum<?>) value).name(); } else if (value instanceof Float) { return JFloatLiteral.get(((Float) value).floatValue()).toSource(); } else if (value instanceof Integer) { return JIntLiteral.get(((Integer) value).intValue()).toSource(); } else if (value instanceof Long) { return JLongLiteral.get(((Long) value).longValue()).toSource(); } else if (value instanceof String) { return '"' + Generator.escape((String) value) + '"'; } else { // TODO(nchalko) handle Annotation types throw new IllegalArgumentException(value.getClass() + " can not be represented as a Java Literal."); } } /** * check if elementClass is iterable. * * @param elementClass class to check * @return true if iterable, otherwise false */ public static boolean isIterableOrMap(final Class<?> elementClass) { // TODO(nchalko) handle iterables everywhere this is called. return elementClass.isArray() || Iterable.class.isAssignableFrom(elementClass) || Map.class.isAssignableFrom(elementClass); } /** * Finds the type that a constraint validator will check. * * <p> * This type comes from the first parameter of the isValid() method on the constraint validator. * However, this is a bit tricky because ConstraintValidator has a parameterized type. When using * Java reflection, we will see multiple isValid() methods, including one that checks * java.lang.Object. * </p> * * <p> * Strategy: for now, assume there are at most two isValid() methods. If there are two, assume one * of them has a type that is assignable from the other. (Most likely, one of them will be * java.lang.Object.) * </p> * * @throws IllegalStateException if there isn't any isValid() method or there are more than two. */ static <T extends Annotation> Class<?> getTypeOfConstraintValidator( final Class<? extends ConstraintValidator<T, ?>> constraintClass) { int candidateCount = 0; Class<?> result = null; for (final Method method : constraintClass.getMethods()) { if (method.getName().equals("isValid") && method.getParameterTypes().length == 2 && method.getReturnType().isAssignableFrom(Boolean.TYPE)) { final Class<?> firstArgType = method.getParameterTypes()[0]; if (result == null || result.isAssignableFrom(firstArgType)) { result = firstArgType; } candidateCount++; } } if (candidateCount == 0) { throw new IllegalStateException("ConstraintValidators must have a isValid method"); } else if (candidateCount > 2) { throw new IllegalStateException("ConstraintValidators must have no more than two isValid methods"); } return result; } // Visible for testing static <A extends Annotation> ImmutableSet<Class<? extends ConstraintValidator<A, ?>>> // getValidatorForType(final Class<?> ptype, final List<Class<? extends ConstraintValidator<A, ?>>> constraintValidatorClasses) { final Class<?> type = Primitives.wrap(ptype); final Map<Class<?>, Class<? extends ConstraintValidator<A, ?>>> map = Maps.newHashMap(); for (final Class<? extends ConstraintValidator<A, ?>> conClass : constraintValidatorClasses) { final Class<?> aType = Primitives.wrap(getTypeOfConstraintValidator(conClass)); if (aType.isAssignableFrom(type)) { map.put(aType, conClass); } } // TODO(nchalko) implement per spec // Handle Arrays and Generics final Set<Class<?>> best = Util.findBestMatches(type, map.keySet()); final Predicate<Class<?>> inBest = new Predicate<Class<?>>() { @Override public boolean apply(final Class<?> key) { return best.contains(key); } }; return ImmutableSet.copyOf(Maps.filterKeys(map, inBest).values()); } /** * Gets the best {@link ConstraintValidator}. * * <p> * The ConstraintValidator chosen to validate a declared type {@code targetType} is the one where * the type supported by the ConstraintValidator is a supertype of {@code targetType} and where * there is no other ConstraintValidator whose supported type is a supertype of {@code type} and * not a supertype of the chosen ConstraintValidator supported type. * </p> * * @param constraint the constraint to find ConstraintValidators for. * @param targetType The type to find a ConstraintValidator for. * @return ConstraintValidator * * @throws UnexpectedTypeException if there is not exactly one maximally specific constraint * validator for targetType. */ private static <A extends Annotation> Class<? extends ConstraintValidator<A, ?>> // getValidatorForType(final ConstraintDescriptor<A> constraint, final Class<?> targetType) throws UnexpectedTypeException { final List<Class<? extends ConstraintValidator<A, ?>>> constraintValidatorClasses = constraint .getConstraintValidatorClasses(); if (constraintValidatorClasses.isEmpty()) { throw new UnexpectedTypeException("No ConstraintValidator found for " + constraint.getAnnotation()); } final ImmutableSet<Class<? extends ConstraintValidator<A, ?>>> best = getValidatorForType(targetType, constraintValidatorClasses); if (best.isEmpty()) { throw new UnexpectedTypeException( "No " + constraint.getAnnotation() + " ConstraintValidator for type " + targetType); } if (best.size() > 1) { throw new UnexpectedTypeException("More than one maximally specific " + constraint.getAnnotation() + " ConstraintValidator for type " + targetType + ", found " + Ordering.usingToString().sortedCopy(best)); } return Iterables.get(best, 0); } private static ConstraintOrigin convertConstraintOriginEnum( final org.hibernate.validator.internal.metadata.core.ConstraintOrigin definedOn) { switch (definedOn) { case DEFINED_IN_HIERARCHY: return ConstraintOrigin.DEFINED_IN_HIERARCHY; case DEFINED_LOCALLY: return ConstraintOrigin.DEFINED_LOCALLY; default: throw new IllegalArgumentException("Unable to convert: unknown ConstraintOrigin value"); } } private final BeanHelper beanHelper; private final Set<BeanHelper> beansToValidate = Sets.newHashSet(); private final JClassType beanType; private final Set<JField> fieldsToWrap = Sets.newHashSet(); private final Set<JMethod> gettersToWrap = Sets.newHashSet(); private final Set<Class<?>> validGroups; private final Map<ConstraintDescriptor<?>, Boolean> validConstraintsMap = Maps.newHashMap(); /** * constructor. */ public GwtSpecificValidatorCreator(final JClassType validatorType, final JClassType beanType, final BeanHelper beanHelper, final TreeLogger logger, final GeneratorContext context, final BeanHelperCache cache, final Class<?>[] validGroupsFromAnnotation) { super(context, logger, validatorType, cache); this.beanType = beanType; this.beanHelper = beanHelper; final Set<Class<?>> tempValidGroups = Sets.newHashSet(validGroupsFromAnnotation); tempValidGroups.add(Default.class); this.validGroups = Collections.unmodifiableSet(tempValidGroups); } @Override protected void compose(final ClassSourceFileComposerFactory composerFactory) { this.addImports(composerFactory, Annotation.class, ConstraintViolation.class, GWT.class, ValidationGroupsMetadata.class, Group.class, GroupChain.class, PathImpl.class, Node.class, GroupChainGenerator.class, GwtBeanDescriptor.class, BeanMetadata.class, GwtValidationContext.class, ArrayList.class, HashSet.class, IllegalArgumentException.class, Set.class, Collection.class, Iterator.class, List.class, ValidationException.class); composerFactory.setSuperclass(AbstractGwtSpecificValidator.class.getCanonicalName() + "<" + this.beanType.getQualifiedSourceName() + ">"); composerFactory.addImplementedInterface(this.validatorType.getName()); } @Override protected void writeClassBody(final SourceWriter sw) throws UnableToCompleteException { this.writeFields(sw); sw.println(); this.writeValidateClassGroups(sw); sw.println(); this.writeExpandDefaultAndValidateClassGroups(sw); sw.println(); this.writeExpandDefaultAndValidatePropertyGroups(sw); sw.println(); this.writeExpandDefaultAndValidateValueGroups(sw); sw.println(); this.writeValidatePropertyGroups(sw); sw.println(); this.writeValidateValueGroups(sw); sw.println(); this.writeGetBeanMetadata(sw); sw.println(); this.writeGetDescriptor(sw); sw.println(); this.writePropertyValidators(sw); sw.println(); this.writeValidateAllNonInheritedProperties(sw); sw.println(); // Write the wrappers after we know which are needed this.writeWrappers(sw); sw.println(); } protected void writeUnsafeNativeLongIfNeeded(final SourceWriter sw, final JType jtype) { if (JPrimitiveType.LONG.equals(jtype)) { // @com.google.gwt.core.client.UnsafeNativeLong sw.print("@"); sw.println(UnsafeNativeLong.class.getCanonicalName()); } } private boolean areConstraintDescriptorGroupsValid(final ConstraintDescriptor<?> constraintDescriptor) { if (this.validConstraintsMap.containsKey(constraintDescriptor)) { return this.validConstraintsMap.get(constraintDescriptor); } else { final boolean areValid = this.checkGroups(constraintDescriptor.getGroups()); // cache result this.validConstraintsMap.put(constraintDescriptor, areValid); return areValid; } } private <T> T[] asArray(final Collection<?> collection, final T[] array) { if (collection == null) { return null; } return collection.toArray(array); } private boolean checkGroups(final Set<Class<?>> groups) { for (final Class<?> group : groups) { if (this.validGroups.contains(group)) { return true; } } return false; } private String constraintDescriptorVar(final String name, final int count) { return name + "_c" + count; } private Annotation getAnnotation(final PropertyDescriptor ppropertyDescription, final boolean useField, final Class<? extends Annotation> expectedAnnotationClass) { Annotation annotation = null; if (useField) { final JField field = this.beanType.findField(ppropertyDescription.getPropertyName()); if (field.getEnclosingType().equals(this.beanType)) { annotation = field.getAnnotation(expectedAnnotationClass); } } else { final JMethod method = this.beanType.findMethod(asGetter(ppropertyDescription), NO_ARGS); if (method.getEnclosingType().equals(this.beanType)) { annotation = method.getAnnotation(expectedAnnotationClass); } } return annotation; } private Annotation[] getAnnotations(final PropertyDescriptor ppropertyDescription, final boolean useField) { final Class<?> clazz = this.beanHelper.getClazz(); if (useField) { try { final Field field = clazz.getDeclaredField(ppropertyDescription.getPropertyName()); return field.getAnnotations(); } catch (final NoSuchFieldException ignore) { // NOPMD // Expected Case } } else { try { final Method method = clazz.getMethod(asGetter(ppropertyDescription)); return method.getAnnotations(); } catch (final NoSuchMethodException ignore) { // NOPMD // Expected Case } } return NO_ANNOTATIONS; } private String getQualifiedSourceNonPrimitiveType(final JType elementType) { final JPrimitiveType primitive = elementType.isPrimitive(); return primitive == null ? elementType.getQualifiedSourceName() : primitive.getQualifiedBoxedSourceName(); } private boolean hasMatchingAnnotation(final Annotation expectedAnnotation, final Annotation[] annotations) throws UnableToCompleteException { // See spec section 2.2. Applying multiple constraints of the same type for (final Annotation annotation : annotations) { // annotations not annotated by @Constraint if (annotation.annotationType().getAnnotation(Constraint.class) == null) { try { // value element has a return type of an array of constraint // annotations final Method valueMethod = annotation.annotationType().getMethod("value"); final Class<?> valueType = valueMethod.getReturnType(); if (valueType.isArray() && Annotation.class.isAssignableFrom(valueType.getComponentType())) { if (Modifier.isAbstract(valueMethod.getModifiers())) { // handle edge case where interface is marked "abstract" valueMethod.setAccessible(true); } final Annotation[] valueAnnotions = (Annotation[]) valueMethod.invoke(annotation); for (final Annotation annotation2 : valueAnnotions) { if (expectedAnnotation.equals(annotation2)) { return true; } } } } catch (final NoSuchMethodException ignore) { // NOPMD // Expected Case. } catch (final Exception e) { throw error(this.logger, e); } } } return false; } private boolean hasMatchingAnnotation(final ConstraintDescriptor<?> constraint) throws UnableToCompleteException { final Annotation expectedAnnotation = constraint.getAnnotation(); final Class<? extends Annotation> expectedAnnotationClass = expectedAnnotation.annotationType(); if (expectedAnnotation.equals(this.beanHelper.getClazz().getAnnotation(expectedAnnotationClass))) { return true; } // See spec section 2.2. Applying multiple constraints of the same type final Annotation[] annotations = this.beanHelper.getClazz().getAnnotations(); return this.hasMatchingAnnotation(expectedAnnotation, annotations); } private boolean hasMatchingAnnotation(final PropertyDescriptor ppropertyDescription, final boolean useField, final ConstraintDescriptor<?> constraint) throws UnableToCompleteException { final Annotation expectedAnnotation = constraint.getAnnotation(); final Class<? extends Annotation> expectedAnnotationClass = expectedAnnotation.annotationType(); if (expectedAnnotation .equals(this.getAnnotation(ppropertyDescription, useField, expectedAnnotationClass))) { return true; } return this.hasMatchingAnnotation(expectedAnnotation, this.getAnnotations(ppropertyDescription, useField)); } private boolean hasValid(final PropertyDescriptor ppropertyDescription, final boolean useField) { return this.getAnnotation(ppropertyDescription, useField, Valid.class) != null; } private boolean isPropertyConstrained(final BeanHelper helper, final PropertyDescriptor ppropertyDescription) { final Set<PropertyDescriptor> propertyDescriptors = helper.getBeanDescriptor().getConstrainedProperties(); final Predicate<PropertyDescriptor> nameMatches = this.newPropertyNameMatches(ppropertyDescription); return Iterables.any(propertyDescriptors, nameMatches); } private boolean isPropertyConstrained(final PropertyDescriptor ppropertyDescription, final boolean useField) { // cascaded counts as constrained // we must know if the @Valid annotation is on a field or a getter final JClassType jClass = this.beanHelper.getJClass(); if (useField && jClass.findField(ppropertyDescription.getPropertyName()).isAnnotationPresent(Valid.class)) { return true; } else if (!useField && jClass.findMethod(asGetter(ppropertyDescription), NO_ARGS).isAnnotationPresent(Valid.class)) { return true; } // for non-cascaded properties for (final ConstraintDescriptor<?> constraint : ppropertyDescription.getConstraintDescriptors()) { final org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl<?> constraintHibernate = (org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl<?>) constraint; if (constraintHibernate.getElementType() == (useField ? ElementType.FIELD : ElementType.METHOD)) { return true; } } return false; } private Predicate<PropertyDescriptor> newPropertyNameMatches(final PropertyDescriptor ppropertyDescription) { return new Predicate<PropertyDescriptor>() { @Override public boolean apply(final PropertyDescriptor input) { return input.getPropertyName().equals(ppropertyDescription.getPropertyName()); } }; } private String toWrapperName(final JField field) { return "_" + field.getName(); } private String toWrapperName(final JMethod method) { return "_" + method.getName(); } private String validateMethodFieldName(final PropertyDescriptor ppropertyDescription) { return "validateProperty_" + ppropertyDescription.getPropertyName(); } private String validateMethodGetterName(final PropertyDescriptor ppropertyDescription) { return "validateProperty_get" + ppropertyDescription.getPropertyName(); } private void writeBeanDescriptor(final SourceWriter sw) { final BeanDescriptor beanDescriptor = this.beanHelper.getBeanDescriptor(); // private final GwtBeanDescriptor <MyBean> beanDescriptor = sw.print("private final "); sw.print(GwtBeanDescriptor.class.getCanonicalName()); sw.print("<" + this.beanHelper.getTypeCanonicalName() + ">"); sw.println(" beanDescriptor = "); sw.indent(); sw.indent(); // GwtBeanDescriptorImpl.builder(Order.class) sw.print(GwtBeanDescriptorImpl.class.getCanonicalName()); sw.println(".builder(" + this.beanHelper.getTypeCanonicalName() + ".class)"); sw.indent(); sw.indent(); // .setConstrained(true) sw.println(".setConstrained(" + beanDescriptor.isBeanConstrained() + ")"); int count = 0; for (final ConstraintDescriptor<?> constraint : beanDescriptor.getConstraintDescriptors()) { if (this.areConstraintDescriptorGroupsValid(constraint)) { // .add(c0) sw.println(".add(" + this.constraintDescriptorVar("this", count) + ")"); count++; } } // .put("myProperty", myProperty_pd) for (final PropertyDescriptor p : beanDescriptor.getConstrainedProperties()) { sw.print(".put(\""); sw.print(p.getPropertyName()); sw.print("\", "); sw.print(p.getPropertyName()); sw.println("_pd)"); } // .setBeanMetadata(beanMetadata) sw.println(".setBeanMetadata(beanMetadata)"); // .build(); sw.println(".build();"); sw.outdent(); sw.outdent(); sw.outdent(); sw.outdent(); } private void writeBeanMetadata(final SourceWriter sw) throws UnableToCompleteException { // private final BeanMetadata beanMetadata = sw.println("private final BeanMetadata beanMetadata ="); sw.indent(); sw.indent(); // new BeanMetadata( sw.println("new " + BeanMetadata.class.getSimpleName() + "("); sw.indent(); sw.indent(); // <<bean class>>, <<default group seq class 1>>, <<default group seq class 2>>, ... final Class<?> beanClazz = this.beanHelper.getClazz(); sw.print(asLiteral(beanClazz)); final GroupSequence groupSeqAnnotation = beanClazz.getAnnotation(GroupSequence.class); final List<Class<?>> groupSequence = new ArrayList<>(); if (groupSeqAnnotation == null) { groupSequence.add(beanClazz); } else { groupSequence.addAll(Arrays.asList(groupSeqAnnotation.value())); } boolean groupSequenceContainsDefault = false; for (final Class<?> group : groupSequence) { sw.println(","); if (group.getName().equals(beanClazz.getName())) { sw.print(asLiteral(Default.class)); groupSequenceContainsDefault = true; } else if (group.getName().equals(Default.class.getName())) { throw error(this.logger, "'Default.class' cannot appear in default group sequence list."); } else { sw.print(asLiteral(group)); } } if (!groupSequenceContainsDefault) { throw error(this.logger, beanClazz.getName() + " must be part of the redefined default group " + "sequence."); } sw.println(");"); sw.outdent(); sw.outdent(); sw.outdent(); sw.outdent(); } private void writeClassLevelConstraintsValidation(final SourceWriter sw, final String groupsVarName) throws UnableToCompleteException { // all class level constraints int count = 0; final Class<?> clazz = this.beanHelper.getClazz(); for (final ConstraintDescriptor<?> constraint : this.beanHelper.getBeanDescriptor() .getConstraintDescriptors()) { if (this.areConstraintDescriptorGroupsValid(constraint)) { if (this.hasMatchingAnnotation(constraint)) { if (!constraint.getConstraintValidatorClasses().isEmpty()) { // NOPMD final Class<? extends ConstraintValidator<? extends Annotation, ?>> validatorClass = getValidatorForType( constraint, clazz); // validate(context, violations, null, object, sw.print("validate(context, violations, null, object, "); // new MyValidtor(), sw.print("new "); sw.print(validatorClass.getCanonicalName()); sw.print("(), "); // TODO(nchalko) use ConstraintValidatorFactory // this.aConstraintDescriptor, groups); sw.print(this.constraintDescriptorVar("this", count)); sw.print(", "); sw.print(groupsVarName); sw.println(");"); } else if (constraint.getComposingConstraints().isEmpty()) { // TODO(nchalko) What does the spec say to do here. this.logger.log(TreeLogger.WARN, "No ConstraintValidator of " + constraint + " for type " + clazz); } // TODO(nchalko) handle constraint.isReportAsSingleViolation() and // hasComposingConstraints } count++; } } } private void writeConstraintDescriptor(final SourceWriter sw, final ConstraintDescriptor<? extends Annotation> constraint, final ElementType elementType, final ConstraintOrigin origin, final String constraintDescripotorVar) throws UnableToCompleteException { final Class<? extends Annotation> annotationType = constraint.getAnnotation().annotationType(); // First list all composing constraints int count = 0; for (final ConstraintDescriptor<?> composingConstraint : constraint.getComposingConstraints()) { this.writeConstraintDescriptor(sw, composingConstraint, elementType, origin, constraintDescripotorVar + "_" + count++); } // private final ConstraintDescriptorImpl<MyAnnotation> constraintDescriptor = ; sw.print("private final "); sw.print(ConstraintDescriptorImpl.class.getCanonicalName()); sw.print("<"); sw.print(annotationType.getCanonicalName()); sw.print(">"); sw.println(" " + constraintDescripotorVar + " = "); sw.indent(); sw.indent(); // ConstraintDescriptorImpl.<MyConstraint> builder() sw.print(ConstraintDescriptorImpl.class.getCanonicalName()); sw.print(".<"); sw.print(annotationType.getCanonicalName()); sw.println("> builder()"); sw.indent(); sw.indent(); // .setAnnotation(new MyAnnotation ) sw.println(".setAnnotation( "); sw.indent(); sw.indent(); this.writeNewAnnotation(sw, constraint); sw.println(")"); sw.outdent(); sw.outdent(); // .setAttributes(builder() sw.println(".setAttributes(attributeBuilder()"); sw.indent(); for (final Map.Entry<String, Object> entry : constraint.getAttributes().entrySet()) { // .put(key, value) sw.print(".put("); final String key = entry.getKey(); sw.print(asLiteral(key)); sw.print(", "); Object value = entry.getValue(); // Add the Default group if it is not already present if ("groups".equals(key) && value instanceof Class[] && ((Class[]) value).length == 0) { value = new Class[] { Default.class }; } sw.print(asLiteral(value)); sw.println(")"); } // .build()) sw.println(".build())"); sw.outdent(); // .setConstraintValidatorClasses(classes ) sw.print(".setConstraintValidatorClasses("); sw.print(asLiteral(this.asArray(constraint.getConstraintValidatorClasses(), new Class[0]))); sw.println(")"); final int ccCount = constraint.getComposingConstraints().size(); for (int i = 0; i < ccCount; i++) { // .addComposingConstraint(cX_X) sw.print(".addComposingConstraint("); sw.print(constraintDescripotorVar + "_" + i); sw.println(")"); } // .getGroups(groups) sw.print(".setGroups("); final Set<Class<?>> groups = constraint.getGroups(); sw.print(asLiteral(this.asArray(groups, new Class<?>[0]))); sw.println(")"); // .setPayload(payload) sw.print(".setPayload("); final Set<Class<? extends Payload>> payload = constraint.getPayload(); sw.print(asLiteral(this.asArray(payload, new Class[0]))); sw.println(")"); // .setReportAsSingleViolation(boolean ) sw.print(".setReportAsSingleViolation("); sw.print(Boolean.toString(constraint.isReportAsSingleViolation())); sw.println(")"); // .setElementType(elementType) sw.print(".setElementType("); sw.print(asLiteral(elementType)); sw.println(")"); // .setDefinedOn(origin) sw.print(".setDefinedOn("); sw.print(asLiteral(origin)); sw.println(")"); // .build(); sw.println(".build();"); sw.outdent(); sw.outdent(); sw.outdent(); sw.outdent(); sw.println(); } private void writeExpandDefaultAndValidate(final SourceWriter sw, final Stage stage) throws UnableToCompleteException { final Class<?> clazz = this.beanHelper.getClazz(); // ArrayList<Class<?>> justGroups = new ArrayList<Class<?>>(); sw.println("ArrayList<Class<?>> justGroups = new ArrayList<Class<?>>();"); // for (Group g : groups) { sw.println("for (Group g : groups) {"); sw.indent(); // if (!g.isDefaultGroup() || !getBeanMetadata().defaultGroupSequenceIsRedefined()) { sw.println("if (!g.isDefaultGroup() || !getBeanMetadata().defaultGroupSequenceIsRedefined()) {"); sw.indent(); // justGroups.add(g.getGroup()); sw.println("justGroups.add(g.getGroup());"); sw.outdent(); // } sw.println("}"); sw.outdent(); // } sw.println("}"); // Class<?>[] justGroupsArray = justGroups.toArray(new Class<?>[justGroups.size()]); sw.println("Class<?>[] justGroupsArray = justGroups.toArray(new Class<?>[justGroups.size()]);"); switch (stage) { case OBJECT: // validateAllNonInheritedProperties(context, object, violations, justGroupsArray); sw.println("validateAllNonInheritedProperties(context, object, violations, " + "justGroupsArray);"); this.writeClassLevelConstraintsValidation(sw, "justGroupsArray"); break; case PROPERTY: // validatePropertyGroups(context, object, propertyName, violations, justGroupsArray); sw.println("validatePropertyGroups(context, object, propertyName, violations, " + "justGroupsArray);"); break; case VALUE: // validateValueGroups(context, beanType, propertyName, value, violations, // justGroupsArray); sw.println("validateValueGroups(context, beanType, propertyName, value, violations, " + "justGroupsArray);"); break; default: throw new IllegalStateException(); } // if (getBeanMetadata().defaultGroupSequenceIsRedefined()) { sw.println("if (getBeanMetadata().defaultGroupSequenceIsRedefined()) {"); sw.indent(); // for (Class<?> g : beanMetadata.getDefaultGroupSequence()) { sw.println("for (Class<?> g : beanMetadata.getDefaultGroupSequence()) {"); sw.indent(); // int numberOfViolations = violations.size(); sw.println("int numberOfViolations = violations.size();"); switch (stage) { case OBJECT: // validateAllNonInheritedProperties(context, object, violations, g); sw.println("validateAllNonInheritedProperties(context, object, violations, g);"); this.writeClassLevelConstraintsValidation(sw, "g"); // validate super classes and super interfaces this.writeValidateInheritance(sw, clazz, Stage.OBJECT, null, false, "g"); break; case PROPERTY: // validatePropertyGroups(context, object, propertyName, violations, g); sw.println("validatePropertyGroups(context, object, propertyName, violations, g);"); break; case VALUE: // validateValueGroups(context, beanType, propertyName, value, violations, g); sw.println("validateValueGroups(context, beanType, propertyName, value, violations, g);"); break; default: throw new IllegalStateException(); } // if (violations.size() > numberOfViolations) { sw.println("if (violations.size() > numberOfViolations) {"); sw.indent(); // break; sw.println("break;"); sw.outdent(); // } sw.println("}"); sw.outdent(); // } sw.println("}"); sw.outdent(); // } sw.println("}"); if (stage == Stage.OBJECT) { // else { sw.println("else {"); sw.indent(); // validate super classes and super interfaces this.writeValidateInheritance(sw, clazz, Stage.OBJECT, null, true, "groups"); // } sw.outdent(); sw.println("}"); } } private void writeExpandDefaultAndValidateClassGroups(final SourceWriter sw) throws UnableToCompleteException { // public <T> void expandDefaultAndValidateClassGroups( sw.println("public <T> void expandDefaultAndValidateClassGroups("); // GwtValidationContext<T> context, BeanType object, // Set<ConstraintViolation<T>> violations, Group... groups) { sw.indent(); sw.indent(); sw.println("GwtValidationContext<T> context,"); sw.println(this.beanHelper.getTypeCanonicalName() + " object,"); sw.println("Set<ConstraintViolation<T>> violations,"); sw.println("Group... groups) {"); sw.outdent(); this.writeExpandDefaultAndValidate(sw, Stage.OBJECT); // } sw.outdent(); sw.println("}"); } private void writeExpandDefaultAndValidatePropertyGroups(final SourceWriter sw) throws UnableToCompleteException { // public <T> void expandDefaultAndValidatePropertyGroups( sw.println("public <T> void expandDefaultAndValidatePropertyGroups("); // GwtValidationContext<T> context, BeanType object, String propertyName, // Set<ConstraintViolation<T>> violations, Group... groups) { sw.indent(); sw.indent(); sw.println("GwtValidationContext<T> context,"); sw.println(this.beanHelper.getTypeCanonicalName() + " object,"); sw.println("String propertyName,"); sw.println("Set<ConstraintViolation<T>> violations,"); sw.println("Group... groups) {"); sw.outdent(); this.writeExpandDefaultAndValidate(sw, Stage.PROPERTY); // } sw.outdent(); sw.println("}"); } private void writeExpandDefaultAndValidateValueGroups(final SourceWriter sw) throws UnableToCompleteException { // public <T> void expandDefaultAndValidateValueGroups( sw.println("public <T> void expandDefaultAndValidateValueGroups("); // GwtValidationContext<T> context, Class<Author> beanType, String propertyName, // Object value, Set<ConstraintViolation<T>> violations, Group... groups) { sw.indent(); sw.indent(); sw.println("GwtValidationContext<T> context,"); sw.println("Class<" + this.beanHelper.getTypeCanonicalName() + "> beanType,"); sw.println("String propertyName,"); sw.println("Object value,"); sw.println("Set<ConstraintViolation<T>> violations,"); sw.println("Group... groups) {"); sw.outdent(); this.writeExpandDefaultAndValidate(sw, Stage.VALUE); // } sw.outdent(); sw.println("}"); } private void writeFields(final SourceWriter sw) throws UnableToCompleteException { // Create a static array of all valid property names. BeanInfo beanInfo; try { beanInfo = Introspector.getBeanInfo(this.beanHelper.getClazz()); } catch (final IntrospectionException e) { throw error(this.logger, e); } // private static final java.util.List<String> ALL_PROPERTY_NAMES = sw.println("private static final java.util.List<String> ALL_PROPERTY_NAMES = "); sw.indent(); sw.indent(); // Collections.<String>unmodifiableList( sw.println("java.util.Collections.<String>unmodifiableList("); sw.indent(); sw.indent(); // java.util.Arrays.<String>asList( sw.print("java.util.Arrays.<String>asList("); // "foo","bar" ); sw.print(Joiner.on(",").join(Iterables.transform(ImmutableList.copyOf(beanInfo.getPropertyDescriptors()), Functions.compose(TO_LITERAL, PROPERTY_DESCRIPTOR_TO_NAME)))); sw.println("));"); sw.outdent(); sw.outdent(); sw.outdent(); sw.outdent(); // Write the metadata for the bean this.writeBeanMetadata(sw); sw.println(); // Create a variable for each constraint of each property for (final PropertyDescriptor p : this.beanHelper.getBeanDescriptor().getConstrainedProperties()) { int count = 0; for (final ConstraintDescriptor<?> constraint : p.getConstraintDescriptors()) { final org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl<?> constraintHibernate = (org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl<?>) constraint; if (this.areConstraintDescriptorGroupsValid(constraint)) { this.writeConstraintDescriptor(sw, constraint, constraintHibernate.getElementType(), convertConstraintOriginEnum(constraintHibernate.getDefinedOn()), this.constraintDescriptorVar(p.getPropertyName(), count++)); } } this.writePropertyDescriptor(sw, p); if (p.isCascaded()) { this.beansToValidate.add(isIterableOrMap(p.getElementClass()) ? this.createBeanHelper(this.beanHelper.getAssociationType(p, true)) : this.createBeanHelper(p.getElementClass())); } } // Create a variable for each constraint of this class. int count = 0; for (final ConstraintDescriptor<?> constraint : this.beanHelper.getBeanDescriptor() .getConstraintDescriptors()) { final org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl<?> constraintHibernate = (org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl<?>) constraint; if (this.areConstraintDescriptorGroupsValid(constraint)) { this.writeConstraintDescriptor(sw, constraint, ElementType.TYPE, convertConstraintOriginEnum(constraintHibernate.getDefinedOn()), this.constraintDescriptorVar("this", count++)); } } // Now write the BeanDescriptor after we already have the // PropertyDescriptors and class constraints this.writeBeanDescriptor(sw); sw.println(); } private void writeFieldWrapperMethod(final SourceWriter sw, final JField field) { this.writeUnsafeNativeLongIfNeeded(sw, field.getType()); // private native fieldType _fieldName(com.example.Bean object) /*-{ sw.print("private native "); sw.print(field.getType().getQualifiedSourceName()); sw.print(" "); sw.print(this.toWrapperName(field)); sw.print("("); sw.print(field.getEnclosingType().getQualifiedSourceName()); sw.println(" object) /*-{"); sw.indent(); // return object.@com.examples.Bean::myMethod(); sw.print("return object.@"); sw.print(field.getEnclosingType().getQualifiedSourceName()); sw.print("::" + field.getName()); sw.println(";"); // }-*/; sw.outdent(); sw.println("}-*/;"); } private void writeGetBeanMetadata(final SourceWriter sw) { // public BeanMetadata getBeanMetadata() { sw.println("public BeanMetadata getBeanMetadata() {"); sw.indent(); // return beanMetadata; sw.println("return beanMetadata;"); // } sw.outdent(); sw.println("}"); } private void writeGetDescriptor(final SourceWriter sw) { // public GwtBeanDescriptor<beanType> // getConstraints(ValidationGroupsMetadata validationGroupsMetadata) { sw.print("public "); sw.print("GwtBeanDescriptor<" + this.beanHelper.getTypeCanonicalName() + "> "); sw.println("getConstraints(ValidationGroupsMetadata validationGroupsMetadata) {"); sw.indent(); // beanDescriptor.setValidationGroupsMetadata(validationGroupsMetadata); sw.println("beanDescriptor.setValidationGroupsMetadata(validationGroupsMetadata);"); // return beanDescriptor; sw.println("return beanDescriptor;"); sw.outdent(); sw.println("}"); } private void writeGetterWrapperMethod(final SourceWriter sw, final JMethod method) { this.writeUnsafeNativeLongIfNeeded(sw, method.getReturnType()); // private native fieldType _getter(Bean object) /*={ sw.print("private native "); sw.print(method.getReturnType().getQualifiedSourceName()); sw.print(" "); sw.print(this.toWrapperName(method)); sw.print("("); sw.print(this.beanType.getName()); sw.println(" object) /*-{"); sw.indent(); // return object.@com.examples.Bean::myMethod()(); sw.print("return object."); sw.print(method.getJsniSignature()); sw.println("();"); // }-*/; sw.outdent(); sw.println("}-*/;"); } private void writeIfPropertyNameNotFound(final SourceWriter sw) { // if (!ALL_PROPERTY_NAMES.contains(propertyName)) { sw.println(" if (!ALL_PROPERTY_NAMES.contains(propertyName)) {"); sw.indent(); // throw new IllegalArgumentException(propertyName // +"is not a valid property of myClass"); sw.print("throw new "); sw.print(IllegalArgumentException.class.getCanonicalName()); sw.print("( propertyName +\" is not a valid property of "); sw.print(this.beanType.getQualifiedSourceName()); sw.println("\");"); // } sw.outdent(); sw.println("}"); } private void writeNewAnnotation(final SourceWriter sw, final ConstraintDescriptor<? extends Annotation> constraint) throws UnableToCompleteException { final Annotation annotation = constraint.getAnnotation(); final Class<? extends Annotation> annotationType = annotation.annotationType(); // new MyAnnotation () { sw.print("new "); sw.print(annotationType.getCanonicalName()); sw.println("(){"); sw.indent(); sw.indent(); // public Class<? extends Annotation> annotationType() { return // MyAnnotation.class; } sw.print("public Class<? extends Annotation> annotationType() { return "); sw.print(annotationType.getCanonicalName()); sw.println(".class; }"); for (final Method method : annotationType.getMethods()) { // method.isAbstract would be better if (method.getDeclaringClass().equals(annotation.annotationType())) { // public returnType method() { return value ;} sw.print("public "); sw.print(method.getReturnType().getCanonicalName()); // TODO handle // generics sw.print(" "); sw.print(method.getName()); sw.print("() { return "); try { final Object value = method.invoke(annotation); sw.print(asLiteral(value)); } catch (final IllegalArgumentException e) { throw error(this.logger, e); } catch (final IllegalAccessException e) { throw error(this.logger, e); } catch (final InvocationTargetException e) { throw error(this.logger, e); } sw.println(";}"); } } sw.outdent(); sw.outdent(); sw.println("}"); } private void writeNewViolations(final SourceWriter sw, final String violationName) { // Set<ConstraintViolation<T>> violations = sw.print("Set<ConstraintViolation<T>> "); sw.print(violationName); sw.println(" = "); sw.indent(); sw.indent(); // new HashSet<ConstraintViolation<T>>(); sw.println("new HashSet<ConstraintViolation<T>>();"); sw.outdent(); sw.outdent(); } private void writePropertyDescriptor(final SourceWriter sw, final PropertyDescriptor ppropertyDescription) { // private final PropertyDescriptor myProperty_pd = sw.print("private final "); sw.print(PropertyDescriptorImpl.class.getCanonicalName()); sw.print(" "); sw.print(ppropertyDescription.getPropertyName()); sw.println("_pd ="); sw.indent(); sw.indent(); // new PropertyDescriptorImpl( sw.println("new " + PropertyDescriptorImpl.class.getCanonicalName() + "("); sw.indent(); sw.indent(); // "myProperty", sw.println("\"" + ppropertyDescription.getPropertyName() + "\","); // MyType.class, sw.println(ppropertyDescription.getElementClass().getCanonicalName() + ".class,"); // isCascaded, sw.print(Boolean.toString(ppropertyDescription.isCascaded()) + ","); // beanMetadata, sw.print("beanMetadata"); // myProperty_c0, // myProperty_c1 ); int count = 0; for (final ConstraintDescriptor<?> constraint : ppropertyDescription.getConstraintDescriptors()) { if (this.areConstraintDescriptorGroupsValid(constraint)) { sw.println(","); // Print the , for the previous line sw.print(this.constraintDescriptorVar(ppropertyDescription.getPropertyName(), count)); count++; } } sw.println(");"); sw.outdent(); sw.outdent(); sw.outdent(); sw.outdent(); } private void writePropertyValidators(final SourceWriter sw) throws UnableToCompleteException { for (final PropertyDescriptor p : this.beanHelper.getBeanDescriptor().getConstrainedProperties()) { if (this.beanHelper.hasField(p)) { this.writeValidatePropertyMethod(sw, p, true); sw.println(); } if (this.beanHelper.hasGetter(p)) { this.writeValidatePropertyMethod(sw, p, false); sw.println(); } } } private void writeValidateAllNonInheritedProperties(final SourceWriter sw) { // private <T> void validateAllNonInheritedProperties( sw.println("private <T> void validateAllNonInheritedProperties("); sw.indent(); sw.indent(); // GwtValidationContext<T> context, BeanType object, // Set<ConstraintViolation<T>> violations, Class<?>... groups) { sw.println("GwtValidationContext<T> context,"); sw.println(this.beanHelper.getTypeCanonicalName() + " object,"); sw.println("Set<ConstraintViolation<T>> violations,"); sw.println("Class<?>... groups) {"); sw.outdent(); for (final PropertyDescriptor p : this.beanHelper.getBeanDescriptor().getConstrainedProperties()) { this.writeValidatePropertyCall(sw, p, false, true); } sw.outdent(); sw.println("}"); } private void writeValidateClassGroups(final SourceWriter sw) throws UnableToCompleteException { // public <T> void validateClassGroups( sw.println("public <T> void validateClassGroups("); // GwtValidationContext<T> context, BeanType object, // Set<ConstraintViolation<T>> violations, Group... groups) { sw.indent(); sw.indent(); sw.println("GwtValidationContext<T> context,"); sw.println(this.beanHelper.getTypeCanonicalName() + " object,"); sw.println("Set<ConstraintViolation<T>> violations,"); sw.println("Class<?>... groups) {"); sw.outdent(); // /// For each group // TODO(nchalko) handle the sequence in the AbstractValidator // See JSR 303 section 3.5 // all reachable fields // all reachable getters (both) at once // including all reachable and cascadable associations sw.println("validateAllNonInheritedProperties(context, object, violations, groups);"); // validate super classes and super interfaces this.writeValidateInheritance(sw, this.beanHelper.getClazz(), Stage.OBJECT, null, false, "groups"); this.writeClassLevelConstraintsValidation(sw, "groups"); // } sw.outdent(); sw.println("}"); } private void writeValidateConstraint(final SourceWriter sw, final PropertyDescriptor ppropertyDescription, final Class<?> elementClass, final ConstraintDescriptor<?> constraint, final String constraintDescriptorVar) throws UnableToCompleteException { this.writeValidateConstraint(sw, ppropertyDescription, elementClass, constraint, constraintDescriptorVar, DEFAULT_VIOLATION_VAR); } /** * Writes the call to actually validate a constraint, including its composite constraints. * <p> * If the constraint is annotated as {@link javax.validation.ReportAsSingleViolation * ReportAsSingleViolation}, then is called recursively and the {@code violationsVar} is changed * to match the {@code constraintDescriptorVar}. * </p> * * @param sw the Source Writer * @param ppropertyDescription the property * @param elementClass The class of the Element * @param constraint the constraint to validate. * @param constraintDescriptorVar the name of the constraintDescriptor variable. * @param violationsVar the name of the variable to hold violations * @throws UnableToCompleteException when validation can not be completed */ private void writeValidateConstraint(final SourceWriter sw, final PropertyDescriptor ppropertyDescription, final Class<?> elementClass, final ConstraintDescriptor<?> constraint, final String constraintDescriptorVar, final String violationsVar) throws UnableToCompleteException { final boolean isComposite = !constraint.getComposingConstraints().isEmpty(); final boolean firstReportAsSingleViolation = constraint.isReportAsSingleViolation() && violationsVar.equals(DEFAULT_VIOLATION_VAR) && isComposite; final boolean reportAsSingleViolation = firstReportAsSingleViolation || !violationsVar.equals(DEFAULT_VIOLATION_VAR); final boolean hasValidator = !constraint.getConstraintValidatorClasses().isEmpty(); final String compositeViolationsVar = constraintDescriptorVar + "_violations"; // Only do this the first time in a constraint composition. if (firstReportAsSingleViolation) { // Report myConstraint as Single Violation sw.print("// Report "); sw.print(constraint.getAnnotation().annotationType().getCanonicalName()); sw.println(" as Single Violation"); this.writeNewViolations(sw, compositeViolationsVar); } if (hasValidator) { Class<? extends ConstraintValidator<? extends Annotation, ?>> validatorClass; try { validatorClass = getValidatorForType(constraint, elementClass); } catch (final UnexpectedTypeException e) { throw error(this.logger, e); } if (firstReportAsSingleViolation) { // if (! sw.println("if (!"); sw.indent(); sw.indent(); } // validate(myContext, violations object, value, new MyValidator(), // constraintDescriptor, groups)); sw.print("validate(myContext, "); sw.print(violationsVar); sw.print(", object, value, "); sw.print("new "); sw.print(validatorClass.getCanonicalName()); sw.print("(), "); sw.print(constraintDescriptorVar); sw.print(", groups)"); if (firstReportAsSingleViolation) { // ) { sw.println(") {"); sw.outdent(); } else if (reportAsSingleViolation) { if (isComposite) { // || sw.println(" ||"); } } else { // ; sw.println(";"); } } else if (!isComposite) { // TODO(nchalko) What does the spec say to do here. this.logger.log(TreeLogger.WARN, "No ConstraintValidator of " + constraint + " for " + ppropertyDescription.getPropertyName() + " of type " + elementClass); } if (firstReportAsSingleViolation) { // if ( sw.print("if ("); sw.indent(); sw.indent(); } int count = 0; for (final ConstraintDescriptor<?> compositeConstraint : constraint.getComposingConstraints()) { final String compositeVar = constraintDescriptorVar + "_" + count++; this.writeValidateConstraint(sw, ppropertyDescription, elementClass, compositeConstraint, compositeVar, firstReportAsSingleViolation ? compositeViolationsVar : violationsVar); if (reportAsSingleViolation) { // || sw.println(" ||"); } else { // ; sw.println(";"); } } if (isComposite && reportAsSingleViolation) { // false sw.print("false"); } if (firstReportAsSingleViolation) { // ) { sw.println(" ) {"); sw.outdent(); // addSingleViolation(myContext, violations, object, value, // constraintDescriptor); sw.print("addSingleViolation(myContext, violations, object, value, "); sw.print(constraintDescriptorVar); sw.println(");"); // } sw.outdent(); sw.println("}"); if (hasValidator) { // } sw.outdent(); sw.println("}"); } } } private void writeValidateFieldCall(final SourceWriter sw, final PropertyDescriptor ppropertyDescription, final boolean useValue, final boolean honorValid) { final String propertyName = ppropertyDescription.getPropertyName(); // validateProperty_<<field>>(context, sw.print(this.validateMethodFieldName(ppropertyDescription)); sw.print("(context, "); sw.print("violations, "); // null, (MyType) value, // or // object, object.getLastName(), if (useValue) { sw.print("null, "); sw.print("("); sw.print(this.getQualifiedSourceNonPrimitiveType( this.beanHelper.getElementType(ppropertyDescription, true))); sw.print(") value"); } else { sw.print("object, "); final JField field = this.beanType.getField(propertyName); if (field.isPublic()) { sw.print("object."); sw.print(propertyName); } else { this.fieldsToWrap.add(field); sw.print(this.toWrapperName(field) + "(object)"); } } sw.print(", "); // honorValid, groups); sw.print(Boolean.toString(honorValid)); sw.println(", groups);"); } private void writeValidateGetterCall(final SourceWriter sw, final PropertyDescriptor ppropertyDescription, final boolean useValue, final boolean honorValid) { // validateProperty_get<<field>>(context, violations, sw.print(this.validateMethodGetterName(ppropertyDescription)); sw.print("(context, "); sw.print("violations, "); // object, object.getMyProp(), // or // null, (MyType) value, if (useValue) { sw.print("null, "); sw.print("("); sw.print(this.getQualifiedSourceNonPrimitiveType( this.beanHelper.getElementType(ppropertyDescription, false))); sw.print(") value"); } else { sw.print("object, "); final JMethod method = this.beanType.findMethod(asGetter(ppropertyDescription), NO_ARGS); if (method.isPublic()) { sw.print("object."); sw.print(asGetter(ppropertyDescription)); sw.print("()"); } else { this.gettersToWrap.add(method); sw.print(this.toWrapperName(method) + "(object)"); } } sw.print(", "); // honorValid, groups); sw.print(Boolean.toString(honorValid)); sw.println(", groups);"); } private void writeValidateInheritance(final SourceWriter sw, final Class<?> clazz, final Stage stage, final PropertyDescriptor property) throws UnableToCompleteException { this.writeValidateInheritance(sw, clazz, stage, property, false, "groups"); } private void writeValidateInheritance(final SourceWriter sw, final Class<?> clazz, final Stage stage, final PropertyDescriptor property, final boolean expandDefaultGroupSequence, final String groupsVarName) throws UnableToCompleteException { this.writeValidateInterfaces(sw, clazz, stage, property, expandDefaultGroupSequence, groupsVarName); final Class<?> superClass = clazz.getSuperclass(); if (superClass != null) { this.writeValidatorCall(sw, superClass, stage, property, expandDefaultGroupSequence, groupsVarName); } } private void writeValidateInterfaces(final SourceWriter sw, final Class<?> clazz, final Stage stage, final PropertyDescriptor ppropertyDescription, final boolean expandDefaultGroupSequence, final String groupsVarName) throws UnableToCompleteException { for (final Class<?> type : clazz.getInterfaces()) { this.writeValidatorCall(sw, type, stage, ppropertyDescription, expandDefaultGroupSequence, groupsVarName); this.writeValidateInterfaces(sw, type, stage, ppropertyDescription, expandDefaultGroupSequence, groupsVarName); } } private void writeValidateIterable(final SourceWriter sw, final PropertyDescriptor ppropertyDescription) { // int i = 0; sw.println("int i = 0;"); // for (Object instance : value) { sw.println("for(Object instance : value) {"); sw.indent(); // if(instance != null && !context.alreadyValidated(instance)) { sw.println(" if (instance != null && !context.alreadyValidated(instance)) {"); sw.indent(); // violations.addAll( sw.println("violations.addAll("); sw.indent(); sw.indent(); // context.getValidator().validate( sw.println("context.getValidator().validate("); sw.indent(); sw.indent(); final Class<?> elementClass = ppropertyDescription.getElementClass(); if (elementClass.isArray() || List.class.isAssignableFrom(elementClass)) { // context.appendIndex("myProperty",i++), sw.print("context.appendIndex(\""); sw.print(ppropertyDescription.getPropertyName()); sw.println("\",i),"); } else { // context.appendIterable("myProperty"), sw.print("context.appendIterable(\""); sw.print(ppropertyDescription.getPropertyName()); sw.println("\"),"); } // instance, groups)); sw.println("instance, groups));"); sw.outdent(); sw.outdent(); sw.outdent(); sw.outdent(); // } sw.outdent(); sw.println("}"); // i++; sw.println("i++;"); // } sw.outdent(); sw.println("}"); } private void writeValidateMap(final SourceWriter sw, final PropertyDescriptor ppropertyDescription) { // for (Entry<?, Type> entry : value.entrySet()) { sw.print("for("); sw.print(Entry.class.getCanonicalName()); sw.println("<?, ?> entry : value.entrySet()) {"); sw.indent(); // if(entry.getValue() != null && // !context.alreadyValidated(entry.getValue())) { sw.println(" if (entry.getValue() != null && !context.alreadyValidated(entry.getValue())) {"); sw.indent(); // violations.addAll( sw.println("violations.addAll("); sw.indent(); sw.indent(); // context.getValidator().validate( sw.println("context.getValidator().validate("); sw.indent(); sw.indent(); // context.appendKey("myProperty",entry.getKey()), sw.print("context.appendKey(\""); sw.print(ppropertyDescription.getPropertyName()); sw.println("\",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 writeValidatePropertyCall(final SourceWriter sw, final PropertyDescriptor property, final boolean useValue, final boolean honorValid) { if (useValue) { // boolean valueTypeMatches = false; sw.println("boolean valueTypeMatches = false;"); } if (this.beanHelper.hasGetter(property)) { if (useValue) { // if ( value == null || value instanceof propertyType) { sw.print("if (value == null || value instanceof "); sw.print(this.getQualifiedSourceNonPrimitiveType(this.beanHelper.getElementType(property, false))); sw.println(") {"); sw.indent(); // valueTypeMatches = true; sw.println("valueTypeMatches = true;"); } // validate_getMyProperty this.writeValidateGetterCall(sw, property, useValue, honorValid); if (useValue) { // } sw.outdent(); sw.println("}"); } } if (this.beanHelper.hasField(property)) { if (useValue) { // if ( value == null || value instanceof propertyType) { sw.print("if ( value == null || value instanceof "); sw.print(this.getQualifiedSourceNonPrimitiveType(this.beanHelper.getElementType(property, true))); sw.println(") {"); sw.indent(); // valueTypeMatches = true; sw.println("valueTypeMatches = true;"); } // validate_myProperty this.writeValidateFieldCall(sw, property, useValue, honorValid); if (useValue) { // } else sw.outdent(); sw.println("}"); } } if (useValue && (this.beanHelper.hasGetter(property) || this.beanHelper.hasField(property))) { // if(!valueTypeMatches) { sw.println("if (!valueTypeMatches) {"); sw.indent(); // throw new ValidationException(value.getClass + // " is not a valid type for " + propertyName); sw.print("throw new ValidationException"); sw.println("(value.getClass() +\" is not a valid type for \"+ propertyName);"); // } sw.outdent(); sw.println("}"); } } private void writeValidatePropertyGroups(final SourceWriter sw) throws UnableToCompleteException { // public <T> void validatePropertyGroups( sw.println("public <T> void validatePropertyGroups("); // GwtValidationContext<T> context, BeanType object, String propertyName, // Set<ConstraintViolation<T>> violations, Class<?>... groups) throws ValidationException { sw.indent(); sw.indent(); sw.println("GwtValidationContext<T> context,"); sw.println(this.beanHelper.getTypeCanonicalName() + " object,"); sw.println("String propertyName,"); sw.println("Set<ConstraintViolation<T>> violations,"); sw.println("Class<?>... groups) throws ValidationException {"); sw.outdent(); for (final PropertyDescriptor property : this.beanHelper.getBeanDescriptor().getConstrainedProperties()) { // if (propertyName.equals(myPropety)) { sw.print("if (propertyName.equals(\""); sw.print(property.getPropertyName()); sw.println("\")) {"); sw.indent(); this.writeValidatePropertyCall(sw, property, false, false); // validate all super classes and interfaces this.writeValidateInheritance(sw, this.beanHelper.getClazz(), Stage.PROPERTY, property); // } sw.outdent(); sw.print("} else "); } this.writeIfPropertyNameNotFound(sw); // } sw.outdent(); sw.println("}"); } private void writeValidatePropertyMethod(final SourceWriter sw, final PropertyDescriptor ppropertyDescription, final boolean useField) throws UnableToCompleteException { final Class<?> elementClass = ppropertyDescription.getElementClass(); final JType elementType = this.beanHelper.getElementType(ppropertyDescription, useField); // private final <T> void validateProperty_{get}<p>( sw.print("private final <T> void "); if (useField) { sw.print(this.validateMethodFieldName(ppropertyDescription)); } else { sw.print(this.validateMethodGetterName(ppropertyDescription)); } sw.println("("); sw.indent(); sw.indent(); // final GwtValidationContext<T> context, sw.println("final GwtValidationContext<T> context,"); // final Set<ConstraintViolation<T>> violations, sw.println("final Set<ConstraintViolation<T>> violations,"); // BeanType object, sw.println(this.beanHelper.getTypeCanonicalName() + " object,"); // final <Type> value, sw.print("final "); sw.print(elementType.getParameterizedQualifiedSourceName()); sw.println(" value,"); // boolean honorValid, sw.println("boolean honorValid,"); // Class<?>... groups) { sw.println("Class<?>... groups) {"); sw.outdent(); // only write the checks if the property is constrained in some way if (this.isPropertyConstrained(ppropertyDescription, useField)) { // context = context.append("myProperty"); sw.print("final GwtValidationContext<T> myContext = context.append(\""); sw.print(ppropertyDescription.getPropertyName()); sw.println("\");"); // only check this property if the TraversableResolver says we can // Node leafNode = myContext.getPath().getLeafNode(); sw.println("Node leafNode = myContext.getPath().getLeafNode();"); // PathImpl path = myContext.getPath().getPathWithoutLeafNode(); sw.println("PathImpl path = myContext.getPath().getPathWithoutLeafNode();"); // boolean isReachable; sw.println("boolean isReachable;"); // try { sw.println("try {"); sw.indent(); // isReachable = myContext.getTraversableResolver().isReachable(object, leafNode, // myContext.getRootBeanClass(), path, ElementType); sw.println("isReachable = myContext.getTraversableResolver().isReachable(object, " + "leafNode, myContext.getRootBeanClass(), path, " + (useField ? asLiteral(ElementType.FIELD) : asLiteral(ElementType.METHOD)) + ");"); // } catch (Exception e) { sw.outdent(); sw.println("} catch (Exception e) {"); sw.indent(); // throw new ValidationException("TraversableResolver isReachable caused an exception", e); sw.println("throw new ValidationException(\"TraversableResolver isReachable caused an " + "exception\", e);"); // } sw.outdent(); sw.println("}"); // if (isReachable) { sw.println("if (isReachable) {"); sw.indent(); // TODO(nchalko) move this out of here to the Validate method if (ppropertyDescription.isCascaded() && this.hasValid(ppropertyDescription, useField)) { // if (honorValid && value != null) { sw.println("if (honorValid && value != null) {"); sw.indent(); // boolean isCascadable; sw.println("boolean isCascadable;"); // try { sw.println("try {"); sw.indent(); // isCascadable = myContext.getTraversableResolver().isCascadable(object, leafNode, // myContext.getRootBeanClass(), path, ElementType) sw.println("isCascadable = myContext.getTraversableResolver().isCascadable(object, " + "leafNode, myContext.getRootBeanClass(), path, " + (useField ? asLiteral(ElementType.FIELD) : asLiteral(ElementType.METHOD)) + ");"); // } catch (Exception e) { sw.outdent(); sw.println("} catch (Exception e) {"); sw.indent(); // throw new ValidationException("TraversableResolver isReachable caused an exception", e); sw.println("throw new ValidationException(\"TraversableResolver isCascadable caused an " + "exception\", e);"); // } sw.outdent(); sw.println("}"); // if (isCascadable) { sw.println("if (isCascadable) {"); sw.indent(); if (isIterableOrMap(elementClass)) { final JClassType associationType = this.beanHelper.getAssociationType(ppropertyDescription, useField); this.createBeanHelper(associationType); if (Map.class.isAssignableFrom(elementClass)) { this.writeValidateMap(sw, ppropertyDescription); } else { this.writeValidateIterable(sw, ppropertyDescription); } } else { this.createBeanHelper(elementClass); // if (!context.alreadyValidated(value)) { sw.println(" if (!context.alreadyValidated(value)) {"); sw.indent(); // violations.addAll(myContext.getValidator().validate(context, value, // groups)); sw.print("violations.addAll("); sw.println("myContext.getValidator().validate(myContext, value, groups));"); // } sw.outdent(); sw.println("}"); } // } sw.outdent(); sw.println("}"); // } sw.outdent(); sw.println("}"); } // It is possible for an annotation with the exact same values to be set on // both the field and the getter. // Keep track of the ones we have used to make sure we don't duplicate. final Set<Object> includedAnnotations = Sets.newHashSet(); int count = 0; for (final ConstraintDescriptor<?> constraint : ppropertyDescription.getConstraintDescriptors()) { if (this.areConstraintDescriptorGroupsValid(constraint)) { final Object annotation = constraint.getAnnotation(); if (this.hasMatchingAnnotation(ppropertyDescription, useField, constraint)) { final String constraintDescriptorVar = this .constraintDescriptorVar(ppropertyDescription.getPropertyName(), count); if (includedAnnotations.contains(annotation)) { // The annotation has been looked at once already during this validate property call // so we know the field and the getter are both annotated with the same constraint. if (!useField) { this.writeValidateConstraint(sw, ppropertyDescription, elementClass, constraint, constraintDescriptorVar); } } else { if (useField) { this.writeValidateConstraint(sw, ppropertyDescription, elementClass, constraint, constraintDescriptorVar); } else { // The annotation hasn't been looked at twice (yet) and we are validating a getter // Write the call if only the getter has this constraint applied to it final boolean hasField = this.beanHelper.hasField(ppropertyDescription); if (!hasField || hasField && !this.hasMatchingAnnotation(ppropertyDescription, true, constraint)) { this.writeValidateConstraint(sw, ppropertyDescription, elementClass, constraint, constraintDescriptorVar); } } } includedAnnotations.add(annotation); } count++; } } // } sw.outdent(); sw.println("}"); } sw.outdent(); sw.println("}"); } private void writeValidateValueGroups(final SourceWriter sw) throws UnableToCompleteException { // public <T> void validateValueGroups( sw.println("public <T> void validateValueGroups("); // GwtValidationContext<T> context, Class<Author> beanType, String propertyName, // Object value, Set<ConstraintViolation<T>> violations, Class<?>... groups) { sw.indent(); sw.indent(); sw.println("GwtValidationContext<T> context,"); sw.println("Class<" + this.beanHelper.getTypeCanonicalName() + "> beanType,"); sw.println("String propertyName,"); sw.println("Object value,"); sw.println("Set<ConstraintViolation<T>> violations,"); sw.println("Class<?>... groups) {"); sw.outdent(); for (final PropertyDescriptor property : this.beanHelper.getBeanDescriptor().getConstrainedProperties()) { // if (propertyName.equals(myPropety)) { sw.print("if (propertyName.equals(\""); sw.print(property.getPropertyName()); sw.println("\")) {"); sw.indent(); if (!isIterableOrMap(property.getElementClass())) { this.writeValidatePropertyCall(sw, property, true, false); } // validate all super classes and interfaces this.writeValidateInheritance(sw, this.beanHelper.getClazz(), Stage.VALUE, property); // } sw.outdent(); sw.print("} else "); } this.writeIfPropertyNameNotFound(sw); sw.outdent(); sw.println("}"); } /** * @param ppropertyDescription Only used if writing a call to validate a property - otherwise can * be null. * @param expandDefaultGroupSequence Only used if writing a call to validate a bean. * @param groupsVarName The name of the variable containing the groups. */ private void writeValidatorCall(final SourceWriter sw, final Class<?> type, final Stage stage, final PropertyDescriptor ppropertyDescription, final boolean expandDefaultGroupSequence, final String groupsVarName) throws UnableToCompleteException { if (this.cache.isClassConstrained(type) && !isIterableOrMap(type)) { final BeanHelper helper = this.createBeanHelper(type); this.beansToValidate.add(helper); switch (stage) { case OBJECT: // myValidator sw.print(helper.getValidatorInstanceName()); if (expandDefaultGroupSequence) { // .expandDefaultAndValidateClassGroups(context,object,violations,groups); sw.println(".expandDefaultAndValidateClassGroups(context, object, violations, " + groupsVarName + ");"); } else { // .validateClassGroups(context,object,violations,groups); sw.println(".validateClassGroups(context, object, violations, " + groupsVarName + ");"); } break; case PROPERTY: if (this.isPropertyConstrained(helper, ppropertyDescription)) { // myValidator.validatePropertyGroups(context,object // ,propertyName, violations, groups); sw.print(helper.getValidatorInstanceName()); sw.print(".validatePropertyGroups(context, object,"); sw.println(" propertyName, violations, " + groupsVarName + ");"); } break; case VALUE: if (this.isPropertyConstrained(helper, ppropertyDescription)) { // myValidator.validateValueGroups(context,beanType // ,propertyName, value, violations, groups); sw.print(helper.getValidatorInstanceName()); sw.print(".validateValueGroups(context, "); sw.print(helper.getTypeCanonicalName()); sw.println(".class, propertyName, value, violations, " + groupsVarName + ");"); } break; default: throw new IllegalStateException(); } } } private void writeWrappers(final SourceWriter sw) { sw.println("// Write the wrappers after we know which are needed"); for (final JField field : this.fieldsToWrap) { this.writeFieldWrapperMethod(sw, field); sw.println(); } for (final JMethod method : this.gettersToWrap) { this.writeGetterWrapperMethod(sw, method); sw.println(); } } }