/** * Copyright (C) 2016 Red Hat, Inc. and/or its affiliates. * * 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 org.jboss.errai.validation.rebind; import static org.jboss.errai.codegen.Parameter.finalOf; import static org.jboss.errai.codegen.meta.MetaClassFactory.parameterizedAs; import static org.jboss.errai.codegen.meta.MetaClassFactory.typeParametersOf; import static org.jboss.errai.codegen.util.Stmt.castTo; import static org.jboss.errai.codegen.util.Stmt.if_; import static org.jboss.errai.codegen.util.Stmt.invokeStatic; import static org.jboss.errai.codegen.util.Stmt.loadLiteral; import static org.jboss.errai.codegen.util.Stmt.loadVariable; import static org.jboss.errai.codegen.util.Stmt.newObject; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.ConstraintViolation; import org.jboss.errai.codegen.InnerClass; import org.jboss.errai.codegen.Statement; import org.jboss.errai.codegen.TernaryStatement; import org.jboss.errai.codegen.builder.AnonymousClassStructureBuilder; import org.jboss.errai.codegen.builder.BlockBuilder; import org.jboss.errai.codegen.builder.ClassStructureBuilder; import org.jboss.errai.codegen.builder.ContextualStatementBuilder; import org.jboss.errai.codegen.builder.impl.ClassBuilder; import org.jboss.errai.codegen.builder.impl.ObjectBuilder; import org.jboss.errai.codegen.meta.MetaClass; import org.jboss.errai.codegen.meta.MetaType; import org.jboss.errai.codegen.util.Bool; import org.jboss.errai.codegen.util.Refs; import org.jboss.errai.codegen.util.Stmt; import org.jboss.errai.ioc.client.container.Factory; import org.jboss.errai.ioc.rebind.ioc.bootstrapper.AbstractBodyGenerator; import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraph; import org.jboss.errai.ioc.rebind.ioc.graph.api.Injectable; import org.jboss.errai.ioc.rebind.ioc.injector.api.InjectionContext; import org.jboss.errai.validation.client.dynamic.DynamicValidator; import org.jboss.errai.validation.client.dynamic.DynamicValidatorUtil; import org.jboss.errai.validation.client.dynamic.GeneratedDynamicValidator; import com.google.gwt.core.client.GWT; import com.google.gwt.validation.client.ProviderValidationMessageResolver; import com.google.gwt.validation.client.ValidationMessageResolver; import com.google.gwt.validation.client.impl.ConstraintViolationImpl; /** * Geneate the {@link Factory} body for the factory producing the {@link DynamicValidator}. This generates a * {@link GeneratedDynamicValidator} for any GWT-translatable {@link ConstraintValidator} found at compile-time. * * @author Max Barkley <mbarkley@redhat.com> */ public class DynamicValidatorBodyGenerator extends AbstractBodyGenerator { private final List<MetaClass> validators; public DynamicValidatorBodyGenerator(final List<MetaClass> validators) { this.validators = validators; } @Override protected List<Statement> generateCreateInstanceStatements(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext) { final List<Statement> statements = new ArrayList<>(validators.size()+2); statements.add(Stmt.declareFinalVariable("dynamicValidator", DynamicValidator.class, ObjectBuilder.newInstanceOf(DynamicValidator.class))); bodyBlockBuilder.privateField("messageResolver", ValidationMessageResolver.class) .initializesWith(Stmt.invokeStatic(GWT.class, "create", Stmt.loadLiteral(ProviderValidationMessageResolver.class))).finish(); validators .stream() .map(validator -> addDynamicValidator(bodyBlockBuilder, validator)) .collect(Collectors.toCollection(() -> statements)); statements.add(loadVariable("dynamicValidator").returnValue()); return statements; } private Statement addDynamicValidator(final ClassStructureBuilder<?> bodyBlockBuilder, final MetaClass validator) { final MetaClass constraintValidatorIface = getConstraintValidatorIface(validator); final MetaClass valueType = getValueType(validator, constraintValidatorIface); final MetaClass annoType = getConstraintType(validator, constraintValidatorIface); final ObjectBuilder annoImpl = createAnnoImpl(annoType); final ClassStructureBuilder<?> generatedValidatorBuilder = ClassBuilder .define("Dynamic" + fullyQualifiedClassNameToCamelCase(validator.getFullyQualifiedName()), validator) .publicScope() .implementsInterface(parameterizedAs(GeneratedDynamicValidator.class, typeParametersOf(valueType))) .body(); final BlockBuilder<?> validateMethod = generatedValidatorBuilder .publicMethod(parameterizedAs(Set.class, typeParametersOf(ConstraintViolation.class)), "validate", finalOf(parameterizedAs(Map.class, typeParametersOf(String.class, Object.class)), "parameters"), finalOf(valueType, "value")) .body(); bodyBlockBuilder.declaresInnerClass(new InnerClass(generatedValidatorBuilder.getClassDefinition())); validateMethod .append(loadVariable("this").invoke("initialize", annoImpl)) .append(if_(Bool.expr(loadVariable("this").invoke("isValid", loadVariable("value"), castTo(ConstraintValidatorContext.class, loadLiteral(null))))) .append(invokeStatic(Collections.class, "emptySet").returnValue()) .finish().else_() .append(Stmt.declareVariable("paramMessage", String.class, castTo(String.class, loadVariable("parameters").invoke("get", Stmt.loadLiteral("message"))))) .append(Stmt.loadVariable("paramMessage").assignValue( new TernaryStatement(Bool.isNotNull(Stmt.loadVariable("paramMessage")), Stmt.loadVariable("paramMessage") .invoke("replaceAll", Stmt.loadLiteral("{"), Stmt.loadLiteral("")) .invoke("replaceAll", Stmt.loadLiteral("}"), Stmt.loadLiteral("")) ,Stmt.loadLiteral("")))) .append(Stmt.declareFinalVariable("message", String.class, Stmt.loadVariable("messageResolver").invoke("get", Refs.get("paramMessage")))) .append(invokeStatic(Collections.class, "singleton", createConstraintViolation()).returnValue()) .finish()) .finish(); return loadVariable("dynamicValidator").invoke("addValidator", loadLiteral(annoType.getFullyQualifiedName()), loadLiteral(valueType.getFullyQualifiedName()), newObject(generatedValidatorBuilder.getClassDefinition())); } private String fullyQualifiedClassNameToCamelCase(final String fqcn) { final StringBuilder builder = new StringBuilder(); boolean capitalize = true; for (int i = 0; i < fqcn.length(); i++) { final char charAt = fqcn.charAt(i); if (capitalize) { builder.append(Character.toUpperCase(charAt)); capitalize = false; } else if (charAt == '.') { capitalize = true; } else { builder.append(charAt); } } return builder.toString(); } private ContextualStatementBuilder createConstraintViolation() { return invokeStatic(ConstraintViolationImpl.class, "builder").invoke("setInvalidValue", loadVariable("value")) .invoke("setMessage", Stmt.invokeStatic(DynamicValidatorUtil.class, "interpolateMessage", Refs.get("parameters"), Refs.get("message"))) .invoke("build"); } private ObjectBuilder createAnnoImpl(final MetaClass annoType) { final AnonymousClassStructureBuilder builder = ObjectBuilder.newInstanceOf(annoType).extend(); Arrays.stream(annoType.getDeclaredMethods()) .forEach(m -> builder.publicOverridesMethod(m.getName()) .append(castTo(m.getReturnType(), loadVariable("parameters").invoke("get", m.getName())).returnValue()).finish()); builder.publicOverridesMethod("annotationType").append(loadLiteral(annoType).returnValue()).finish(); return builder.finish(); } private MetaClass getConstraintType(final MetaClass validator, final MetaClass constraintValidatorIface) { final MetaType annoTypeVariable = constraintValidatorIface.getParameterizedType().getTypeParameters()[0]; if (!(annoTypeVariable instanceof MetaClass)) { throw new RuntimeException("Cannot determine constraint type for " + validator.getFullyQualifiedName()); } final MetaClass annoType = (MetaClass) annoTypeVariable; return annoType; } private MetaClass getValueType(final MetaClass validator, final MetaClass constraintValidatorIface) { final MetaType valueTypeVariable = constraintValidatorIface.getParameterizedType().getTypeParameters()[1]; if (!(valueTypeVariable instanceof MetaClass)) { throw new RuntimeException("Cannot determine validated type of " + validator.getFullyQualifiedName()); } final MetaClass valueType = (MetaClass) valueTypeVariable; return valueType; } private MetaClass getConstraintValidatorIface(final MetaClass validator) { final Optional<MetaClass> ifaceOptional = validator.getAllSuperTypesAndInterfaces().stream() .filter(iface -> iface.getFullyQualifiedName().equals(ConstraintValidator.class.getName())).findAny(); if (!ifaceOptional.isPresent()) { throw new RuntimeException("Tried to generate dynamic validator for type that isn't a ConstraintValidator: " + validator.getFullyQualifiedName()); } return ifaceOptional.get(); } }