package org.gwtoolbox.bean.rebind.validation;
import com.google.gwt.core.ext.typeinfo.JType;
import org.gwtoolbox.commons.collections.client.attributes.Attributes;
import org.gwtoolbox.commons.generator.rebind.AnnotationWrapper;
import org.gwtoolbox.commons.generator.rebind.GeneratorUtils;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import javax.validation.groups.Default;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* @author Uri Boness
*/
public class ConstraintDefinition {
private Message message;
private Class<? extends ConstraintValidator<?, ?>> validatorClass;
private Attributes attributes;
private Set<Class> groups;
private Set<Class<? extends Payload>> payload;
public ConstraintDefinition(Annotation annotation, JType type) {
Class propertyClass = GeneratorUtils.loadClass(type);
Class<? extends Annotation> annotationType = annotation.annotationType();
AnnotationWrapper wrapper = new AnnotationWrapper(annotation);
attributes = new AnnotationAttributes(annotation);
groups = createClassSet((Class[]) wrapper.getValue("groups"));
if (groups.isEmpty()) {
groups.add(Default.class);
}
groups = GroupUtils.expandGroups(groups);
payload = createClassSet((Class[]) wrapper.getValue("payload"));
message = new Message(wrapper.getString("message"));
// resolving the validator class
Constraint constraint = annotationType.getAnnotation(Constraint.class);
Class<? extends ConstraintValidator<?, ?>>[] validatorClasses = constraint.validatedBy();
if (validatorClasses.length == 0) {
validatorClass = DefaultValidators.getValidatorClass(annotation, type);
} else {
for (Class<? extends ConstraintValidator<?, ?>> validatorClass : validatorClasses) {
if (constraintValidatorSupportsType(validatorClass, propertyClass)) {
this.validatorClass = validatorClass;
break;
}
}
}
if (validatorClass == null) {
throw new RuntimeException("Could not resolve appropriate constraint validator for annotation '" +
annotation.annotationType().getName() + "' on date type '" + type.getQualifiedSourceName() + "'");
}
}
public Message getMessage() {
return message;
}
public Class<? extends ConstraintValidator> getValidatorClass() {
return validatorClass;
}
public Attributes getAttributes() {
return attributes;
}
public Set<Class> getGroups() {
return groups;
}
public Set<Class<? extends Payload>> getPayload() {
return payload;
}
//================================================ Helper Methods ==================================================
private <T> Set<Class<T>> createClassSet(Class<T>[] classes) {
return new HashSet<Class<T>>(Arrays.asList(classes));
}
/**
* According to JSR-303, each constraint annotation can define several constraint validator classes that can beconfigred
* and validate the data. The main difference between these classes is that they must support different data types
* which they can validate. The data type is determined by the generic parameter declaired by the class, which also
* determines the first parameter that is passed in to the {@link ConstraintValidator#isValid(Object, ConstraintValidatorContext)}
* method. This first parameter is therefore used to determine what validator class should be used for the given
* property type.
*
* @param validatorClass The validator class.
* @param propertyClass The property class.
* @return
*/
private boolean constraintValidatorSupportsType(Class<? extends ConstraintValidator<?, ?>> validatorClass, Class propertyClass) {
for (Method method : validatorClass.getMethods()) {
if ("isValid".equals(method.getName()) && method.getParameterTypes().length == 2 &&
method.getParameterTypes()[1] == ConstraintValidatorContext.class && method.getReturnType() == boolean.class) {
// then it's probably the constraint isValid method, so we need to check the type of the first
// parameter of this method. This type indicates what data type the constraint supports.
Class paramType = method.getParameterTypes()[0];
return paramType.isAssignableFrom(propertyClass);
}
}
return false;
}
}