io.reinert.requestor.rebind.JsonAutoBeanGenerator.java Source code

Java tutorial

Introduction

Here is the source code for io.reinert.requestor.rebind.JsonAutoBeanGenerator.java

Source

/*
 * Copyright 2014 Danilo Reinert
 *
 * 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 io.reinert.requestor.rebind;

import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.google.gwt.core.client.GWT;
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.JPackage;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanCodex;
import com.google.web.bindery.autobean.shared.AutoBeanFactory;
import com.google.web.bindery.autobean.shared.AutoBeanUtils;

import io.reinert.requestor.Json;
import io.reinert.requestor.serialization.DeserializationContext;
import io.reinert.requestor.serialization.Deserializer;
import io.reinert.requestor.serialization.HasImpl;
import io.reinert.requestor.serialization.Serdes;
import io.reinert.requestor.serialization.SerializationContext;
import io.reinert.requestor.serialization.Serializer;
import io.reinert.requestor.serialization.UnableToDeserializeException;
import io.reinert.requestor.serialization.UnableToSerializeException;
import io.reinert.requestor.serialization.json.JsonObjectSerdes;
import io.reinert.requestor.serialization.json.JsonRecordReader;
import io.reinert.requestor.serialization.json.JsonRecordWriter;

/**
 * Generator for {@link io.reinert.requestor.Json} annotated types powered by GWT AutoBean Framework.
 *
 * @author Danilo Reinert
 */
public class JsonAutoBeanGenerator extends Generator {

    private static String factoryFieldName = "myFactory";
    private static String factoryTypeName = "MyFactory";

    @Override
    public String generate(TreeLogger logger, GeneratorContext ctx, String typeName)
            throws UnableToCompleteException {
        TypeOracle typeOracle = ctx.getTypeOracle();
        assert typeOracle != null;

        JClassType intfType = typeOracle.findType(typeName);
        if (intfType == null) {
            logger.log(TreeLogger.ERROR, "Unable to find metadata for type '" + typeName + "'", null);
            throw new UnableToCompleteException();
        }

        if (intfType.isInterface() == null) {
            logger.log(TreeLogger.ERROR, intfType.getQualifiedSourceName() + " is not an interface", null);
            throw new UnableToCompleteException();
        }

        // TODO: check if type was already generated and reuse it
        TreeLogger typeLogger = logger.branch(TreeLogger.ALL, "Generating Json SerDes powered by AutoBeans...",
                null);
        final SourceWriter sourceWriter = getSourceWriter(typeLogger, ctx, intfType);

        if (sourceWriter != null) {
            sourceWriter.println();

            ArrayDeque<JClassType> annotatedTypes = new ArrayDeque<JClassType>();
            ArrayList<Json> jsonAnnotations = new ArrayList<Json>();

            final ArrayDeque<String> allTypesAndWrappers = new ArrayDeque<String>();

            for (JClassType type : typeOracle.getTypes()) {
                Json annotation = type.getAnnotation(Json.class);
                if (annotation != null && type.isInterface() != null) {
                    annotatedTypes.add(type);
                    jsonAnnotations.add(annotation);

                    final String listWrapperTypeName = generateListWrapperInterface(sourceWriter, type);
                    sourceWriter.println();

                    final String setWrapperTypeName = generateSetWrapperInterface(sourceWriter, type);
                    sourceWriter.println();

                    // Add current type name for single de/serialization
                    allTypesAndWrappers.add(type.getQualifiedSourceName());
                    allTypesAndWrappers.add(listWrapperTypeName);
                    allTypesAndWrappers.add(setWrapperTypeName);
                }
            }

            final ArrayDeque<String> serdesFields = new ArrayDeque<String>();
            final ArrayDeque<String> providerFields = new ArrayDeque<String>();

            if (!allTypesAndWrappers.isEmpty()) {
                generateFactoryInterface(sourceWriter, allTypesAndWrappers);
                sourceWriter.println();

                generateFactoryField(sourceWriter);
                sourceWriter.println();

                int i = 0;
                for (JClassType annotatedType : annotatedTypes) {
                    final String providerFieldName = generateProviderField(sourceWriter, annotatedType);
                    providerFields.add(providerFieldName);

                    final String serdesFieldName = generateSerdesClassAndField(logger, typeOracle, sourceWriter,
                            annotatedType, jsonAnnotations.get(i++));
                    serdesFields.add(serdesFieldName);
                }
            }

            generateFields(sourceWriter);
            generateConstructor(sourceWriter, serdesFields, providerFields);
            generateMethods(sourceWriter);

            sourceWriter.commit(typeLogger);
        }

        return typeName + "Impl";
    }

    private String asStringCsv(String[] array) {
        StringBuilder result = new StringBuilder();
        for (String s : array) {
            result.append('"').append(s).append('"').append(", ");
        }
        result.replace(result.length() - 2, result.length(), "");
        return result.toString();
    }

    private String firstCharToLowerCase(String s) {
        return Character.toLowerCase(s.charAt(0)) + s.substring(1);
    }

    private String firstCharToUpperCase(String s) {
        return Character.toUpperCase(s.charAt(0)) + s.substring(1);
    }

    private void generateConstructor(SourceWriter srcWriter, Iterable<String> serdes, Iterable<String> providers) {
        srcWriter.println("public GeneratedJsonSerdesImpl() {");
        for (String s : serdes) {
            srcWriter.println("    serdesList.add(%s);", s);
        }
        for (String s : providers) {
            srcWriter.println("    providersList.add(%s);", s);
        }
        srcWriter.println("}");
        srcWriter.println();
    }

    private void generateFactoryField(SourceWriter w) {
        w.println("private static %s %s = GWT.create(%s.class);", factoryTypeName, factoryFieldName,
                factoryTypeName);
    }

    private void generateFactoryInterface(SourceWriter w, Iterable<String> typeNames) {
        w.println("interface %s extends AutoBeanFactory {", factoryTypeName);
        for (String typeName : typeNames) {
            w.println("    AutoBean<%s> %s();", typeName, replaceDotByUpperCase(firstCharToLowerCase(typeName)));
        }
        w.println("}");
    }

    private void generateFields(SourceWriter srcWriter) {
        // Initialize a field with binary name of the remote service interface
        srcWriter.println("private final ArrayList<Serdes<?>> serdesList = new ArrayList<Serdes<?>>();");
        srcWriter.println("private final ArrayList<GeneratedProvider<?>> providersList = "
                + "new ArrayList<GeneratedProvider<?>>();");
        srcWriter.println();
    }

    private String generateListWrapperInterface(SourceWriter w, JClassType type) {
        String wrapperTypeName = getListWrapperTypeName(type);

        w.println("interface %s {", wrapperTypeName);
        w.println("    List<%s> getResult();", type.getQualifiedSourceName());
        w.println("    void setResult(List<%s> result);", type.getQualifiedSourceName());
        w.println("}");

        return wrapperTypeName;
    }

    private void generateMethods(SourceWriter srcWriter) {
        srcWriter.println("@Override");
        srcWriter.println("public List<Serdes<?>> getGeneratedSerdes() {");
        srcWriter.println("    return serdesList;");
        srcWriter.println("}");
        srcWriter.println();
        srcWriter.println("@Override");
        srcWriter.println("public List<GeneratedProvider<?>> getGeneratedProviders() {");
        srcWriter.println("    return providersList;");
        srcWriter.println("}");
        srcWriter.println();
    }

    private String generateProviderField(SourceWriter w, JClassType type) {
        final String fieldName = getFieldName(type);
        final String autoBeanFactoryMethodName = factoryFieldName + "." + fieldName;
        final String providerFieldName = fieldName + "Provider";

        w.println("private final GeneratedProvider %s = new GeneratedProvider<%s>() {", providerFieldName,
                type.getQualifiedSourceName());
        w.println("    @Override");
        w.println("    public Class<%s> getType() {", type.getQualifiedSourceName());
        w.println("        return %s.class;", type.getQualifiedSourceName());
        w.println("    }");
        w.println();
        w.println("    @Override");
        w.println("    public %s get() {", type.getQualifiedSourceName());
        w.println("        return %s().as();", autoBeanFactoryMethodName);
        w.println("    }");
        w.println("};");
        w.println();

        return providerFieldName;
    }

    /**
     * Create the serdes and return the field name.
     */
    private String generateSerdesClassAndField(TreeLogger logger, TypeOracle oracle, SourceWriter w,
            JClassType type, Json annotation) {
        final String qualifiedSourceName = type.getQualifiedSourceName();
        final String fieldName = getFieldName(type);
        final String listWrapperTypeName = getListWrapperTypeName(type);
        final String setWrapperTypeName = getSetWrapperTypeName(type);
        final String listWrapperFactoryMethodName = factoryFieldName + "."
                + firstCharToLowerCase(listWrapperTypeName);
        final String setWrapperFactoryMethodName = factoryFieldName + "."
                + firstCharToLowerCase(setWrapperTypeName);

        final String serdesFieldName = fieldName + "Serdes";
        final String serdesTypeName = getTypeName(type) + "Serdes";

        // serializer field as anonymous class
        w.println("private static class %s extends JsonObjectSerdes<%s> implements HasImpl {", serdesTypeName,
                qualifiedSourceName);

        // static field for impl array
        final String autoBeanInstanceClass = factoryFieldName + "." + fieldName + "().getClass()";
        w.println("    private final Class[] IMPL = new Class[]{ %s };", autoBeanInstanceClass);

        // static field to content-types
        w.println("    private final String[] PATTERNS = new String[]{ %s };", asStringCsv(annotation.value()));
        w.println();

        // constructor
        w.println("    public %s() {", serdesTypeName);
        w.println("        super(%s.class);", qualifiedSourceName);
        w.println("    }");
        w.println();

        // mediaType
        w.println("    @Override");
        w.println("    public Class[] implTypes() {");
        w.println("        return IMPL;");
        w.println("    }");
        w.println();

        // mediaType
        w.println("    @Override");
        w.println("    public String[] mediaType() {");
        w.println("        return PATTERNS;");
        w.println("    }");
        w.println();

        // readJson - used when any of deserialize alternatives succeeded (see JsonObjectSerdes)
        // TODO: improve this by not requiring parsing the json to an js array and latter stringifying it (see below)
        // Here would be no-op
        w.println("    @Override");
        w.println("    public %s readJson(JsonRecordReader r, DeserializationContext ctx) {", qualifiedSourceName);
        w.println("        return AutoBeanCodex.decode(%s, %s.class, JsonObjectSerdes.stringify(r)).as();",
                factoryFieldName, qualifiedSourceName);
        w.println("    }");
        w.println();

        // writeJson - not used
        w.println("    @Override");
        w.println("    public void writeJson(%s o, JsonRecordWriter w, SerializationContext ctx) {",
                qualifiedSourceName);
        w.println("        return;");
        w.println("    }");
        w.println();

        // deserialize - deserialize single object using ObjectMapper
        w.println("    @Override");
        w.println("    public %s deserialize(String s, DeserializationContext ctx) {", qualifiedSourceName);
        w.println("        try {");
        w.println("            return AutoBeanCodex.decode(%s, %s.class, s).as();", factoryFieldName,
                qualifiedSourceName);
        w.println("        } catch (java.lang.Exception e) {");
        w.println("            throw new UnableToDeserializeException(\"The auto-generated AutoBean deserializer"
                + " failed to deserialize the response body to \" + ctx.getRequestedType().getName() + \".\", e);");
        w.println("        }");
        w.println("    }");
        w.println();

        // deserialize
        w.println("    @Override");
        w.println("    public <C extends Collection<%s>> C deserialize(Class<C> c, "
                + "String s, DeserializationContext ctx) {", qualifiedSourceName);
        w.println("        try {");
        w.println("            if (c == List.class || c == Collection.class)");
        w.println(
                "                return (C) AutoBeanCodex.decode(%s, %s.class, "
                        + "\"{\\\"result\\\":\" + s + \"}\").as().getResult();",
                factoryFieldName, listWrapperTypeName);
        w.println("            else if (c == Set.class)");
        w.println(
                "                return (C) AutoBeanCodex.decode(%s, %s.class, "
                        + "\"{\\\"result\\\":\" + s + \"}\").as().getResult();",
                factoryFieldName, setWrapperTypeName);
        w.println("            else");
        // TODO: improve this by not requiring parsing the json to an js array and latter stringifying it
        // An alternative would be manually traverse the json array and passing each json object to serialize method
        w.println("                return super.deserialize(c, s, ctx);");
        w.println("        } catch (java.lang.Exception e) {");
        w.println("            throw new UnableToDeserializeException(\"The auto-generated AutoBean deserializer"
                + " failed to deserialize the response body"
                + " to \" + c.getName() + \"<\" + ctx.getRequestedType().getName() + \">.\", e);");
        w.println("        }");
        w.println("    }");

        // serialize
        w.println("    @Override");
        w.println("    public String serialize(%s o, SerializationContext ctx) {", qualifiedSourceName);
        w.println("        try {");
        w.println("            return AutoBeanCodex.encode(AutoBeanUtils.getAutoBean(o)).getPayload();");
        w.println("        } catch (java.lang.Exception e) {");
        w.println("            throw new UnableToSerializeException(\"The auto-generated AutoBean serializer"
                + " failed to serialize the instance of \" + o.getClass().getName() + \" to JSON.\", e);");
        w.println("        }");
        w.println("    }");
        w.println();

        // serialize
        w.println("    @Override");
        w.println("    public String serialize(Collection<%s> c, SerializationContext ctx) {", qualifiedSourceName);
        w.println("        try {");
        w.println("            if (c instanceof List) {");
        w.println("                final AutoBean<%s> autoBean = %s();", listWrapperTypeName,
                listWrapperFactoryMethodName);
        w.println("                autoBean.as().setResult((List) c);");
        w.println("                final String json = AutoBeanCodex.encode(autoBean).getPayload();");
        w.println("                return json.substring(10, json.length() -1);");
        w.println("            }");
        w.println("            if (c instanceof Set) {");
        w.println("                final AutoBean<%s> autoBean = %s();", setWrapperTypeName,
                setWrapperFactoryMethodName);
        w.println("                autoBean.as().setResult((Set) c);");
        w.println("                final String json = AutoBeanCodex.encode(autoBean).getPayload();");
        w.println("                return json.substring(10, json.length() -1);");
        w.println("            }");
        w.println("            return super.serialize(c, ctx);");
        w.println("        } catch (java.lang.Exception e) {");
        w.println("            throw new UnableToSerializeException(\"The auto-generated AutoBean serializer"
                + " failed to serialize the instance of \" + c.getClass().getName() + \" to JSON.\", e);");
        w.println("        }");
        w.println("    }");

        // end anonymous class
        w.println("};");
        w.println();

        // serializer field as anonymous class
        w.println("private final %s %s = new %s();", serdesTypeName, serdesFieldName, serdesTypeName);
        w.println();

        return serdesFieldName;
    }

    private String generateSetWrapperInterface(SourceWriter w, JClassType type) {
        String wrapperTypeName = getSetWrapperTypeName(type);

        w.println("interface %s {", wrapperTypeName);
        w.println("    Set<%s> getResult();", type.getQualifiedSourceName());
        w.println("    void setResult(Set<%s> result);", type.getQualifiedSourceName());
        w.println("}");

        return wrapperTypeName;
    }

    private String getFieldName(JType type) {
        return replaceDotByUpperCase(type.getQualifiedSourceName());
    }

    private String getListWrapperTypeName(JType type) {
        return getTypeName(type) + "ListWrapper";
    }

    private String getSetWrapperTypeName(JType type) {
        return getTypeName(type) + "SetWrapper";
    }

    private SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext ctx, JClassType intfType) {
        JPackage serviceIntfPkg = intfType.getPackage();
        String packageName = serviceIntfPkg == null ? "" : serviceIntfPkg.getName();
        PrintWriter printWriter = ctx.tryCreate(logger, packageName, getTypeSimpleName());
        if (printWriter == null) {
            return null;
        }

        ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName,
                getTypeSimpleName());

        String[] imports = new String[] {
                // java.util
                ArrayList.class.getCanonicalName(), Collection.class.getCanonicalName(),
                List.class.getCanonicalName(), Iterator.class.getCanonicalName(), Set.class.getCanonicalName(),
                // com.google.gwt.core.client
                GWT.class.getCanonicalName(),
                // com.google.web.bindery.autobean.shared
                AutoBean.class.getCanonicalName(), AutoBeanCodex.class.getCanonicalName(),
                AutoBeanFactory.class.getCanonicalName(), AutoBeanUtils.class.getCanonicalName(),
                // io.reinert.requestor.serialization
                DeserializationContext.class.getCanonicalName(), Deserializer.class.getCanonicalName(),
                HasImpl.class.getCanonicalName(), Serdes.class.getCanonicalName(),
                Serializer.class.getCanonicalName(), SerializationContext.class.getCanonicalName(),
                UnableToDeserializeException.class.getName(), UnableToSerializeException.class.getName(),
                // io.reinert.requestor.serialization.json
                JsonObjectSerdes.class.getCanonicalName(), JsonRecordReader.class.getCanonicalName(),
                JsonRecordWriter.class.getCanonicalName(), };

        for (String imp : imports) {
            composerFactory.addImport(imp);
        }

        composerFactory.addImplementedInterface(intfType.getErasedType().getQualifiedSourceName());

        return composerFactory.createSourceWriter(ctx, printWriter);
    }

    private String getTypeName(JType type) {
        final String fieldName = getFieldName(type);
        return firstCharToUpperCase(fieldName);
    }

    private String getTypeSimpleName() {
        return "GeneratedJsonSerdesImpl";
    }

    private String replaceDotByUpperCase(String s) {
        StringBuilder result = new StringBuilder();

        for (int i = 0; i < s.length(); i++) {
            final char c = s.charAt(i);
            if (c == '.') {
                result.append(Character.toUpperCase(s.charAt(++i)));
            } else {
                result.append(c);
            }
        }

        return result.toString();
    }
}