blob: 081c496b60764a24281f056ed6e3dd43d6555e91 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* 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 com.google.gwt.validation.rebind;
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.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JRawType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.thirdparty.guava.common.base.Function;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.validation.client.impl.GwtSpecificValidator;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
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;
/**
* <strong>EXPERIMENTAL</strong> and subject to change. Do not use this in
* production code.
* <p>
* A simple struct for the various values associated with a Bean that can be
* validated.
*/
public final class BeanHelper {
public static final Function<BeanHelper, Class<?>> TO_CLAZZ =
new Function<BeanHelper, Class<?>>() {
public Class<?> apply(BeanHelper helper) {
return helper.getClazz();
}
};
private static final Validator serverSideValidator =
Validation.buildDefaultValidatorFactory().getValidator();
// stash the map in a ThreadLocal, since each GWT module lives in its own
// thread in DevMode
private static final ThreadLocal<Map<JClassType, BeanHelper>> threadLocalHelperMap =
new ThreadLocal<Map<JClassType, BeanHelper>>() {
@Override
protected synchronized Map<JClassType, BeanHelper> initialValue() {
return new HashMap<JClassType, BeanHelper>();
}
};
/**
* Visible for testing.
*/
public static void clearBeanHelpersForTests() {
threadLocalHelperMap.get().clear();
}
public static BeanHelper createBeanHelper(Class<?> clazz, TreeLogger logger,
GeneratorContext context) throws UnableToCompleteException {
JClassType beanType = context.getTypeOracle().findType(
clazz.getCanonicalName());
return createBeanHelper(clazz, beanType, logger, context);
}
public static Map<JClassType, BeanHelper> getBeanHelpers() {
return Collections.unmodifiableMap(threadLocalHelperMap.get());
}
protected static BeanHelper createBeanHelper(JClassType jType,
TreeLogger logger, GeneratorContext context)
throws UnableToCompleteException {
JClassType erasedType = jType.getErasedType();
try {
Class<?> clazz = Class.forName(erasedType.getQualifiedBinaryName());
return createBeanHelper(clazz, erasedType, logger, context);
} catch (ClassNotFoundException e) {
logger.log(TreeLogger.ERROR, "Unable to create BeanHelper for "
+ erasedType, e);
throw new UnableToCompleteException();
}
}
protected static boolean isClassConstrained(Class<?> clazz) {
return serverSideValidator.getConstraintsForClass(clazz).isBeanConstrained();
}
static BeanHelper getBeanHelper(JClassType beanType) {
return getBeanHelpers().get(beanType.getErasedType());
}
/**
* Write an Empty Interface implementing {@link GwtSpecificValidator} with
* Generic parameter of the bean type.
*/
static void writeInterface(GeneratorContext context, TreeLogger logger,
BeanHelper bean) {
PrintWriter pw = context.tryCreate(logger, bean.getPackage(),
bean.getValidatorName());
if (pw != null) {
TreeLogger interfaceLogger = logger.branch(TreeLogger.TRACE,
"Creating the interface for " + bean.getFullyQualifiedValidatorName());
ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
bean.getPackage(), bean.getValidatorName());
factory.addImplementedInterface(GwtSpecificValidator.class.getCanonicalName()
+ " <" + bean.getTypeCanonicalName() + ">");
factory.addImport(GWT.class.getCanonicalName());
factory.makeInterface();
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();
}
}
private static synchronized void addBeanHelper(BeanHelper helper) {
threadLocalHelperMap.get().put(helper.getJClass(), helper);
}
private static BeanHelper createBeanHelper(Class<?> clazz,
JClassType beanType, TreeLogger logger, GeneratorContext context)
throws UnableToCompleteException {
BeanHelper helper = getBeanHelper(beanType);
if (helper == null) {
BeanDescriptor bean;
try {
bean = serverSideValidator.getConstraintsForClass(clazz);
} catch (ValidationException e) {
logger.log(TreeLogger.ERROR,
"Unable to create a validator for " + clazz.getCanonicalName()
+ " because " + e.getMessage(), e);
throw new UnableToCompleteException();
}
helper = new BeanHelper(beanType, clazz, bean);
addBeanHelper(helper);
writeInterface(context, logger, helper);
// now recurse on all Cascaded elements
for (PropertyDescriptor p : bean.getConstrainedProperties()) {
if (p.isCascaded()) {
createBeanHelper(p, helper, logger, context);
}
}
}
return helper;
}
private static void createBeanHelper(PropertyDescriptor p, BeanHelper parent,
TreeLogger logger, GeneratorContext context)
throws UnableToCompleteException {
Class<?> elementClass = p.getElementClass();
if (GwtSpecificValidatorCreator.isIterableOrMap(elementClass)) {
if (parent.hasField(p)) {
JClassType type = parent.getAssociationType(p, true);
createBeanHelper(type.getErasedType(), logger, context);
}
if (parent.hasGetter(p)) {
JClassType type = parent.getAssociationType(p, false);
createBeanHelper(type.getErasedType(), logger, context);
}
} else {
if (serverSideValidator.getConstraintsForClass(elementClass).isBeanConstrained()) {
createBeanHelper(elementClass, logger, context);
}
}
}
private final BeanDescriptor beanDescriptor;
private final JClassType jClass;
private Class<?> clazz;
private BeanHelper(JClassType jClass, Class<?> clazz,
BeanDescriptor beanDescriptor) {
super();
this.beanDescriptor = beanDescriptor;
this.jClass = jClass;
this.clazz = clazz;
}
public JClassType getAssociationType(PropertyDescriptor p, boolean useField) {
JType type = this.getElementType(p, useField);
JArrayType jArray = type.isArray();
if (jArray != null) {
return jArray.getComponentType().isClassOrInterface();
}
JParameterizedType pType = type.isParameterized();
JClassType[] typeArgs;
if (pType == null) {
JRawType rType = type.isRawType();
typeArgs = rType.getGenericType().getTypeParameters();
} else {
typeArgs = pType.getTypeArgs();
}
// it is either a Iterable or a Map use the last type arg.
return typeArgs[typeArgs.length - 1].isClassOrInterface();
}
public BeanDescriptor getBeanDescriptor() {
return beanDescriptor;
}
/*
* The server side validator needs an actual class.
*/
public Class<?> getClazz() {
return clazz;
}
public String getDescriptorName() {
return jClass.getName() + "Descriptor";
}
public String getFullyQualifiedValidatorName() {
return getPackage() + "." + getValidatorName();
}
public JClassType getJClass() {
return jClass;
}
public String getPackage() {
return jClass.getPackage().getName();
}
public String getTypeCanonicalName() {
return jClass.getQualifiedSourceName();
}
public String getValidatorInstanceName() {
return getFullyQualifiedValidatorName() + ".INSTANCE";
}
public String getValidatorName() {
return makeJavaSafe("_" + jClass.getName() + "Validator");
}
@Override
public String toString() {
return getTypeCanonicalName();
}
JType getElementType(PropertyDescriptor p, boolean useField) {
if (useField) {
return jClass.findField(p.getPropertyName()).getType();
} else {
return jClass.findMethod(GwtSpecificValidatorCreator.asGetter(p),
GwtSpecificValidatorCreator.NO_ARGS).getReturnType();
}
}
boolean hasField(PropertyDescriptor p) {
JField field = jClass.findField(p.getPropertyName());
return field != null;
}
boolean hasGetter(PropertyDescriptor p) {
JType[] paramTypes = new JType[]{};
try {
jClass.getMethod(GwtSpecificValidatorCreator.asGetter(p), paramTypes);
return true;
} catch (NotFoundException e) {
return false;
}
}
private String makeJavaSafe(String in) {
return in.replaceAll("\\.", "_");
}
}