com.kk_electronic.gwt.rebind.JsonEncoderGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.kk_electronic.gwt.rebind.JsonEncoderGenerator.java

Source

/*
 * Copyright 2010 kk-electronic a/s. 
 * 
 * This file is part of KKPortal.
 *
 * KKPortal 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.
 *
 * KKPortal 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 KKPortal.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package com.kk_electronic.gwt.rebind;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.Map.Entry;

import com.google.gwt.core.ext.Generator;
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.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JGenericType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.inject.Singleton;
import com.kk_electronic.kkportal.core.rpc.FrameEncoder;
import com.kk_electronic.kkportal.core.rpc.JsonEncoderHelper;
import com.kk_electronic.kkportal.core.rpc.RemoteService;
import com.kk_electronic.kkportal.core.rpc.Rename;
import com.kk_electronic.kkportal.core.rpc.jsonformat.JsonValue;
import com.kk_electronic.kkportal.core.rpc.jsonformat.UnableToDeserialize;
import com.kk_electronic.kkportal.core.rpc.jsonformat.UnableToSerialize;

/**
 * Generates the Helper class for JsonEncoder as well as attempts to implement any missing JsonValue classes. 
 * 
 * @author Rasmus Carlsen
 *
 */
public class JsonEncoderGenerator extends Generator {

    private String packageName;
    private String className;
    private TypeOracle typeOracle;
    private JClassType classType;
    private TreeLogger logger;

    private HashMap<String, String> map = new HashMap<String, String>();
    private ArrayList<JClassType> worklist = new ArrayList<JClassType>();

    @Override
    public String generate(TreeLogger logger, GeneratorContext context, String typeName)
            throws UnableToCompleteException {
        try {
            typeOracle = context.getTypeOracle();
            classType = typeOracle.getType(typeName);
            packageName = classType.getPackage().getName();
            className = classType.getSimpleSourceName() + "Impl";
            this.logger = logger;

            retrieveInformation();
            generateJsonFormatterClasses(context);
            generateHelperClass(context);

        } catch (NotFoundException e) {
            logger.log(TreeLogger.ERROR, "Exception during ClassMap creation.", e);
            throw new UnableToCompleteException();
        }
        return packageName + "." + className;
        //return null;
    }

    /**
     * @throws UnableToCompleteException 
     * 
     */
    private void retrieveInformation() throws UnableToCompleteException {
        JClassType desiredInterface;
        try {
            desiredInterface = typeOracle.getType(AsyncCallback.class.getCanonicalName());
        } catch (NotFoundException e) {
            logger.log(TreeLogger.ERROR, "Unable to retrieve the AsyncCallback class");
            return;
        }

        processJsonValueClasses();
        JClassType[] remoteServices = getClasses(RemoteService.class.getCanonicalName(), true);
        for (JClassType service : remoteServices) {
            JMethod[] methods = service.getMethods();
            for (JMethod m : methods) {
                JParameter[] paras = m.getParameters();
                for (JParameter p : paras) {
                    JType t = p.getType();

                    if (t == null) {
                        continue;
                    }

                    JClassType jct = t.isInterface();
                    if (jct != null) {
                        if (jct.isAssignableTo(desiredInterface)) {
                            processGenerics(jct);
                            jct = null;
                        }
                    } else {
                        jct = t.isClass();
                    }
                    if (jct == null) {
                        continue;
                    }
                    processClass(jct);
                }
            }
        }

    }

    private void processClass(JClassType jct) {
        processGenerics(jct);
        if (checkClass(jct) && jct.isInterface() == null) {
            processFields(jct);
        }
    }

    private void processGenerics(JType jct) {
        JParameterizedType jpt = jct.isParameterized();
        if (jpt == null) {
            return;
        }
        JClassType[] cd = jpt.getTypeArgs();
        for (JClassType type : cd) {
            processClass(type);
        }
    }

    private void processFields(JClassType jct) {
        JField[] fields = jct.getFields();
        for (JField jField : fields) {
            JClassType jClass = jField.getType().isClass();
            processGenerics(jField.getType());
            if (jClass != null && !jField.isTransient()) {
                processClass(jClass);
            }
        }
    }

    private boolean checkClass(JClassType type) {
        boolean b = false;
        if (!map.containsKey(type.getQualifiedSourceName()) && type.isWildcard() == null
                && !worklist.contains(type)) {
            b = worklist.add(type);
        }
        return b;
    }

    /**
     * @throws UnableToCompleteException 
     * 
     */
    private void processJsonValueClasses() throws UnableToCompleteException {
        JClassType desiredInterface;
        try {
            desiredInterface = typeOracle.getType(JsonValue.class.getCanonicalName());
        } catch (NotFoundException e) {
            logger.log(TreeLogger.ERROR, "Unable to retrieve the JsonValue class");
            throw new UnableToCompleteException();
        }

        JClassType[] types = getClasses(JsonValue.class.getCanonicalName(), false);
        for (JClassType jClassType : types) {
            JClassType[] interfaces = jClassType.getImplementedInterfaces();
            for (JClassType classType : interfaces) {
                JParameterizedType parameterizedType = classType.isParameterized();
                if (parameterizedType == null || !classType.isAssignableFrom(desiredInterface)) {
                    continue;
                }
                String jsonValueClassName = jClassType.getQualifiedSourceName();
                JClassType[] cd = parameterizedType.getTypeArgs();
                for (JClassType typeArg : cd) {
                    String originalClassName = typeArg.getQualifiedSourceName();
                    map.put(originalClassName, jsonValueClassName);
                }
            }
        }
    }

    /**
     * @param context
     * @throws UnableToCompleteException 
     */
    private void generateJsonFormatterClasses(GeneratorContext context) throws UnableToCompleteException {
        if (worklist == null || worklist.size() == 0) {
            return;
        }
        for (JClassType jc : worklist) {
            if (!jc.isDefaultInstantiable()) {
                logger.log(TreeLogger.ERROR,
                        "Unable to generate class for " + jc.getSimpleSourceName() + " mising default constructor");
            } else {
                SubClassGenerator gen = new SubClassGenerator();
                String implentationClassName;
                implentationClassName = gen.generateSubClass(context, jc);
                if (implentationClassName == null) {
                    return;
                }
                map.put(jc.getQualifiedSourceName(), implentationClassName);
            }
        }
    }

    /**
     * @param context
     */
    private void generateHelperClass(GeneratorContext context) throws UnableToCompleteException, NotFoundException {
        PrintWriter printWriter = context.tryCreate(logger, packageName, className);

        if (printWriter == null) {
            return;
        }

        ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(packageName, className);

        composer.addImplementedInterface(JsonEncoderHelper.class.getCanonicalName());

        composer.addImport(Singleton.class.getCanonicalName());
        composer.addImport(Map.class.getCanonicalName());
        composer.addImport(HashMap.class.getCanonicalName());
        composer.addImport(JsonEncoderHelper.class.getCanonicalName());
        composer.addImport(JsonValue.class.getCanonicalName());
        composer.addAnnotationDeclaration("@Singleton");

        SourceWriter sourceWriter = composer.createSourceWriter(context, printWriter);

        writeHelperClass(sourceWriter);

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

        context.commit(logger, printWriter);
    }

    private void writeHelperClass(SourceWriter sw) {
        //Write variables
        sw.println("private HashMap<Class<?>, JsonValue<?>> map = new HashMap<Class<?>, JsonValue<?>>();");
        sw.println(); // Empty line for style

        //Write constructor
        sw.println("public " + className + "() {");
        sw.indent();
        for (Entry<String, String> entry : map.entrySet()) {
            sw.println("map.put(" + entry.getKey() + ".class, new " + entry.getValue() + "());");
        }
        sw.outdent();
        sw.println("}");
        sw.println(); // Empty line for style

        //Write functions
        sw.println("@Override");
        sw.println("public Map<Class<?>, JsonValue<?>> getGeneratedMap() {");
        sw.indent();
        sw.println("return map;");
        sw.outdent();
        sw.println("}");
    }

    private JClassType[] getClasses(String classCanonicalName, boolean allowAbstract) {
        Vector<JClassType> vector = new Vector<JClassType>();
        JClassType desiredInterface;
        try {
            desiredInterface = typeOracle.getType(classCanonicalName);
        } catch (NotFoundException e) {
            logger.log(TreeLogger.ERROR, "Can't find marker interface: " + classCanonicalName, e);
            return new JClassType[] {};
        }
        for (JClassType j : typeOracle.getTypes()) {
            if (j.isAssignableTo(desiredInterface) && (!j.isAbstract() || allowAbstract)) {
                vector.add(j);
            }
        }
        JClassType[] classArray = new JClassType[vector.size()];
        vector.copyInto(classArray);
        return classArray;
    }

    private class SubClassGenerator {
        private String packageName;
        private String className;
        private JField[] fields;
        private JClassType jc;
        private String suffix;

        private String getParameterizedSimpleSourceName(JGenericType type) {
            StringBuffer sb = new StringBuffer();

            sb.append('<');
            boolean needComma = false;
            for (JClassType typeParam : type.getTypeParameters()) {
                if (needComma) {
                    sb.append(", ");
                } else {
                    needComma = true;
                }
                sb.append(typeParam.getParameterizedQualifiedSourceName());
            }
            sb.append('>');
            return sb.toString();
        }

        /**
         * @param context
         * @param localPackageName
         * @param jc
         * @param localClassName
         */
        public String generateSubClass(GeneratorContext context, JClassType jc) {
            this.jc = jc;
            this.className = "Json" + jc.getSimpleSourceName();
            JParameterizedType parameterizedType = jc.isParameterized();
            if (parameterizedType != null) {
                this.jc = parameterizedType.getBaseType();
                this.suffix = getParameterizedSimpleSourceName(parameterizedType.getBaseType());
            } else {
                this.suffix = "";
            }
            this.packageName = jc.getPackage().getName();
            PrintWriter printWriter = context.tryCreate(logger, packageName, className);

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

            ClassSourceFileComposerFactory composer = null;

            /*
             * Changes the name to fit with any generics of the object.
             */
            composer = new ClassSourceFileComposerFactory(packageName, className + suffix);
            fields = getFields(this.jc);

            composer.addImport(List.class.getCanonicalName());
            composer.addImport(JSONValue.class.getCanonicalName());
            composer.addImport(JSONObject.class.getCanonicalName());
            composer.addImport(FrameEncoder.class.getCanonicalName());
            composer.addImport(UnableToSerialize.class.getCanonicalName());
            composer.addImport(UnableToDeserialize.class.getCanonicalName());

            composer.addImplementedInterface(
                    JsonValue.class.getCanonicalName() + "<" + jc.getQualifiedSourceName() + suffix + ">");

            SourceWriter sourceWriter = composer.createSourceWriter(context, printWriter);

            writeFromJson(sourceWriter);
            writeToJson(sourceWriter);
            writeNatives(sourceWriter);

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

            context.commit(logger, printWriter);
            return packageName + "." + className;
        }

        private void writeNatives(SourceWriter sw) {
            String s = "obj.@" + jc.getQualifiedSourceName() + "::";

            //Native set function
            sw.println();
            sw.print("private native void setFields(" + jc.getQualifiedSourceName() + suffix + " obj");
            for (JField f : fields) {
                sw.print(", " + getFieldType(f) + " " + f.getName());
            }
            sw.println(") /*-{");
            sw.indent();
            for (JField f : fields) {
                sw.println(s + f.getName() + " = " + f.getName() + ";");
            }
            sw.outdent();
            sw.println("}-*/;");

            //Native get functions

            for (JField f : fields) {
                sw.println();
                sw.println("private native " + getFieldType(f) + " get" + f.getName() + "("
                        + jc.getQualifiedSourceName() + suffix + " obj) /*-{");
                sw.indent();
                sw.println("return " + s + f.getName() + ";");
                sw.outdent();
                sw.println("}-*/;");
            }
        }

        private String getFieldType(JField f) {
            return f.getType().getParameterizedQualifiedSourceName();
        }

        private String getFieldType(JField f, boolean addClass) {
            JType j = f.getType();
            String type;
            if (addClass) {
                if (j.isParameterized() != null) {
                    type = recursiveTypeFind(j);
                } else {
                    type = j.getQualifiedSourceName();
                    if (addClass) {
                        type = type + ".class";
                    }
                }
            } else {
                type = j.getParameterizedQualifiedSourceName();
            }
            return type;
        }

        private String recursiveTypeFind(JType j) {
            String type = j.getQualifiedSourceName();
            type = type + ".class";

            if (j.isParameterized() != null) {
                JClassType[] a = j.isParameterized().getTypeArgs();

                boolean first = true;
                for (JClassType jClassType : a) {
                    if (!first) {
                        type = type + ", ";
                        first = false;
                    }
                    type = type + "," + recursiveTypeFind(jClassType);
                }
            }
            return type;
        }

        private void writeToJson(SourceWriter sw) {
            sw.println();
            sw.println("@Override");
            sw.println("public void toJson(StringBuilder response, " + jc.getQualifiedSourceName() + suffix
                    + " object,");
            sw.indent();
            sw.indentln("FrameEncoder<JSONValue> encoder) throws UnableToSerialize {");
            sw.println("response.append(\"{\");");
            Boolean first = true;
            String t = "response.append(\",\");";
            for (JField f : fields) {
                if (!first) {
                    sw.println();
                    sw.println(t);
                    sw.println();
                } else {
                    sw.println();
                    first = false;
                }
                sw.println("encoder.encode(\"" + getJsonName(f) + "\", response);");
                sw.println("response.append(\":\");");
                sw.println("encoder.encode(get" + f.getName() + "(object), response);");
            }
            sw.println();
            sw.println("response.append(\"}\");");
            sw.outdent();
            sw.println("}");
        }

        private void writeFromJson(SourceWriter sw) {
            //Begin Function
            sw.println();
            sw.println("@Override");
            sw.println("public " + jc.getQualifiedSourceName() + suffix
                    + " fromJson(JSONValue jsonValue, List<Class<?>> subtypes,");
            sw.indent();
            sw.indentln("FrameEncoder<JSONValue> encoder) throws UnableToDeserialize {");

            //boiler plate null check
            sw.println("if (jsonValue.isObject() == null)");
            sw.indentln("throw new UnableToDeserialize(\"Expected Json Object\");");
            sw.println("JSONObject jsonObject = jsonValue.isObject();");
            sw.println();

            //make vars
            for (JField f : fields) {
                sw.println(getFieldType(f) + " " + f.getName() + " = null;");
            }
            sw.println();

            //validate
            for (JField f : fields) {
                sw.print(f.getName() + " = encoder.validate(jsonObject.get(\"" + getJsonName(f) + "\"), ");
                if (f.getType().isTypeParameter() == null) {
                    sw.println(f.getName() + ", new Class<?>[]{" + getFieldType(f, true) + "});");
                } else {
                    sw.println(f.getName() + ", subtypes);");
                }
            }
            sw.println();

            //create new object
            sw.println(jc.getQualifiedSourceName() + suffix + " obj = new " + jc.getQualifiedSourceName() + suffix
                    + "();");

            //Call native set
            sw.print("setFields(obj");
            for (JField f : fields) {
                sw.print(", " + f.getName());
            }
            sw.println(");");

            //return object
            sw.println("return obj;");

            //End function
            sw.outdent();
            sw.println("}");
        }

        /**
         * @param f
         * @return
         */
        private String getJsonName(JField f) {
            Rename rename = f.getAnnotation(Rename.class);
            if (rename != null) {
                return rename.value();
            } else {
                return f.getName();
            }
        }

        private JField[] getFields(JClassType j) {
            assert (j != null);
            Vector<JField> vector = new Vector<JField>();

            for (JField f : j.getFields()) {
                if (!f.isTransient()) {
                    vector.add(f);
                }
            }
            JField[] fieldArray = new JField[vector.size()];
            vector.copyInto(fieldArray);
            Arrays.sort(fieldArray, new Comparator<JField>() {
                @Override
                public int compare(JField o1, JField o2) {
                    return getJsonName(o1).compareTo(getJsonName(o2));
                }
            });
            return fieldArray;
        }

    }
}