Package org.fusesource.restygwt.rebind

Source Code of org.fusesource.restygwt.rebind.JsonEncoderDecoderClassCreator$Subtype

/**
* Copyright (C) 2009-2012 the original author or authors.
* See the notice.md file distributed with this work for additional
* information regarding copyright ownership.
*
* 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.fusesource.restygwt.rebind;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.fusesource.restygwt.client.Json;
import org.fusesource.restygwt.client.Json.Style;
import static org.fusesource.restygwt.rebind.util.AnnotationUtils.*;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonValue;
import com.google.gwt.core.ext.BadPropertyValueException;
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.JConstructor;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONNull;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;

/**
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*
*         Updates: added getter & setter support, enhanced generics support
* @author <a href="http://www.acuedo.com">Dave Finch</a>
*
*         added polymorphic support
* @author <a href="http://charliemason.info">Charlie Mason</a>
*
*/

public class JsonEncoderDecoderClassCreator extends BaseSourceCreator {
    private static final String JSON_ENCODER_SUFFIX = "_Generated_JsonEncoderDecoder_";

    private String JSON_ENCODER_DECODER_CLASS = JsonEncoderDecoderInstanceLocator.JSON_ENCODER_DECODER_CLASS;
    protected static final String JSON_VALUE_CLASS = JSONValue.class.getName();
    private static final String JSON_OBJECT_CLASS = JSONObject.class.getName();
    private static final String JSON_ARRAY_CLASS = JSONArray.class.getName();
    private static final String JSON_NULL_CLASS = JSONNull.class.getName();
    protected static final String JSON_STRING_CLASS = JSONString.class.getName();

    protected JsonEncoderDecoderInstanceLocator locator;

    public JsonEncoderDecoderClassCreator(TreeLogger logger, GeneratorContext context, JClassType source) {
        super(logger, context, source, JSON_ENCODER_SUFFIX);
    }

    @Override
    public void generate() throws UnableToCompleteException {
        final JsonTypeInfo typeInfo = getAnnotation(source, JsonTypeInfo.class);
        final boolean isLeaf = isLeaf(source);

        final List<Subtype> possibleTypes = getPossibleTypes(typeInfo, isLeaf);

        final JClassType sourceClazz = source.isClass() == null ? source.isInterface() : source.isClass();
        if (sourceClazz == null) {
            getLogger().log(ERROR, "Type is not a class");
            throw new UnableToCompleteException();
        }

    if (sourceClazz.isEnum() == null && sourceClazz.isAbstract()) {
            if (typeInfo == null) {
                getLogger().log(ERROR, "Abstract classes must be annotated with JsonTypeInfo");
                throw new UnableToCompleteException();
            }
        }
        Json jsonAnnotation = getAnnotation(source, Json.class);
        final Style classStyle = jsonAnnotation != null ? jsonAnnotation.style() : Style.DEFAULT;
        final String railsWrapperName = jsonAnnotation != null && jsonAnnotation.name().length() > 0 ? jsonAnnotation.name() : sourceClazz.getName().toLowerCase();
        locator = new JsonEncoderDecoderInstanceLocator(context, getLogger());

        generateSingleton(shortName);

        generateEncodeMethod(source, classStyle, typeInfo, railsWrapperName, possibleTypes, isLeaf, locator);

        generateDecodeMethod(source, classStyle, typeInfo, railsWrapperName, possibleTypes, isLeaf, locator);
    }

    @Override
    protected ClassSourceFileComposerFactory createComposerFactory() {
  ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, shortName);
  composerFactory.setSuperclass(JSON_ENCODER_DECODER_CLASS + "<" + source.getParameterizedQualifiedSourceName() + ">");
  return composerFactory;
    }

    private List<Subtype> getPossibleTypes(final JsonTypeInfo typeInfo, final boolean isLeaf) throws UnableToCompleteException
    {
        if (typeInfo == null)
            return Lists.newArrayList(new Subtype(null, source));
        Collection<Type> subTypes = findJsonSubTypes(source);
        if(subTypes.isEmpty()) {
            JsonSubTypes foundAnnotation = getAnnotation(source, JsonSubTypes.class);
            if(foundAnnotation != null) {
                Type[] value = foundAnnotation.value();
                subTypes = Arrays.asList(value);
            }
        }
        PossibleTypesVisitor v = new PossibleTypesVisitor(context, source, isLeaf, getLogger(), subTypes);
        return v.visit(typeInfo.use());
    }

    private Collection<Type> findJsonSubTypes(JClassType clazz) {
        if (clazz == null)
            return Collections.emptyList();
        else if (clazz.isAnnotationPresent(JsonSubTypes.class)) {
            JsonSubTypes annotation = getAnnotation(clazz, JsonSubTypes.class);
            Set<Type> result = new HashSet<JsonSubTypes.Type>();
            Type[] value = annotation.value();
            for (Type type : value) {
                result.add(type);
                Class<?> subclazz = type.value();
                String newSubClassName = subclazz.getName().replaceAll("\\$", ".");
                JClassType subJClazz = context.getTypeOracle().findType(newSubClassName);
                if(!isSame(clazz, subclazz)) {
                    result.addAll(findJsonSubTypes(subJClazz));
                }
            }
            return result;
        } else
            return Collections.emptyList();
        }

    private boolean isSame(JClassType clazz, Class<?> subclazz) {
        return (clazz.getPackage().getName()+"."+clazz.getName()).equals(subclazz.getName());
    }

    protected void generateSingleton(String shortName)
    {
        p();
        p("public static final " + shortName + " INSTANCE = new " + shortName + "();");
        p();
    }

    private void generateEncodeMethod(JClassType classType,
            final Style classStyle,
            JsonTypeInfo typeInfo,
            String railsWrapperName,
            List<Subtype> possibleTypes,
            boolean isLeaf,
            final JsonEncoderDecoderInstanceLocator locator) throws UnableToCompleteException
    {
        if (null != classType.isEnum()) {
            generateEnumEncodeMethod(classType, JSON_VALUE_CLASS);
            return;
        }

        p("public " + JSON_VALUE_CLASS + " encode(" + source.getParameterizedQualifiedSourceName() + " value) {").i(1);
        {
            p("if( value==null ) {").i(1);
            {
                p("return null;");
            }
            i(-1).p("}");

            boolean returnWrapper = false; // if set, return rrc

            p(JSON_OBJECT_CLASS + " rc = new " + JSON_OBJECT_CLASS + "();");
            if (classStyle == Style.RAILS) {
                returnWrapper = true;
                p(JSON_OBJECT_CLASS + " rrc = new " + JSON_OBJECT_CLASS + "();");
                p("rrc.put(\"" + railsWrapperName + "\" , rc);");
            }

            for (Subtype possibleType : possibleTypes) {

                if (!possibleType.clazz.isAssignableTo(classType)) {
                    getLogger().log(DEBUG, "Only assignable classes are allowed: " + possibleType.clazz.getParameterizedQualifiedSourceName() + " is not assignable to: " + classType.getParameterizedQualifiedSourceName());
                    continue;
                }

                if (!isLeaf) {
                    // Generate a decoder for each possible type
                    p("if(value.getClass().getName().equals(\"" + possibleType.clazz.getQualifiedBinaryName() + "\"))");
                    p("{");
                }

                if (possibleType.clazz.isEnum() != null) {
                    generateEnumEncodeMethodBody(possibleType, typeInfo);
                } else {

                    // Try to find a constuctor that is annotated as creator
                    final JConstructor creator = findCreator(possibleType.clazz);

                    List<JField> orderedFields = creator == null ? null : getOrderedFields(getFields(possibleType.clazz), creator);

                    if (typeInfo != null) {
                        switch (typeInfo.include()) {
                            case PROPERTY:
                                p("com.google.gwt.json.client.JSONValue className=org.fusesource.restygwt.client.AbstractJsonEncoderDecoder.STRING.encode(\"" + possibleType.tag + "\");");
                                p("if( className!=null ) { ").i(1);
                                p("rc.put(" + wrap(getTypeInfoPropertyValue(typeInfo)) + ", className);");
                                i(-1).p("}");
                                break;
                            case WRAPPER_OBJECT:
                                returnWrapper = true;
                                p(JSON_OBJECT_CLASS + " rrc = new " + JSON_OBJECT_CLASS + "();");
                                p("rrc.put(\"" + possibleType.tag + "\", rc);");
                                break;
                            case WRAPPER_ARRAY:
                                returnWrapper = true;
                                p(JSON_ARRAY_CLASS + " rrc = new " + JSON_ARRAY_CLASS + "();");
                                p("rrc.set(0, org.fusesource.restygwt.client.AbstractJsonEncoderDecoder.STRING.encode(\"" + possibleType.tag + "\"));");
                                p("rrc.set(1, rc);");
                        }
                    }

                    p(possibleType.clazz.getParameterizedQualifiedSourceName() + " parseValue = (" + possibleType.clazz.getParameterizedQualifiedSourceName() + ")value;");

                    for (final JField field : getFields(possibleType.clazz)) {

                        final String getterName = getGetterName(field);

                        boolean ignoreField = false;
                        if(getAnnotation(possibleType.clazz, JsonIgnoreProperties.class) != null) {
                            for(String s : getAnnotation(possibleType.clazz, JsonIgnoreProperties.class).value()) {
                                if(s.equals(field.getName())) {
                                    ignoreField = true;
                                    break;
                                }
                            }
                        }

                        // If can ignore some fields right off the back..
                        // if there is a creator encode only final fields with JsonProperty annotation
                        if (ignoreField || getterName == null && (field.isStatic() || (field.isFinal() && !(creator != null && orderedFields.contains(field))) || field.isTransient()
                                || field.isAnnotationPresent(JsonIgnore.class))) {
                            continue;
                        }

                        branch("Processing field: " + field.getName(), new Branch<Void>() {
                            @Override
                            public Void execute() throws UnableToCompleteException {
                                // TODO: try to get the field with a setter or
                                // JSNI
                                if (getterName != null || field.isDefaultAccess() || field.isProtected() || field.isPublic()) {

                                    Json jsonAnnotation = getAnnotation(field, Json.class);
                                    JsonProperty jsonPropertyAnnotation = getAnnotation(field, JsonProperty.class);

                                    String name = field.getName();
                                    String jsonName = name;

                                    if (jsonAnnotation != null && jsonAnnotation.name().length() > 0) {
                                        jsonName = jsonAnnotation.name();
                                    }
                                    if (jsonPropertyAnnotation != null && jsonPropertyAnnotation.value() != null && jsonPropertyAnnotation.value().length() > 0) {
                                        jsonName = jsonPropertyAnnotation.value();
                                    }

                                    String fieldExpr = "parseValue." + name;
                                    if (getterName != null) {
                                        fieldExpr = "parseValue." + getterName + "()";
                                    }

                                    Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
                                    String expression = locator.encodeExpression(field.getType(), fieldExpr, style);

                                    p("{").i(1);
                                    {
                                        if (null != field.getType().isEnum()) {
                                            p("if(" + fieldExpr + " == null) {").i(1);
                                            p("rc.put(" + wrap(jsonName) + ", " + JSON_NULL_CLASS + ".getInstance());");
                                            i(-1).p("} else {").i(1);
                                        }

                                        p(JSON_VALUE_CLASS + " v=" + expression + ";");
                                        p("if( v!=null ) {").i(1);
                                        {
                                            p("rc.put(" + wrap(jsonName) + ", v);");
                                        }
                                        i(-1).p("}");

                                        if (null != field.getType().isEnum()) {
                                            i(-1).p("}");
                                        }

                                    }
                                    i(-1).p("}");

                                } else {
                                    getLogger().log(DEBUG, "private field gets ignored: " + field.getEnclosingType().getQualifiedSourceName() + "." + field.getName());
                                }
                                return null;
                            }
                        });

                    }

                    if (returnWrapper) {
                        p("return rrc;");
                    } else {
                        p("return rc;");
                    }
                }
                if (!isLeaf) {
                    p("}");
                }
            }

            if (!isLeaf) {
                // Shouldn't get called
                p("return null;");
            }
        }
        i(-1).p("}");
        p();
    }

    private void generateEnumEncodeMethodBody(final Subtype possibleType, final JsonTypeInfo typeInfo) {
        p("if( value==null ) {").i(1);
        {
            p("return " + JSON_NULL_CLASS + ".getInstance();").i(-1);
        }
        p("}");
        p(JSON_OBJECT_CLASS + " rrc = new " + JSON_OBJECT_CLASS + "();");
        p(JSON_VALUE_CLASS + " className=org.fusesource.restygwt.client.AbstractJsonEncoderDecoder.STRING.encode(\""
                + possibleType.tag + "\");");
        p("rrc.put(" + wrap(getTypeInfoPropertyValue(typeInfo)) + ", className);");
        p("rrc.put(\"name\", new " + JSON_STRING_CLASS + "(value." + getValueMethod(possibleType.clazz) + "()));");
        p("return rrc;");
    }

    private void generateEnumEncodeMethod(JClassType classType, String jsonValueClass)
    {
        p();
        p("public " + jsonValueClass + " encode(" + classType.getParameterizedQualifiedSourceName() + " value) {").i(1);
        {
            p("if( value==null ) {").i(1);
            {
                p("return " + JSON_NULL_CLASS + ".getInstance();").i(-1);
            }
            p("}");
            p("return new " + JSON_STRING_CLASS + "(value." + getValueMethod(classType) + "());");
            i(-1).p("}");
        }
        p();
    }

    protected String getValueMethod(JClassType classType) {
        String method = "name";
        for(JMethod jm : classType.isEnum().getMethods() ) {
            if (jm.isAnnotationPresent(JsonValue.class)) {
                method = jm.getName();
                break;
            }
        }
        return method;
    }

    private void generateDecodeMethod(JClassType classType,
            final Style classStyle,
            JsonTypeInfo typeInfo,
            String railsWrapperName,
            List<Subtype> possibleTypes,
            boolean isLeaf,
            final JsonEncoderDecoderInstanceLocator locator) throws UnableToCompleteException
    {
        if (null != classType.isEnum()) {
            generateEnumDecodeMethod(classType, JSON_VALUE_CLASS);
            return;
        }

        p("public " + source.getName() + " decode(" + JSON_VALUE_CLASS + " value) {").i(1);
        {
            p("if( value == null || value.isNull()!=null ) {").i(1);
            {
                p("return null;").i(-1);
            }
            p("}");
            if (classStyle == Style.RAILS) {
                p(JSON_OBJECT_CLASS + " object = toObjectFromWrapper(value, \"" + railsWrapperName + "\");");
            } else if (typeInfo != null && typeInfo.include() == As.WRAPPER_ARRAY) {
                p(JSON_ARRAY_CLASS + " array = (" + JSON_ARRAY_CLASS + ")value;");
                if (!isLeaf)
                    p("String sourceName = org.fusesource.restygwt.client.AbstractJsonEncoderDecoder.STRING.decode(array.get(0));");
                p(JSON_OBJECT_CLASS + " object = toObject(array.get(1));");
            } else {
                p(JSON_OBJECT_CLASS + " object = toObject(value);");
            }

            if (!isLeaf && typeInfo != null && typeInfo.include() == As.PROPERTY) {
                p("String sourceName = org.fusesource.restygwt.client.AbstractJsonEncoderDecoder.STRING.decode(object.get(" + wrap(getTypeInfoPropertyValue(typeInfo)) + "));");
            }

            for (Subtype possibleType : possibleTypes) {

                if (!possibleType.clazz.isAssignableTo(classType)) {
                    getLogger().log(DEBUG, "Only assignable classes are allowed: " + possibleType.clazz.getParameterizedQualifiedSourceName() + " is not assignable to: " + classType.getParameterizedQualifiedSourceName());
                    continue;
                }

                if (typeInfo != null) {
                    if (typeInfo.include() == As.WRAPPER_OBJECT) {
                        if (!isLeaf) {
                            p("if(object.containsKey(\"" + possibleType.tag + "\"))");
                            p("{");
                        }
                        p("object = toObjectFromWrapper(value, \"" + possibleType.tag + "\");");
                    } else if (!isLeaf) {
                        p("if(sourceName.equals(\"" + possibleType.tag + "\"))");
                        p("{");
                    }
                }

                if (possibleType.clazz.isEnum() != null) {
                    generateEnumDecodeMethodBody(possibleType.clazz);
                } else {
                    // Try to find a constuctor that is annotated as creator
                    final JConstructor creator = findCreator(possibleType.clazz);

                    List<JField> orderedFields = null;
                    if (creator != null) {
                        p("// We found a creator so we use the annotated constructor");
                        p("" + possibleType.clazz.getParameterizedQualifiedSourceName() + " rc = new " + possibleType.clazz.getParameterizedQualifiedSourceName() + "(");
                        i(1).p("// The arguments are placed in the order they appear within the annotated constructor").i(-1);
                        orderedFields = getOrderedFields(getFields(possibleType.clazz), creator);
                        final JField lastField = orderedFields.get(orderedFields.size() - 1);
                        for (final JField field : orderedFields) {
                            branch("Processing field: " + field.getName(), new Branch<Void>() {
                                @Override
                                public Void execute() throws UnableToCompleteException {
                                    Json jsonAnnotation = getAnnotation(field, Json.class);
                                    Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
                                    String jsonName = field.getName();
                                    if (jsonAnnotation != null && jsonAnnotation.name().length() > 0) {
                                        jsonName = jsonAnnotation.name();
                                    }
                                    String objectGetter = "object.get(" + wrap(jsonName) + ")";
                                    String expression = locator.decodeExpression(field.getType(), objectGetter, style);

                                    String defaultValue = getDefaultValue(field);
                                    i(1).p("" + (objectGetter + " == null || " + objectGetter + " instanceof " + JSON_NULL_CLASS + " ? " + defaultValue + " : " + expression + ((field != lastField) ? ", " : ""))).i(-1);

                                    return null;
                                }
                            });
                        }
                        p(");");
                    }

                    if (orderedFields == null){
                        p("" + possibleType.clazz.getParameterizedQualifiedSourceName() + " rc = new " + possibleType.clazz.getParameterizedQualifiedSourceName() + "();");
                    }

                    for (final JField field : getFields(possibleType.clazz)) {

                        boolean ignoreField = false;
                        if(getAnnotation(possibleType.clazz, JsonIgnoreProperties.class) != null) {
                            for(String s : getAnnotation(possibleType.clazz, JsonIgnoreProperties.class).value()) {
                                if(s.equals(field.getName())) {
                                    ignoreField = true;
                                    break;
                                }
                            }
                        }
                        if(ignoreField) {
                            continue;
                        }

                        if (orderedFields != null && orderedFields.contains(field)){
                            continue;
                        }

                        final String setterName = getSetterName(field);

                        // If can ignore some fields right off the back..
                        if (setterName == null && (field.isStatic() || field.isFinal() || field.isTransient()) ||
                                field.isAnnotationPresent(JsonIgnore.class)) {
                            continue;
                        }

                        branch("Processing field: " + field.getName(), new Branch<Void>() {
                            @Override
                            public Void execute() throws UnableToCompleteException {

                                // TODO: try to set the field with a setter
                                // or JSNI
                                if (setterName != null || field.isDefaultAccess() || field.isProtected() || field.isPublic()) {

                                    Json jsonAnnotation = getAnnotation(field, Json.class);
                                    Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
                                    JsonProperty jsonPropertyAnnotation = getAnnotation(field, JsonProperty.class);

                                    String name = field.getName();
                                    String jsonName = name;

                                    if (jsonAnnotation != null && jsonAnnotation.name().length() > 0) {
                                        jsonName = jsonAnnotation.name();
                                    }
                                    if (jsonPropertyAnnotation != null && jsonPropertyAnnotation.value() != null && jsonPropertyAnnotation.value().length() > 0) {
                                        jsonName = jsonPropertyAnnotation.value();
                                    }

                                    String objectGetter = "object.get(" + wrap(jsonName) + ")";
                                    String expression = locator.decodeExpression(field.getType(), objectGetter, style);

                                    boolean needNullHandling = !locator.hasCustomEncoderDecoder(field.getType());

                                    String cast = field.getType().isPrimitive() == JPrimitiveType.SHORT ? "(short) " : "";

                                    if (needNullHandling) {
                                        p("if(" + objectGetter + " != null) {").i(1);
                                        p("if(" + objectGetter + " instanceof " + JSON_NULL_CLASS + ") {").i(1);
                                        String defaultValue = getDefaultValue(field);

                                        assignFieldValue(name, defaultValue, cast, setterName);
                                        i(-1);
                                        p("} else {").i(1);
                                    }

                                    assignFieldValue(name, expression, cast, setterName);

                                    if (needNullHandling) {
                                        i(-1);
                                        p("}").i(-1);
                                        p("}");
                                    }

                                } else {
                                    getLogger().log(DEBUG, "private field gets ignored: " + field.getEnclosingType().getQualifiedSourceName() + "." + field.getName());
                                }
                                return null;
                            }
                        });
                    }

                    p("return rc;");
                }
                if (typeInfo != null && !isLeaf) {
                    p("}");
                }
            }

            if (typeInfo != null && !isLeaf) {
                p("return null;");
            }
            i(-1).p("}");
            p();
        }
    }

    private void generateEnumDecodeMethodBody(JClassType classType) {
        p(JSON_VALUE_CLASS + " str = object.get(\"name\");");
        p("if( null == str || str.isString() == null ) {").i(1);
        {
            p("throw new DecodingException(\"Expected a string field called 'name' for enum; not found\");").i(-1);
        }
        p("}");
        decodeEnum(classType, "str.isString().stringValue()");
    }

    private String getDefaultValue(JField field) {
        return field.getType().isPrimitive() == null ? "null" : field.getType().isPrimitive().getUninitializedFieldExpression() + "";
    }

    private void assignFieldValue(String name, String expression, String cast, String setterName) {
        if (setterName != null) {
            p("rc." + setterName + "(" + cast + expression + ");");
        } else {
            p("rc." + name + "=" + cast + expression + ";");
        }
    }

    protected void generateEnumDecodeMethod(JClassType classType, String jsonValueClass)
    {
        p();
        p("public " + classType.getName() + " decode(" + jsonValueClass + " value) {").i(1);
        {
        p("if( value == null || value.isNull()!=null ) {").i(1);
        {
            p("return null;").i(-1);
        }
        p("}");
        p(JSON_STRING_CLASS + " str = value.isString();");
        p("if( null == str ) {").i(1);
        {
            p("throw new DecodingException(\"Expected a json string (for enum), but was given: \"+value);").i(-1);
        }
        p("}");

        String value = "str.stringValue()";
        decodeEnum(classType, value);
        }
        p("}");
        p();
    }

    protected void decodeEnum(JClassType classType, String value) {
        String className = classType.getParameterizedQualifiedSourceName();
        String method = getValueMethod(classType);
        if ( method == null ) {
            p("return Enum.valueOf(" + className + ".class, " + value + ");").i(-1);
        }
        else {
            p("for(" + className + " v: " + className + ".values()) {").i(1);
            {
                p("if(v." + method + "().equals(" + value + ")) {").i(1);
                {
                    p("return v;").i(-1);
                }
                p("}").i(-1);
            }
            p("}");
            p("throw new DecodingException(\"can not find enum for given value: \"+" + value + ");").i(-1);
        }
    }

    public static Map<Class<?>, RestyJsonTypeIdResolver> getRestyResolverClassMap(GeneratorContext context, TreeLogger logger) throws UnableToCompleteException {
  if (sTypeIdResolverMap == null) {
      try {
    Map<Class<?>, RestyJsonTypeIdResolver> map = Maps.newHashMap();
    List<String> values = context.getPropertyOracle().getConfigurationProperty("org.fusesource.restygwt.jsontypeidresolver").getValues();
    for (String value : values)
        try {
      Class<?> clazz = Class.forName(value);
      RestyJsonTypeIdResolver resolver = (RestyJsonTypeIdResolver) clazz.newInstance();
      map.put(resolver.getTypeIdResolverClass(), resolver);
        } catch (Exception e) {
      logger.log(WARN, "Could not access class: " + values.get(0), e);
        }
        sTypeIdResolverMap = map;
      } catch (BadPropertyValueException e) {
    logger.log(ERROR, "Could not acccess property: RestyJsonTypeIdResolver", e);
    throw new UnableToCompleteException();
      }
  }
  return sTypeIdResolverMap;
    }

    private List<JField> getOrderedFields(List<JField> fields, JConstructor creator) throws UnableToCompleteException {
  List<JField> orderedFields = new ArrayList<JField>();
  for (JParameter param : creator.getParameters()) {
      JsonProperty prop = getAnnotation(param, JsonProperty.class);
      if (prop != null) {
    for (JField field : fields) {
        if (field.getName().equals(prop.value())) {
      orderedFields.add(field);
        }
    }
      } else {
    getLogger().log(ERROR, "a constructor annotated with @JsonCreator requires that all paramaters are annotated with @JsonProperty.");
        throw new UnableToCompleteException();
      }
  }

  return orderedFields;
    }

    private JConstructor findCreator(JClassType sourceClazz) {
  for (JConstructor constructor : sourceClazz.getConstructors()) {
      if (getAnnotation(constructor, JsonCreator.class) != null) {
    return constructor;
      }
  }

  return null;
    }

    /**
     *
     * @param field
     * @return the name for the setter for the specified field or null if a
     *         setter can't be found.
     */
    private String getSetterName(JField field) {
  String fieldName = field.getName();
  fieldName = "set" + upperCaseFirstChar(fieldName);
  JClassType type = field.getEnclosingType();
  if (exists(type, field, fieldName, true)) {
      return fieldName;
  }
    return null;
    }

    /**
     *
     * @param field
     * @return the name for the getter for the specified field or null if a
     *         getter can't be found.
     */
    private String getGetterName(JField field) {
  String fieldName = field.getName();
  JType booleanType = null;
  try {
      booleanType = find(Boolean.class, getLogger(), context);
  } catch (UnableToCompleteException e) {
      // do nothing
  }
  JClassType type = field.getEnclosingType();
  if (field.getType().equals(JPrimitiveType.BOOLEAN) || field.getType().equals(booleanType)) {
      fieldName = "is" + upperCaseFirstChar(field.getName());
      if (exists(type, field, fieldName, false)) {
    return fieldName;
      }
      fieldName = "has" + upperCaseFirstChar(field.getName());
      if (exists(type, field, fieldName, false)) {
    return fieldName;
      }
  }
  fieldName = "get" + upperCaseFirstChar(field.getName());
  if (exists(type, field, fieldName, false)) {
      return fieldName;
  }
    return null;
    }

    private String upperCaseFirstChar(String in) {
  if (in.length() == 1) {
      return in.toUpperCase();
  }
    return in.substring(0, 1).toUpperCase() + in.substring(1);
    }

    /**
     * checks whether a getter or setter exists on the specified type or any of
     * its super classes excluding Object. respects JsonIgnore accordingly.
     *
     * @param type
     * @param field
     * @param fieldName
     * @param isSetter
     * @return
     */
    private boolean exists(JClassType type, JField field, String fieldName, boolean isSetter) {
        if ( field instanceof DummyJField ){
            return true;
        }

  JType[] args = null;
  if (isSetter) {
      args = new JType[] { field.getType() };
  } else {
      args = new JType[] {};
  }
  JMethod m = type.findMethod(fieldName, args);
  if (null != m) {
        if(getAnnotation(m, JsonIgnore.class) != null)
            return false;
        if(isSetter)
            return true;
        JClassType returnType = m.getReturnType().isClassOrInterface();
        JClassType fieldType = field.getType().isClassOrInterface();
        if(returnType == null || fieldType == null) {
            // at least one is a primitive type
            return m.getReturnType().equals(field.getType());
        }
        // both are non-primitives
        return returnType.isAssignableFrom(fieldType);
  }
    try {
    JType objectType = find(Object.class, getLogger(), context);
    JClassType superType = type.getSuperclass();
    if (!objectType.equals(superType)) {
        return exists(superType, field, fieldName, isSetter);
    }
    } catch (UnableToCompleteException e) {
    // do nothing
    }
  return false;
    }

    /**
     * Inspects the supplied type and all super classes up to but excluding
     * Object and returns a list of all fields found in these classes.
     *
     * @param type
     * @return
     */
    private List<JField> getFields(JClassType type) {
        List<JField> allFields = getFields(new ArrayList<JField>(), type);
        Map<String, JMethod> getters = new HashMap<String, JMethod>();
        Map<String, JType> setters = new HashMap<String, JType>();
        for( JMethod m: type.getInheritableMethods() ){
            if( m.getName().startsWith("set") &&
                    m.getParameterTypes().length == 1 &&
                    m.getReturnType() == JPrimitiveType.VOID &&
                        getAnnotation(m, JsonIgnore.class) == null){
                setters.put( m.getName().replaceFirst("^set", ""), m.getParameterTypes()[0] );
            }
            else if( m.getName().startsWith("get") &&
                    m.getParameterTypes().length == 0 &&
                    m.getReturnType() != JPrimitiveType.VOID &&
                        getAnnotation(m, JsonIgnore.class) == null){
                getters.put( m.getName().replaceFirst("^get", ""), m );
            }
        }
        for( Map.Entry<String, JMethod> entry: getters.entrySet() ){
            if ( setters.containsKey( entry.getKey() ) && setters.get( entry.getKey() ).equals( entry.getValue().getReturnType() ) ) {
                String name = entry.getKey().substring(0, 1).toLowerCase() + entry.getKey().substring(1);
                boolean found = false;
                for( JField f : allFields ){
                    if( f.getName().equals( name ) ){
                        found = true;
                        break;
                    }
                }
                JField f = type.findField( name );
                // is getter annotated, if yes use this annotation for the field
                JsonProperty propName = null;
                if ( entry.getValue().isAnnotationPresent(JsonProperty.class) ) {
                    propName = getAnnotation(entry.getValue(), JsonProperty.class);
                }
                // is setter annotated, if yes use this annotation for the field
                JMethod m = type.findMethod("s" + entry.getValue().getName().substring(1),
                        new JType[]{ entry.getValue().getReturnType() });
                if ( m != null && m.isAnnotationPresent(JsonProperty.class) ) {
                    propName = getAnnotation(m, JsonProperty.class);
                }
                // if have a field and an annotation from the getter/setter then use that annotation
                if ( propName != null && found && !f.getName().equals(propName.value())) {
                    allFields.remove(f);
                    DummyJField dummy = new DummyJField( name, entry.getValue().getReturnType() );
                    dummy.setAnnotation( propName );
                    allFields.add(dummy);
                }
                if ( ! found && !( f != null && f.isAnnotationPresent( JsonIgnore.class ) ) ){
                    DummyJField dummy = new DummyJField( name, entry.getValue().getReturnType() );
                    if ( entry.getValue().isAnnotationPresent(JsonProperty.class) ) {
                        dummy.setAnnotation( getAnnotation(entry.getValue(), JsonProperty.class) );
                    }
                    allFields.add( dummy );
                }
            }
        }
        return allFields;
    }

    private List<JField> getFields(List<JField> allFields, JClassType type) {
        JField[] fields = type.getFields();
        for (JField field : fields) {
            if (!field.isTransient() && !field.isAnnotationPresent(JsonIgnore.class)) {
                allFields.add(field);
            }
        }
        try {
            JType objectType = find(Object.class, getLogger(), context);
            if (!objectType.equals(type)) {
                JClassType superType = type.getSuperclass();
                return getFields(allFields, superType);
            }
        }
        catch (UnableToCompleteException e) {
      // do nothing
        }

      return allFields;
    }

    public static String getTypeInfoPropertyValue(final JsonTypeInfo typeInfo)
    {
        if (typeInfo.include() == JsonTypeInfo.As.PROPERTY)
            if(typeInfo.property() == null || "".equals(typeInfo.property()))
                return typeInfo.use().getDefaultPropertyName();

        return typeInfo.property();
    }

    public static boolean isLeaf(JClassType source)
    {
        return !(source.getSubtypes() != null && source.getSubtypes().length > 0);
    }

    public static class Subtype {
    final String tag;
    final JClassType clazz;

    public Subtype(String tag, JClassType clazz) {
        this.tag = tag;
        this.clazz = clazz;
    }
    }

    private static Map<Class<?>, RestyJsonTypeIdResolver> sTypeIdResolverMap = null;
}
TOP

Related Classes of org.fusesource.restygwt.rebind.JsonEncoderDecoderClassCreator$Subtype

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.