package org.gwtoolbox.bean.rebind.validation;
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 org.gwtoolbox.bean.rebind.BeanOracle;
import org.gwtoolbox.bean.rebind.JProperty;
import org.gwtoolbox.commons.generator.rebind.AnnotationWrapper;
import org.gwtoolbox.commons.generator.rebind.GeneratorUtils;
import javax.validation.Constraint;
import javax.validation.Valid;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author Uri Boness
*/
public class ValidationOracle {
private BeanOracle beanOracle;
private Map<String, List<ConstraintDefinition>> constraintsByProperty = new HashMap<String, List<ConstraintDefinition>>();
private Set<String> constrainedProperties = new HashSet<String>();
private Set<String> cascadeProperties = new HashSet<String>();
private List<ConstraintDefinition> classLevelConstraints;
public ValidationOracle(BeanOracle beanOracle) {
this.beanOracle = beanOracle;
classLevelConstraints = loadClassLevelConstraints(beanOracle);
for (JProperty property : beanOracle.getProperties()) {
List<ConstraintDefinition> constraints = loadConstraints(property);
constraintsByProperty.put(property.getName(), constraints);
if (!constraints.isEmpty()) {
constrainedProperties.add(property.getName());
}
if (isCascade(property)) {
cascadeProperties.add(property.getName());
}
}
}
public BeanOracle getBeanOracle() {
return beanOracle;
}
public boolean hasClassLevelConstraints() {
return classLevelConstraints != null && !classLevelConstraints.isEmpty();
}
public List<ConstraintDefinition> getClassLevelConstraints() {
return classLevelConstraints;
}
public boolean hasValidatedProperties() {
return !cascadeProperties.isEmpty() || !constrainedProperties.isEmpty();
}
public boolean isValidated(String propertyName) {
return isCascade(propertyName) || !constraintsByProperty.get(propertyName).isEmpty();
}
public boolean isCascade(String propertyName) {
return cascadeProperties.contains(propertyName);
}
public List<ConstraintDefinition> getConstraintDefinitions(String propertyName) {
List<ConstraintDefinition> definitions = constraintsByProperty.get(propertyName);
return definitions != null ? definitions : Collections.EMPTY_LIST;
}
//================================================ Helper Methods ==================================================
private boolean isCascade(JProperty property) {
JField field = property.getField();
if (field != null && field.isAnnotationPresent(Valid.class)) {
return true;
}
JMethod getter = property.getGetter();
return getter != null && getter.isAnnotationPresent(Valid.class);
}
private List<ConstraintDefinition> loadClassLevelConstraints(BeanOracle beanOracle) {
Class beanClass = GeneratorUtils.loadClass(beanOracle.getType());
List<Annotation> constraintAnnotations = loadConstraintAnnotations(beanClass);
if (constraintAnnotations == null || constraintAnnotations.isEmpty()) {
return null;
}
classLevelConstraints = new ArrayList<ConstraintDefinition>(constraintAnnotations.size());
for (Annotation annotation : constraintAnnotations) {
classLevelConstraints.add(new ConstraintDefinition(annotation, beanOracle.getType()));
}
return classLevelConstraints;
}
private List<ConstraintDefinition> loadConstraints(JProperty property) {
List<Annotation> constraintAnnotations = loadConstraintAnnotations(property);
List<ConstraintDefinition> definitions = new ArrayList<ConstraintDefinition>(constraintAnnotations.size());
for (Annotation annotation : constraintAnnotations) {
definitions.add(new ConstraintDefinition(annotation, property.getType()));
}
return definitions;
}
private List<Annotation> loadConstraintAnnotations(JProperty property) {
List<Annotation> annotations = new ArrayList<Annotation>();
Field field = resolveField(property);
if (field != null) {
annotations.addAll(loadConstraintAnnotations(field));
}
Method getter = resolveGetterMethod(property);
if (getter != null) {
annotations.addAll(loadConstraintAnnotations(getter));
}
return annotations;
}
private List<Annotation> loadConstraintAnnotations(AnnotatedElement element) {
List<Annotation> result = new ArrayList<Annotation>();
for (Annotation annotation : element.getAnnotations()) {
// the the annotation is marked with the @Constraint annotation we just add it
if (annotation.annotationType().isAnnotationPresent(Constraint.class)) {
result.add(annotation);
continue;
}
// now we're looking for a list annotation. According to the specs the list annotation should only have
// a value attribute of an array type where the components of the array are annotations marked with the @Constraint
// annotation
AnnotationWrapper wrapper = new AnnotationWrapper(annotation);
if (!wrapper.getAttributeNames().contains("value")) {
continue;
}
Object value = wrapper.getValue("value");
if (!value.getClass().isArray()) {
continue;
}
Class subAnnotationType = value.getClass().getComponentType();
if (!subAnnotationType.isAnnotation()) {
continue;
}
if (!subAnnotationType.isAnnotationPresent(Constraint.class)) {
continue;
}
for (int i = 0; i < Array.getLength(value); i++) {
annotation = (Annotation) Array.get(value, i);
result.add(annotation);
}
}
return result;
}
private Field resolveField(JProperty property) {
if (property.getField() == null) {
return null;
}
JClassType type = property.getField().getEnclosingType();
Class clazz = GeneratorUtils.loadClass(type);
try {
return clazz.getDeclaredField(property.getField().getName());
} catch (NoSuchFieldException e) {
return null;
}
}
private Method resolveGetterMethod(JProperty property) {
if (property.getGetter() == null) {
return null;
}
return GeneratorUtils.resolveMethod(property.getGetter());
}
}