/*
* Copyright 2010 kk-electronic a/s.
*
* This file is part of KKPortal.
*
* KKPortal is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KKPortal is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with KKPortal. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kk_electronic.gwt.rebind;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.Map.Entry;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JGenericType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.inject.Singleton;
import com.kk_electronic.kkportal.core.rpc.FrameEncoder;
import com.kk_electronic.kkportal.core.rpc.JsonEncoderHelper;
import com.kk_electronic.kkportal.core.rpc.RemoteService;
import com.kk_electronic.kkportal.core.rpc.Rename;
import com.kk_electronic.kkportal.core.rpc.jsonformat.JsonValue;
import com.kk_electronic.kkportal.core.rpc.jsonformat.UnableToDeserialize;
import com.kk_electronic.kkportal.core.rpc.jsonformat.UnableToSerialize;
/**
* Generates the Helper class for JsonEncoder as well as attempts to implement any missing JsonValue classes.
*
* @author Rasmus Carlsen
*
*/
public class JsonEncoderGenerator extends Generator{
private String packageName;
private String className;
private TypeOracle typeOracle;
private JClassType classType;
private TreeLogger logger;
private HashMap<String, String> map = new HashMap<String, String>();
private ArrayList<JClassType> worklist = new ArrayList<JClassType>();
@Override
public String generate(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
try {
typeOracle = context.getTypeOracle();
classType = typeOracle.getType(typeName);
packageName = classType.getPackage().getName();
className = classType.getSimpleSourceName() + "Impl";
this.logger = logger;
retrieveInformation();
generateJsonFormatterClasses(context);
generateHelperClass(context);
} catch (NotFoundException e) {
logger.log(TreeLogger.ERROR, "Exception during ClassMap creation.", e);
throw new UnableToCompleteException();
}
return packageName + "." + className;
//return null;
}
/**
* @throws UnableToCompleteException
*
*/
private void retrieveInformation() throws UnableToCompleteException {
JClassType desiredInterface;
try {
desiredInterface = typeOracle.getType(AsyncCallback.class.getCanonicalName());
} catch (NotFoundException e) {
logger.log(TreeLogger.ERROR, "Unable to retrieve the AsyncCallback class");
return;
}
processJsonValueClasses();
JClassType[] remoteServices = getClasses(RemoteService.class.getCanonicalName(), true);
for (JClassType service : remoteServices) {
JMethod[] methods = service.getMethods();
for (JMethod m : methods) {
JParameter[] paras = m.getParameters();
for (JParameter p : paras) {
JType t = p.getType();
if (t == null) {
continue;
}
JClassType jct = t.isInterface();
if (jct != null) {
if (jct.isAssignableTo(desiredInterface)) {
processGenerics(jct);
jct = null;
}
} else {
jct = t.isClass();
}
if (jct == null){
continue;
}
processClass(jct);
}
}
}
}
private void processClass(JClassType jct) {
processGenerics(jct);
if(checkClass(jct) && jct.isInterface() == null) {
processFields(jct);
}
}
private void processGenerics(JType jct) {
JParameterizedType jpt = jct.isParameterized();
if (jpt == null) {
return;
}
JClassType[] cd = jpt.getTypeArgs();
for (JClassType type : cd) {
processClass(type);
}
}
private void processFields(JClassType jct) {
JField[] fields = jct.getFields();
for (JField jField : fields) {
JClassType jClass = jField.getType().isClass();
processGenerics(jField.getType());
if (jClass != null && !jField.isTransient()) {
processClass(jClass);
}
}
}
private boolean checkClass(JClassType type) {
boolean b = false;
if (!map.containsKey(type.getQualifiedSourceName()) && type.isWildcard() == null && !worklist.contains(type)) {
b = worklist.add(type);
}
return b;
}
/**
* @throws UnableToCompleteException
*
*/
private void processJsonValueClasses() throws UnableToCompleteException {
JClassType desiredInterface;
try {
desiredInterface = typeOracle.getType(JsonValue.class.getCanonicalName());
} catch (NotFoundException e) {
logger.log(TreeLogger.ERROR, "Unable to retrieve the JsonValue class");
throw new UnableToCompleteException();
}
JClassType[] types = getClasses(JsonValue.class.getCanonicalName(), false);
for (JClassType jClassType : types) {
JClassType[] interfaces = jClassType.getImplementedInterfaces();
for (JClassType classType : interfaces) {
JParameterizedType parameterizedType = classType.isParameterized();
if (parameterizedType == null || !classType.isAssignableFrom(desiredInterface)) {
continue;
}
String jsonValueClassName = jClassType.getQualifiedSourceName();
JClassType[] cd = parameterizedType.getTypeArgs();
for (JClassType typeArg : cd) {
String originalClassName = typeArg.getQualifiedSourceName();
map.put(originalClassName, jsonValueClassName);
}
}
}
}
/**
* @param context
* @throws UnableToCompleteException
*/
private void generateJsonFormatterClasses(GeneratorContext context) throws UnableToCompleteException {
if (worklist == null || worklist.size() == 0) {
return;
}
for (JClassType jc : worklist) {
if (!jc.isDefaultInstantiable()) {
logger.log(TreeLogger.ERROR, "Unable to generate class for " + jc.getSimpleSourceName() + " mising default constructor");
} else {
SubClassGenerator gen = new SubClassGenerator();
String implentationClassName;
implentationClassName = gen.generateSubClass(context, jc);
if(implentationClassName == null){
return;
}
map.put(jc.getQualifiedSourceName(), implentationClassName);
}
}
}
/**
* @param context
*/
private void generateHelperClass(GeneratorContext context) throws UnableToCompleteException, NotFoundException {
PrintWriter printWriter = context.tryCreate(logger, packageName, className);
if (printWriter == null) {
return;
}
ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(packageName,className);
composer.addImplementedInterface(JsonEncoderHelper.class.getCanonicalName());
composer.addImport(Singleton.class.getCanonicalName());
composer.addImport(Map.class.getCanonicalName());
composer.addImport(HashMap.class.getCanonicalName());
composer.addImport(JsonEncoderHelper.class.getCanonicalName());
composer.addImport(JsonValue.class.getCanonicalName());
composer.addAnnotationDeclaration("@Singleton");
SourceWriter sourceWriter = composer.createSourceWriter(context,printWriter);
writeHelperClass(sourceWriter);
sourceWriter.outdent();
sourceWriter.println("}");
context.commit(logger, printWriter);
}
private void writeHelperClass(SourceWriter sw) {
//Write variables
sw.println("private HashMap<Class<?>, JsonValue<?>> map = new HashMap<Class<?>, JsonValue<?>>();");
sw.println(); // Empty line for style
//Write constructor
sw.println("public " + className + "() {");
sw.indent();
for (Entry<String, String> entry : map.entrySet()) {
sw.println("map.put(" + entry.getKey() + ".class, new " + entry.getValue() + "());");
}
sw.outdent();
sw.println("}");
sw.println(); // Empty line for style
//Write functions
sw.println("@Override");
sw.println("public Map<Class<?>, JsonValue<?>> getGeneratedMap() {");
sw.indent();
sw.println("return map;");
sw.outdent();
sw.println("}");
}
private JClassType[] getClasses(String classCanonicalName, boolean allowAbstract) {
Vector<JClassType> vector = new Vector<JClassType>();
JClassType desiredInterface;
try {
desiredInterface = typeOracle.getType(classCanonicalName);
} catch (NotFoundException e) {
logger.log(TreeLogger.ERROR, "Can't find marker interface: " + classCanonicalName, e);
return new JClassType[]{};
}
for(JClassType j : typeOracle.getTypes()){
if(j.isAssignableTo(desiredInterface) && (!j.isAbstract() || allowAbstract)){
vector.add(j);
}
}
JClassType[] classArray = new JClassType[vector.size()];
vector.copyInto(classArray);
return classArray;
}
private class SubClassGenerator {
private String packageName;
private String className;
private JField[] fields;
private JClassType jc;
private String suffix;
private String getParameterizedSimpleSourceName(JGenericType type){
StringBuffer sb = new StringBuffer();
sb.append('<');
boolean needComma = false;
for (JClassType typeParam : type.getTypeParameters()) {
if (needComma) {
sb.append(", ");
} else {
needComma = true;
}
sb.append(typeParam.getParameterizedQualifiedSourceName());
}
sb.append('>');
return sb.toString();
}
/**
* @param context
* @param localPackageName
* @param jc
* @param localClassName
*/
public String generateSubClass(GeneratorContext context, JClassType jc) {
this.jc = jc;
this.className = "Json" + jc.getSimpleSourceName();
JParameterizedType parameterizedType = jc.isParameterized();
if(parameterizedType != null){
this.jc = parameterizedType.getBaseType();
this.suffix = getParameterizedSimpleSourceName(parameterizedType.getBaseType());
} else {
this.suffix = "";
}
this.packageName = jc.getPackage().getName();
PrintWriter printWriter = context.tryCreate(logger, packageName, className);
if (printWriter == null) {
return null;
}
ClassSourceFileComposerFactory composer = null;
/*
* Changes the name to fit with any generics of the object.
*/
composer = new ClassSourceFileComposerFactory(packageName, className + suffix);
fields = getFields(this.jc);
composer.addImport(List.class.getCanonicalName());
composer.addImport(JSONValue.class.getCanonicalName());
composer.addImport(JSONObject.class.getCanonicalName());
composer.addImport(FrameEncoder.class.getCanonicalName());
composer.addImport(UnableToSerialize.class.getCanonicalName());
composer.addImport(UnableToDeserialize.class.getCanonicalName());
composer.addImplementedInterface(JsonValue.class.getCanonicalName() + "<" + jc.getQualifiedSourceName() + suffix + ">");
SourceWriter sourceWriter = composer.createSourceWriter(context,printWriter);
writeFromJson(sourceWriter);
writeToJson(sourceWriter);
writeNatives(sourceWriter);
sourceWriter.outdent();
sourceWriter.println("}");
context.commit(logger, printWriter);
return packageName + "." + className;
}
private void writeNatives(SourceWriter sw) {
String s = "obj.@" + jc.getQualifiedSourceName() + "::";
//Native set function
sw.println();
sw.print("private native void setFields(" + jc.getQualifiedSourceName() + suffix + " obj");
for (JField f : fields) {
sw.print(", " + getFieldType(f) + " " + f.getName());
}
sw.println(") /*-{");
sw.indent();
for (JField f : fields) {
sw.println(s + f.getName() + " = " + f.getName() + ";");
}
sw.outdent();
sw.println("}-*/;");
//Native get functions
for (JField f : fields) {
sw.println();
sw.println("private native " + getFieldType(f) + " get" + f.getName() + "(" + jc.getQualifiedSourceName() + suffix + " obj) /*-{");
sw.indent();
sw.println("return " + s + f.getName() + ";");
sw.outdent();
sw.println("}-*/;");
}
}
private String getFieldType(JField f) {
return f.getType().getParameterizedQualifiedSourceName();
}
private String getFieldType(JField f, boolean addClass) {
JType j = f.getType();
String type;
if (addClass) {
if (j.isParameterized() != null) {
type = recursiveTypeFind(j);
} else {
type = j.getQualifiedSourceName();
if (addClass) {
type = type + ".class";
}
}
} else {
type = j.getParameterizedQualifiedSourceName();
}
return type;
}
private String recursiveTypeFind(JType j) {
String type = j.getQualifiedSourceName();
type = type + ".class";
if (j.isParameterized() != null) {
JClassType[] a = j.isParameterized().getTypeArgs();
boolean first = true;
for (JClassType jClassType : a) {
if (!first) {
type = type + ", ";
first = false;
}
type = type + "," + recursiveTypeFind(jClassType);
}
}
return type;
}
private void writeToJson(SourceWriter sw) {
sw.println();
sw.println("@Override");
sw.println("public void toJson(StringBuilder response, " + jc.getQualifiedSourceName() + suffix + " object,");
sw.indent();
sw.indentln("FrameEncoder<JSONValue> encoder) throws UnableToSerialize {");
sw.println("response.append(\"{\");");
Boolean first = true;
String t = "response.append(\",\");";
for (JField f : fields) {
if (!first) {
sw.println();
sw.println(t);
sw.println();
} else {
sw.println();
first = false;
}
sw.println("encoder.encode(\"" + getJsonName(f) + "\", response);");
sw.println("response.append(\":\");");
sw.println("encoder.encode(get" + f.getName() + "(object), response);");
}
sw.println();
sw.println("response.append(\"}\");");
sw.outdent();
sw.println("}");
}
private void writeFromJson(SourceWriter sw) {
//Begin Function
sw.println();
sw.println("@Override");
sw.println("public " + jc.getQualifiedSourceName() + suffix + " fromJson(JSONValue jsonValue, List<Class<?>> subtypes,");
sw.indent();
sw.indentln("FrameEncoder<JSONValue> encoder) throws UnableToDeserialize {");
//boiler plate null check
sw.println("if (jsonValue.isObject() == null)");
sw.indentln("throw new UnableToDeserialize(\"Expected Json Object\");");
sw.println("JSONObject jsonObject = jsonValue.isObject();");
sw.println();
//make vars
for (JField f : fields) {
sw.println(getFieldType(f) + " " + f.getName() + " = null;");
}
sw.println();
//validate
for (JField f : fields) {
sw.print(f.getName() + " = encoder.validate(jsonObject.get(\"" + getJsonName(f) + "\"), ");
if(f.getType().isTypeParameter() == null) {
sw.println(f.getName() + ", new Class<?>[]{" + getFieldType(f, true) + "});");
} else {
sw.println(f.getName() + ", subtypes);");
}
}
sw.println();
//create new object
sw.println(jc.getQualifiedSourceName() + suffix + " obj = new " + jc.getQualifiedSourceName() + suffix + "();");
//Call native set
sw.print("setFields(obj");
for (JField f : fields) {
sw.print(", " + f.getName());
}
sw.println(");");
//return object
sw.println("return obj;");
//End function
sw.outdent();
sw.println("}");
}
/**
* @param f
* @return
*/
private String getJsonName(JField f) {
Rename rename = f.getAnnotation(Rename.class);
if (rename != null) {
return rename.value();
} else {
return f.getName();
}
}
private JField[] getFields(JClassType j) {
assert(j != null);
Vector<JField> vector = new Vector<JField>();
for (JField f : j.getFields()) {
if(!f.isTransient()){
vector.add(f);
}
}
JField[] fieldArray = new JField[vector.size()];
vector.copyInto(fieldArray);
Arrays.sort(fieldArray, new Comparator<JField>() {
@Override
public int compare(JField o1, JField o2) {
return getJsonName(o1).compareTo(getJsonName(o2));
}
});
return fieldArray;
}
}
}