/* * 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("\\.", "_"); } }