package org.gwtoolbox.bean.rebind.validation;
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.JArrayType;
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 org.gwtoolbox.bean.client.BeanInfo;
import org.gwtoolbox.bean.client.BeanInfoRegistry;
import org.gwtoolbox.bean.client.PropertyDescriptor;
import org.gwtoolbox.bean.client.validation.AbstractBeanValidator;
import org.gwtoolbox.bean.client.validation.BeanValidator;
import org.gwtoolbox.bean.client.validation.PathImpl;
import org.gwtoolbox.bean.client.validation.ViolationRegistry;
import org.gwtoolbox.bean.rebind.BeanGeneratorUtils;
import org.gwtoolbox.bean.rebind.BeanOracle;
import org.gwtoolbox.bean.rebind.BeanOracleBuilder;
import org.gwtoolbox.bean.rebind.JProperty;
import org.gwtoolbox.bean.rebind.validation.config.BeanValidationConfig;
import org.gwtoolbox.bean.rebind.validation.config.BeanValidationConfigHolder;
import org.gwtoolbox.commons.collections.client.Pair;
import org.gwtoolbox.commons.collections.client.attributes.Attributes;
import org.gwtoolbox.commons.generator.rebind.EasyTreeLogger;
import org.gwtoolbox.commons.generator.rebind.GeneratorUtils;
import javax.validation.ConstraintValidator;
import javax.validation.groups.Default;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.util.*;
import static org.gwtoolbox.commons.generator.rebind.GeneratorUtils.escapeForWriter;
/**
* @author Uri Boness
*/
public class BeanValidatorGenerator {
private static int varNameCounter = 0;
public static String generate(EasyTreeLogger logger, GeneratorContext context, JClassType beanType) throws UnableToCompleteException {
String packageName = beanType.getPackage().getName();
String validatorClassName = "gtx__" + beanType.getSimpleSourceName() + "Validator";
String qualifiedValidatorClassName = packageName + "." + validatorClassName;
SourceWriter sourceWriter = getSourceWriter(logger, context, packageName, validatorClassName, beanType.getQualifiedSourceName());
if (sourceWriter == null) {
return qualifiedValidatorClassName;
}
BeanOracle beanOracle = new BeanOracleBuilder(context.getTypeOracle()).build(logger, beanType);
BeanValidationConfig config = BeanValidationConfigHolder.getConfig();
ValidationOracle validationOracle = new ValidationOracle(beanOracle);
write(logger, context, sourceWriter, validationOracle);
sourceWriter.commit(logger);
return qualifiedValidatorClassName;
}
private static SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext context, String packageName, String validatorClassName, String beanClassName) {
PrintWriter printWriter = context.tryCreate(logger, packageName, validatorClassName);
if (printWriter == null) {
return null;
}
ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, validatorClassName);
//TODO add imports here
composerFactory.addImport(BeanValidator.class.getName());
composerFactory.addImport(AbstractBeanValidator.class.getName());
composerFactory.addImport(ViolationRegistry.class.getName());
composerFactory.addImport(ConstraintValidator.class.getName());
composerFactory.addImport(PropertyDescriptor.class.getName());
composerFactory.addImport(BeanInfo.class.getName());
composerFactory.addImport(BeanInfoRegistry.class.getName());
composerFactory.addImport(ViolationRegistry.class.getName());
composerFactory.addImport(Set.class.getName());
composerFactory.addImport(HashSet.class.getName());
composerFactory.addImport(Map.class.getName());
composerFactory.addImport(Map.class.getName() + ".Entry");
composerFactory.addImport(List.class.getName());
composerFactory.addImport(ArrayList.class.getName());
composerFactory.addImport(HashMap.class.getName());
composerFactory.addImport(Default.class.getName());
composerFactory.addImport(PathImpl.class.getName());
composerFactory.addImport(PathImpl.class.getName() + ".NodeImpl");
composerFactory.addImport(Collection.class.getName());
composerFactory.setSuperclass(AbstractBeanValidator.class.getName() + "<" + beanClassName + ">");
return composerFactory.createSourceWriter(context, printWriter);
}
private static void write(EasyTreeLogger logger, GeneratorContext context, SourceWriter writer, ValidationOracle oracle) throws UnableToCompleteException {
writeAbstractMethodsImplementation(logger, context, writer, oracle);
for (JProperty property : oracle.getBeanOracle().getProperties()) {
if (oracle.isValidated(property.getName())) {
writePropertyValidateMethod(logger, context, writer, oracle, property);
}
}
}
private static void writeAbstractMethodsImplementation(EasyTreeLogger logger, GeneratorContext context, SourceWriter writer, ValidationOracle oracle) throws UnableToCompleteException {
String beanClassName = oracle.getBeanOracle().getType().getQualifiedSourceName();
writer.println("public void validate(ViolationRegistry registry, " + beanClassName + " bean, Set<Class> groups) {");
for (JProperty property : oracle.getBeanOracle().getProperties()) {
if (oracle.isValidated(property.getName())) {
writer.println(" validate_" + property.getName() + "(registry, bean, groups);");
}
}
//TODO currently we only validate the class level constraint iff there are no violations on the properties level.
if (oracle.hasClassLevelConstraints()) {
writer.println("if (!registry.hasViolations()) {");
writer.println(" validateAtClassLevel(registry, bean, groups);");
writer.println("}");
}
writer.println("}");
if (oracle.hasClassLevelConstraints()) {
writeClassLevelValidationMethod(logger, context, writer, oracle);
}
writer.println("public void validateProperty(ViolationRegistry registry, " + beanClassName + " bean, String propertyName, Set<Class> groups) {");
if (oracle.hasValidatedProperties()) {
for (JProperty property : oracle.getBeanOracle().getProperties()) {
if (oracle.isValidated(property.getName())) {
writer.println(" if (propertyName.equals(\"" + property.getName() + "\")) {");
writer.println(" validate_" + property.getName() + "(registry, bean, groups);");
writer.println(" return;");
writer.println(" }");
}
}
}
writer.println("}");
writer.println("public List<Set<Class>> computeGroupSequence(Set<Class> groups) {");
writer.println(" Set<Class> expandedGroups = new HashSet<Class>();");
writer.println(" for (Class group : groups) {");
writer.println(" Set<Class> groupSet = getGroupSet(group);");
writer.println(" expandedGroups.addAll(groupSet);");
writer.println(" }");
writer.println(" List<Set<Class>> sequence = new ArrayList<Set<Class>>();");
writer.println(" sequence.add(expandedGroups);");
writer.println(" return sequence;");
writer.println("}");
writer.println("private Set<Class> getGroupSet(Class group) {");
writer.println(" Set<Class> groupSet = new HashSet<Class>();");
writer.println(" groupSet.add(group);");
for (Class superGroup : GroupUtils.getSuperGroups()) {
Set<Class> groupSet = GroupUtils.getGroupSet(superGroup);
writer.println(" if (group == " + superGroup.getName() + ".class) {");
for (Class subGroup : groupSet) {
writer.println(" groupSet.add(" + subGroup.getName() + ".class);");
}
writer.println(" return groupSet;");
writer.println(" }");
}
writer.println(" return groupSet;");
writer.println("}");
writer.println();
writer.println("public static String replaceParameters(String template, Map<String, Object> attributes) {");
writer.println(" for (Map.Entry<String, Object> entry : attributes.entrySet()) {");
writer.println(" template = template.replace(\"{\" + entry.getKey() + \"}\", String.valueOf(entry.getValue()));");
writer.println(" }");
writer.println(" return template;");
writer.println("}");
}
private static void writeClassLevelValidationMethod(EasyTreeLogger logger, GeneratorContext context, SourceWriter writer, ValidationOracle oracle) throws UnableToCompleteException {
String beanTypeName = oracle.getBeanOracle().getType().getQualifiedSourceName();
writer.println("private void validateAtClassLevel(ViolationRegistry registry, " + beanTypeName + " bean, Set<Class> groups) {");
List<ConstraintDefinition> definitions = oracle.getClassLevelConstraints();
int i = 0;
for (ConstraintDefinition definition : definitions) {
writer.print(" if (");
boolean first = true;
for (Class group : definition.getGroups()) {
if (!first) {
writer.print(" || ");
}
writer.print("groups.contains(" + group.getName() + ".class)");
first = false;
}
writer.println(") {");
Class clazz = definition.getValidatorClass();
writer.println(" Map<String, Object> attributes" + i + " = new HashMap<String, Object>();");
Attributes attributes = definition.getAttributes();
for (String name : attributes.getNames()) {
Object value = attributes.get(name);
indent(writer, 3);
String varName = writeAnnotationValue(writer, context, value);
outdent(writer, 3);
writer.println(" attributes" + i + ".put(\"" + name + "\", " + varName + ");");
}
writer.println(" ConstraintValidator validator" + i + " = new " + clazz.getName() + "();");
writer.println(" validator" + i + ".initialize(attributes" + i + ");");
writer.println(" if (!validator" + i + ".isValid(bean, null)) {");
Message message = definition.getMessage();
writer.println(" String messageTemplate = \"" + message.getTemplate() + "\";");
if (message.getMessagesMethod() != null) {
String holderClassName = BeanValidationConfigHolder.getConfig().getMessagesClassHolderClassName();
writer.println(" String message = " + holderClassName + ".get()." + message.getMessagesMethod().getName() + "();");
} else {
writer.println(" String message = messageTemplate;");
}
writer.println(" message = replaceParameters(message, attributes" + i + ");");
writer.println(" registry.register(bean, null, message, messageTemplate, bean);");
writer.println(" }");
writer.println(" }");
}
writer.println("}");
}
private static void writePropertyValidateMethod(EasyTreeLogger logger, GeneratorContext context, SourceWriter writer, ValidationOracle oracle, JProperty property) throws UnableToCompleteException {
String beanTypeName = oracle.getBeanOracle().getType().getQualifiedSourceName();
String propertyTypeName = GeneratorUtils.getBoxedTypeName(property.getType());
writer.println("private void validate_" + property.getName() + "(ViolationRegistry registry, " + beanTypeName + " bean, Set<Class> groups) {");
List<ConstraintDefinition> definitions = oracle.getConstraintDefinitions(property.getName());
writer.println(" " + propertyTypeName + " propVal = bean." + property.getGetter().getName() + "();");
int i = 0;
for (ConstraintDefinition definition : definitions) {
writer.print(" if (");
boolean first = true;
for (Class group : definition.getGroups()) {
if (!first) {
writer.print(" || ");
}
writer.print("groups.contains(" + group.getName() + ".class)");
first = false;
}
writer.println(") {");
Class clazz = definition.getValidatorClass();
writer.println(" Map<String, Object> attributes" + i + " = new HashMap<String, Object>();");
Attributes attributes = definition.getAttributes();
for (String name : attributes.getNames()) {
Object value = attributes.get(name);
indent(writer, 3);
String varName = writeAnnotationValue(writer, context, value);
outdent(writer, 3);
writer.println(" attributes" + i + ".put(\"" + name + "\", " + varName + ");");
}
writer.println(" ConstraintValidator validator" + i + " = new " + clazz.getName() + "();");
writer.println(" validator" + i + ".initialize(attributes" + i + ");");
writer.println(" if (!validator" + i + ".isValid(propVal, null)) {");
Message message = definition.getMessage();
writer.println(" String messageTemplate = \"" + message.getTemplate() + "\";");
if (message.getMessagesMethod() != null) {
String holderClassName = BeanValidationConfigHolder.getConfig().getMessagesClassHolderClassName();
writer.println(" String message = " + holderClassName + ".get()." + message.getMessagesMethod().getName() + "();");
} else {
writer.println(" String message = messageTemplate;");
}
writer.println(" message = replaceParameters(message, attributes" + i + ");");
writer.println(" registry.register(bean, \"" + property.getName() + "\", message, messageTemplate, propVal);");
writer.println(" }");
writer.println(" }");
}
if (oracle.isCascade(property.getName())) {
if (property.isBean()) {
writer.println(" if (propVal != null) {");
writer.println(" BeanInfo subBeanInfo = BeanInfoRegistry.get().getBeanInfo(" + property.getType().getQualifiedSourceName() + ".class);");
writer.println(" registry.push(new PathImpl.NodeImpl(\"" + property.getName() + "\"));");
writer.println(" AbstractBeanValidator subValidator = (AbstractBeanValidator) subBeanInfo.getValidator();");
writer.println(" subValidator.validate(registry, propVal, groups);");
writer.println(" registry.pop();");
writer.println(" }");
} else if (property.isCollection()) {
JClassType elementType = BeanGeneratorUtils.findCollectionElementType(property.getType(), context);
if (BeanGeneratorUtils.isBean(elementType)) {
String elementTypeName = elementType.getQualifiedSourceName();
writer.println(" if (propVal != null) {");
writer.println(" int i = 0;");
writer.println(" for (" + elementTypeName + " element : ((Collection<" + elementTypeName + ">) propVal)) {");
writer.println(" BeanInfo subBeanInfo = BeanInfoRegistry.get().getBeanInfo(" + elementTypeName + ".class);");
writer.println(" registry.push(new PathImpl.NodeImpl(\"" + property.getName() + "\", i++));");
writer.println(" AbstractBeanValidator subValidator = (AbstractBeanValidator) subBeanInfo.getValidator();");
writer.println(" subValidator.validate(registry, element, groups);");
writer.println(" registry.pop();");
writer.println(" }");
writer.println(" }");
}
} else if (property.isMap()) {
Pair<JClassType, JClassType> keyValueTypes = BeanGeneratorUtils.findMapKeyValueTypes(property.getType(), context);
if (BeanGeneratorUtils.isBean(keyValueTypes.getValue2())) {
String keyTypeName = keyValueTypes.getValue1().getQualifiedSourceName();
String valueTypeName = keyValueTypes.getValue2().getQualifiedSourceName();
writer.println(" if (propVal != null) {");
writer.println(" for (Map.Entry<" + keyTypeName + ", " + valueTypeName + "> entry : ((Map<" + keyTypeName + ", " + valueTypeName + ">) propVal).entrySet()) {");
writer.println(" BeanInfo subBeanInfo = BeanInfoRegistry.get().getBeanInfo(" + valueTypeName + ".class);");
writer.println(" registry.push(new PathImpl.NodeImpl(\"" + property.getName() + "\", entry.getKey()));");
writer.println(" AbstractBeanValidator subValidator = (AbstractBeanValidator) subBeanInfo.getValidator();");
writer.println(" subValidator.validate(registry, entry.getValue(), groups);");
writer.println(" registry.pop();");
writer.println(" }");
writer.println(" }");
}
} else if (property.isArray() && BeanGeneratorUtils.isBean(property.getType().isArray().getComponentType())) {
JArrayType arrayType = property.getType().isArray();
String componentTypeName = arrayType.getComponentType().getQualifiedSourceName();
writer.println(" if (propVal != null) {");
String arrayVar = nextVarName();
writer.println(" " + componentTypeName + "[] " + arrayVar + " = (" + componentTypeName + "[]) propVal;");
writer.println(" for (int i = 0; i < " + arrayVar + ".length; i++) {");
writer.println(" BeanInfo subBeanInfo = BeanInfoRegistry.get().getBeanInfo(" + componentTypeName + ".class);");
writer.println(" registry.push(new PathImpl.NodeImpl(\"" + property.getName() + "\", i++));");
writer.println(" AbstractBeanValidator subValidator = (AbstractBeanValidator) subBeanInfo.getValidator();");
writer.println(" subValidator.validate(registry, " + arrayVar + "[i], groups);");
writer.println(" registry.pop();");
writer.println(" }");
writer.println(" }");
}
}
writer.println("}");
}
private static String writeAnnotationValue(SourceWriter writer, GeneratorContext context, Object value) {
String varName = nextVarName();
if (String.class.isInstance(value)) {
writer.println("String " + varName + " = \"" + escapeForWriter(String.valueOf(value)) + "\";");
return varName;
}
if (Enum.class.isInstance(value)) {
String typeName = value.getClass().getCanonicalName();
writer.println(typeName + " " + varName + " = " + typeName + "." + ((Enum) value).name() + ";");
return varName;
}
if (Class.class.isInstance(value)) {
writer.println("Class " + varName + " = " + ((Class) value).getName() + ".class;");
return varName;
}
if (value.getClass().isArray()) {
int length = Array.getLength(value);
StringBuilder vars = new StringBuilder();
for (int i = 0; i < length; i++) {
Object item = Array.get(value, i);
if (vars.length() != 0) {
vars.append(", ");
}
vars.append(writeAnnotationValue(writer, context, item));
}
String componentyTypeName = value.getClass().getComponentType().getCanonicalName();
writer.println(componentyTypeName + "[] " + varName + " = new " + componentyTypeName + "[] { " + vars.toString() + " };");
return varName;
}
if (Number.class.isInstance(value)) {
writer.println(value.getClass().getSimpleName() + " " + varName + " = new " + value.getClass().getSimpleName() + "(" + String.valueOf(value) + ");");
return varName;
}
writer.println(value.getClass().getName() + " " + varName + " = " + String.valueOf(value) + ";");
return varName;
}
private static String nextVarName() {
return "var" + varNameCounter++;
}
private static void indent(SourceWriter writer, int indent) {
for (int i = 0; i < indent; i++) {
writer.indent();
}
}
private static void outdent(SourceWriter writer, int outdent) {
for (int i = 0; i < outdent; i++) {
writer.outdent();
}
}
}