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