fr.putnami.pwt.core.model.rebind.ModelCreator.java Source code

Java tutorial

Introduction

Here is the source code for fr.putnami.pwt.core.model.rebind.ModelCreator.java

Source

/**
 * This file is part of pwt.
 *
 * pwt is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * pwt is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with pwt. If not,
 * see <http://www.gnu.org/licenses/>.
 */
package fr.putnami.pwt.core.model.rebind;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
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.TreeLogger.Type;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

import java.io.PrintWriter;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import fr.putnami.pwt.core.model.client.model.AbstractModel;
import fr.putnami.pwt.core.model.client.model.Model;
import fr.putnami.pwt.core.model.client.model.ModelCollection;
import fr.putnami.pwt.core.model.client.model.PropertyDescription;
import fr.putnami.pwt.core.model.client.util.PrimitiveUtils;

public class ModelCreator {

    private static final Set<String> DISCARD_MODEL_TYPES = Sets.newHashSet(
            // Primitives
            Boolean.class.getName(), Byte.class.getName(), Character.class.getName(), Double.class.getName(),
            Float.class.getName(), Integer.class.getName(), Long.class.getName(), Short.class.getName(),
            // BigDecimal
            BigDecimal.class.getName(),
            // Others
            Object.class.getName(), String.class.getName(), Date.class.getName(), Timestamp.class.getName(),
            // Collections
            List.class.getName(), Set.class.getName(), Map.class.getName());

    private static final Set<Class> COLLECTION_TYPES = Sets.newHashSet();

    private JClassType beanType;
    private String proxyModelQualifiedName;

    private Set<JType> imports = Sets.newHashSet();
    private Map<JType, String> subModels = Maps.newHashMap();
    private Map<String, JType> propertyTypes = Maps.newHashMap();
    private Map<String, JType> publicFields = Maps.newHashMap();
    private Map<String, JMethod> getters = Maps.newHashMap();
    private Map<String, JMethod> setters = Maps.newHashMap();

    private JClassType parentType;

    public ModelCreator(JClassType beanType) {
        this.beanType = beanType;
        this.proxyModelQualifiedName = beanType.getQualifiedSourceName() + "_Model";
        this.proxyModelQualifiedName = this.proxyModelQualifiedName.replace(beanType.getName(),
                beanType.getName().replace('.', '_'));
    }

    public String create(TreeLogger logger, GeneratorContext context) {
        PrintWriter printWriter = this.getPrintWriter(logger, context, this.proxyModelQualifiedName);
        if (printWriter == null) {
            return this.proxyModelQualifiedName;
        }

        JField[] fields = this.beanType.getFields();
        JMethod[] methods = this.beanType.getMethods();

        this.parentType = this.beanType.getSuperclass();
        this.imports.add(this.parentType);

        this.listPublicFields(fields);
        this.listGetters(methods);
        this.listSetters(methods);

        this.createSubModels(logger, context);

        SourceWriter srcWriter = this.getSourceWriter(printWriter, context);

        srcWriter.indent();
        srcWriter.println();
        this.generateSingleton(logger, srcWriter);
        srcWriter.println();
        srcWriter.println();
        this.generateStaticInitializer(logger, srcWriter);
        srcWriter.println();
        this.generateConstructor(logger, srcWriter);
        srcWriter.println();
        this.generateCreate(logger, srcWriter);
        srcWriter.println();
        this.generateInternalSet(logger, srcWriter);
        srcWriter.println();
        this.generateInternalGet(logger, srcWriter);

        srcWriter.outdent();

        srcWriter.commit(logger);
        return this.proxyModelQualifiedName;
    }

    private void createSubModels(TreeLogger logger, GeneratorContext context) {
        for (JType jType : this.imports) {
            if (jType == null) {
                continue;
            }
            if (jType.isEnum() != null) {
                continue;
            }
            if (ModelCreator.DISCARD_MODEL_TYPES.contains(jType.getQualifiedSourceName())) {
                continue;
            }
            if (jType instanceof JClassType) {
                ModelCreator creator = new ModelCreator((JClassType) jType);
                String subModelType = creator.create(logger, context);
                this.subModels.put(jType, subModelType);
            }
        }
    }

    private void generateSingleton(TreeLogger logger, SourceWriter srcWriter) {
        String className = this.proxyModelQualifiedName.indexOf('.') == -1 ? this.proxyModelQualifiedName
                : this.proxyModelQualifiedName.substring(this.proxyModelQualifiedName.lastIndexOf('.') + 1,
                        this.proxyModelQualifiedName.length());
        srcWriter.println("public static final %s INSTANCE = new %s();", className, className);
    }

    private void generateStaticInitializer(TreeLogger logger, SourceWriter srcWriter) {

        srcWriter
                .println("protected static final Map<String, PropertyDescription> PROPERTIES = Maps.newHashMap();");
        srcWriter.println("static{");
        srcWriter.indent();

        for (String propertyName : this.propertyTypes.keySet()) {
            JType propertyType = this.propertyTypes.get(propertyName);
            String simplePropertyTypeName = propertyType.getSimpleSourceName();
            String modelName = this.subModels.get(propertyType);
            if (modelName != null) {
                modelName += ".INSTANCE";
            }

            try {
                if (propertyType.isPrimitive() == null) {
                    Class<?> propertyClass = Class.forName(propertyType.getQualifiedSourceName());

                    if (Collection.class.isAssignableFrom(propertyClass)) {
                        JParameterizedType parametrizedType = propertyType.isParameterized();
                        JType subType = propertyType;
                        if (parametrizedType != null) {
                            subType = parametrizedType.getTypeArgs()[0];
                            String submodelName = this.subModels.get(subType);
                            if (submodelName != null) {
                                submodelName += ".INSTANCE";
                            } else {
                                submodelName = subType.getSimpleSourceName() + ".class";
                            }
                            modelName = String.format("new ModelCollection<%s>(%s.class, %s)",
                                    subType.getSimpleSourceName(), propertyType.getSimpleSourceName(),
                                    submodelName);
                        } else {
                            logger.branch(Type.WARN, String.format(
                                    "Property [%s] on bean %s is a raw collection type. You cannot use it on editors.",
                                    propertyName, this.beanType.getQualifiedSourceName()));
                            modelName = "new ModelCollection((Model) null)";
                        }
                    }
                }
            } catch (ClassNotFoundException e) {
                logger.branch(Type.WARN,
                        String.format("%s class not found.", propertyType.getQualifiedSourceName()));
            }
            Boolean getter = this.getters.containsKey(propertyName);
            Boolean setter = this.setters.containsKey(propertyName);

            srcWriter.print("PROPERTIES.put(\"%s\", newPropertyDescription(\"%s\", %s.class, %s, %s, %s",
                    propertyName, propertyName, simplePropertyTypeName, modelName, getter, setter);
            this.generateValidators(srcWriter, propertyName);
            srcWriter.println("));");
        }
        srcWriter.outdent();
        srcWriter.println("}");

        srcWriter.println("protected Map<String, PropertyDescription> getProperties(){");
        srcWriter.println("   return PROPERTIES;");
        srcWriter.println("}");
    }

    private void generateValidators(SourceWriter w, String propertyName) {
        JField field = this.beanType.getField(propertyName);
        if (field != null) {
            appendTrueValidator(w, field);
            appendFalseValidator(w, field);
            appendFutureValidator(w, field);
            appendMaxValidator(w, field);
            appendMinValidator(w, field);
            appendNotNullValidator(w, field);
            appendNullValidator(w, field);
            appendPastValidator(w, field);
            appendPatternValidator(w, field);
            appendSizeValidator(w, field);
        }
    }

    private void appendSizeValidator(SourceWriter w, JField field) {
        Size sizeAnnotation = field.getAnnotation(Size.class);
        if (sizeAnnotation != null) {
            w.println(", new SizeValidator(\"%s\", %s, %s)", sizeAnnotation.message(), sizeAnnotation.min(),
                    sizeAnnotation.max());
        }
    }

    private void appendPatternValidator(SourceWriter w, JField field) {
        Pattern patternAnnotation = field.getAnnotation(Pattern.class);
        if (patternAnnotation != null) {
            w.println(", new PatternValidator(\"%s\", \"%s\")", patternAnnotation.message(),
                    patternAnnotation.regexp().replace("\\", "\\\\"), patternAnnotation.flags());
        }
    }

    private void appendPastValidator(SourceWriter w, JField field) {
        Past pastAnnotation = field.getAnnotation(Past.class);
        if (pastAnnotation != null) {
            w.println(", new PastValidator(\"%s\")", pastAnnotation.message());
        }
    }

    private void appendNullValidator(SourceWriter w, JField field) {
        Null nullAnnotation = field.getAnnotation(Null.class);
        if (nullAnnotation != null) {
            w.println(", new NullValidator(\"%s\")", nullAnnotation.message());
        }
    }

    private void appendNotNullValidator(SourceWriter w, JField field) {
        NotNull notNullAnnotation = field.getAnnotation(NotNull.class);
        if (notNullAnnotation != null) {
            w.println(", new NotNullValidator(\"%s\")", notNullAnnotation.message());
        }
    }

    private void appendMinValidator(SourceWriter w, JField field) {
        Min minAnnotation = field.getAnnotation(Min.class);
        if (minAnnotation != null) {
            w.println(", new MinValidator(\"%s\", %s)", minAnnotation.message(), minAnnotation.value());
        }
    }

    private void appendMaxValidator(SourceWriter w, JField field) {
        Max maxAnnotation = field.getAnnotation(Max.class);
        if (maxAnnotation != null) {
            w.println(", new MaxValidator(\"%s\", %s)", maxAnnotation.message(), maxAnnotation.value());
        }
    }

    private void appendFutureValidator(SourceWriter w, JField field) {
        Future futureAnnotation = field.getAnnotation(Future.class);
        if (futureAnnotation != null) {
            w.println(", new FutureValidator(\"%s\")", futureAnnotation.message());
        }
    }

    private void appendFalseValidator(SourceWriter w, JField field) {
        AssertFalse falseAnnotation = field.getAnnotation(AssertFalse.class);
        if (falseAnnotation != null) {
            w.println(", new AssertFalseValidator(\"%s\")", falseAnnotation.message());
        }
    }

    private void appendTrueValidator(SourceWriter w, JField field) {
        AssertTrue trueAnnotation = field.getAnnotation(AssertTrue.class);
        if (trueAnnotation != null) {
            w.println(", new AssertTrueValidator(\"%s\")", trueAnnotation.message());
        }
    }

    private void generateConstructor(TreeLogger logger, SourceWriter srcWriter) {
        int lastIndex = this.proxyModelQualifiedName.lastIndexOf('.');
        String className = lastIndex == -1 ? this.proxyModelQualifiedName
                : this.proxyModelQualifiedName.substring(lastIndex + 1, this.proxyModelQualifiedName.length());
        srcWriter.println("public %s(){", className);
        srcWriter.indent();

        if (this.subModels.get(this.parentType) != null) {
            srcWriter.println("super(%s.INSTANCE, %s.class);", this.subModels.get(this.parentType),
                    this.beanType.getSimpleSourceName());
            srcWriter.println();
        } else {
            srcWriter.println("super(%s.class);", this.beanType.getSimpleSourceName());
            srcWriter.println();
        }

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

    private void generateCreate(TreeLogger logger, SourceWriter srcWriter) {
        srcWriter.println("public %s newInstance() {", this.beanType.getSimpleSourceName());
        srcWriter.indent();
        if (!this.beanType.isAbstract()) {
            srcWriter.println("return new %s();", this.beanType.getSimpleSourceName());
        } else {
            srcWriter.println("throw new RuntimeException(\"Can not instantiate the abstract class %s\");",
                    this.beanType.getSimpleSourceName());
        }
        srcWriter.outdent();
        srcWriter.println("}");
    }

    private void generateInternalGet(TreeLogger logger, SourceWriter srcWriter) {
        srcWriter.println("protected <P> P internalGet(%s bean, String fieldName){",
                this.beanType.getSimpleSourceName());
        srcWriter.indent();
        for (String propertyName : this.propertyTypes.keySet()) {
            JType propertyType = this.propertyTypes.get(propertyName);
            JPrimitiveType primitiveType = propertyType.isPrimitive();
            JMethod getter = this.getters.get(propertyName);
            if (getter != null) {
                if (primitiveType != null) {
                    String boxedName = primitiveType.getQualifiedBoxedSourceName();
                    boxedName = boxedName.substring(boxedName.lastIndexOf(".") + 1, boxedName.length());
                    srcWriter.println(
                            "if(\"%s\".equals(fieldName)){  return (P) PrimitiveUtils.castTo%s(bean.%s()); }",
                            propertyName, boxedName, getter.getName());
                } else {
                    srcWriter.println("if(\"%s\".equals(fieldName)){  return (P) bean.%s(); }", propertyName,
                            getter.getName());
                }
            } else if (this.publicFields.containsKey(propertyName)) {
                if (primitiveType != null) {
                    String boxedName = primitiveType.getQualifiedBoxedSourceName();
                    boxedName = boxedName.substring(boxedName.lastIndexOf(".") + 1, boxedName.length());
                    srcWriter.println(
                            "if(\"%s\".equals(fieldName)){  return (P) PrimitiveUtils.castTo%s(bean.%s); }",
                            propertyName, boxedName, propertyName);
                } else {
                    srcWriter.println("if(\"%s\".equals(fieldName)){  return (P) bean.%s; }", propertyName,
                            propertyName);
                }
            }
        }
        srcWriter.println();
        srcWriter.println("return null;");
        srcWriter.outdent();
        srcWriter.println("}");
    }

    private void generateInternalSet(TreeLogger logger, SourceWriter srcWriter) {
        srcWriter.println("protected <P> void internalSet(%s bean, String fieldName, P value){",
                this.beanType.getSimpleSourceName());
        srcWriter.indent();
        for (String propertyName : this.propertyTypes.keySet()) {
            JType propertyType = this.propertyTypes.get(propertyName);
            JPrimitiveType primitiveType = propertyType.isPrimitive();
            JMethod setter = this.setters.get(propertyName);
            if (setter != null) {
                if (primitiveType != null) {
                    srcWriter.println(
                            "if(\"%s\".equals(fieldName)){  bean.%s((%s) PrimitiveUtils.asPrimitive((%s)value)); }",
                            propertyName, setter.getName(), propertyType.getSimpleSourceName(),
                            primitiveType.getQualifiedBoxedSourceName());
                } else {
                    srcWriter.println("if(\"%s\".equals(fieldName)){  bean.%s((%s) value); }", propertyName,
                            setter.getName(), propertyType.getSimpleSourceName());
                }
            } else if (this.publicFields.containsKey(propertyName)) {
                if (primitiveType != null) {
                    srcWriter.println(
                            "if(\"%s\".equals(fieldName)){ bean.%s = PrimitiveUtils.asPrimitive((%s) value); }",
                            propertyName, propertyName, primitiveType.getQualifiedBoxedSourceName());
                } else {
                    srcWriter.println("if(\"%s\".equals(fieldName)){  bean.%s = (%s) value; }", propertyName,
                            propertyName, propertyType.getSimpleSourceName());
                }
            }
        }
        srcWriter.outdent();
        srcWriter.println("}");
    }

    private void listPublicFields(JField[] fields) {
        for (JField field : fields) {
            if (field.isPublic() && !field.isFinal()) {
                this.publicFields.put(field.getName(), field.getType());
                this.propertyTypes.put(field.getName(), field.getType());
                this.addImport(field.getType());
            }
        }
    }

    private void listSetters(JMethod[] methods) {
        for (JMethod method : methods) {
            if (method.getName().startsWith("set") && method.getParameters().length == 1
                    && method.getReturnType().equals(JPrimitiveType.VOID) && method.isPublic()) {
                this.setters.put(this.extractPropertyNameFromMethod(method), method);
                this.propertyTypes.put(this.extractPropertyNameFromMethod(method),
                        method.getParameters()[0].getType());
                this.addImport(method.getParameters()[0].getType());
            }
        }
    }

    private void listGetters(JMethod[] methods) {
        for (JMethod method : methods) {
            if (method.getName().startsWith("get") && method.getName().length() > 3
                    || method.getName().startsWith("is") && method.getName().length() > 2
                            && method.getParameters().length == 0
                            && !method.getReturnType().equals(JPrimitiveType.VOID) && method.isPublic()) {

                this.getters.put(this.extractPropertyNameFromMethod(method), method);
                this.propertyTypes.put(this.extractPropertyNameFromMethod(method), method.getReturnType());
                this.addImport(method.getReturnType());
            }
        }
    }

    private void addImport(JType type) {
        JParameterizedType parametrizedType = type.isParameterized();
        if (parametrizedType != null) {
            this.imports.add(parametrizedType.getRawType());
            this.imports.addAll(Lists.newArrayList(parametrizedType.getTypeArgs()));
        } else {
            this.imports.add(type);
        }
    }

    private String extractPropertyNameFromMethod(JMethod method) {
        String propertyName = method.getName();
        if (propertyName.startsWith("is")) {
            propertyName = propertyName.substring(2, propertyName.length());
        } else {
            propertyName = propertyName.substring(3, propertyName.length());
        }
        String firstChar = propertyName.substring(0, 1);
        propertyName = propertyName.replaceFirst(firstChar, firstChar.toLowerCase());

        return propertyName;
    }

    private SourceWriter getSourceWriter(PrintWriter printWriter, GeneratorContext ctx) {
        String packageName = this.proxyModelQualifiedName.indexOf('.') == -1 ? ""
                : this.proxyModelQualifiedName.substring(0, this.proxyModelQualifiedName.lastIndexOf('.'));
        String className = this.proxyModelQualifiedName.indexOf('.') == -1 ? this.proxyModelQualifiedName
                : this.proxyModelQualifiedName.substring(this.proxyModelQualifiedName.lastIndexOf('.') + 1,
                        this.proxyModelQualifiedName.length());

        ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, className);

        composerFactory.addImport(Map.class.getName());
        composerFactory.addImport(Maps.class.getName());
        composerFactory.addImport(GWT.class.getName());
        composerFactory.addImport(Model.class.getName());
        composerFactory.addImport(AbstractModel.class.getName());
        composerFactory.addImport(ModelCollection.class.getName());
        composerFactory.addImport(PropertyDescription.class.getName());
        composerFactory.addImport(PrimitiveUtils.class.getName());
        composerFactory.addImport(this.beanType.getQualifiedSourceName());
        composerFactory.addImport("fr.putnami.pwt.core.editor.client.validator.*");

        for (JType jType : this.imports) {
            if (jType.isPrimitive() != null) {
                continue;
            }
            composerFactory.addImport(jType.getQualifiedSourceName());
        }
        for (String submodel : this.subModels.values()) {
            composerFactory.addImport(submodel);
        }

        composerFactory.setSuperclass(
                AbstractModel.class.getSimpleName() + "<" + this.beanType.getSimpleSourceName() + ">");
        return composerFactory.createSourceWriter(ctx, printWriter);
    }

    private PrintWriter getPrintWriter(TreeLogger logger, GeneratorContext ctx, String targetQualifiedName) {
        String packageName = this.proxyModelQualifiedName.indexOf('.') == -1 ? ""
                : this.proxyModelQualifiedName.substring(0, this.proxyModelQualifiedName.lastIndexOf('.'));
        int lastIndex = this.proxyModelQualifiedName.lastIndexOf('.');
        String className = lastIndex == -1 ? this.proxyModelQualifiedName
                : this.proxyModelQualifiedName.substring(lastIndex + 1, this.proxyModelQualifiedName.length());

        return ctx.tryCreate(logger, packageName, className);
    }
}