/*******************************************************************************
* 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.shared.CompactJsonDto;
import org.eclipse.che.dto.shared.DelegateTo;
import org.eclipse.che.dto.shared.SerializationIndex;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
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.LinkedList;
import java.util.List;
import java.util.Map;
/** Abstract base class for the source generating template for a single DTO. */
abstract class DtoImpl {
private final Class<?> dtoInterface;
private final DtoTemplate enclosingTemplate;
private final boolean compactJson;
private final String implClassName;
private final List<Method> dtoMethods;
DtoImpl(DtoTemplate enclosingTemplate, Class<?> dtoInterface) {
this.enclosingTemplate = enclosingTemplate;
this.dtoInterface = dtoInterface;
this.implClassName = dtoInterface.getSimpleName() + "Impl";
this.compactJson = DtoTemplate.implementsInterface(dtoInterface, CompactJsonDto.class);
this.dtoMethods = ImmutableList.copyOf(calcDtoMethods());
}
protected boolean isCompactJson() {
return compactJson;
}
public Class<?> getDtoInterface() {
return dtoInterface;
}
public DtoTemplate getEnclosingTemplate() {
return enclosingTemplate;
}
protected String getJavaFieldName(String getterName) {
String fieldName;
if (getterName.startsWith("get")) {
fieldName = getterName.substring(3);
} else {
// starts with "is", see method '#ignoreMethod(Method)'
fieldName = getterName.substring(2);
}
return normalizeIdentifier(Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1));
}
private String normalizeIdentifier(String fieldName) {
// use $ prefix according to http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.8
switch (fieldName) {
case "default":
fieldName = "$" + fieldName;
break;
// add other keywords here
}
return fieldName;
}
private String getCamelCaseName(String fieldName) {
// see normalizeIdentifier method
if (fieldName.charAt(0) == '$') {
fieldName = fieldName.substring(1);
}
return Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
}
protected String getImplClassName() {
return implClassName;
}
protected String getSetterName(String fieldName) {
return "set" + getCamelCaseName(fieldName);
}
protected String getWithName(String fieldName) {
return "with" + getCamelCaseName(fieldName);
}
protected String getListAdderName(String fieldName) {
return "add" + getCamelCaseName(fieldName);
}
protected String getMapPutterName(String fieldName) {
return "put" + getCamelCaseName(fieldName);
}
protected String getClearName(String fieldName) {
return "clear" + getCamelCaseName(fieldName);
}
protected String getEnsureName(String fieldName) {
return "ensure" + getCamelCaseName(fieldName);
}
protected String getJsonFieldName(String getterName) {
String fieldName;
if (getterName.startsWith("get")) {
fieldName = getterName.substring(3);
} else {
// starts with "is", see method '#ignoreMethod(Method)'
fieldName = getterName.substring(2);
}
return Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1);
}
/**
* Our super interface may implement some other interface (or not). We need to know because if it does then we need to directly extend
* said super interfaces impl class.
*/
protected Class<?> getSuperInterface(Class<?> dto) {
Class<?>[] superInterfaces = dto.getInterfaces();
return superInterfaces.length == 0 ? null : superInterfaces[0];
}
protected List<Method> getDtoGetters(Class<?> dto) {
List<Method> getters = new ArrayList<>();
if (enclosingTemplate.isDtoInterface(dto)) {
addDtoGetters(dto, getters);
}
return getters;
}
protected List<Method> getInheritedDtoGetters(Class<?> dto) {
List<Method> getters = new ArrayList<>();
if (enclosingTemplate.isDtoInterface(dto)) {
Class<?> superInterface = getSuperInterface(getDtoInterface());
while (superInterface != null) {
addDtoGetters(superInterface, getters);
superInterface = getSuperInterface(superInterface);
}
addDtoGetters(dto, getters);
}
return getters;
}
private void addDtoGetters(Class<?> dto, List<Method> getters) {
for (Method method : dto.getDeclaredMethods()) {
if (isDtoGetter(method)) {
getters.add(method);
}
}
}
/** Check is specified method is DTO getter. */
protected boolean isDtoGetter(Method method) {
if (method.isAnnotationPresent(DelegateTo.class)) {
return false;
}
String methodName = method.getName();
if ((methodName.startsWith("get") || methodName.startsWith("is")) && method.getParameterTypes().length == 0) {
if (methodName.startsWith("is") && methodName.length() > 2) {
return method.getReturnType() == Boolean.class || method.getReturnType() == boolean.class;
}
return methodName.length() > 3;
}
return false;
}
/** Tests whether or not a given generic type is allowed to be used as a generic. */
protected static boolean isWhitelisted(Class<?> genericType) {
return DtoTemplate.jreWhitelist.contains(genericType);
}
/** Tests whether or not a given return type is a number primitive or its wrapper type. */
protected static boolean isNumber(Class<?> returnType) {
final Class<?>[] numericTypes = {int.class, long.class, short.class, float.class, double.class, byte.class,
Integer.class, Long.class, Short.class, Float.class, Double.class, Byte.class};
for (Class<?> standardPrimitive : numericTypes) {
if (returnType.equals(standardPrimitive)) {
return true;
}
}
return false;
}
/** Tests whether or not a given return type is a boolean primitive or its wrapper type. */
protected static boolean isBoolean(Class<?> returnType) {
return returnType.equals(Boolean.class) || returnType.equals(boolean.class);
}
protected static String getPrimitiveName(Class<?> returnType) {
if (returnType.equals(Integer.class) || returnType.equals(int.class)) {
return "int";
} else if (returnType.equals(Long.class) || returnType.equals(long.class)) {
return "long";
} else if (returnType.equals(Short.class) || returnType.equals(short.class)) {
return "short";
} else if (returnType.equals(Float.class) || returnType.equals(float.class)) {
return "float";
} else if (returnType.equals(Double.class) || returnType.equals(double.class)) {
return "double";
} else if (returnType.equals(Byte.class) || returnType.equals(byte.class)) {
return "byte";
} else if (returnType.equals(Boolean.class) || returnType.equals(boolean.class)) {
return "boolean";
} else if (returnType.equals(Character.class) || returnType.equals(char.class)) {
return "char";
}
throw new IllegalArgumentException("Unknown wrapper class type.");
}
/** Tests whether or not a given return type is a java.util.List. */
public static boolean isList(Class<?> returnType) {
return returnType.equals(List.class);
}
/** Tests whether or not a given return type is a java.util.Map. */
public static boolean isMap(Class<?> returnType) {
return returnType.equals(Map.class);
}
/**
* Expands the type and its first generic parameter (which can also have a first generic parameter (...)).
* <p/>
* For example, JsonArray<JsonStringMap<JsonArray<SomeDto>>> would produce [JsonArray, JsonStringMap, JsonArray,
* SomeDto].
*/
public static List<Type> expandType(Type curType) {
List<Type> types = new LinkedList<>();
do {
types.add(curType);
if (curType instanceof ParameterizedType) {
Type[] genericParamTypes = ((ParameterizedType)curType).getActualTypeArguments();
Type rawType = ((ParameterizedType)curType).getRawType();
boolean map = rawType instanceof Class<?> && rawType == Map.class;
if (!map && genericParamTypes.length != 1) {
throw new IllegalStateException("Multiple type parameters are not supported (neither are zero type parameters)");
}
Type genericParamType = map ? genericParamTypes[1] : genericParamTypes[0];
if (genericParamType instanceof Class<?>) {
Class<?> genericParamTypeClass = (Class<?>)genericParamType;
if (isWhitelisted(genericParamTypeClass)) {
assert genericParamTypeClass.equals(
String.class) : "For JSON serialization there can be only strings or DTO types. ";
}
}
curType = genericParamType;
} else {
if (curType instanceof Class) {
Class<?> clazz = (Class<?>)curType;
if (isList(clazz) || isMap(clazz)) {
throw new DtoTemplate.MalformedDtoInterfaceException(
"JsonArray and JsonStringMap MUST have a generic type specified (and no... ? doesn't cut it!).");
}
}
curType = null;
}
} while (curType != null);
return types;
}
public static Class<?> getRawClass(Type type) {
return (Class<?>)((type instanceof ParameterizedType) ? ((ParameterizedType)type).getRawType() : type);
}
/**
* Returns public methods specified in DTO interface.
* <p/>
* <p>For compact DTO (see {@link org.eclipse.che.dto.shared.CompactJsonDto}) methods are ordered corresponding to {@link
* org.eclipse.che.dto.shared.SerializationIndex} annotation.
* <p/>
* <p>Gaps in index sequence are filled with {@code null}s.
*/
protected List<Method> getDtoMethods() {
return dtoMethods;
}
private Method[] calcDtoMethods() {
if (!compactJson) {
return dtoInterface.getMethods();
}
Map<Integer, Method> methodsMap = new HashMap<>();
int maxIndex = 0;
for (Method method : dtoInterface.getMethods()) {
SerializationIndex serializationIndex = method.getAnnotation(SerializationIndex.class);
Preconditions.checkNotNull(serializationIndex, "Serialization index is not specified for %s in %s",
method.getName(), dtoInterface.getSimpleName());
// "53" is the number of bits in JS integer.
// This restriction will allow to add simple bit-field
// "serialization-skipping-list" in the future.
int index = serializationIndex.value();
Preconditions.checkState(index > 0 && index <= 53, "Serialization index out of range [1..53] for %s in %s",
method.getName(), dtoInterface.getSimpleName());
Preconditions.checkState(!methodsMap.containsKey(index), "Duplicate serialization index for %s in %s",
method.getName(), dtoInterface.getSimpleName());
maxIndex = Math.max(index, maxIndex);
methodsMap.put(index, method);
}
Method[] result = new Method[maxIndex];
for (int index = 0; index < maxIndex; index++) {
result[index] = methodsMap.get(index + 1);
}
return result;
}
protected boolean isLastMethod(Method method) {
Preconditions.checkNotNull(method);
return method == dtoMethods.get(dtoMethods.size() - 1);
}
/**
* @return String representing the source definition for the DTO impl as an inner class.
*/
abstract String serialize();
}