/*
* Copyright 2012 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.GwtSpecificValidator;
import com.google.gwt.core.client.GWT;
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.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.PropertyDescriptor;
/**
* A cache and factory for BeanHelpers. There should be one BeanHelperCache per compilation run.
* <p>
* (public for tests)
* </p>
*/
public class BeanHelperCache { // public for testing
private final Map<JClassType, BeanHelper> cache;
private final Validator serverSideValidator;
/**
* Creates a cache. There should be one cache per compiler run. (public for tests.)
*/
public BeanHelperCache() {
this.cache = new HashMap<JClassType, BeanHelper>();
this.serverSideValidator = Validation.buildDefaultValidatorFactory().getValidator();
}
/**
* Clears the cache. (Public for testing.)
*/
public void clear() {
this.cache.clear();
}
/**
* Creates a BeanHelper and writes an interface containing its instance. Also, recursively creates
* any BeanHelpers on its constrained properties. (Public for testing.)
*/
public BeanHelper createHelper(final Class<?> clazz, final TreeLogger logger,
final GeneratorContext context) throws UnableToCompleteException {
final JClassType beanType = context.getTypeOracle().findType(clazz.getCanonicalName());
return this.doCreateHelper(clazz, beanType, logger, context);
}
/**
* Creates a BeanHelper and writes an interface containing its instance. Also, recursively creates
* any BeanHelpers on its constrained properties.
*/
BeanHelper createHelper(final JClassType pjtype, final TreeLogger plogger,
final GeneratorContext pcontext) throws UnableToCompleteException {
final JClassType erasedType = pjtype.getErasedType();
try {
final Class<?> clazz = Class.forName(erasedType.getQualifiedBinaryName());
return this.doCreateHelper(clazz, erasedType, plogger, pcontext);
} catch (final ClassNotFoundException e) {
plogger.log(TreeLogger.ERROR, "Unable to create BeanHelper for " + erasedType, e);
throw new UnableToCompleteException(); // NOPMD
}
}
List<BeanHelper> getAllBeans() {
return Util.sortMostSpecificFirst(this.cache.values(), BeanHelper.TO_CLAZZ);
}
BeanHelper getBean(final JClassType key) {
return this.cache.get(key);
}
boolean isClassConstrained(final Class<?> clazz) {
return this.serverSideValidator.getConstraintsForClass(clazz).isBeanConstrained();
}
private BeanHelper doCreateHelper(final Class<?> clazz, final JClassType beanType,
final TreeLogger logger, final GeneratorContext context) throws UnableToCompleteException {
BeanHelper helper = this.getBean(beanType);
if (helper == null) {
BeanDescriptor bean;
try {
bean = this.serverSideValidator.getConstraintsForClass(clazz);
} catch (final ValidationException e) {
logger.log(TreeLogger.ERROR, "Unable to create a validator for " + clazz.getCanonicalName()
+ " because " + e.getMessage(), e);
throw new UnableToCompleteException(); // NOPMD
}
helper = new BeanHelper(beanType, clazz, bean);
this.cache.put(helper.getJClass(), helper);
this.writeInterface(context, logger, helper);
// now recurse on all Cascaded elements
for (final PropertyDescriptor p : bean.getConstrainedProperties()) {
// TODO(idol) only bother creating objects for properties that have constrains in the groups
// specified in @GwtValidation, but not others
if (p.isCascaded()) {
this.doCreateHelperForProp(p, helper, logger, context);
}
}
}
return helper;
}
/**
* Creates the appropriate BeanHelper for a property on a bean.
*/
private void doCreateHelperForProp(final PropertyDescriptor propertyDescriptor,
final BeanHelper parent, final TreeLogger logger, final GeneratorContext context)
throws UnableToCompleteException {
final Class<?> elementClass = propertyDescriptor.getElementClass();
if (GwtSpecificValidatorCreator.isIterableOrMap(elementClass)) {
if (parent.hasField(propertyDescriptor)) {
final JClassType type = parent.getAssociationType(propertyDescriptor, true);
this.createHelper(type.getErasedType(), logger, context);
}
if (parent.hasGetter(propertyDescriptor)) {
final JClassType type = parent.getAssociationType(propertyDescriptor, false);
this.createHelper(type.getErasedType(), logger, context);
}
} else {
if (this.serverSideValidator.getConstraintsForClass(elementClass).isBeanConstrained()) {
this.createHelper(elementClass, logger, context);
}
}
}
/**
* Write an Empty Interface implementing
* {@link de.knightsoftnet.validators.client.impl.GwtSpecificValidator} with Generic parameter of
* the bean type.
*/
private void writeInterface(final GeneratorContext context, final TreeLogger logger,
final BeanHelper bean) {
final PrintWriter pw = context.tryCreate(logger, bean.getPackage(), bean.getValidatorName());
if (pw != null) {
final TreeLogger interfaceLogger = logger.branch(TreeLogger.TRACE,
"Creating the interface for " + bean.getFullyQualifiedValidatorName());
final ClassSourceFileComposerFactory factory =
new ClassSourceFileComposerFactory(bean.getPackage(), bean.getValidatorName());
factory.addImplementedInterface(
GwtSpecificValidator.class.getCanonicalName() + " <" + bean.getTypeCanonicalName() + ">");
factory.addImport(GWT.class.getCanonicalName());
factory.makeInterface();
final SourceWriter sw = factory.createSourceWriter(context, pw);
// static MyValidator INSTANCE = GWT.create(MyValidator.class);
sw.print("static ");
sw.print(bean.getValidatorName());
sw.print(" INSTANCE = GWT.create(");
sw.print(bean.getValidatorName());
sw.println(".class);");
sw.commit(interfaceLogger);
pw.close();
}
}
}