org.rapla.rest.gwtjsonrpc.rebind.SerializerCreator.java Source code

Java tutorial

Introduction

Here is the source code for org.rapla.rest.gwtjsonrpc.rebind.SerializerCreator.java

Source

// Copyright 2008 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 org.rapla.rest.gwtjsonrpc.rebind;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.rapla.rest.gwtjsonrpc.client.impl.JsonSerializer;
import org.rapla.rest.gwtjsonrpc.client.impl.ser.EnumSerializer;
import org.rapla.rest.gwtjsonrpc.client.impl.ser.JavaLangString_JsonSerializer;
import org.rapla.rest.gwtjsonrpc.client.impl.ser.JavaUtilDate_JsonSerializer;
import org.rapla.rest.gwtjsonrpc.client.impl.ser.ListSerializer;
import org.rapla.rest.gwtjsonrpc.client.impl.ser.ObjectArraySerializer;
import org.rapla.rest.gwtjsonrpc.client.impl.ser.ObjectMapSerializer;
import org.rapla.rest.gwtjsonrpc.client.impl.ser.ObjectSerializer;
import org.rapla.rest.gwtjsonrpc.client.impl.ser.PrimitiveArraySerializer;
import org.rapla.rest.gwtjsonrpc.client.impl.ser.SetSerializer;
import org.rapla.rest.gwtjsonrpc.client.impl.ser.StringMapSerializer;

import com.google.gwt.core.client.JavaScriptObject;
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.JPackage;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

class SerializerCreator {
    private static final String SER_SUFFIX = "_JsonSerializer";
    private static final Comparator<JField> FIELD_COMP = new Comparator<JField>() {
        @Override
        public int compare(final JField o1, final JField o2) {
            return o1.getName().compareTo(o2.getName());
        }
    };

    private static final HashMap<String, String> defaultSerializers;
    private static final HashMap<String, String> parameterizedSerializers;
    static {
        defaultSerializers = new HashMap<String, String>();
        parameterizedSerializers = new HashMap<String, String>();

        defaultSerializers.put(java.lang.String.class.getCanonicalName(),
                JavaLangString_JsonSerializer.class.getCanonicalName());
        defaultSerializers.put(java.util.Date.class.getCanonicalName(),
                JavaUtilDate_JsonSerializer.class.getCanonicalName());
        //    defaultSerializers.put(java.sql.Date.class.getCanonicalName(),
        //        JavaSqlDate_JsonSerializer.class.getCanonicalName());
        //    defaultSerializers.put(java.sql.Timestamp.class.getCanonicalName(),
        //        JavaSqlTimestamp_JsonSerializer.class.getCanonicalName());
        parameterizedSerializers.put(java.util.List.class.getCanonicalName(),
                ListSerializer.class.getCanonicalName());
        parameterizedSerializers.put(java.util.Map.class.getCanonicalName(),
                ObjectMapSerializer.class.getCanonicalName());
        parameterizedSerializers.put(java.util.Set.class.getCanonicalName(),
                SetSerializer.class.getCanonicalName());
    }

    private final HashMap<String, String> generatedSerializers;
    private final GeneratorContext context;
    private JClassType targetType;

    SerializerCreator(final GeneratorContext c) {
        context = c;
        generatedSerializers = new HashMap<String, String>();
    }

    String create(final JClassType targetType, final TreeLogger logger) throws UnableToCompleteException {
        if (targetType.isParameterized() != null || targetType.isArray() != null) {
            ensureSerializersForTypeParameters(logger, targetType);
        }
        String sClassName = serializerFor(targetType);
        if (sClassName != null) {
            return sClassName;
        }

        checkCanSerialize(logger, targetType, true);
        recursivelyCreateSerializers(logger, targetType);

        this.targetType = targetType;
        final SourceWriter srcWriter = getSourceWriter(logger, context);
        final String sn = getSerializerQualifiedName(targetType);
        if (!generatedSerializers.containsKey(targetType.getQualifiedSourceName())) {
            generatedSerializers.put(targetType.getQualifiedSourceName(), sn);
        }
        if (srcWriter == null) {
            return sn;
        }

        if (!targetType.isAbstract()) {
            generateSingleton(srcWriter);
        }
        if (targetType.isEnum() != null) {
            generateEnumFromJson(srcWriter);
        } else {
            generateInstanceMembers(srcWriter);
            generatePrintJson(srcWriter);
            generateFromJson(srcWriter);
            generateGetSets(srcWriter);
        }

        srcWriter.commit(logger);
        return sn;
    }

    private void recursivelyCreateSerializers(final TreeLogger logger, final JType targetType)
            throws UnableToCompleteException {
        if (targetType.isPrimitive() != null || isBoxedPrimitive(targetType)) {
            return;
        }

        final JClassType targetClass = targetType.isClass();
        if (needsSuperSerializer(targetClass)) {
            create(targetClass.getSuperclass(), logger);
        }

        for (final JField f : sortFields(targetClass)) {
            ensureSerializer(logger, f.getType());
        }
    }

    Set<JClassType> createdType = new HashSet<JClassType>();

    private void ensureSerializer(final TreeLogger logger, final JType type) throws UnableToCompleteException {
        if (ensureSerializersForTypeParameters(logger, type)) {
            return;
        }

        final String qsn = type.getQualifiedSourceName();
        if (defaultSerializers.containsKey(qsn) || parameterizedSerializers.containsKey(qsn)) {
            return;
        }

        JClassType type2 = (JClassType) type;
        if (createdType.contains(type2)) {
            return;
        }
        createdType.add(type2);
        create(type2, logger);
    }

    private boolean ensureSerializersForTypeParameters(final TreeLogger logger, final JType type)
            throws UnableToCompleteException {
        if (isJsonPrimitive(type) || isBoxedPrimitive(type)) {
            return true;
        }

        if (type.isArray() != null) {
            ensureSerializer(logger, type.isArray().getComponentType());
            return true;
        }

        if (type.isParameterized() != null) {
            for (final JClassType t : type.isParameterized().getTypeArgs()) {
                ensureSerializer(logger, t);
            }
        }

        return false;
    }

    void checkCanSerialize(final TreeLogger logger, final JType type) throws UnableToCompleteException {
        checkCanSerialize(logger, type, false);
    }

    Set<JClassType> checkedType = new HashSet<JClassType>();

    void checkCanSerialize(final TreeLogger logger, final JType type, boolean allowAbstractType)
            throws UnableToCompleteException {
        if (type.isPrimitive() == JPrimitiveType.LONG) {
            logger.log(TreeLogger.ERROR, "Type 'long' not supported in JSON encoding", null);
            throw new UnableToCompleteException();
        }

        //    if (type.isPrimitive() == JPrimitiveType.VOID) {
        //      logger.log(TreeLogger.ERROR,
        //          "Type 'void' not supported in JSON encoding", null);
        //      throw new UnableToCompleteException();
        //    }

        final String qsn = type.getQualifiedSourceName();
        if (type.isEnum() != null) {
            return;
        }

        if (isJsonPrimitive(type) || isBoxedPrimitive(type)) {
            return;
        }

        if (type.isArray() != null) {
            final JType leafType = type.isArray().getLeafType();
            if (leafType.isPrimitive() != null || isBoxedPrimitive(leafType)) {
                if (type.isArray().getRank() != 1) {
                    logger.log(TreeLogger.ERROR, "gwtjsonrpc does not support "
                            + "(de)serializing of multi-dimensional arrays of primitves");
                    // To work around this, we would need to generate serializers for
                    // them, this can be considered a todo
                    throw new UnableToCompleteException();
                } else
                    // Rank 1 arrays work fine.
                    return;
            }
            checkCanSerialize(logger, type.isArray().getComponentType());
            return;
        }

        if (defaultSerializers.containsKey(qsn)) {
            return;
        }

        if (type.isParameterized() != null) {
            final JClassType[] typeArgs = type.isParameterized().getTypeArgs();
            for (final JClassType t : typeArgs) {
                checkCanSerialize(logger, t);
            }
            if (parameterizedSerializers.containsKey(qsn)) {
                return;
            }
        } else if (parameterizedSerializers.containsKey(qsn)) {
            logger.log(TreeLogger.ERROR, "Type " + qsn + " requires type paramter(s)", null);
            throw new UnableToCompleteException();
        }

        if (qsn.startsWith("java.") || qsn.startsWith("javax.")) {
            logger.log(TreeLogger.ERROR, "Standard type " + qsn + " not supported in JSON encoding", null);
            throw new UnableToCompleteException();
        }

        if (type.isInterface() != null) {
            logger.log(TreeLogger.ERROR, "Interface " + qsn + " not supported in JSON encoding", null);
            throw new UnableToCompleteException();
        }

        final JClassType ct = (JClassType) type;
        if (checkedType.contains(ct)) {
            return;
        }
        checkedType.add(ct);
        if (ct.isAbstract() && !allowAbstractType) {
            logger.log(TreeLogger.ERROR, "Abstract type " + qsn + " not supported here", null);
            throw new UnableToCompleteException();
        }
        for (final JField f : sortFields(ct)) {
            final TreeLogger branch = logger.branch(TreeLogger.DEBUG, "In type " + qsn + ", field " + f.getName());
            checkCanSerialize(branch, f.getType());
        }
    }

    String serializerFor(final JType t) {
        if (t.isArray() != null) {
            final JType componentType = t.isArray().getComponentType();
            if (componentType.isPrimitive() != null || isBoxedPrimitive(componentType))
                return PrimitiveArraySerializer.class.getCanonicalName();
            else
                return ObjectArraySerializer.class.getCanonicalName() + "<" + componentType.getQualifiedSourceName()
                        + ">";
        }

        if (isStringMap(t)) {
            return StringMapSerializer.class.getName();
        }

        final String qsn = t.getQualifiedSourceName();
        if (defaultSerializers.containsKey(qsn)) {
            return defaultSerializers.get(qsn);
        }

        if (parameterizedSerializers.containsKey(qsn)) {
            return parameterizedSerializers.get(qsn);
        }

        return generatedSerializers.get(qsn);
    }

    private boolean isStringMap(final JType t) {
        return t.isParameterized() != null && t.getErasedType().isClassOrInterface() != null
                && t.isParameterized().getTypeArgs().length > 0
                && t.isParameterized().getTypeArgs()[0].getQualifiedSourceName().equals(String.class.getName())
                && t.getErasedType().isClassOrInterface()
                        .isAssignableTo(context.getTypeOracle().findType(Map.class.getName()));
    }

    private void generateSingleton(final SourceWriter w) {
        w.print("public static final ");
        w.print("javax.inject.Provider<" + getSerializerSimpleName() + ">");

        w.print(" INSTANCE_PROVIDER = new javax.inject.Provider<");
        w.print(getSerializerSimpleName());
        w.println(">(){");
        w.print("public " + getSerializerSimpleName() + " get(){return INSTANCE;} ");
        w.println("};");
        w.println();

        w.print("public static final ");
        w.print(getSerializerSimpleName());
        w.print(" INSTANCE = new ");
        w.print(getSerializerSimpleName());
        w.println("();");
        w.println();
    }

    private void generateInstanceMembers(final SourceWriter w) {
        for (final JField f : sortFields(targetType)) {
            final JType ft = f.getType();
            if (needsTypeParameter(ft)) {
                final String serType = serializerFor(ft);
                w.print("private final ");
                w.print(serType);
                w.print(" ");
                w.print("ser_" + f.getName());
                w.print(" = ");
                boolean useProviders = true;
                generateSerializerReference(ft, w, useProviders);
                w.println(";");
            }
        }
        w.println();
    }

    void generateSerializerReference(final JType type, final SourceWriter w, boolean useProviders) {
        String serializerFor = serializerFor(type);
        if (type.isArray() != null) {
            final JType componentType = type.isArray().getComponentType();
            if (componentType.isPrimitive() != null || isBoxedPrimitive(componentType)) {
                w.print(PrimitiveArraySerializer.class.getCanonicalName());
                w.print(".INSTANCE");
            } else {
                w.print("new " + serializerFor + "(");
                generateSerializerReference(componentType, w, useProviders);
                w.print(")");
            }

        } else if (needsTypeParameter(type)) {
            w.print("new " + serializerFor + "(");
            final JClassType[] typeArgs = type.isParameterized().getTypeArgs();
            int n = 0;
            if (isStringMap(type)) {
                n++;
            }
            boolean first = true;
            for (; n < typeArgs.length; n++) {
                if (first) {
                    first = false;
                } else {
                    w.print(", ");
                }
                generateSerializerReference(typeArgs[n], w, useProviders);
            }
            w.print(")");

        } else {
            //      String sourceName = type.getQualifiedSourceName();

            w.print(serializerFor + ".INSTANCE" + (useProviders ? "_PROVIDER" : ""));
        }
    }

    private void generateGetSets(final SourceWriter w) {
        for (final JField f : sortFields(targetType)) {
            if (f.isPrivate()) {
                w.print("private static final native ");
                w.print(f.getType().getQualifiedSourceName());
                w.print(" objectGet_" + f.getName());
                w.print("(");
                w.print(targetType.getQualifiedSourceName() + " instance");
                w.print(")");
                w.println("/*-{ ");
                w.indent();

                w.print("return instance.@");
                w.print(targetType.getQualifiedSourceName());
                w.print("::");
                w.print(f.getName());
                w.println(";");

                w.outdent();
                w.println("}-*/;");

                w.print("private static final native void ");
                w.print(" objectSet_" + f.getName());
                w.print("(");
                w.print(targetType.getQualifiedSourceName() + " instance, ");
                w.print(f.getType().getQualifiedSourceName() + " value");
                w.print(")");
                w.println("/*-{ ");
                w.indent();

                w.print("instance.@");
                w.print(targetType.getQualifiedSourceName());
                w.print("::");
                w.print(f.getName());
                w.println(" = value;");

                w.outdent();
                w.println("}-*/;");
            }

            if (f.getType() == JPrimitiveType.CHAR || isBoxedCharacter(f.getType())) {
                w.print("private static final native String");
                w.print(" jsonGet0_" + f.getName());
                w.print("(final JavaScriptObject instance)");
                w.println("/*-{ ");
                w.indent();
                w.print("return instance.");
                w.print(f.getName());
                w.println(";");
                w.outdent();
                w.println("}-*/;");

                w.print("private static final ");
                w.print(f.getType() == JPrimitiveType.CHAR ? "char" : "Character");
                w.print(" jsonGet_" + f.getName());
                w.print("(JavaScriptObject instance)");
                w.println(" {");
                w.indent();
                w.print("return ");
                w.print(JsonSerializer.class.getName());
                w.print(".toChar(");
                w.print("jsonGet0_" + f.getName());
                w.print("(instance)");
                w.println(");");
                w.outdent();
                w.println("}");
            } else {
                w.print("private static final native ");
                if (f.getType().isArray() != null) {
                    w.print("JavaScriptObject");
                } else if (isJsonPrimitive(f.getType())) {
                    w.print(f.getType().getQualifiedSourceName());
                } else if (isBoxedPrimitive(f.getType())) {
                    w.print(boxedTypeToPrimitiveTypeName(f.getType()));
                } else {
                    w.print("Object");
                }
                w.print(" jsonGet_" + f.getName());
                w.print("(JavaScriptObject instance)");
                w.println("/*-{ ");
                w.indent();

                w.print("return instance.");
                w.print(f.getName());
                w.println(";");

                w.outdent();
                w.println("}-*/;");
            }

            w.println();
        }
    }

    private void generateEnumFromJson(final SourceWriter w) {
        w.print("public ");
        w.print(targetType.getQualifiedSourceName());
        w.println(" fromJson(Object in) {");
        w.indent();
        w.print("return in != null");
        w.print(" ? " + targetType.getQualifiedSourceName() + ".valueOf((String)in)");
        w.print(" : null");
        w.println(";");
        w.outdent();
        w.println("}");
        w.println();
    }

    private void generatePrintJson(final SourceWriter w) {
        final JField[] fieldList = sortFields(targetType);
        w.print("protected int printJsonImpl(int fieldCount, StringBuilder sb, ");
        w.println("Object instance) {");
        w.indent();

        w.print("final ");
        w.print(targetType.getQualifiedSourceName());
        w.print(" src = (");
        w.print(targetType.getQualifiedSourceName());
        w.println(")instance;");

        if (needsSuperSerializer(targetType)) {
            w.print("fieldCount = super.printJsonImpl(fieldCount, sb, (");
            w.print(targetType.getSuperclass().getQualifiedSourceName());
            w.println(")src);");
        }

        final String docomma = "if (fieldCount++ > 0) sb.append(\",\");";
        for (final JField f : fieldList) {
            final String doget;
            if (f.isPrivate()) {
                doget = "objectGet_" + f.getName() + "(src)";
            } else {
                doget = "src." + f.getName();
            }

            final String doname = "sb.append(\"\\\"" + f.getName() + "\\\":\");";
            if (f.getType() == JPrimitiveType.CHAR || isBoxedCharacter(f.getType())) {
                w.println(docomma);
                w.println(doname);
                w.println("sb.append(\"\\\"\");");
                w.println("sb.append(" + JsonSerializer.class.getSimpleName() + ".escapeChar(" + doget + "));");
                w.println("sb.append(\"\\\"\");");
            } else if (isJsonString(f.getType())) {
                w.println("if (" + doget + " != null) {");
                w.indent();
                w.println(docomma);
                w.println(doname);
                w.println("sb.append(" + JsonSerializer.class.getSimpleName() + ".escapeString(" + doget + "));");
                w.outdent();
                w.println("}");
                w.println();
            } else if (f.getType().isPrimitive() != null) {
                w.println(docomma);
                w.println(doname);
                w.println("sb.append(" + doget + ");");
                w.println();
            } else if (isJsonPrimitive(f.getType()) || isBoxedPrimitive(f.getType())) {
                w.println("if (" + doget + " != null) {");
                w.println(docomma);
                w.println(doname);
                w.println("sb.append(" + doget + ");");
                w.println();
                w.println("}");
            } else {
                w.println("if (" + doget + " != null) {");
                w.indent();
                w.println(docomma);
                w.println(doname);
                if (needsTypeParameter(f.getType())) {
                    w.print("ser_" + f.getName());
                } else {
                    w.print(serializerFor(f.getType()) + ".INSTANCE");
                }
                w.println(".printJson(sb, " + doget + ");");
                w.outdent();
                w.println("}");
                w.println();
            }
        }

        w.println("return fieldCount;");
        w.outdent();
        w.println("}");
        w.println();
    }

    private void generateFromJson(final SourceWriter w) {
        w.print("public ");
        w.print(targetType.getQualifiedSourceName());
        w.println(" fromJson(Object in) {");
        w.indent();
        if (targetType.isAbstract()) {
            w.println("throw new UnsupportedOperationException();");
        } else {
            w.println("if (in == null) return null;");
            w.println("final JavaScriptObject jso = (JavaScriptObject)in;");
            w.print("final ");
            w.print(targetType.getQualifiedSourceName());
            w.print(" dst = new ");
            w.println(targetType.getQualifiedSourceName() + "();");
            w.println("fromJsonImpl(jso, dst);");
            w.println("return dst;");
        }
        w.outdent();
        w.println("}");
        w.println();

        w.print("protected void fromJsonImpl(JavaScriptObject jso,");
        w.print(targetType.getQualifiedSourceName());
        w.println(" dst) {");
        w.indent();

        if (needsSuperSerializer(targetType)) {
            w.print("super.fromJsonImpl(jso, (");
            w.print(targetType.getSuperclass().getQualifiedSourceName());
            w.println(")dst);");
        }

        for (final JField f : sortFields(targetType)) {
            final String doget = "jsonGet_" + f.getName() + "(jso)";
            final String doset0, doset1;

            if (f.isPrivate()) {
                doset0 = "objectSet_" + f.getName() + "(dst, ";
                doset1 = ")";
            } else {
                doset0 = "dst." + f.getName() + " = ";
                doset1 = "";
            }

            JType type = f.getType();
            if (type.isArray() != null) {
                final JType ct = type.isArray().getComponentType();
                w.println("if (" + doget + " != null) {");
                w.indent();

                w.print("final ");
                w.print(ct.getQualifiedSourceName());
                w.print("[] tmp = new ");
                w.print(ct.getQualifiedSourceName());
                w.print("[");
                w.print(ObjectArraySerializer.class.getName());
                w.print(".size(" + doget + ")");
                w.println("];");

                w.println("ser_" + f.getName() + ".fromJson(" + doget + ", tmp);");

                w.print(doset0);
                w.print("tmp");
                w.print(doset1);
                w.println(";");

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

            } else if (isJsonPrimitive(type)) {
                w.print(doset0);
                w.print(doget);
                w.print(doset1);
                w.println(";");

            } else if (isBoxedPrimitive(type)) {
                w.print(doset0);
                w.print("( " + doget + " != null) ? ");
                //w.print("new " + type.getQualifiedSourceName() + "(");
                w.print(doget);
                //w.print(")");
                w.print(":null");
                w.print(doset1);
                w.println(";");

            } else {
                w.print(doset0);
                if (needsTypeParameter(type)) {
                    w.print("ser_" + f.getName());
                } else {
                    String serializerFor = serializerFor(type);
                    w.print(serializerFor + ".INSTANCE");
                }
                w.print(".fromJson(" + doget + ")");
                w.print(doset1);
                w.println(";");
            }
        }

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

    static boolean isJsonPrimitive(final JType t) {
        return t.isPrimitive() != null || isJsonString(t);
    }

    static boolean isBoxedPrimitive(final JType t) {
        final String qsn = t.getQualifiedSourceName();
        return qsn.equals(Boolean.class.getCanonicalName()) || qsn.equals(Byte.class.getCanonicalName())
                || isBoxedCharacter(t) || qsn.equals(Double.class.getCanonicalName())
                || qsn.equals(Float.class.getCanonicalName()) || qsn.equals(Integer.class.getCanonicalName())
                || qsn.equals(Short.class.getCanonicalName());
    }

    static boolean isBoxedCharacter(JType t) {
        return t.getQualifiedSourceName().equals(Character.class.getCanonicalName());
    }

    private String boxedTypeToPrimitiveTypeName(JType t) {
        final String qsn = t.getQualifiedSourceName();
        if (qsn.equals(Boolean.class.getCanonicalName()))
            return "Boolean";
        if (qsn.equals(Byte.class.getCanonicalName()))
            return "Byte";
        if (qsn.equals(Character.class.getCanonicalName()))
            return "java.lang.String";
        if (qsn.equals(Double.class.getCanonicalName()))
            return "Double";
        if (qsn.equals(Float.class.getCanonicalName()))
            return "Float";
        if (qsn.equals(Integer.class.getCanonicalName()))
            return "Integer";
        if (qsn.equals(Short.class.getCanonicalName()))
            return "Short";
        throw new IllegalArgumentException(t + " is not a boxed type");
    }

    static boolean isJsonString(final JType t) {
        return t.getQualifiedSourceName().equals(String.class.getCanonicalName());
    }

    private SourceWriter getSourceWriter(final TreeLogger logger, final GeneratorContext ctx) {
        final JPackage targetPkg = targetType.getPackage();
        final String pkgName = targetPkg == null ? "" : targetPkg.getName();
        final PrintWriter pw;
        final ClassSourceFileComposerFactory cf;

        pw = ctx.tryCreate(logger, pkgName, getSerializerSimpleName());
        if (pw == null) {
            return null;
        }

        cf = new ClassSourceFileComposerFactory(pkgName, getSerializerSimpleName());
        cf.addImport(JavaScriptObject.class.getCanonicalName());
        cf.addImport(JsonSerializer.class.getCanonicalName());
        if (targetType.isEnum() != null) {
            cf.addImport(EnumSerializer.class.getCanonicalName());
            cf.setSuperclass(
                    EnumSerializer.class.getSimpleName() + "<" + targetType.getQualifiedSourceName() + ">");
        } else if (needsSuperSerializer(targetType)) {
            cf.setSuperclass(getSerializerQualifiedName(targetType.getSuperclass()));
        } else {
            cf.addImport(ObjectSerializer.class.getCanonicalName());
            cf.setSuperclass(
                    ObjectSerializer.class.getSimpleName() + "<" + targetType.getQualifiedSourceName() + ">");
        }
        return cf.createSourceWriter(ctx, pw);
    }

    private static boolean needsSuperSerializer(JClassType type) {
        type = type.getSuperclass();
        while (!Object.class.getName().equals(type.getQualifiedSourceName())) {
            if (sortFields(type).length > 0) {
                return true;
            }
            type = type.getSuperclass();
        }
        return false;
    }

    private String getSerializerQualifiedName(final JClassType targetType) {
        final String[] name;
        name = ProxyCreator.synthesizeTopLevelClassName(targetType, SER_SUFFIX);
        return name[0].length() == 0 ? name[1] : name[0] + "." + name[1];
    }

    private String getSerializerSimpleName() {
        return ProxyCreator.synthesizeTopLevelClassName(targetType, SER_SUFFIX)[1];
    }

    static boolean needsTypeParameter(final JType ft) {
        return ft.isArray() != null || (ft.isParameterized() != null
                && parameterizedSerializers.containsKey(ft.getQualifiedSourceName()));
    }

    private static JField[] sortFields(final JClassType targetType) {
        final ArrayList<JField> r = new ArrayList<JField>();
        for (final JField f : targetType.getFields()) {
            if (!f.isStatic() && !f.isTransient() && !f.isFinal()) {
                r.add(f);
            }
        }
        Collections.sort(r, FIELD_COMP);
        return r.toArray(new JField[r.size()]);
    }
}