/******************************************************************************* * Copyright (c) 2012-2014 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.dto.generator; import org.eclipse.che.dto.server.JsonSerializable; import org.eclipse.che.dto.shared.DelegateRule; import org.eclipse.che.dto.shared.DTOImpl; import org.eclipse.che.dto.shared.DelegateTo; import org.eclipse.che.dto.shared.JsonArray; import org.eclipse.che.dto.shared.JsonStringMap; import org.eclipse.che.dto.shared.SerializationIndex; import com.google.common.base.Preconditions; import com.google.common.primitives.Primitives; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** Generates the source code for a generated Client DTO impl. */ public class DtoImplClientTemplate extends DtoImpl { private static final String CLIENT_DTO_MARKER = " @" + DTOImpl.class.getCanonicalName() + "(\"client\")\n"; DtoImplClientTemplate(DtoTemplate template, Class<?> superInterface) { super(template, superInterface); } @Override String serialize() { StringBuilder builder = new StringBuilder(); final Class<?> dtoInterface = getDtoInterface(); final String dtoInterfaceName = dtoInterface.getCanonicalName(); emitPreamble(dtoInterface, builder); List<Method> getters = getDtoGetters(dtoInterface); // Enumerate the getters and emit field names and getters + setters. emitFields(getters, builder); emitGettersAndSetters(getters, builder); List<Method> inheritedGetters = getInheritedDtoGetters(dtoInterface); List<Method> methods = new ArrayList<>(); methods.addAll(getters); Set<String> getterNames = new HashSet<>(); for (Method getter : getters) { getterNames.add(getter.getName()); } for (Method getter : inheritedGetters) { if (getterNames.add(getter.getName())) { methods.add(getter); } } // equals, hashCode, serialization and copy constructor emitEqualsAndHashCode(methods, builder); emitSerializer(methods, builder); emitDeserializer(methods, builder); emitDeserializerShortcut(builder); emitCopyConstructor(methods, builder); // Delegation DTO methods. emitDelegateMethods(builder); // "builder" method, it is method that set field and return "this" instance emitWithMethods(getters, dtoInterfaceName, builder); // Implement withXXX methods that are declared directly in this DTO even if there are no any getter for the fields. // Need that to override methods from super DTO and return correct type for with method. // @DTO // public interface A { // String getProperty(); // void setProperty(String property); // A withProperty(); // } // // @DTO // public interface B extends A { // // New methods // ... // // Override method to return type B instead of A. // B withProperty(); // } getterNames.clear(); for (Method getter : getters) { getterNames.add(getter.getName()); } for (Method method : dtoInterface.getDeclaredMethods()) { if (method.getName().startsWith("with")) { String noPrefixName = method.getName().substring(4); // Check do we already generate withXXX method or not. // If there is getter in DTO interface we already have withXXX method if (!getterNames.contains("get" + noPrefixName) && !getterNames.contains("is" + noPrefixName)) { String fieldName = Character.toLowerCase(noPrefixName.charAt(0)) + noPrefixName.substring(1); String parameterFqn = getFqParameterizedName(method.getGenericParameterTypes()[0]); emitWithMethod(method.getName(), fieldName, parameterFqn, dtoInterfaceName, builder); } } } emitPostamble(builder); return builder.toString(); } private void emitEqualsAndHashCode(List<Method> getters, StringBuilder builder) { builder.append(" @Override\n"); builder.append(" public boolean equals(Object o) {\n"); builder.append(" if (!(o instanceof ").append(getImplClassName()).append(")) {\n"); builder.append(" return false;\n"); builder.append(" }\n"); builder.append(" ").append(getImplClassName()).append(" other = (").append(getImplClassName()).append(") o;\n"); for (Method getter : getters) { String fieldName = getJavaFieldName(getter.getName()); Class<?> returnType = getter.getReturnType(); if (returnType.isPrimitive()) { builder.append(" if (this.").append(fieldName).append(" != other.").append(fieldName).append(") {\n"); builder.append(" return false;\n"); builder.append(" }\n"); } else { builder.append(" if (this.").append(fieldName).append(" != null) {\n"); builder.append(" if (!this.").append(fieldName).append(".equals(other.").append(fieldName).append(")) {\n"); builder.append(" return false;\n"); builder.append(" }\n"); builder.append(" } else {\n"); builder.append(" if (other.").append(fieldName).append(" != null) {\n"); builder.append(" return false;\n"); builder.append(" }\n"); builder.append(" }\n"); } } builder.append(" return true;\n"); builder.append(" }\n\n"); // this isn't the greatest hash function in the world, but it meets the requirement that for any // two objects A and B, A.equals(B) only if A.hashCode() == B.hashCode() builder.append(" @Override\n"); builder.append(" public int hashCode() {\n"); builder.append(" int hash = 7;\n"); for (Method method : getters) { Class<?> type = method.getReturnType(); String fieldName = getJavaFieldName(method.getName()); if (type.isPrimitive()) { Class<?> wrappedType = Primitives.wrap(type); builder.append(" hash = hash * 31 + ").append(wrappedType.getName()).append(".valueOf(").append(fieldName) .append(").hashCode();\n"); } else { builder.append(" hash = hash * 31 + (").append(fieldName).append(" != null ? ").append(fieldName).append( ".hashCode() : 0);\n"); } } builder.append(" return hash;\n"); builder.append(" }\n\n"); } private void emitFactoryMethod(StringBuilder builder) { builder.append(" public static "); builder.append(getImplClassName()); builder.append(" make() {"); builder.append("\n return new "); builder.append(getImplClassName()); builder.append("();\n }\n\n"); } private void emitFields(List<Method> getters, StringBuilder builder) { for (Method getter : getters) { String fieldName = getJavaFieldName(getter.getName()); builder.append(" "); builder.append(getFieldTypeAndAssignment(getter, fieldName)); } builder.append("\n"); } /** Emits a method to get a field. Getting a collection ensures that the collection is created. */ private void emitGetter(Method method, String methodName, String fieldName, String returnType, StringBuilder builder) { builder.append(" @Override\n public "); builder.append(returnType); builder.append(" "); builder.append(methodName); builder.append("() {\n"); // Initialize the collection. Class<?> returnTypeClass = method.getReturnType(); if (isList(returnTypeClass) || isMap(returnTypeClass)) { builder.append(" "); builder.append(getEnsureName(fieldName)); builder.append("();\n"); } builder.append(" return "); builder.append(fieldName); builder.append(";\n }\n\n"); } private void emitGettersAndSetters(List<Method> getters, StringBuilder builder) { for (Method getter : getters) { String fieldName = getJavaFieldName(getter.getName()); if (fieldName == null) { continue; } Class<?> returnTypeClass = getter.getReturnType(); String returnType = getFqParameterizedName(getter.getGenericReturnType()); // Getter. emitGetter(getter, getter.getName(), fieldName, returnType, builder); // Setter. emitSetter(fieldName, returnType, builder); // List/Map-specific methods. if (isList(returnTypeClass)) { emitListAdd(getter, fieldName, builder); emitClear(fieldName, builder); emitEnsureCollection(getter, fieldName, builder); } else if (isMap(returnTypeClass)) { emitMapPut(getter, fieldName, builder); emitClear(fieldName, builder); emitEnsureCollection(getter, fieldName, builder); } } } private void emitDelegateMethod(String returnType, Method method, Class<?> delegateToType, String delegateToMethod, StringBuilder builder) { builder.append(" public ").append(returnType).append(" ").append(method.getName()).append("("); Type[] parameterTypes = method.getGenericParameterTypes(); String[] parameters = new String[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { if (i > 0) { builder.append(", "); } builder.append(getFqParameterizedName(parameterTypes[i])); String parameter = "$p" + i; builder.append(" ").append(parameter); parameters[i] = parameter; } builder.append(") {\n"); builder.append(" "); if (!"void".equals(returnType)) { builder.append("return "); } builder.append(delegateToType.getCanonicalName()).append(".").append(delegateToMethod).append("("); builder.append("this"); for (String parameter : parameters) { builder.append(", "); builder.append(parameter); } builder.append(");\n"); builder.append(" }\n\n"); } private void emitDelegateMethods(StringBuilder builder) { for (Method method : getDtoMethods()) { DelegateTo delegateTo = method.getAnnotation(DelegateTo.class); if (delegateTo != null) { DelegateRule serverRule = delegateTo.server(); String returnType = getFqParameterizedName(method.getGenericReturnType()); emitDelegateMethod(returnType, method, serverRule.type(), serverRule.method(), builder); } } } private void emitSerializer(List<Method> getters, StringBuilder builder) { builder.append(" public JSONObject toJsonObject() {\n"); if (isCompactJson()) { builder.append(" JSONArray result = new JSONArray();\n"); for (Method method : getters) { emitSerializeFieldForMethodCompact(method, builder); } } else { builder.append(" JSONObject result = new JSONObject();\n"); for (Method getter : getters) { emitSerializeFieldForMethod(getter, builder); } } builder.append(" return result;\n"); builder.append(" }\n"); builder.append("\n"); builder.append(" @Override\n"); builder.append(" public String toJson() {\n"); builder.append(" return toJsonObject().toString();\n"); builder.append(" }\n"); builder.append("\n"); builder.append(" @Override\n"); builder.append(" public String toString() {\n"); builder.append(" return toJson();\n"); builder.append(" }\n\n"); } private void emitSerializeFieldForMethod(Method getter, final StringBuilder builder) { final String jsonFieldName = getJsonFieldName(getter.getName()); final String fieldNameOut = jsonFieldName + "Out"; final String baseIndentation = " "; builder.append("\n"); List<Type> expandedTypes = expandType(getter.getGenericReturnType()); emitSerializerImpl(expandedTypes, 0, builder, getJavaFieldName(getter.getName()), fieldNameOut, baseIndentation); builder.append(" result.put(\""); builder.append(jsonFieldName); builder.append("\", "); builder.append(fieldNameOut); builder.append(");\n"); } private void emitSerializeFieldForMethodCompact(Method getter, StringBuilder builder) { if (getter == null) { builder.append(" result.set(0, JSONNull.getInstance());\n"); return; } final String jsonFieldName = getJsonFieldName(getter.getName()); final String fieldNameOut = jsonFieldName + "Out"; final String baseIndentation = " "; builder.append("\n"); List<Type> expandedTypes = expandType(getter.getGenericReturnType()); emitSerializerImpl(expandedTypes, 0, builder, getJavaFieldName(getter.getName()), fieldNameOut, baseIndentation); if (isLastMethod(getter)) { if (isList(getRawClass(expandedTypes.get(0)))) { builder.append(" if (").append(fieldNameOut).append(".size() != 0) {\n"); builder.append(" result.set(result.size(), ").append(fieldNameOut).append(");\n"); builder.append(" }\n"); return; } } builder.append(" result.add(").append(fieldNameOut).append(");\n"); } /** * Produces code to serialize the type with the given variable names. * * @param expandedTypes * the type and its generic (and its generic (..)) expanded into a list, @see {@link #expandType(java.lang.reflect.Type)} * @param depth * the depth (in the generics) for this recursive call. This can be used to index into {@code expandedTypes} * @param builder * StringBuilder to add generated code for serialization * @param inVar * the java type that will be the input for serialization * @param outVar * the JsonElement subtype that will be the output for serialization * @param i * indentation string */ private void emitSerializerImpl(List<Type> expandedTypes, int depth, StringBuilder builder, String inVar, String outVar, String i) { Type type = expandedTypes.get(depth); String childInVar = inVar + "_"; String childOutVar = outVar + "_"; String entryVar = "entry" + depth; Class<?> rawClass = getRawClass(type); if (isList(rawClass)) { String childInTypeName = getImplName(expandedTypes.get(depth + 1), false); builder.append(i).append("JSONArray ").append(outVar).append(" = new JSONArray();\n"); if (depth == 0) { builder.append(i).append("this.").append(getEnsureName(inVar)).append("();\n"); } builder.append(i).append("for (").append(childInTypeName).append(" ").append(childInVar).append(" : ").append( depth == 0 ? "this." + inVar : inVar).append(") {\n"); } else if (isMap(rawClass)) { String childInTypeName = getImplName(expandedTypes.get(depth + 1), false); builder.append(i).append("JSONObject ").append(outVar).append(" = new JSONObject();\n"); if (depth == 0) { builder.append(i).append("this.").append(getEnsureName(inVar)).append("();\n"); } builder.append(i).append("for (java.util.Map.Entry<String, ").append(childInTypeName).append("> ").append( entryVar).append(" : ").append(depth == 0 ? "this." + inVar : inVar).append(".entrySet()) {\n"); builder.append(i).append(" ").append(childInTypeName).append(" ").append(childInVar).append(" = ").append( entryVar).append(".getValue();\n"); } else if (rawClass.isEnum()) { builder.append(i).append("JSONValue ").append(outVar).append(" = (").append(depth == 0 ? "this." + inVar : inVar).append( " == null) ? JSONNull.getInstance() : new JSONString(").append(depth == 0 ? "this." + inVar : inVar) .append(".name());\n"); } else if (getEnclosingTemplate().isDtoInterface(rawClass)) { builder.append(i).append("JSONValue ").append(outVar).append(" = ").append(depth == 0 ? "this." + inVar : inVar).append( " == null ? JSONNull.getInstance() : ((").append(getImplNameForDto((Class<?>)expandedTypes.get(depth))).append(")") .append(inVar).append(").toJsonObject();\n"); } else if (rawClass.equals(String.class)) { builder.append(i).append("JSONValue ").append(outVar).append(" = (").append(depth == 0 ? "this." + inVar : inVar).append( " == null) ? JSONNull.getInstance() : new JSONString(").append(depth == 0 ? "this." + inVar : inVar).append(");\n"); } else if (isNumber(rawClass)) { if (rawClass.isPrimitive()) { builder.append(i).append("JSONValue ").append(outVar).append(" = new JSONNumber(") .append(depth == 0 ? "this." + inVar : inVar).append(");\n"); } else { builder.append(i).append("JSONValue ").append(outVar).append(depth == 0 ? " = this." + inVar : inVar).append( " == null ? JSONNull.getInstance() : new JSONNumber(").append(depth == 0 ? "this." + inVar : inVar).append(");\n"); } } else if (isBoolean(rawClass)) { if (rawClass.isPrimitive()) { builder.append(i).append("JSONValue ").append(outVar).append(" = JSONBoolean.getInstance(") .append(depth == 0 ? "this." + inVar : inVar).append(");\n"); } else { builder.append(i).append("JSONValue ").append(outVar).append(depth == 0 ? " = this." + inVar : inVar).append( " == null ? JSONNull.getInstance() : JSONBoolean.getInstance(").append(depth == 0 ? "this." + inVar : inVar) .append(");\n"); } } else { final Class<?> dtoImplementation = getEnclosingTemplate().getDtoImplementation(rawClass); if (dtoImplementation != null) { builder.append(i).append("JSONValue ").append(outVar).append(" = ").append(depth == 0 ? "this." + inVar : inVar).append( " == null ? JSONNull.getInstance() : ((").append(dtoImplementation.getCanonicalName()).append(")") .append(depth == 0 ? "this." + inVar : inVar).append(").toJsonObject();\n"); } else { throw new IllegalArgumentException("Unable to generate client implementation for DTO interface " + getDtoInterface().getCanonicalName() + ". Type " + rawClass + " is not allowed to use in DTO interface."); } } if (depth + 1 < expandedTypes.size()) { emitSerializerImpl(expandedTypes, depth + 1, builder, childInVar, childOutVar, i + " "); } if (isList(rawClass)) { builder.append(i).append(" ").append(outVar).append(".set(").append(outVar).append(".size(), ").append(childOutVar) .append(");\n"); builder.append(i).append("}\n"); } else if (isMap(rawClass)) { builder.append(i).append(" ").append(outVar).append(".put(").append(entryVar).append(".getKey(), ").append( childOutVar).append(");\n"); builder.append(i).append("}\n"); } } /** Generates a static factory method that creates a new instance based on a JsonElement. */ private void emitDeserializer(List<Method> getters, StringBuilder builder) { builder.append(" public static ").append(getImplClassName()).append(" fromJsonObject(JSONValue jsonValue) {\n"); builder.append(" if (jsonValue == null || jsonValue.isNull() != null) {\n"); builder.append(" return null;\n"); builder.append(" }\n\n"); builder.append(" ").append(getImplClassName()).append(" dto = new ").append(getImplClassName()).append("();\n"); if (isCompactJson()) { for (Method method : getters) { emitDeserializeFieldForMethodCompact(method, builder); } } else { builder.append(" JSONObject json = jsonValue.isObject();\n"); for (Method getter : getters) { emitDeserializeFieldForMethod(getter, builder); } } builder.append("\n return dto;\n"); builder.append(" }\n\n"); } private void emitDeserializerShortcut(StringBuilder builder) { builder.append(" public static "); builder.append(getImplClassName()); builder.append(" fromJsonString(String jsonString) {\n"); builder.append(" if (jsonString == null) {\n"); builder.append(" return null;\n"); builder.append(" }\n\n"); builder.append(" return fromJsonObject(JSONParser.parseStrict(jsonString));\n"); builder.append(" }\n\n"); } private void emitDeserializeFieldForMethod(Method getter, StringBuilder builder) { final String fieldName = getJsonFieldName(getter.getName()); final String fieldNameIn = fieldName + "In"; final String fieldNameOut = fieldName + "Out"; final String baseIndentation = " "; builder.append("\n"); builder.append(" if (json.containsKey(\"").append(fieldName).append("\")) {\n"); List<Type> expandedTypes = expandType(getter.getGenericReturnType()); builder.append(" JSONValue ").append(fieldNameIn).append(" = json.get(\"").append(fieldName).append( "\");\n"); emitDeserializerImpl(expandedTypes, 0, builder, fieldNameIn, fieldNameOut, baseIndentation); builder.append(" dto.").append(getSetterName(fieldName)).append("(").append(fieldNameOut).append(");\n"); builder.append(" }\n"); } private void emitDeserializeFieldForMethodCompact(Method method, final StringBuilder builder) { final String fieldName = getJsonFieldName(method.getName()); final String fieldNameIn = fieldName + "In"; final String fieldNameOut = fieldName + "Out"; final String baseIndentation = " "; SerializationIndex serializationIndex = Preconditions.checkNotNull(method.getAnnotation(SerializationIndex.class)); int index = serializationIndex.value() - 1; builder.append("\n"); builder.append(" if (").append(index).append(" < json.size()) {\n"); List<Type> expandedTypes = expandType(method.getGenericReturnType()); builder.append(" JSONValue ").append(fieldNameIn).append(" = json.get(").append(index).append(");\n"); emitDeserializerImpl(expandedTypes, 0, builder, fieldNameIn, fieldNameOut, baseIndentation); builder.append(" dto.").append(getSetterName(fieldName)).append("(").append(fieldNameOut).append(");\n"); builder.append(" }\n"); } /** * Produces code to deserialize the type with the given variable names. * * @param expandedTypes * the type and its generic (and its generic (..)) expanded into a list, @see {@link #expandType(java.lang.reflect.Type)} * @param depth * the depth (in the generics) for this recursive call. This can be used to index into {@code expandedTypes} * @param builder * StringBuilder to add generated code for deserialization * @param inVar * the java type that will be the input for deserialization * @param outVar * the JsonElement subtype that will be the output for serialization * @param i * indentation string */ private void emitDeserializerImpl(List<Type> expandedTypes, int depth, StringBuilder builder, String inVar, String outVar, String i) { Type type = expandedTypes.get(depth); String childInVar = inVar + "_"; String childInVarIterator = childInVar + "_iterator"; String childOutVar = outVar + "_"; Class<?> rawClass = getRawClass(type); if (isList(rawClass)) { builder.append(i).append(getImplName(type, false)).append(" ").append(outVar).append(" = null;\n"); builder.append(i).append("if (").append(inVar).append(" != null && ").append(inVar).append(".isNull() == null) {\n"); builder.append(i).append(" ").append(outVar).append(" = new ").append(getImplName(type, true)).append("();\n"); builder.append(i).append(" for (int ").append(childInVarIterator).append(" = 0; ").append(childInVarIterator).append(" < ") .append(inVar).append(".isArray().size(); ").append(childInVarIterator).append("++) {\n"); builder.append(i).append(" JSONValue ").append(childInVar).append(" = ").append(inVar).append(".isArray().get(") .append(childInVarIterator).append(");\n"); emitDeserializerImpl(expandedTypes, depth + 1, builder, childInVar, childOutVar, i + " "); builder.append(i).append(" ").append(outVar).append(".add(").append(childOutVar).append(");\n"); builder.append(i).append(" }\n"); builder.append(i).append("}\n"); } else if (isMap(rawClass)) { // TODO: Handle type String entryVar = "key" + depth; String entriesVar = "keySet" + depth; builder.append(i).append(getImplName(type, false)).append(" ").append(outVar).append(" = null;\n"); builder.append(i).append("if (").append(inVar).append(" != null && ").append(inVar).append(".isNull() == null) {\n"); builder.append(i).append(" ").append(outVar).append(" = new ").append(getImplName(type, true)).append("();\n"); builder.append(i).append(" java.util.Set<String> ").append(entriesVar).append( " = ").append(inVar).append(".isObject().keySet();\n"); builder.append(i).append(" for (String ").append(entryVar).append(" : ").append(entriesVar) .append(") {\n"); builder.append(i).append(" JSONValue ").append(childInVar).append(" = ").append(inVar). append(".isObject().get(").append(entryVar).append(");\n"); emitDeserializerImpl(expandedTypes, depth + 1, builder, childInVar, childOutVar, i + " "); builder.append(i).append(" ").append(outVar).append(".put(").append(entryVar).append(", ").append( childOutVar).append(");\n"); builder.append(i).append(" }\n"); builder.append(i).append("}\n"); } else if (rawClass.isEnum()) { String primitiveName = rawClass.getCanonicalName(); builder.append(i).append(primitiveName).append(" ").append(outVar).append(" = ").append(primitiveName). append(".valueOf(").append(inVar).append(".isString().stringValue());\n"); } else if (getEnclosingTemplate().isDtoInterface(rawClass)) { String className = getImplName(rawClass, false); builder.append(i).append(className).append(" ").append(outVar).append(" = ").append(getImplNameForDto(rawClass)) .append(".fromJsonObject(").append(inVar).append(");\n"); } else if (rawClass.equals(String.class)) { String primitiveName = rawClass.getSimpleName(); builder.append(i).append(primitiveName).append(" ").append(outVar).append(" = ").append(inVar).append(".isString() != null ? ") .append(inVar).append(".isString().stringValue() : null;\n"); } else if (isNumber(rawClass)) { String primitiveName = rawClass.getSimpleName(); String typeCast = rawClass.equals(double.class) || rawClass.equals(Double.class) ? "" : "(" + getPrimitiveName(rawClass) + ")"; if (rawClass.isPrimitive()) { builder.append(i).append(primitiveName).append(" ").append(outVar).append(" = ").append(typeCast).append(inVar) .append(".isNumber().doubleValue();\n"); } else { if (isInteger(rawClass)) { builder.append(i).append(primitiveName).append(" ").append(outVar).append(" = ").append(inVar) .append(".isNumber() != null ? ((Double)").append(inVar).append( ".isNumber().doubleValue()).intValue() : null;\n"); } else if (isFloat(rawClass)) { builder.append(i).append(primitiveName).append(" ").append(outVar).append(" = ").append(inVar) .append(".isNumber() != null ? ((Double)").append(inVar).append( ".isNumber().doubleValue()).floatValue() : null;\n"); } else if (isLong(rawClass)) { builder.append(i).append(primitiveName).append(" ").append(outVar).append(" = ").append(inVar) .append(".isNumber() != null ? ((Double)").append(inVar).append( ".isNumber().doubleValue()).longValue() : null;\n"); } else if (isDouble(rawClass)) { builder.append(i).append(primitiveName).append(" ").append(outVar).append(" = ").append(inVar) .append(".isNumber() != null ? ((Double)").append(inVar).append( ".isNumber().doubleValue()).doubleValue() : null;\n"); } } } else if (isBoolean(rawClass)) { String primitiveName = rawClass.getSimpleName(); if (rawClass.isPrimitive()) { builder.append(i).append(primitiveName).append(" ").append(outVar).append(" = ").append(inVar) .append(".isBoolean().booleanValue();\n"); } else { builder.append(i).append(primitiveName).append(" ").append(outVar).append(" = ").append(inVar) .append(".isBoolean() != null ? ").append(inVar).append(".isBoolean().booleanValue() : null;\n"); } } else { final Class<?> dtoImplementation = getEnclosingTemplate().getDtoImplementation(rawClass); if (dtoImplementation != null) { String className = getImplName(rawClass, false); builder.append(i).append(className).append(" ").append(outVar).append(" = ") .append(dtoImplementation.getCanonicalName()).append(".fromJsonObject(").append(inVar).append(");\n"); } else { throw new IllegalArgumentException("Unable to generate client implementation for DTO interface " + getDtoInterface().getCanonicalName() + ". Type " + rawClass + " is not allowed to use in DTO interface."); } } } private boolean isDouble(Class<?> returnType) { return returnType.equals(Double.class); } private boolean isLong(Class<?> returnType) { return returnType.equals(Long.class); } private boolean isFloat(Class<?> returnType) { return returnType.equals(Float.class); } private boolean isInteger(Class<?> returnType) { return returnType.equals(Integer.class); } private void emitPreamble(Class<?> dtoInterface, StringBuilder builder) { builder.append(CLIENT_DTO_MARKER); builder.append(" public static class "); builder.append(getImplClassName()); Class<?> superType = getSuperInterface(getDtoInterface()); if (superType != null && superType != JsonSerializable.class) { // We need to extend something. builder.append(" extends "); final Class<?> superTypeImpl = getEnclosingTemplate().getDtoImplementation(superType); if (superTypeImpl == null) { builder.append(superType.getSimpleName()).append("Impl"); } else { builder.append(superTypeImpl.getCanonicalName()); } } builder.append(" implements "); builder.append(dtoInterface.getCanonicalName()); builder.append(", JsonSerializable "); builder.append(" {\n\n"); emitFactoryMethod(builder); emitDefaultConstructor(builder); } private void emitPostamble(StringBuilder builder) { builder.append(" }\n\n"); } private void emitDefaultConstructor(StringBuilder builder) { builder.append(" protected "); builder.append(getImplClassName()); builder.append("() {\n"); builder.append(" }\n\n"); } private void emitSetter(String fieldName, String returnType, StringBuilder builder) { builder.append(" public "); builder.append("void"); builder.append(" "); builder.append(getSetterName(fieldName)); builder.append("("); builder.append(returnType); builder.append(" v) {\n"); builder.append(" this."); builder.append(fieldName); builder.append(" = "); builder.append("v;\n }\n\n"); } private void emitWithMethods(List<Method> getters, String dtoInterfaceName, StringBuilder builder) { for (Method getter : getters) { String fieldName = getJavaFieldName(getter.getName()); emitWithMethod(getWithName(fieldName), fieldName, getFqParameterizedName(getter.getGenericReturnType()), dtoInterfaceName, builder); } } private void emitWithMethod(String methodName, String fieldName, String paramType, String dtoInterfaceName, StringBuilder builder) { builder.append(" public "); builder.append(dtoInterfaceName); builder.append(" "); builder.append(methodName); builder.append("("); builder.append(paramType); builder.append(" v) {\n"); builder.append(" this."); builder.append(fieldName); builder.append(" = "); builder.append("v;\n return this;\n }\n\n"); } /** * Emits an add method to add to a list. If the list is null, it is created. * * @param method * a method with a list return type */ private void emitListAdd(Method method, String fieldName, StringBuilder builder) { builder.append(" public void "); builder.append(getListAdderName(fieldName)); builder.append("("); builder.append(getTypeArgumentImplName((ParameterizedType)method.getGenericReturnType(), 0)); builder.append(" v) {\n "); builder.append(getEnsureName(fieldName)); builder.append("();\n "); builder.append(fieldName); builder.append(".add(v);\n"); builder.append(" }\n\n"); } /** * Emits a put method to put a value into a map. If the map is null, it is created. * * @param method * a method with a map return value */ private void emitMapPut(Method method, String fieldName, StringBuilder builder) { builder.append(" public void "); builder.append(getMapPutterName(fieldName)); builder.append("(String k, "); builder.append(getTypeArgumentImplName((ParameterizedType)method.getGenericReturnType(), 1)); builder.append(" v) {\n "); builder.append(getEnsureName(fieldName)); builder.append("();\n "); builder.append(fieldName); builder.append(".put(k, v);\n"); builder.append(" }\n\n"); } /** Emits a method to clear a list or map. Clearing the collections ensures that the collection is created. */ private void emitClear(String fieldName, StringBuilder builder) { builder.append(" public void "); builder.append(getClearName(fieldName)); builder.append("() {\n "); builder.append(getEnsureName(fieldName)); builder.append("();\n "); builder.append(fieldName); builder.append(".clear();\n"); builder.append(" }\n\n"); } private void emitCopyConstructor(List<Method> getters, StringBuilder builder) { String dtoInterface = getDtoInterface().getCanonicalName(); String implClassName = getImplClassName(); builder.append(" public ").append(implClassName).append("(").append(dtoInterface).append(" origin) {\n"); for (Method method : getters) { emitDeepCopyForGetters(expandType(method.getGenericReturnType()), 0, builder, "origin", method, " "); } builder.append(" }\n\n"); } private void emitDeepCopyForGetters(List<Type> expandedTypes, int depth, StringBuilder builder, String origin, Method getter, String i) { String getterName = getter.getName(); String fieldName = getJavaFieldName(getterName); String fieldNameIn = fieldName + "In"; String fieldNameOut = fieldName + "Out"; Type type = expandedTypes.get(depth); Class<?> rawClass = getRawClass(type); String rawTypeName = getImplName(type, false); if (isList(rawClass) || isMap(rawClass)) { builder.append(i).append(rawTypeName).append(" ").append(fieldNameIn).append(" = ").append(origin).append(".") .append(getterName).append("();\n"); builder.append(i).append("if (").append(fieldNameIn).append(" != null) {\n"); builder.append(i).append(" ").append(rawTypeName).append(" ").append(fieldNameOut) .append(" = new ").append(getImplName(type, true)).append("();\n"); emitDeepCopyCollections(expandedTypes, depth, builder, fieldNameIn, fieldNameOut, i); builder.append(i).append(" ").append("this.").append(fieldName).append(" = ").append(fieldNameOut).append(";\n"); builder.append(i).append("}\n"); } else if (getEnclosingTemplate().isDtoInterface(rawClass)) { builder.append(i).append(rawTypeName).append(" ").append(fieldNameIn).append(" = ").append(origin).append(".") .append(getterName).append("();\n"); builder.append(i).append("this.").append(fieldName).append(" = "); emitCheckNullAndCopyDto(rawClass, fieldNameIn, builder); builder.append(";\n"); } else { builder.append(i).append("this.").append(fieldName).append(" = ") .append(origin).append(".").append(getterName).append("();\n"); } } private void emitDeepCopyCollections(List<Type> expandedTypes, int depth, StringBuilder builder, String varIn, String varOut, String i) { Type type = expandedTypes.get(depth); String childVarIn = varIn + "_"; String childVarOut = varOut + "_"; String entryVar = "entry" + depth; Class<?> rawClass = getRawClass(type); Class<?> childRawType = getRawClass(expandedTypes.get(depth + 1)); final String childTypeName = getImplName(expandedTypes.get(depth + 1), false); if (isList(rawClass)) { builder.append(i).append(" for (").append(childTypeName).append(" ").append(childVarIn) .append(" : ").append(varIn).append(") {\n"); } else if (isMap(rawClass)) { builder.append(i).append(" for (java.util.Map.Entry<String, ").append(childTypeName).append("> ").append(entryVar) .append(" : ").append(varIn).append(".entrySet()) {\n"); builder.append(i).append(" ").append(childTypeName).append(" ").append(childVarIn).append(" = ").append( entryVar).append(".getValue();\n"); } if (isList(childRawType) || isMap(childRawType)) { builder.append(i).append(" if (").append(childVarIn).append(" != null) {\n"); builder.append(i).append(" ").append(childTypeName).append(" ").append(childVarOut) .append(" = new ").append(getImplName(expandedTypes.get(depth + 1), true)).append("();\n"); emitDeepCopyCollections(expandedTypes, depth + 1, builder, childVarIn, childVarOut, i + " "); builder.append(i).append(" ").append(varOut); if (isList(rawClass)) { builder.append(".add("); } else { builder.append(".put(").append(entryVar).append(".getKey(), "); } builder.append(childVarOut); builder.append(");\n"); builder.append(i).append(" ").append("}\n"); } else { builder.append(i).append(" ").append(varOut); if (isList(rawClass)) { builder.append(".add("); } else { builder.append(".put(").append(entryVar).append(".getKey(), "); } if (getEnclosingTemplate().isDtoInterface(childRawType)) { emitCheckNullAndCopyDto(childRawType, childVarIn, builder); } else { builder.append(childVarIn); } builder.append(");\n"); } builder.append(i).append(" }\n"); } private void emitCheckNullAndCopyDto(Class<?> dto, String fieldName, StringBuilder builder) { String implName = dto.getSimpleName() + "Impl"; builder.append(fieldName).append(" == null ? null : ") .append("new ").append(implName).append("(").append(fieldName).append(")"); } /** Emit a method that ensures a collection is initialized. */ private void emitEnsureCollection(Method method, String fieldName, StringBuilder builder) { builder.append(" protected void "); builder.append(getEnsureName(fieldName)); builder.append("() {\n"); builder.append(" if ("); builder.append(fieldName); builder.append(" == null) {\n "); builder.append(fieldName); builder.append(" = new "); builder.append(getImplName(method.getGenericReturnType(), true)); builder.append("();\n"); builder.append(" }\n"); builder.append(" }\n"); } /** * Appends a suitable type for the given type. For example, at minimum, this will replace DTO interfaces with their implementation * classes and JSON collections with corresponding Java types. If a suitable type cannot be determined, this will throw an exception. * * @param genericType * the type as returned by e.g. method.getGenericReturnType() */ private void appendType(Type genericType, final StringBuilder builder) { builder.append(getImplName(genericType, false)); } /** * In most cases we simply echo the return type and field name, except for JsonArray<T>, which is special in the server impl case, * since it must be represented by a List<T> for Gson to correctly serialize/deserialize it. * * @param method * The getter method. * @return String representation of what the field type should be, as well as the assignment (initial value) to said field type, if any. */ private String getFieldTypeAndAssignment(Method method, String fieldName) { StringBuilder builder = new StringBuilder(); builder.append("protected "); appendType(method.getGenericReturnType(), builder); builder.append(" "); builder.append(fieldName); builder.append(";\n"); return builder.toString(); } /** * Returns the fully-qualified type name using Java concrete implementation classes. * <p/> * For example, for JsonArray<JsonStringMap<Dto>>, this would return "ArrayList<Map<String, DtoImpl>>". */ private String getImplName(Type type, boolean allowJreCollectionInterface) { Class<?> rawClass = getRawClass(type); String fqName = getFqParameterizedName(type); fqName = fqName.replaceAll(JsonArray.class.getCanonicalName(), ArrayList.class.getCanonicalName()); fqName = fqName.replaceAll(JsonStringMap.class.getCanonicalName() + "<", HashMap.class.getCanonicalName() + "<String, "); if (allowJreCollectionInterface) { if (isList(rawClass)) { fqName = fqName.replaceFirst(List.class.getCanonicalName(), ArrayList.class.getCanonicalName()); } else if (isMap(rawClass)) { fqName = fqName.replaceFirst(Map.class.getCanonicalName(), HashMap.class.getCanonicalName()); } } return fqName; } /** Returns the fully-qualified type name including parameters. */ private String getFqParameterizedName(Type type) { if (type instanceof Class<?>) { return ((Class<?>)type).getCanonicalName(); } else if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType)type; StringBuilder sb = new StringBuilder(getRawClass(pType).getCanonicalName()); sb.append('<'); final Type[] actualTypeArguments = pType.getActualTypeArguments(); for (int i = 0; i < actualTypeArguments.length; i++) { if (i > 0) { sb.append(", "); } sb.append(getFqParameterizedName(actualTypeArguments[i])); } sb.append('>'); return sb.toString(); } else { throw new IllegalArgumentException("We do not handle this type"); } } /** * Returns the fully-qualified type name using Java concrete implementation classes of the first type argument for a parameterized * type. * If one is not specified, returns "Object". * * @param type * the parameterized type * @return the first type argument */ private String getTypeArgumentImplName(ParameterizedType type, int index) { Type[] typeArgs = type.getActualTypeArguments(); if (typeArgs.length == 0) { return "Object"; } return getImplName(typeArgs[index], false); } private String getImplNameForDto(Class<?> dtoInterface) { if (getEnclosingTemplate().isDtoInterface(dtoInterface)) { // This will eventually get a generated impl type. return dtoInterface.getSimpleName() + "Impl"; } return dtoInterface.getCanonicalName(); } }