// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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 com.google.collide.dtogen;
import com.google.collide.dtogen.shared.ClientToServerDto;
import com.google.collide.dtogen.shared.RoutableDto;
import com.google.collide.dtogen.shared.SerializationIndex;
import com.google.collide.dtogen.shared.ServerToClientDto;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.json.shared.JsonStringMap;
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.Iterator;
import java.util.List;
import java.util.Map;
/**
* Generates the source code for a generated Server DTO impl.
*
*/
public class DtoImplServerTemplate extends DtoImpl {
private static final String JSON_ARRAY = JsonArray.class.getCanonicalName();
private static final String JSON_ARRAY_ADAPTER =
JsonStringMap.class.getPackage().getName().replace(".shared", ".server.JsonArrayListAdapter");
private static final String JSON_MAP = JsonStringMap.class.getCanonicalName();
private static final String JSON_MAP_ADAPTER =
JsonStringMap.class.getPackage().getName().replace(".shared", ".server.JsonStringMapAdapter");
private static final String ROUTABLE_DTO_IMPL =
RoutableDto.class.getPackage().getName().replace("dtogen.shared", "dtogen.server")
+ ".RoutableDtoServerImpl";
DtoImplServerTemplate(DtoTemplate template, int routingType, Class<?> superInterface) {
super(template, routingType, superInterface);
}
@Override
String serialize() {
StringBuilder builder = new StringBuilder();
Class<?> dtoInterface = getDtoInterface();
List<Method> methods = getDtoMethods();
emitPreamble(dtoInterface, builder);
// Enumerate the getters and emit field names and getters + setters.
emitFields(methods, builder);
emitMethods(methods, builder);
emitEqualsAndHashcode(methods, builder);
emitSerializer(methods, builder);
emitDeserializer(methods, builder);
emitDeserializerShortcut(methods, builder);
builder.append(" }\n");
// Emit a testing mock
emitMockPreamble(dtoInterface, builder);
builder.append(" }\n");
return builder.toString();
}
private void emitDefaultRoutingTypeConstructor(StringBuilder builder) {
builder.append(" private ");
builder.append(getImplClassName());
builder.append("() {");
builder.append("\n super(");
builder.append("" + getRoutingType());
builder.append(");\n ");
builder.append("}\n\n");
}
private void emitEqualsAndHashcode(List<Method> methods, StringBuilder builder) {
builder.append("\n");
builder.append(" @Override\n");
builder.append(" public boolean equals(Object o) {\n");
// if this class inherits from anything, check that the superclass fields are also equal
Class<?> superType = getSuperInterface();
if (superType != null) {
builder.append(" if (!super.equals(o)) {\n");
builder.append(" return false;\n");
builder.append(" }\n");
}
builder.append(" if (!(o instanceof " + getImplClassName() + ")) {\n");
builder.append(" return false;\n");
builder.append(" }\n");
builder.append(" " + getImplClassName() + " other = (" + getImplClassName() + ") o;\n");
for (Method method : methods) {
if (!ignoreMethod(method)) {
String fieldName = getFieldName(method.getName());
String hasFieldName = getHasFieldName(fieldName);
builder.append(" if (this." + hasFieldName + " != other." + hasFieldName + ") {\n");
builder.append(" return false;\n");
builder.append(" }\n");
builder.append(" if (this." + hasFieldName + ") {\n");
if (method.getReturnType().isPrimitive()) {
builder.append(" if (this." + fieldName + " != other." + fieldName + ") {\n");
} else {
builder.append(" if (!this." + fieldName + ".equals(other." + fieldName +
")) {\n");
}
builder.append(" return false;\n");
builder.append(" }\n");
builder.append(" }\n");
}
}
builder.append(" return true;\n");
builder.append(" }\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("\n");
builder.append(" @Override\n");
builder.append(" public int hashCode() {\n");
// if this class inherits from anything, include the superclass hashcode
if (superType != null) {
builder.append(" int hash = super.hashCode();\n");
} else {
builder.append(" int hash = 1;\n");
}
for (Method method : methods) {
if (!ignoreMethod(method)) {
Class<?> type = method.getReturnType();
String fieldName = getFieldName(method.getName());
builder.append(" hash = hash * 31 + (" + getHasFieldName(fieldName) + " ? ");
if (type.isPrimitive()) {
Class<?> wrappedType = Primitives.wrap(type);
builder.append(wrappedType.getName() + ".valueOf(" + fieldName + ").hashCode()");
} else {
builder.append(fieldName + ".hashCode()");
}
builder.append(" : 0);\n");
}
}
builder.append(" return hash;\n");
builder.append(" }\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> methods, StringBuilder builder) {
for (Method method : methods) {
if (!ignoreMethod(method)) {
String methodName = method.getName();
String fieldName = getFieldName(methodName);
builder.append(" ");
builder.append(getFieldTypeAndAssignment(method, fieldName));
// Emit a boolean to track whether the DTO has the field.
builder.append(" private boolean ");
builder.append(getHasFieldName(fieldName));
builder.append(";\n");
}
}
}
private void emitHasField(String fieldName, StringBuilder builder) {
String camelCaseFieldName = getCamelCaseName(fieldName);
builder.append("\n public boolean has");
builder.append(camelCaseFieldName);
builder.append("() {\n");
builder.append(" return ");
builder.append(getHasFieldName(fieldName));
builder.append(";\n }\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("\n @Override\n public ");
builder.append(returnType);
builder.append(" ");
builder.append(methodName);
builder.append("() {\n");
// Initialize the collection.
Class<?> returnTypeClass = method.getReturnType();
if (isJsonArray(returnTypeClass) || isJsonStringMap(returnTypeClass)) {
builder.append(" ");
builder.append(getEnsureName(fieldName));
builder.append("();\n");
}
builder.append(" return ");
emitReturn(method, fieldName, builder);
builder.append(";\n }\n");
}
private void emitMethods(List<Method> methods, StringBuilder builder) {
for (Method method : methods) {
if (ignoreMethod(method)) {
continue;
}
String methodName = method.getName();
String fieldName = getFieldName(methodName);
Class<?> returnTypeClass = method.getReturnType();
String returnType = method
.getGenericReturnType()
.toString()
.replace('$', '.')
.replace("class ", "")
.replace("interface ", "");
// HasField.
emitHasField(fieldName, builder);
// Getter.
emitGetter(method, methodName, fieldName, returnType, builder);
// Setter.
emitSetter(method, getImplClassName(), fieldName, builder);
// List-specific methods.
if (isJsonArray(returnTypeClass)) {
emitListAdd(method, fieldName, builder);
emitClear(fieldName, builder);
emitEnsureCollection(method, fieldName, builder);
} else if (isJsonStringMap(returnTypeClass)) {
emitMapPut(method, fieldName, builder);
emitClear(fieldName, builder);
emitEnsureCollection(method, fieldName, builder);
}
}
}
private void emitSerializer(List<Method> methods, StringBuilder builder) {
builder.append("\n @Override\n");
builder.append(" public JsonElement toJsonElement() {\n");
if (isCompactJson()) {
builder.append(" JsonArray result = new JsonArray();\n");
for (Method method : methods) {
emitSerializeFieldForMethodCompact(method, builder);
}
} else {
builder.append(" JsonObject result = new JsonObject();\n");
for (Method method : methods) {
emitSerializeFieldForMethod(method, 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 gson.toJson(toJsonElement());\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");
}
private void emitSerializeFieldForMethod(Method method, final StringBuilder builder) {
if (method.getName().equals("getType")) {
String typeFieldName = "_type";
if (getRoutingType() == RoutableDto.NON_ROUTABLE_TYPE) {
typeFieldName = "type";
}
builder.append(" result.add(\"" + typeFieldName
+ "\", new JsonPrimitive(getType()));\n");
return;
}
final String fieldName = getFieldName(method.getName());
final String fieldNameOut = fieldName + "Out";
final String baseIndentation = " ";
builder.append("\n");
List<Type> expandedTypes = expandType(method.getGenericReturnType());
emitSerializerImpl(expandedTypes, 0, builder, fieldName, fieldNameOut, baseIndentation);
builder.append(" result.add(\"" + fieldName + "\", ").append(fieldNameOut).append(");\n");
}
private void emitSerializeFieldForMethodCompact(Method method, StringBuilder builder) {
if (method == null) {
builder.append(" result.add(JsonNull.INSTANCE);\n");
return;
}
final String fieldName = getFieldName(method.getName());
final String fieldNameOut = fieldName + "Out";
final String baseIndentation = " ";
builder.append("\n");
List<Type> expandedTypes = expandType(method.getGenericReturnType());
emitSerializerImpl(expandedTypes, 0, builder, fieldName, fieldNameOut, baseIndentation);
if (isLastMethod(method)) {
if (isJsonArray(getRawClass(expandedTypes.get(0)))) {
builder.append(" if (").append(fieldNameOut).append(".size() != 0) {\n");
builder.append(" result.add(").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(Type)}
* @param depth the depth (in the generics) for this recursive call. This can
* be used to index into {@code expandedTypes}
* @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 (isJsonArray(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(getEnsureName(inVar)).append("();\n");
}
builder.append(i).append("for (").append(childInTypeName).append(" ")
.append(childInVar).append(" : ").append(inVar).append(") {\n");
} else if (isJsonStringMap(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(getEnsureName(inVar)).append("();\n");
}
builder.append(i).append("for (Map.Entry<String, ").append(childInTypeName).append("> ")
.append(entryVar).append(" : ").append(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("JsonElement ").append(outVar).append(" = (").append(inVar)
.append(" == null) ? JsonNull.INSTANCE : new JsonPrimitive(").append(inVar)
.append(".name());\n");
} else if (getEnclosingTemplate().isDtoInterface(rawClass)) {
builder.append(i).append("JsonElement ").append(outVar).append(" = ").append(inVar)
.append(" == null ? JsonNull.INSTANCE : ").append(inVar).append(".toJsonElement();\n");
} else if (rawClass.equals(String.class)) {
builder.append(i).append("JsonElement ").append(outVar)
.append(" = (").append(inVar).append(" == null) ? JsonNull.INSTANCE : new JsonPrimitive(")
.append(inVar).append(");\n");
} else {
builder.append(i).append("JsonPrimitive ").append(outVar).append(" = new JsonPrimitive(")
.append(inVar).append(");\n");
}
if (depth + 1 < expandedTypes.size()) {
emitSerializerImpl(expandedTypes, depth + 1, builder, childInVar, childOutVar, i
+ " ");
}
if (isJsonArray(rawClass)) {
builder.append(i).append(" ").append(outVar).append(".add(").append(childOutVar)
.append(");\n");
builder.append(i).append("}\n");
} else if (isJsonStringMap(rawClass)) {
builder.append(i).append(" ").append(outVar).append(".add(").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> methods, StringBuilder builder) {
builder.append("\n public static ");
builder.append(getImplClassName());
builder.append(" fromJsonElement(JsonElement jsonElem) {\n");
builder.append(" if (jsonElem == null || jsonElem.isJsonNull()) {\n");
builder.append(" return null;\n");
builder.append(" }\n\n");
builder.append(" ").append(getImplClassName()).append(" dto = new ")
.append(getImplClassName()).append("();\n");
if (isCompactJson()) {
builder.append(" JsonArray json = jsonElem.getAsJsonArray();\n");
for (Method method : methods) {
if (method == null) {
continue;
}
emitDeserializeFieldForMethodCompact(method, builder);
}
} else {
builder.append(" JsonObject json = jsonElem.getAsJsonObject();\n");
for (Method method : methods) {
emitDeserializeFieldForMethod(method, builder);
}
}
builder.append("\n return dto;\n");
builder.append(" }");
}
private void emitDeserializerShortcut(List<Method> methods, StringBuilder builder) {
builder.append("\n");
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 fromJsonElement(new JsonParser().parse(jsonString));\n");
builder.append(" }\n");
}
private void emitDeserializeFieldForMethod(Method method, final StringBuilder builder) {
if (method.getName().equals("getType")) {
// The type is set in the constructor.
return;
}
final String fieldName = getFieldName(method.getName());
final String fieldNameIn = fieldName + "In";
final String fieldNameOut = fieldName + "Out";
final String baseIndentation = " ";
builder.append("\n");
builder.append(" if (json.has(\"").append(fieldName).append("\")) {\n");
List<Type> expandedTypes = expandType(method.getGenericReturnType());
builder.append(" JsonElement ").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 = getFieldName(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(" JsonElement ").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(Type)}
* @param depth the depth (in the generics) for this recursive call. This can
* be used to index into {@code expandedTypes}
* @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 emitDeserializerImpl(List<Type> expandedTypes, int depth, StringBuilder builder,
String inVar, String outVar, String i) {
Type type = expandedTypes.get(depth);
String childInVar = inVar + "_";
String childOutVar = outVar + "_";
Class<?> rawClass = getRawClass(type);
if (isJsonArray(rawClass)) {
String inVarIterator = inVar + "Iterator";
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(".isJsonNull()) {\n");
builder.append(i).append(" ").append(outVar).append(" = new ")
.append(getImplName(type, false)).append("();\n");
builder.append(i).append(" ").append(getImplName(Iterator.class, true))
.append("<JsonElement> ").append(inVarIterator).append(" = ").append(inVar)
.append(".getAsJsonArray().iterator();\n");
builder.append(i).append(" while (").append(inVarIterator).append(".hasNext()) {\n");
builder.append(i).append(" JsonElement ").append(childInVar).append(" = ")
.append(inVarIterator).append(".next();\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 (isJsonStringMap(rawClass)) {
// TODO: Handle type
String entryVar = "entry" + depth;
String entriesVar = "entries" + 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(".isJsonNull()) {\n");
builder.append(i).append(" ").append(outVar).append(" = new ")
.append(getImplName(type, false)).append("();\n");
builder.append(i).append(" java.util.Set<Map.Entry<String, JsonElement>> ")
.append(entriesVar).append(" = ").append(inVar)
.append(".getAsJsonObject().entrySet();\n");
builder.append(i).append(" for (Map.Entry<String, JsonElement> ").append(entryVar)
.append(" : ").append(entriesVar).append(") {\n");
builder.append(i).append(" JsonElement ").append(childInVar).append(" = ")
.append(entryVar).append(".getValue();\n");
emitDeserializerImpl(expandedTypes, depth + 1, builder, childInVar, childOutVar, i + " ");
builder.append(i).append(" ").append(outVar).append(".put(").append(entryVar)
.append(".getKey(), ").append(childOutVar).append(");\n");
builder.append(i).append(" }\n");
builder.append(i).append("}\n");
} else if (getEnclosingTemplate().isDtoInterface(rawClass)) {
String implClassName = getImplName(rawClass, false);
builder.append(i).append(implClassName).append(" ").append(outVar).append(" = ")
.append(implClassName).append(".fromJsonElement(").append(inVar).append(");\n");
} else if (rawClass.isPrimitive()) {
String primitiveName = rawClass.getSimpleName();
String primitiveNameCap =
primitiveName.substring(0, 1).toUpperCase() + primitiveName.substring(1);
builder.append(i).append(primitiveName).append(" ").append(outVar).append(" = ")
.append(inVar).append(".getAs").append(primitiveNameCap).append("();\n");
} else {
// Use gson to handle all other types.
String rawClassName = rawClass.getName().replace('$', '.');
builder.append(i).append(rawClassName).append(" ").append(outVar).append(" = gson.fromJson(")
.append(inVar).append(", ").append(rawClassName).append(".class);\n");
}
}
private void emitMockPreamble(Class<?> dtoInterface, StringBuilder builder) {
builder.append("\n public static class ");
builder.append("Mock" + getImplClassName());
builder.append(" extends ");
builder.append(getImplClassName());
builder.append(" {\n");
builder.append(" protected Mock");
builder.append(getImplClassName());
builder.append("() {}\n\n");
emitFactoryMethod(builder);
}
private void emitPreamble(Class<?> dtoInterface, StringBuilder builder) {
builder.append("\n public static class ");
builder.append(getImplClassName());
Class<?> superType = getSuperInterface();
if (superType != null) {
// We need to extend something.
builder.append(" extends ");
if (superType.equals(ServerToClientDto.class) || superType.equals(ClientToServerDto.class)) {
// We special case RoutableDto's impl since it isnt generated.
builder.append(ROUTABLE_DTO_IMPL);
} else {
builder.append(superType.getSimpleName() + "Impl");
}
}
builder.append(" implements ");
builder.append(dtoInterface.getCanonicalName());
builder.append(", JsonSerializable");
builder.append(" {\n\n");
// If this guy is Routable, we make two constructors. One is a private
// default constructor that hard codes the routing type, the other is a
// protected constructor for any subclasses of this impl to pass up its
// routing type.
if (getRoutingType() != RoutableDto.NON_ROUTABLE_TYPE) {
emitDefaultRoutingTypeConstructor(builder);
emitProtectedConstructor(builder);
}
// If this DTO is allowed to be constructed on the server, we expose a
// static factory method. A DTO is allowed to be constructed if it is a
// ServerToClientDto, or if it is not a top level type (non-routable).
if (DtoTemplate.implementsServerToClientDto(dtoInterface)
|| getRoutingType() == RoutableDto.NON_ROUTABLE_TYPE) {
emitFactoryMethod(builder);
}
}
private void emitProtectedConstructor(StringBuilder builder) {
builder.append(" protected ");
builder.append(getImplClassName());
builder.append("(int type) {\n super(type);\n");
builder.append(" }\n\n");
}
private void emitReturn(Method method, String fieldName, StringBuilder builder) {
if (isJsonArray(method.getReturnType())) {
// Wrap the returned List in the server adapter.
builder.append("(");
builder.append(JSON_ARRAY);
builder.append(") new ");
builder.append(JSON_ARRAY_ADAPTER);
builder.append("(");
builder.append(fieldName);
builder.append(")");
} else if (isJsonStringMap(method.getReturnType())) {
// Wrap the JsonArray.
builder.append("(");
builder.append(JSON_MAP);
builder.append(") new ");
builder.append(JSON_MAP_ADAPTER);
builder.append("(");
builder.append(fieldName);
builder.append(")");
} else {
builder.append(fieldName);
}
}
private void emitSetter(Method method, String implName, String fieldName, StringBuilder builder) {
builder.append("\n public ");
builder.append(implName);
builder.append(" ");
builder.append(getSetterName(fieldName));
builder.append("(");
appendType(method.getGenericReturnType(), builder);
builder.append(" v) {\n");
builder.append(" ");
builder.append(getHasFieldName(fieldName));
builder.append(" = true;\n");
builder.append(" ");
builder.append(fieldName);
builder.append(" = ");
builder.append("v;\n return this;\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("\n public void ");
builder.append(getListAdderName(fieldName));
builder.append("(");
builder.append(getTypeArgumentImplName((ParameterizedType) method.getGenericReturnType()));
builder.append(" v) {\n ");
builder.append(getEnsureName(fieldName));
builder.append("();\n ");
builder.append(fieldName);
builder.append(".add(v);\n");
builder.append(" }\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("\n public void ");
builder.append(getMapPutterName(fieldName));
builder.append("(String k, ");
builder.append(getTypeArgumentImplName((ParameterizedType) method.getGenericReturnType()));
builder.append(" v) {\n ");
builder.append(getEnsureName(fieldName));
builder.append("();\n ");
builder.append(fieldName);
builder.append(".put(k, v);\n");
builder.append(" }\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("\n 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");
}
/**
* Emit a method that ensures a collection is initialized.
*/
private void emitEnsureCollection(Method method, String fieldName, StringBuilder builder) {
builder.append("\n private void ");
builder.append(getEnsureName(fieldName));
builder.append("() {\n");
builder.append(" if (!");
builder.append(getHasFieldName(fieldName));
builder.append(") {\n ");
builder.append(getSetterName(fieldName));
builder.append("(");
builder.append(fieldName);
builder.append(" != null ? ");
builder.append(fieldName);
builder.append(" : new ");
builder.append(getImplName(method.getGenericReturnType(), false));
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, true));
}
/**
* 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.
*
* 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 (isJsonArray(rawClass)) {
fqName =
fqName.replaceFirst(ArrayList.class.getCanonicalName(), List.class.getCanonicalName());
} else if (isJsonStringMap(rawClass)) {
fqName =
fqName.replaceFirst(HashMap.class.getCanonicalName(), Map.class.getCanonicalName());
}
}
return fqName;
}
/**
* Returns the fully-qualified type name including parameters.
*/
private String getFqParameterizedName(Type type) {
if (type instanceof Class<?>) {
return getImplNameForDto((Class<?>) type);
} else if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
StringBuilder sb = new StringBuilder(getRawClass(pType).getCanonicalName());
sb.append('<');
for (int i = 0; i < pType.getActualTypeArguments().length; i++) {
sb.append(getFqParameterizedName(pType.getActualTypeArguments()[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) {
Type[] typeArgs = type.getActualTypeArguments();
if (typeArgs.length == 0) {
return "Object";
}
return getImplName(typeArgs[0], false);
}
/**
* Returns the name of the field that indicates the specified field was set.
*/
private String getHasFieldName(String fieldName) {
return "_has" + getCamelCaseName(fieldName);
}
private String getImplNameForDto(Class<?> dtoInterface) {
if (getEnclosingTemplate().isDtoInterface(dtoInterface)) {
// This will eventually get a generated impl type.
return dtoInterface.getSimpleName() + "Impl";
}
return dtoInterface.getCanonicalName();
}
}