Package org.gwtoolbox.bean.rebind

Source Code of org.gwtoolbox.bean.rebind.BeanInfoGenerator

/*
* Copyright 2002-2008 the original author or authors.
*
* 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.gwtoolbox.bean.rebind;

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.*;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import org.gwtoolbox.bean.client.*;
import org.gwtoolbox.bean.client.annotation.AttributesClass;
import org.gwtoolbox.bean.client.annotation.Bean;
import org.gwtoolbox.bean.client.annotation.Description;
import org.gwtoolbox.bean.client.annotation.DisplayName;
import org.gwtoolbox.bean.client.validation.BeanValidator;
import org.gwtoolbox.bean.rebind.validation.BeanValidatorGenerator;
import org.gwtoolbox.commons.generator.rebind.AnnotationWrapper;
import org.gwtoolbox.commons.generator.rebind.EasyTreeLogger;
import org.gwtoolbox.commons.generator.rebind.GeneratorUtils;

import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
* Helper generator that generates the bean info classes for a specific bean type.
*
* @author Uri Boness
*/
public class BeanInfoGenerator {

    private static volatile int typeNameCounter = 0;

    private final static ThreadLocal<Set<String>> typeRegistryThreadLocal = new ThreadLocal<Set<String>>();

    public static String generate(TreeLogger logger, GeneratorContext context, JClassType beanType) throws UnableToCompleteException {
        String packageName = beanType.getPackage().getName();

        String className = "gtx__" + beanType.getSimpleSourceName() + "BeanInfo";
        String qualifiedBeanClassName = packageName + "." + className;
        SourceWriter sourceWriter = getSourceWriter(logger, context, packageName, className);
        if (sourceWriter == null) {
            return qualifiedBeanClassName;
        }

        typeRegistryThreadLocal.set(new HashSet<String>());
        write(new EasyTreeLogger(logger), sourceWriter, context, beanType, className);
        typeRegistryThreadLocal.remove();
        sourceWriter.commit(logger);
        return qualifiedBeanClassName;
    }

    private static SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext context, String packageName, String beanClassName) {

        PrintWriter printWriter = context.tryCreate(logger, packageName, beanClassName);

        if (printWriter == null) {
            return null;
        }

        ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, beanClassName);

        //TODO add imports here
        composerFactory.addImport(BeanInfo.class.getName());
        composerFactory.addImport(BeanInfoRegistry.class.getName());
        composerFactory.addImport(PropertyDescriptor.class.getName());
        composerFactory.addImport(AbstractPropertyDescriptor.class.getName());
        composerFactory.addImport(Map.class.getName());
        composerFactory.addImport(HashMap.class.getName());
        composerFactory.addImport(Type.class.getName());
        composerFactory.addImport(TypeImpl.class.getName());
        composerFactory.addImport(TypeRegistry.class.getName());
        composerFactory.addImport(PathValueProxy.class.getName());
        composerFactory.addImport(DefaultPathValueProxy.class.getName());
        composerFactory.addImport(PathDescriptorProxy.class.getName());
        composerFactory.addImport(DefaultPathDescriptorProxy.class.getName());
        composerFactory.addImport(UnsupportedOperationException.class.getName());
        composerFactory.addImport(TypeMismatchException.class.getName());
        composerFactory.addImport(BeanValidator.class.getName());

        composerFactory.addImplementedInterface(BeanInfo.class.getName());

        return composerFactory.createSourceWriter(context, printWriter);
    }

    private static void write(EasyTreeLogger logger, SourceWriter writer, GeneratorContext context, JClassType beanType, String className) throws UnableToCompleteException {

        logger = logger.branchDebug("Writing bean info for '" + beanType.getQualifiedSourceName() + "'");

        BeanOracle beanOracle = new BeanOracleBuilder(context.getTypeOracle()).build(logger, beanType);
        JProperty[] properties = beanOracle.getProperties();

        StringBuilder sb = new StringBuilder();
        for (JProperty property : properties) {
            String descriptorClassName = property.getName() + "_Descriptor";
            String varName = property.getName() + "_descriptor";
            writer.println("private final " + descriptorClassName + " " + varName + " = new " + descriptorClassName + "(this);");
            if (sb.length() != 0) {
                sb.append(", ");
            }
            sb.append(varName);
        }
        writer.println("private final PropertyDescriptor[] descriptors = new PropertyDescriptor[] {");
        writer.indent();
        writer.println(sb.toString());
        writer.outdent();
        writer.println("};");

        writer.println("private final BeanInfoRegistry beanInfoRegistry;");
        writer.println();
        writer.println("public " + className + "(BeanInfoRegistry beanInfoRegistry) {");
        writer.println("    this.beanInfoRegistry = beanInfoRegistry;");
        writer.println("}");

        writer.println("public BeanInfoRegistry getRegistry() {");
        writer.println("    return beanInfoRegistry;");
        writer.println("}");

        writer.println("public Object newInstance() {");
        writer.println("    return new " + beanType.getQualifiedSourceName() + "();");
        writer.println("}");

        writer.println("private final static Type type;");
        writer.println();
        writer.println("static {");
        writer.indent();
        String typeVarName = writeTypeDesclaration(logger, writer, context, beanType);
        writer.println("type = " + typeVarName + ";");
        writer.outdent();
        writer.println("}");
        writer.println("public Type getType() {");
        writer.println("    return type;");
        writer.println("}");

        writer.println("public PropertyDescriptor[] getPropertyDescriptors() {");
        writer.println("    return descriptors;");
        writer.println("}");

        writer.println("public PropertyDescriptor getPropertyDescriptor(String propertyName) {");
        writer.indent();

        for (JProperty property : properties) {
            writer.println("if (\"" + property.getName() + "\".equals(propertyName)) {");
            writer.println("    return " + property.getName() + "_descriptor;");
            writer.println("}");
            writer.println();
        }

        writer.println("throw new IllegalArgumentException(\"Unknown property '\" + propertyName + \"' in bean '" + beanType.getQualifiedSourceName() + "'\");");

        writer.outdent();
        writer.println("}");

        writer.println("public String getDisplayName() {");
        writer.println("    return \"" + resolveDisplayName(beanType) + "\";");
        writer.println("}");

        writer.println("public String getDescription() {");
        writer.println("    return \"" + resolveDescription(beanType) + "\";");
        writer.println("}");

        String validatorClassName = BeanValidatorGenerator.generate(logger, context, beanType);
        writer.println("private final static BeanValidator<" + beanType.getQualifiedSourceName() + "> validator = new " + validatorClassName + "();");
        writer.println();
        writer.println("public BeanValidator<" + beanType.getQualifiedSourceName() + "> getValidator() {");
        writer.println("    return validator;");
        writer.println("}");


        writer.println();
        writer.println("public PathValueProxy getPathValueProxy(String propertyPath) {");
        writer.println("    return new DefaultPathValueProxy(this, propertyPath);");
        writer.println("}");

        writer.println();
        writer.println("public PathDescriptorProxy getPathDescriptorProxy(String propertyPath) {");
        writer.println("    return new DefaultPathDescriptorProxy(this, propertyPath);");
        writer.println("}");


        writer.println();
        writer.beginJavaDocComment();
        writer.println("Property Descriptor Inner Classes");
        writer.endJavaDocComment();

        for (JProperty property : properties) {
            writeDescriptorClass(logger, writer, context, beanType, property);
            writer.println();
            writer.println();
        }
    }

    private static String writeTypeDesclaration(EasyTreeLogger logger, SourceWriter writer, GeneratorContext context, JType type)
            throws UnableToCompleteException {

        String typeId = type.toString();
        String varName = nextTypeVarName();

        if (typeRegistryThreadLocal.get().contains(typeId)) {
            writer.println("Type " + varName + " = TypeRegistry.get().findType(\"" + typeId + "\");");
            return varName;
        }

        writer.println("Type " + varName + " = null;");

        boolean array = type.isArray() != null;
        boolean bean = type.isClass() != null && type.isClass().isAnnotationPresent(Bean.class);
        JClassType collectionType = context.getTypeOracle().findType(Collection.class.getName());
        boolean collection = type.isClassOrInterface() != null && type.isClassOrInterface().isAssignableTo(collectionType);
        JClassType mapType = context.getTypeOracle().findType(Map.class.getName());
        boolean map = type.isClassOrInterface() != null && type.isClassOrInterface().isAssignableTo(mapType);
        boolean primitive = type.isPrimitive() != null;
        boolean number = isNumber(type, context.getTypeOracle());
        JClassType dateType = context.getTypeOracle().findType(Date.class.getName());
        boolean date = type.isClassOrInterface() != null && type.isClassOrInterface().isAssignableTo(dateType);
        boolean isEnum = type.isEnum() != null;

        if (array) {
            JType componentType = type.isArray().getComponentType();
            writeTypeDesclaration(logger, writer, context, componentType);
            writer.println(varName + " = new TypeImpl(" + type.getQualifiedSourceName() + ".class, false, false, false, false, false, false, false, \"" + componentType.toString() +"\");");
        } else if (collection) {
            JClassType elementType = BeanGeneratorUtils.findCollectionElementType(type, context);
            writeTypeDesclaration(logger, writer, context, elementType);
            writer.println(varName + " = new TypeImpl(" + type.getQualifiedSourceName() + ".class, false, false, true, false, false, false, false, \"" + elementType.toString() +"\");");
        } else if (map) {
            writer.println(varName + " = new TypeImpl(" + type.getQualifiedSourceName() + ".class, false, false, false, true, false, false, false, null);");
        } else {
            writer.println(varName + " = new TypeImpl(" + type.getQualifiedSourceName() + ".class, " + primitive + ", " + bean + ", " + collection + ", " + map + ", " + date + ", " + number + ", " + isEnum + ", null);");
        }

        writer.outdent();

        typeRegistryThreadLocal.get().add(typeId);
        writer.println("TypeRegistry.get().register(\"" + typeId + "\", " + varName + ");");
        return varName;

    }

    private static boolean isNumber(JType type, TypeOracle typeOracle) throws UnableToCompleteException {
        JClassType numberType = typeOracle.findType(Number.class.getName());
        if (type.isClassOrInterface() != null) {
            return numberType.isAssignableFrom(type.isClassOrInterface());
        }
        if (type.isPrimitive() != null) {
            return typeOracle.findType(type.isPrimitive().getQualifiedBoxedSourceName()).isAssignableTo(numberType);
        }
        return false;
    }


    private static void writeDescriptorClass(
            EasyTreeLogger logger,
            SourceWriter writer,
            GeneratorContext context,
            JClassType beanType,
            JProperty property) throws UnableToCompleteException {

        String boxedTypeName = boxedTypeName(property.getType());
        String beanTypeName = beanType.getQualifiedSourceName();

        String propertyTypeId = property.getType().toString();

        logger = logger.branchDebug("Writing property descriptor for '" + property.getName() + "'");

        writer.println("private static class " + property.getName() + "_Descriptor extends AbstractPropertyDescriptor<" + beanTypeName + ", " + boxedTypeName + "> {");

        writer.println("    public " + property.getName() + "_Descriptor(BeanInfo beanInfo) {");
        writer.println("        super(\"" + property.getName() + "\", \"" + propertyTypeId + "\", beanInfo);");
        writer.println("        buildPropertyType();");
        writer.println("    }");

        writer.println("public boolean isReadable() {");
        writer.println("    return " + (property.getGetter() != null) + ";");
        writer.println("}");

        writer.println("public boolean isWritable() {");
        writer.println("    return " + (property.getSetter() != null) + ";");
        writer.println("}");

        logger.debug("writing setter method for '" + property.getName() + "' ('" + boxedTypeName + "')");
        writer.println("    public void setValue(" + beanTypeName + " bean, " + boxedTypeName + " value) {");
        if (property.getSetter() != null) {
            if (property.getType().isPrimitive() != null) {
                writer.println("        if (value == null) {");
                writer.println("            throw new TypeMismatchException(" + property.getType().getQualifiedSourceName() + ".class, null);");
                writer.println("        }");
            }
            writer.println("        bean." + property.getSetter().getName() + "(value);");
        } else {
            writer.println("        throw new UnsupportedOperationException(\"Property '" + property.getName() +
                    "' of bean '" + beanType.getQualifiedSourceName() + "' is not writable (it has no setter\");");
        }
        writer.println("    }");

        logger.debug("writing getter method for '" + property.getName() + "' ('" + boxedTypeName + "')");
        writer.println("    public " + boxedTypeName + " getValue(" + beanTypeName + " bean) {");
        if (property.getGetter() != null) {
            writer.println("        return bean." + property.getGetter().getName() + "();");
        } else {
            writer.println("        throw new UnsupportedOperationException(\"Property '" + property.getName() +
                    "' of bean '" + beanType.getQualifiedSourceName() + "' is not readable (it has no getter\");");
        }
        writer.println("    }");

        String displayName = resolveDisplayName(property);
        if (displayName != null) {
            writer.println("    public String getDisplayName() {");
            writer.println("        return \"" + displayName + "\";");
            writer.println("    }");
        }

        String description = resolveDescription(property);
        if (description != null) {
            writer.println("    public String getDescription() {");
            writer.println("        return \"" + description + "\";");
            writer.println("    }");
        }

        writer.println("    private static Type<" + boxedTypeName + "> buildPropertyType() {");
        String typeVarName = writeTypeDesclaration(logger, writer, context, property.getType());
        writer.println("        return " + typeVarName + ";");
        writer.println("    }");

        writer.indent();
        writeAttributes(logger, writer, context, beanType, property);
        writer.outdent();

        writer.println("}");
    }

    private static void writeAttributes(EasyTreeLogger logger, SourceWriter writer, GeneratorContext context, JClassType beanType, JProperty property) throws UnableToCompleteException {

        writer.println("private final static Map<String, Map<String, Object>> attributesByClassName = new HashMap<String, Map<String, Object>>();");
        writer.println();

        List<Annotation> annotations = extratAllAnnotations(property);
        int i = 0;
        for (Annotation annotation : annotations) {
            if (!annotation.annotationType().isAnnotationPresent(AttributesClass.class)) {
                continue;
            }
            AttributesClass attributesClass = annotation.annotationType().getAnnotation(AttributesClass.class);
            Class<? extends Map<String, Object>> attributesType = attributesClass.value();

            writer.println("static {");
            String typeName = attributesType.getName();
            String varName = "attributes_" + (i++);
            writer.println("    " + typeName + " " + varName + " = new " + typeName + "();");
            AnnotationWrapper wrapper = new AnnotationWrapper(annotation);
            for (String attributeName : wrapper.getAttributeNames()) {
                Object value = wrapper.getValue(attributeName);
                String valueText = String.valueOf(value);
                if (String.class.isInstance(value)) {
                    valueText = "\"" + valueText + "\"";
                } else if (Enum.class.isInstance(value)) {
                    valueText = value.getClass().getName() + "." + ((Enum) value).name();
                }
                writer.println("    " + varName + ".put(\"" + attributeName + "\", " + valueText + ");");
            }
            writer.println("    attributesByClassName.put(\"" + typeName + "\", " + varName + ");");
            writer.println("}");
            writer.println();
        }

        writer.println("public boolean hasAttributes(Class<? extends Map<String, Object>> attributesType) {");
        writer.println("    return attributesByClassName.containsKey(attributesType.getName());");
        writer.println("}");
        writer.println();

        writer.println("public <T extends Map<String, Object>> T getAttributes(Class<T> attributesType) {");
        writer.println("    return (T) attributesByClassName.get(attributesType.getName());");
        writer.println("}");
    }

    //================================================ Helper Methods ==================================================

    private static List<Annotation> extratAllAnnotations(JProperty property) {
        List<Annotation> annotations = new ArrayList<Annotation>();

        JMethod jmethod = property.getSetter();
        if (jmethod != null) {
            Method method = GeneratorUtils.resolveMethod(jmethod);
            annotations.addAll(Arrays.asList(method.getAnnotations()));
        }

        jmethod = property.getGetter();
        if (jmethod != null) {
            Method method = GeneratorUtils.resolveMethod(jmethod);
            annotations.addAll(Arrays.asList(method.getAnnotations()));
        }

        JField jfield = property.getField();
        if (jfield != null) {
            Field field = GeneratorUtils.resolveField(jfield);
            annotations.addAll(Arrays.asList(field.getAnnotations()));
        }

        return annotations;
    }

    private static String boxedTypeName(JType type) {
        JPrimitiveType primitiveType = type.isPrimitive();
        if (primitiveType == null) {
            return type.getQualifiedSourceName();
        }
        return primitiveType.getQualifiedBoxedSourceName();
    }

    private static String resolveDisplayName(JProperty property) {
        JMethod method = property.getSetter();
        if (method != null && method.isAnnotationPresent(DisplayName.class)) {
            return method.getAnnotation(DisplayName.class).value();
        }
        method = property.getGetter();
        if (method != null && method.isAnnotationPresent(DisplayName.class)) {
            return method.getAnnotation(DisplayName.class).value();
        }
        JField field = property.getField();
        if (field != null && field.isAnnotationPresent(DisplayName.class)) {
            return field.getAnnotation(DisplayName.class).value();
        }
        return null;
    }

    private static String resolveDescription(JProperty property) {
        JMethod method = property.getSetter();
        if (method != null && method.isAnnotationPresent(Description.class)) {
            return method.getAnnotation(Description.class).value();
        }
        method = property.getGetter();
        if (method != null && method.isAnnotationPresent(Description.class)) {
            return method.getAnnotation(Description.class).value();
        }
        JField field = property.getField();
        if (field != null && field.isAnnotationPresent(Description.class)) {
            return field.getAnnotation(Description.class).value();
        }
        return null;
    }

    private static String resolveDisplayName(JClassType beanType) {
        if (beanType.isAnnotationPresent(DisplayName.class)) {
            return beanType.getAnnotation(DisplayName.class).value();
        }
        return beanType.getSimpleSourceName();
    }

    private static String resolveDescription(JClassType beanType) {
        if (beanType.isAnnotationPresent(Description.class)) {
            return beanType.getAnnotation(Description.class).value();
        }
        return "";
    }


    private static String nextTypeVarName() {
        return "type" + (typeNameCounter++);
    }
}
TOP

Related Classes of org.gwtoolbox.bean.rebind.BeanInfoGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.