com.totsp.gwittir.serial.json.rebind.JSONCodecGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.totsp.gwittir.serial.json.rebind.JSONCodecGenerator.java

Source

/*
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package com.totsp.gwittir.serial.json.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.TreeLogger.Type;
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.JGenericType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONBoolean;
import com.google.gwt.json.client.JSONNull;
import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

import com.totsp.gwittir.rebind.beans.BeanResolver;
import com.totsp.gwittir.rebind.beans.IntrospectorGenerator;
import com.totsp.gwittir.rebind.beans.RProperty;
import com.totsp.gwittir.serial.client.SerializationException;
import com.totsp.gwittir.serial.json.client.JSONCodec;
import com.totsp.gwittir.serial.json.client.JSONField;
import com.totsp.gwittir.serial.json.client.JSONOmit;

import java.io.PrintWriter;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 *
 * @author kebernet
 */
public class JSONCodecGenerator extends IntrospectorGenerator {
    private static final HashSet<String> CORE_TYPES = new HashSet<String>();

    static {
        CORE_TYPES.add(String.class.getCanonicalName());
        CORE_TYPES.add(Double.class.getCanonicalName());
        CORE_TYPES.add(double.class.getCanonicalName());
        CORE_TYPES.add(Integer.class.getCanonicalName());
        CORE_TYPES.add(int.class.getCanonicalName());
        CORE_TYPES.add(Float.class.getCanonicalName());
        CORE_TYPES.add(float.class.getCanonicalName());
        CORE_TYPES.add(Long.class.getCanonicalName());
        CORE_TYPES.add(long.class.getCanonicalName());
        CORE_TYPES.add(Boolean.class.getCanonicalName());
        CORE_TYPES.add(boolean.class.getCanonicalName());
        CORE_TYPES.add(Short.class.getCanonicalName());
        CORE_TYPES.add(short.class.getCanonicalName());
        CORE_TYPES.add(Character.class.getCanonicalName());
        CORE_TYPES.add(char.class.getCanonicalName());
    }

    private HashSet<BeanResolver> children = new HashSet<BeanResolver>();
    private JClassType collectionType;
    private JClassType numberType;
    private JType listType;
    private JType setType;
    private List<BeanResolver> types;

    public String fromType(JType type, String innerExpression) {
        if (type.getQualifiedSourceName().equals(String.class.getCanonicalName())) {
            return innerExpression + ".isString().stringValue()";
        } else if (type.getQualifiedSourceName().equals(Double.class.getCanonicalName())
                || ((type.isPrimitive() != null) && JPrimitiveType.DOUBLE.equals(type.isPrimitive()))) {
            return innerExpression + ".isNumber().doubleValue()";
        } else if (type.getQualifiedSourceName().equals(Float.class.getCanonicalName())
                || ((type.isPrimitive() != null) && JPrimitiveType.FLOAT.equals(type.isPrimitive()))) {
            return " (float) " + innerExpression + ".isNumber().doubleValue()";
        } else if (type.getQualifiedSourceName().equals(Integer.class.getCanonicalName())
                || ((type.isPrimitive() != null) && JPrimitiveType.INT.equals(type.isPrimitive()))) {
            return "Double.valueOf(" + innerExpression + ".isNumber().doubleValue()) .intValue()";
        } else if (type.getQualifiedSourceName().equals(Short.class.getCanonicalName())
                || ((type.isPrimitive() != null) && JPrimitiveType.SHORT.equals(type.isPrimitive()))) {
            return "Short.valueOf(" + innerExpression + ".isNumber().doubleValue()) .shortValue()";
        } else if (type.getQualifiedSourceName().equals(Character.class.getCanonicalName())
                || ((type.isPrimitive() != null) && JPrimitiveType.CHAR.equals(type.isPrimitive()))) {
            return "" + innerExpression + ".isString().stringValue().charAt(0)";
        } else if (type.getQualifiedSourceName().equals(Long.class.getCanonicalName())
                || ((type.isPrimitive() != null) && JPrimitiveType.LONG.equals(type.isPrimitive()))) {
            return "Double.valueOf( " + innerExpression + ".isNumber().doubleValue()).longValue()";
        } else if (type.getQualifiedSourceName().equals(Boolean.class.getCanonicalName())
                || ((type.isPrimitive() != null) && JPrimitiveType.BOOLEAN.equals(type.isPrimitive()))) {
            return innerExpression + ".isBoolean().booleanValue() ";
        }

        BeanResolver child = findType(type);

        if (child != null) {
            this.children.add(child);

            return "CODEC_" + child.getType().getQualifiedSourceName().replaceAll("\\.", "_")
                    + ".deserializeFromJSONObject(" + innerExpression + ".isObject())";
        }

        throw new RuntimeException("" + type);
    }

    @Override
    public String generate(TreeLogger logger, GeneratorContext context, String typeName)
            throws UnableToCompleteException {
        logger.log(Type.INFO, "Generating codec for " + typeName);

        JClassType type = null;
        JClassType jsonCodec = null;

        try {
            type = context.getTypeOracle().getType(typeName);
            jsonCodec = context.getTypeOracle().getType(JSONCodec.class.getCanonicalName());
            this.collectionType = context.getTypeOracle().getType(Collection.class.getCanonicalName());
            this.listType = context.getTypeOracle().getType(List.class.getCanonicalName());
            this.setType = context.getTypeOracle().getType(Set.class.getCanonicalName());
            this.numberType = context.getTypeOracle().getType(Number.class.getCanonicalName());
        } catch (NotFoundException ex) {
            logger.log(TreeLogger.ERROR, typeName, ex);

            return null;
        }

        if (type.isClass() != null) { //Don't regenerate from Impls.

            return type.getQualifiedSourceName();
        }

        JClassType subtype = type.asParameterizationOf((JGenericType) jsonCodec).getTypeArgs()[0];
        this.types = this.getIntrospectableTypes(logger, context.getTypeOracle());

        BeanResolver thisType = null;

        for (BeanResolver r : this.types) {
            if (r.getType().equals(subtype)) {
                thisType = r;

                break;
            }
        }

        if (thisType == null) {
            logger.log(Type.ERROR, "Unable to find introspectable type " + subtype);
            throw new UnableToCompleteException();
        }

        this.writeClassSerializer(logger, context, thisType);

        this.writeTopSerializer(logger, context, type, subtype);

        return type.getQualifiedSourceName() + "_Impl";
    }

    public void writeReader(SourceWriter writer, RProperty prop) {
        if (prop.getWriteMethod() == null) {
            return;
        }

        JSONField field = prop.getReadMethod() == null ? null
                : prop.getReadMethod().getBaseMethod().getAnnotation(JSONField.class);
        JSONOmit omit = prop.getReadMethod() == null ? null
                : prop.getReadMethod().getBaseMethod().getAnnotation(JSONOmit.class);
        System.out.println(prop.getName() + " omit " + omit + " field " + field);
        if (omit != null) {
            return;
        }

        String fieldName = (field == null) ? prop.getName() : field.value();

        try {
            writer.println("if(root.containsKey(\"" + fieldName + "\")){");

            if (prop.getType().isPrimitive() == null) {
                writer.println("if(root.get(\"" + fieldName + "\").isNull() != null) {");
                writer.println(this.setterPrefix(prop) + "null);");
                writer.println("} else {");
            }

            if (prop.getType().isArray() != null) {
                JArrayType arrayType = prop.getType().isArray();
                JType paramType = arrayType.getComponentType();
                writer.println("JSONArray array = root.get(\"" + fieldName + "\").isArray();");
                writer.println(paramType.getQualifiedSourceName() + "[] value = new "
                        + paramType.getQualifiedSourceName() + "[ array.size() ];");
                writer.println("for(int i=0; i<array.size(); i++){");
                writer.indent();

                writer.println(" value[i] = " + this.fromType(paramType, "array.get(i)") + ";");

                writer.outdent();
                writer.println("}"); //endfor
                writer.println(this.setterPrefix(prop) + " value );");
            } else if (prop.getType() instanceof JClassType
                    && ((JClassType) prop.getType()).isAssignableTo(this.collectionType)) {
                // get the parameter type
                JClassType propType = (JClassType) prop.getType();
                JType paramType = propType.asParameterizationOf((JGenericType) this.collectionType)
                        .getTypeArgs()[0];
                writer.println("JSONArray array = root.get(\"" + fieldName + "\").isArray();");
                writer.println(propType.getParameterizedQualifiedSourceName() + " col = "
                        + this.newCollectionExpression(propType) + ";");
                writer.println("for(int i=0; i<array.size(); i++){");
                writer.indent();

                writer.println(" col.add(" + this.fromType(paramType, "array.get(i)") + ");");

                writer.outdent();
                writer.println("}"); //endfor
                writer.println(this.setterPrefix(prop) + " col );");
            } else {
                writer.println(
                        setterPrefix(prop) + fromType(prop.getType(), "root.get(\"" + fieldName + "\")") + ");");
            }

            if (prop.getType().isPrimitive() == null) {
                writer.println("}"); //end null else
            }

            writer.println("}"); //end contains key
        } catch (Exception e) {
            System.out.println("Exception on prop " + prop);
            throw new RuntimeException(e);
        }
    }

    private boolean isCoreType(JType type) {
        return CORE_TYPES.contains(type.getQualifiedSourceName());
    }

    private BeanResolver findType(JType type) {
        for (BeanResolver r : this.types) {
            if (r.getType().equals(type)) {
                return r;
            }
        }

        return null;
    }

    private String newCollectionExpression(JClassType type) {
        if (type.isParameterized().getBaseType().equals(this.listType)) {
            return "new java.util.ArrayList()";
        } else if (type.isParameterized().getBaseType().equals(this.setType)) {
            return "new java.util.HashSet()";
        } else {
            return "new " + type.getParameterizedQualifiedSourceName() + "()";
        }
    }

    private String setterPrefix(RProperty prop) {
        return "destination." + prop.getWriteMethod().getBaseMethod().getName() + "( ";
    }

    private String toType(JType type, String innerExpression) {
        //System.out.println("toType " + type);
        if ((type.isPrimitive() == JPrimitiveType.DOUBLE) || (type.isPrimitive() == JPrimitiveType.FLOAT)
                || (type.isPrimitive() == JPrimitiveType.LONG) || (type.isPrimitive() == JPrimitiveType.INT)
                || (type.isPrimitive() == JPrimitiveType.SHORT)) {
            return " new JSONNumber( (double) " + innerExpression + ")";
        } else if (type.isPrimitive() == JPrimitiveType.BOOLEAN) {
            return " JSONBoolean.getInstance( " + innerExpression + " ) ";
        } else if (type.isPrimitive() == JPrimitiveType.CHAR) {
            return " new JSONString( Character.toString(" + innerExpression + ") )";
        }

        StringBuilder sb = new StringBuilder(innerExpression + " == null ? JSONNull.getInstance() : ");

        if (type.getQualifiedSourceName().equals("java.lang.String")) {
            sb = sb.append(" new JSONString( " + innerExpression + " ) ");
        } else if (type.getQualifiedSourceName().equals("java.lang.Character")) {
            sb = sb.append(" new JSONString( Character.toString(" + innerExpression + ") ) ");
        } else if (type.isClassOrInterface() != null && type.isClassOrInterface().isAssignableTo(this.numberType)) {
            sb = sb.append("new JSONNumber( ((Number) " + innerExpression + ").doubleValue())");
        } else if (type.getQualifiedSourceName().equals("java.lang.Boolean")) {
            sb.append(" JSONBoolean.getInstance( " + innerExpression + " ) ");
        } else {

            BeanResolver child = findType(type);
            if (child == null) {
                throw new RuntimeException(type + " is not introspectable!");
            }
            this.children.add(child);
            sb = sb.append("CODEC_" + type.getQualifiedSourceName().replaceAll("\\.", "_")
                    + ".serializeToJSONObject( " + innerExpression + " ) ");
        }

        return sb.toString();
    }

    private void writeClassSerializer(TreeLogger logger, GeneratorContext context, BeanResolver type) {
        logger.log(Type.INFO, "Creating JSON Serializer for " + type.getType());

        String classTypeName = type.getType().getSimpleSourceName() + "_JSONCodec";
        ClassSourceFileComposerFactory mcf = new ClassSourceFileComposerFactory(
                type.getType().getPackage().getName(), classTypeName);
        mcf.addImport(JSONParser.class.getCanonicalName());
        mcf.addImport(JSONObject.class.getCanonicalName());
        mcf.addImport(JSONArray.class.getCanonicalName());
        mcf.addImport(JSONBoolean.class.getCanonicalName());
        mcf.addImport(JSONNumber.class.getCanonicalName());
        mcf.addImport(JSONString.class.getCanonicalName());
        mcf.addImport(JSONNull.class.getCanonicalName());
        mcf.addImport(GWT.class.getCanonicalName());
        mcf.addImport(SerializationException.class.getCanonicalName());
        mcf.addImplementedInterface(
                JSONCodec.class.getCanonicalName() + "<" + type.getType().getQualifiedSourceName() + ">");

        PrintWriter printWriter = context.tryCreate(logger, type.getType().getPackage().getName(), classTypeName);

        if (printWriter == null) {
            logger.log(Type.INFO, "Already genned " + classTypeName);

            return;
        }

        SourceWriter writer = mcf.createSourceWriter(context, printWriter);

        writeDeserializer(writer, type);
        writeSerializer(writer, type);

        HashSet<BeanResolver> childrenCopy = new HashSet<BeanResolver>(this.children);
        this.children.clear();

        for (BeanResolver child : childrenCopy) {
            writer.println(" private static final " + JSONCodec.class.getCanonicalName() + "<"
                    + child.getType().getQualifiedSourceName() + "> CODEC_"
                    + child.getType().getQualifiedSourceName().replaceAll("\\.", "_") + " = GWT.create("
                    + child.getType().getQualifiedSourceName() + "_JSONCodec.class);");
            writeClassSerializer(logger, context, child);
        }

        writer.println(" public String getMimeType() { return MIME_TYPE; }");
        writer.println("}"); // close the class

        context.commit(logger, printWriter);
    }

    private void writeDeserializer(SourceWriter writer, BeanResolver r) {
        writer.println("public " + r.toString() + " deserialize(String data) throws SerializationException { ");
        writer.indent();
        writer.println("try {");
        writer.indent();
        writer.println("JSONObject root = JSONParser.parse(data).isObject(); ");
        writer.println(" return this.deserializeFromJSONObject(root);");
        writer.println("} catch (Exception e) { ");
        writer.indent();
        writer.println("throw new SerializationException(e);");
        writer.outdent();
        writer.println("}");
        writer.println("}");

        writer.println("public " + r.toString()
                + " deserializeFromJSONObject(JSONObject root) throws SerializationException {");
        writer.indent();
        writer.println(" if(root == null) return null;");
        writer.println("try {");
        writer.indent();

        writer.println(r.getType().getQualifiedSourceName() + " destination = new "
                + r.getType().getQualifiedSourceName() + "();");

        for (RProperty p : r.getProperties().values()) {
            if (p.getWriteMethod() != null) {
                writeReader(writer, p);
            }
        }

        writer.println(" return destination;");
        writer.outdent();
        writer.println("} catch (Exception e) { ");
        writer.indent();
        writer.println("throw new SerializationException(e);");
        writer.outdent();
        writer.println("}");
        writer.outdent();
        writer.println("}");
    }

    private void writeSerializer(SourceWriter writer, BeanResolver r) {
        writer.println("public JSONObject serializeToJSONObject( " + r.getType().getQualifiedSourceName()
                + " source ) throws SerializationException { ");
        writer.indent();
        writer.println(" JSONObject destination = new JSONObject();");

        for (RProperty prop : r.getProperties().values()) {
            if (prop.getName().equals("class") || prop.getReadMethod() == null) {
                continue;
            }

            JSONField field = prop.getReadMethod().getBaseMethod().getAnnotation(JSONField.class);
            JSONOmit omit = prop.getReadMethod().getBaseMethod().getAnnotation(JSONOmit.class);
            System.out.println(" ws \t " + prop.getName() + " "
                    + prop.getReadMethod().getBaseMethod().getEnclosingType()
                    + prop.getReadMethod().getBaseMethod().getReadableDeclaration() + " " + omit + " " + field);
            if (omit != null) {
                continue;
            }

            String fieldName = (field == null) ? prop.getName() : field.value();

            if (prop.getReadMethod() != null) {
                JClassType classType = prop.getType().isClassOrInterface();
                JArrayType arrayType = prop.getType().isArray();
                System.out.println(prop.getName() + "  ArrayType " + arrayType + " :: "
                        + ((arrayType == null ? "" : "" + arrayType.getComponentType())));
                if ((classType != null) && (classType.isAssignableTo(this.collectionType)) || arrayType != null) {
                    JType subType = (arrayType != null) ? arrayType.getComponentType()
                            : classType.asParameterizationOf(this.collectionType.isGenericType()).getTypeArgs()[0];
                    writer.println();
                    writer.println(
                            " if( source." + prop.getReadMethod().getBaseMethod().getName() + "() == null ){");
                    writer.println("destination.put(\"" + fieldName + "\", JSONNull.getInstance());");
                    writer.println(" } else { ");
                    writer.println("int i=0; JSONArray value = new JSONArray();");
                    writer.println("for( " + subType.getQualifiedSourceName() + " o : source."
                            + prop.getReadMethod().getBaseMethod().getName() + "()){");
                    writer.println("   value.set(i++, " + toType(subType, " o ") + ");");
                    writer.println("}");
                    writer.println("destination.put(\"" + fieldName + "\", value);"); //TODO JSONField
                    writer.println("}");
                } else {
                    writer.print("destination.put( \"" + fieldName + "\", "); //TODO JSONField
                    writer.print(toType(prop.getType(),
                            " source." + prop.getReadMethod().getBaseMethod().getName() + "() ") + ");");
                }
            }
        }

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

        writer.println("public String serialize(" + r.getType().getQualifiedSourceName()
                + " source ) throws SerializationException { ");
        writer.println("   return serializeToJSONObject(source).toString();");
        writer.println("}");
    }

    private void writeTopSerializer(TreeLogger logger, GeneratorContext context, JClassType typeFor,
            JClassType subType) {
        ClassSourceFileComposerFactory mcf = new ClassSourceFileComposerFactory(typeFor.getPackage().getName(),
                typeFor.getSimpleSourceName() + "_Impl");
        mcf.addImplementedInterface(typeFor.getParameterizedQualifiedSourceName());
        mcf.setSuperclass(subType.getParameterizedQualifiedSourceName() + "_JSONCodec");

        PrintWriter printWriter = context.tryCreate(logger, typeFor.getPackage().getName(),
                typeFor.getSimpleSourceName() + "_Impl");

        if (printWriter == null) {
            logger.log(Type.INFO, "Already genned " + typeFor.getSimpleSourceName() + "_Impl");

            return;
        }

        SourceWriter writer = mcf.createSourceWriter(context, printWriter);
        writer.println(" public String getMimeType() { return MIME_TYPE; }");
        writer.println("}");
        context.commit(logger, printWriter);
    }
}