/*
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.totsp.gwittir.serial.json.rebind;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JGenericType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONBoolean;
import com.google.gwt.json.client.JSONNull;
import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.totsp.gwittir.rebind.beans.BeanResolver;
import com.totsp.gwittir.rebind.beans.IntrospectorGenerator;
import com.totsp.gwittir.rebind.beans.RProperty;
import com.totsp.gwittir.serial.client.SerializationException;
import com.totsp.gwittir.serial.json.client.JSONCodec;
import com.totsp.gwittir.serial.json.client.JSONField;
import com.totsp.gwittir.serial.json.client.JSONOmit;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
*
* @author kebernet
*/
public class JSONCodecGenerator extends IntrospectorGenerator {
private static final HashSet<String> CORE_TYPES = new HashSet<String>();
static {
CORE_TYPES.add(String.class.getCanonicalName());
CORE_TYPES.add(Double.class.getCanonicalName());
CORE_TYPES.add(double.class.getCanonicalName());
CORE_TYPES.add(Integer.class.getCanonicalName());
CORE_TYPES.add(int.class.getCanonicalName());
CORE_TYPES.add(Float.class.getCanonicalName());
CORE_TYPES.add(float.class.getCanonicalName());
CORE_TYPES.add(Long.class.getCanonicalName());
CORE_TYPES.add(long.class.getCanonicalName());
CORE_TYPES.add(Boolean.class.getCanonicalName());
CORE_TYPES.add(boolean.class.getCanonicalName());
CORE_TYPES.add(Short.class.getCanonicalName());
CORE_TYPES.add(short.class.getCanonicalName());
CORE_TYPES.add(Character.class.getCanonicalName());
CORE_TYPES.add(char.class.getCanonicalName());
}
private HashSet<BeanResolver> children = new HashSet<BeanResolver>();
private JClassType collectionType;
private JClassType numberType;
private JType listType;
private JType setType;
private List<BeanResolver> types;
public String fromType(JType type, String innerExpression) {
if (type.getQualifiedSourceName().equals(String.class.getCanonicalName())) {
return innerExpression + ".isString().stringValue()";
} else if (type.getQualifiedSourceName()
.equals(Double.class.getCanonicalName()) ||
((type.isPrimitive() != null) &&
JPrimitiveType.DOUBLE.equals(type.isPrimitive()))) {
return innerExpression + ".isNumber().doubleValue()";
} else if (type.getQualifiedSourceName()
.equals(Float.class.getCanonicalName()) ||
((type.isPrimitive() != null) &&
JPrimitiveType.FLOAT.equals(type.isPrimitive()))) {
return " (float) " + innerExpression + ".isNumber().doubleValue()";
} else if (type.getQualifiedSourceName()
.equals(Integer.class.getCanonicalName()) ||
((type.isPrimitive() != null) &&
JPrimitiveType.INT.equals(type.isPrimitive()))) {
return "Double.valueOf(" + innerExpression +
".isNumber().doubleValue()) .intValue()";
} else if (type.getQualifiedSourceName()
.equals(Short.class.getCanonicalName()) ||
((type.isPrimitive() != null) &&
JPrimitiveType.SHORT.equals(type.isPrimitive()))) {
return "Short.valueOf(" + innerExpression +
".isNumber().doubleValue()) .shortValue()";
} else if (type.getQualifiedSourceName()
.equals(Character.class.getCanonicalName()) ||
((type.isPrimitive() != null) &&
JPrimitiveType.CHAR.equals(type.isPrimitive()))) {
return "" + innerExpression +
".isString().stringValue().charAt(0)";
} else if (type.getQualifiedSourceName()
.equals(Long.class.getCanonicalName()) ||
((type.isPrimitive() != null) &&
JPrimitiveType.LONG.equals(type.isPrimitive()))) {
return "Double.valueOf( " + innerExpression +
".isNumber().doubleValue()).longValue()";
} else if (type.getQualifiedSourceName()
.equals(Boolean.class.getCanonicalName()) ||
((type.isPrimitive() != null) &&
JPrimitiveType.BOOLEAN.equals(type.isPrimitive()))) {
return innerExpression + ".isBoolean().booleanValue() ";
}
BeanResolver child = findType(type);
if (child != null) {
this.children.add(child);
return "CODEC_" +
child.getType().getQualifiedSourceName().replaceAll("\\.", "_") +
".deserializeFromJSONObject(" + innerExpression + ".isObject())";
}
throw new RuntimeException(""+type);
}
@Override
public String generate(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
logger.log(Type.INFO, "Generating codec for " + typeName);
JClassType type = null;
JClassType jsonCodec = null;
try {
type = context.getTypeOracle().getType(typeName);
jsonCodec = context.getTypeOracle()
.getType(JSONCodec.class.getCanonicalName());
this.collectionType = context.getTypeOracle()
.getType(Collection.class.getCanonicalName());
this.listType = context.getTypeOracle()
.getType(List.class.getCanonicalName());
this.setType = context.getTypeOracle()
.getType(Set.class.getCanonicalName());
this.numberType = context.getTypeOracle()
.getType(Number.class.getCanonicalName());
} catch (NotFoundException ex) {
logger.log(TreeLogger.ERROR, typeName, ex);
return null;
}
if (type.isClass() != null) { //Don't regenerate from Impls.
return type.getQualifiedSourceName();
}
JClassType subtype = type.asParameterizationOf((JGenericType) jsonCodec)
.getTypeArgs()[0];
this.types = this.getIntrospectableTypes(logger, context.getTypeOracle());
BeanResolver thisType = null;
for (BeanResolver r : this.types) {
if (r.getType().equals(subtype)) {
thisType = r;
break;
}
}
if (thisType == null) {
logger.log(Type.ERROR,
"Unable to find introspectable type " + subtype);
throw new UnableToCompleteException();
}
this.writeClassSerializer(logger, context, thisType);
this.writeTopSerializer(logger, context, type, subtype);
return type.getQualifiedSourceName() + "_Impl";
}
public void writeReader(SourceWriter writer, RProperty prop) {
if(prop.getWriteMethod() == null ){
return;
}
JSONField field = prop.getReadMethod() == null ? null : prop.getReadMethod().getBaseMethod()
.getAnnotation(JSONField.class);
JSONOmit omit = prop.getReadMethod() == null ? null : prop.getReadMethod().getBaseMethod()
.getAnnotation(JSONOmit.class);
System.out.println( prop.getName() + " omit "+omit + " field "+field );
if (omit != null) {
return;
}
String fieldName = (field == null) ? prop.getName() : field.value();
try {
writer.println("if(root.containsKey(\"" + fieldName + "\")){");
if (prop.getType().isPrimitive() == null) {
writer.println("if(root.get(\"" + fieldName +
"\").isNull() != null) {");
writer.println(this.setterPrefix(prop) + "null);");
writer.println("} else {");
}
if(prop.getType().isArray() != null){
JArrayType arrayType = prop.getType().isArray();
JType paramType = arrayType.getComponentType();
writer.println("JSONArray array = root.get(\"" + fieldName +
"\").isArray();");
writer.println( paramType.getQualifiedSourceName()+"[] value = new "+
paramType.getQualifiedSourceName()+"[ array.size() ];");
writer.println("for(int i=0; i<array.size(); i++){");
writer.indent();
writer.println(" value[i] = " +
this.fromType(paramType, "array.get(i)") + ";");
writer.outdent();
writer.println("}"); //endfor
writer.println(this.setterPrefix(prop) + " value );");
} else if (prop.getType() instanceof JClassType &&
((JClassType) prop.getType()).isAssignableTo(
this.collectionType)) {
// get the parameter type
JClassType propType = (JClassType) prop.getType();
JType paramType = propType.asParameterizationOf((JGenericType) this.collectionType)
.getTypeArgs()[0];
writer.println("JSONArray array = root.get(\"" + fieldName +
"\").isArray();");
writer.println(propType.getParameterizedQualifiedSourceName() +
" col = " + this.newCollectionExpression(propType) + ";");
writer.println("for(int i=0; i<array.size(); i++){");
writer.indent();
writer.println(" col.add(" +
this.fromType(paramType, "array.get(i)") + ");");
writer.outdent();
writer.println("}"); //endfor
writer.println(this.setterPrefix(prop) + " col );");
} else {
writer.println(setterPrefix(prop) +
fromType(prop.getType(), "root.get(\"" + fieldName + "\")") +
");");
}
if (prop.getType().isPrimitive() == null) {
writer.println("}"); //end null else
}
writer.println("}"); //end contains key
} catch (Exception e) {
System.out.println("Exception on prop " + prop);
throw new RuntimeException(e);
}
}
private boolean isCoreType(JType type) {
return CORE_TYPES.contains(type.getQualifiedSourceName());
}
private BeanResolver findType(JType type) {
for (BeanResolver r : this.types) {
if (r.getType().equals(type)) {
return r;
}
}
return null;
}
private String newCollectionExpression(JClassType type) {
if (type.isParameterized().getBaseType().equals(this.listType)) {
return "new java.util.ArrayList()";
} else if (type.isParameterized().getBaseType().equals(this.setType)) {
return "new java.util.HashSet()";
} else {
return "new " + type.getParameterizedQualifiedSourceName() + "()";
}
}
private String setterPrefix(RProperty prop) {
return "destination." +
prop.getWriteMethod().getBaseMethod().getName() + "( ";
}
private String toType(JType type, String innerExpression) {
//System.out.println("toType " + type);
if ((type.isPrimitive() == JPrimitiveType.DOUBLE) ||
(type.isPrimitive() == JPrimitiveType.FLOAT) ||
(type.isPrimitive() == JPrimitiveType.LONG) ||
(type.isPrimitive() == JPrimitiveType.INT) ||
(type.isPrimitive() == JPrimitiveType.SHORT)) {
return " new JSONNumber( (double) " + innerExpression + ")";
} else if (type.isPrimitive() == JPrimitiveType.BOOLEAN) {
return " JSONBoolean.getInstance( " + innerExpression + " ) ";
} else if (type.isPrimitive() == JPrimitiveType.CHAR ){
return " new JSONString( Character.toString("+ innerExpression +") )";
}
StringBuilder sb = new StringBuilder(innerExpression +
" == null ? JSONNull.getInstance() : ");
if (type.getQualifiedSourceName().equals("java.lang.String")) {
sb = sb.append(" new JSONString( " + innerExpression + " ) ");
} else if(type.getQualifiedSourceName().equals("java.lang.Character")){
sb = sb.append(" new JSONString( Character.toString(" + innerExpression + ") ) ");
}else if (type.isClassOrInterface() != null && type.isClassOrInterface().isAssignableTo(this.numberType)) {
sb = sb.append("new JSONNumber( ((Number) " + innerExpression +
").doubleValue())");
} else if (type.getQualifiedSourceName().equals("java.lang.Boolean")) {
sb.append(" JSONBoolean.getInstance( " + innerExpression + " ) ");
} else {
BeanResolver child = findType(type);
if (child == null) {
throw new RuntimeException(type+" is not introspectable!");
}
this.children.add(child);
sb = sb.append("CODEC_" +
type.getQualifiedSourceName().replaceAll("\\.", "_") +
".serializeToJSONObject( " + innerExpression + " ) ");
}
return sb.toString();
}
private void writeClassSerializer(TreeLogger logger,
GeneratorContext context, BeanResolver type) {
logger.log(Type.INFO, "Creating JSON Serializer for " + type.getType());
String classTypeName = type.getType().getSimpleSourceName() +
"_JSONCodec";
ClassSourceFileComposerFactory mcf = new ClassSourceFileComposerFactory(type.getType()
.getPackage()
.getName(),
classTypeName);
mcf.addImport(JSONParser.class.getCanonicalName());
mcf.addImport(JSONObject.class.getCanonicalName());
mcf.addImport(JSONArray.class.getCanonicalName());
mcf.addImport(JSONBoolean.class.getCanonicalName());
mcf.addImport(JSONNumber.class.getCanonicalName());
mcf.addImport(JSONString.class.getCanonicalName());
mcf.addImport(JSONNull.class.getCanonicalName());
mcf.addImport(GWT.class.getCanonicalName());
mcf.addImport(SerializationException.class.getCanonicalName());
mcf.addImplementedInterface(JSONCodec.class.getCanonicalName() + "<" +
type.getType().getQualifiedSourceName() + ">");
PrintWriter printWriter = context.tryCreate(logger,
type.getType().getPackage().getName(), classTypeName);
if (printWriter == null) {
logger.log(Type.INFO, "Already genned " + classTypeName);
return;
}
SourceWriter writer = mcf.createSourceWriter(context, printWriter);
writeDeserializer(writer, type);
writeSerializer(writer, type);
HashSet<BeanResolver> childrenCopy = new HashSet<BeanResolver>(this.children);
this.children.clear();
for (BeanResolver child : childrenCopy) {
writer.println(" private static final " +
JSONCodec.class.getCanonicalName() + "<" +
child.getType().getQualifiedSourceName() + "> CODEC_" +
child.getType().getQualifiedSourceName().replaceAll("\\.", "_") +
" = GWT.create(" + child.getType().getQualifiedSourceName() +
"_JSONCodec.class);");
writeClassSerializer(logger, context, child);
}
writer.println(" public String getMimeType() { return MIME_TYPE; }");
writer.println("}"); // close the class
context.commit(logger, printWriter);
}
private void writeDeserializer(SourceWriter writer, BeanResolver r) {
writer.println("public " + r.toString() +
" deserialize(String data) throws SerializationException { ");
writer.indent();
writer.println("try {");
writer.indent();
writer.println("JSONObject root = JSONParser.parse(data).isObject(); ");
writer.println(" return this.deserializeFromJSONObject(root);");
writer.println("} catch (Exception e) { ");
writer.indent();
writer.println("throw new SerializationException(e);");
writer.outdent();
writer.println("}");
writer.println("}");
writer.println("public " + r.toString() +
" deserializeFromJSONObject(JSONObject root) throws SerializationException {");
writer.indent();
writer.println(" if(root == null) return null;");
writer.println("try {");
writer.indent();
writer.println(r.getType().getQualifiedSourceName() +
" destination = new " + r.getType().getQualifiedSourceName() +
"();");
for (RProperty p : r.getProperties().values()) {
if (p.getWriteMethod() != null) {
writeReader(writer, p);
}
}
writer.println(" return destination;");
writer.outdent();
writer.println("} catch (Exception e) { ");
writer.indent();
writer.println("throw new SerializationException(e);");
writer.outdent();
writer.println("}");
writer.outdent();
writer.println("}");
}
private void writeSerializer(SourceWriter writer, BeanResolver r) {
writer.println("public JSONObject serializeToJSONObject( " +
r.getType().getQualifiedSourceName() +
" source ) throws SerializationException { ");
writer.indent();
writer.println(" JSONObject destination = new JSONObject();");
for (RProperty prop : r.getProperties().values()) {
if (prop.getName().equals("class") || prop.getReadMethod() == null) {
continue;
}
JSONField field = prop.getReadMethod().getBaseMethod()
.getAnnotation(JSONField.class);
JSONOmit omit = prop.getReadMethod().getBaseMethod()
.getAnnotation(JSONOmit.class);
System.out.println(" ws \t "+prop.getName() +" "+ prop.getReadMethod().getBaseMethod().getEnclosingType()+ prop.getReadMethod().getBaseMethod().getReadableDeclaration() + " "+ omit +" "+field );
if (omit != null) {
continue;
}
String fieldName = (field == null) ? prop.getName() : field.value();
if (prop.getReadMethod() != null) {
JClassType classType = prop.getType().isClassOrInterface();
JArrayType arrayType = prop.getType().isArray();
System.out.println(prop.getName()+ " ArrayType "+arrayType +" :: "+((arrayType == null ? "" : ""+arrayType.getComponentType())));
if ((classType != null) &&
(classType.isAssignableTo(this.collectionType)) ||
arrayType != null) {
JType subType = (arrayType != null)
? arrayType.getComponentType()
: classType.asParameterizationOf(this.collectionType.isGenericType())
.getTypeArgs()[0];
writer.println();
writer.println(" if( source." +
prop.getReadMethod().getBaseMethod().getName() +
"() == null ){");
writer.println("destination.put(\"" + fieldName +
"\", JSONNull.getInstance());");
writer.println(" } else { ");
writer.println(
"int i=0; JSONArray value = new JSONArray();");
writer.println("for( " + subType.getQualifiedSourceName() +
" o : source." +
prop.getReadMethod().getBaseMethod().getName() +
"()){");
writer.println(" value.set(i++, " +
toType(subType, " o ") + ");");
writer.println("}");
writer.println("destination.put(\"" + fieldName +
"\", value);"); //TODO JSONField
writer.println("}");
} else {
writer.print("destination.put( \"" + fieldName + "\", "); //TODO JSONField
writer.print(toType(prop.getType(),
" source." +
prop.getReadMethod().getBaseMethod().getName() +
"() ") + ");");
}
}
}
writer.outdent();
writer.println("return destination;");
writer.println("}");
writer.println("public String serialize(" +
r.getType().getQualifiedSourceName() +
" source ) throws SerializationException { ");
writer.println(" return serializeToJSONObject(source).toString();");
writer.println("}");
}
private void writeTopSerializer(TreeLogger logger,
GeneratorContext context, JClassType typeFor, JClassType subType) {
ClassSourceFileComposerFactory mcf = new ClassSourceFileComposerFactory(typeFor.getPackage()
.getName(),
typeFor.getSimpleSourceName() + "_Impl");
mcf.addImplementedInterface(typeFor.getParameterizedQualifiedSourceName());
mcf.setSuperclass(subType.getParameterizedQualifiedSourceName() +
"_JSONCodec");
PrintWriter printWriter = context.tryCreate(logger,
typeFor.getPackage().getName(),
typeFor.getSimpleSourceName() + "_Impl");
if (printWriter == null) {
logger.log(Type.INFO,
"Already genned " + typeFor.getSimpleSourceName() + "_Impl");
return;
}
SourceWriter writer = mcf.createSourceWriter(context, printWriter);
writer.println(" public String getMimeType() { return MIME_TYPE; }");
writer.println("}");
context.commit(logger, printWriter);
}
}