/* * Copyright 2014 Grow Bit * * 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 org.turbogwt.net.http.rebind; import com.github.nmorel.gwtjackson.client.ObjectMapper; import com.github.nmorel.gwtjackson.client.ObjectReader; import com.github.nmorel.gwtjackson.client.ObjectWriter; import com.google.gwt.core.client.GWT; 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.JPackage; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.turbogwt.core.util.client.Overlays; import org.turbogwt.net.serialization.client.DeserializationContext; import org.turbogwt.net.serialization.client.Deserializer; import org.turbogwt.net.serialization.client.Serdes; import org.turbogwt.net.serialization.client.SerializationContext; import org.turbogwt.net.serialization.client.Serializer; import org.turbogwt.net.serialization.client.json.JsonObjectSerdes; import org.turbogwt.net.serialization.client.json.JsonRecordReader; import org.turbogwt.net.serialization.client.json.JsonRecordWriter; import org.turbogwt.net.serialization.shared.Json; /** * Generator for {@link org.turbogwt.net.serialization.shared.Json} annotated types. * * @author Danilo Reinert */ public class JsonSerdesGenerator extends Generator { @Override public String generate(TreeLogger logger, GeneratorContext ctx, String typeName) throws UnableToCompleteException { TypeOracle typeOracle = ctx.getTypeOracle(); assert typeOracle != null; JClassType intfType = typeOracle.findType(typeName); if (intfType == null) { logger.log(TreeLogger.ERROR, "Unable to find metadata for type '" + typeName + "'", null); throw new UnableToCompleteException(); } if (intfType.isInterface() == null) { logger.log(TreeLogger.ERROR, intfType.getQualifiedSourceName() + " is not an interface", null); throw new UnableToCompleteException(); } TreeLogger typeLogger = logger.branch(TreeLogger.ALL, "Generating SerDes powered by Gwt Jackson...", null); final SourceWriter sourceWriter = getSourceWriter(typeLogger, ctx, intfType); if (sourceWriter != null) { sourceWriter.println(); final ArrayList<String> serdes = new ArrayList<>(); for (JClassType type : typeOracle.getTypes()) { Json annotation = type.getAnnotation(Json.class); if (annotation != null) { serdes.add(generateSerdes(sourceWriter, type, annotation)); } } generateFields(sourceWriter); generateConstructor(sourceWriter, serdes); generateIteratorMethod(sourceWriter); sourceWriter.commit(typeLogger); } return typeName + "Impl"; } private String getTypeSimpleName() { return "GeneratedJsonSerdesImpl"; } private SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext ctx, JClassType intfType) { JPackage serviceIntfPkg = intfType.getPackage(); String packageName = serviceIntfPkg == null ? "" : serviceIntfPkg.getName(); PrintWriter printWriter = ctx.tryCreate(logger, packageName, getTypeSimpleName()); if (printWriter == null) { return null; } ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, getTypeSimpleName()); String[] imports = new String[] { // java ArrayList.class.getCanonicalName(), Collection.class.getCanonicalName(), HashSet.class.getCanonicalName(), Iterator.class.getCanonicalName(), LinkedHashSet.class.getCanonicalName(), LinkedList.class.getCanonicalName(), List.class.getCanonicalName(), Set.class.getCanonicalName(), TreeSet.class.getCanonicalName(), // com.github.nmorel.gwtjackson ObjectMapper.class.getCanonicalName(), ObjectReader.class.getCanonicalName(), ObjectWriter.class.getCanonicalName(), // com.google.gwt GWT.class.getCanonicalName(), // org.turbogwt Overlays.class.getCanonicalName(), DeserializationContext.class.getCanonicalName(), Deserializer.class.getCanonicalName(), JsonRecordReader.class.getCanonicalName(), JsonObjectSerdes.class.getCanonicalName(), JsonRecordWriter.class.getCanonicalName(), Serdes.class.getCanonicalName(), SerializationContext.class.getCanonicalName(), Serializer.class.getCanonicalName() }; for (String imp : imports) { composerFactory.addImport(imp); } composerFactory.addImplementedInterface(intfType.getErasedType().getQualifiedSourceName()); return composerFactory.createSourceWriter(ctx, printWriter); } /** * Create the serdes and return the field name. */ private String generateSerdes(SourceWriter srcWriter, JClassType type, Json annotation) { final String qualifiedSourceName = type.getQualifiedSourceName(); final String qualifiedCamelCaseFieldName = replaceDotByUpperCase(qualifiedSourceName); final String qualifiedCamelCaseTypeName = Character.toUpperCase(qualifiedCamelCaseFieldName.charAt(0)) + qualifiedCamelCaseFieldName.substring(1); final String singleMapperType = qualifiedCamelCaseTypeName + "Mapper"; final String arrayListMapperType = qualifiedCamelCaseTypeName + "ArrayListMapper"; final String linkedListMapperType = qualifiedCamelCaseTypeName + "LinkedListMapper"; final String hashSetMapperType = qualifiedCamelCaseTypeName + "HashSetMapper"; final String linkedHashSetMapperType = qualifiedCamelCaseTypeName + "LinkedHashSetMapper"; final String treeSetMapperType = qualifiedCamelCaseTypeName + "TreeSetMapper"; // interfaces extending Gwt Jackson srcWriter.println("interface %s extends ObjectMapper<%s> {}", singleMapperType, qualifiedSourceName); srcWriter.println("interface %s extends ObjectMapper<ArrayList<%s>> {}", arrayListMapperType, qualifiedSourceName); srcWriter.println("interface %s extends ObjectMapper<LinkedList<%s>> {}", linkedListMapperType, qualifiedSourceName); srcWriter.println("interface %s extends ObjectMapper<HashSet<%s>> {}", hashSetMapperType, qualifiedSourceName); srcWriter.println("interface %s extends ObjectMapper<TreeSet<%s>> {}", treeSetMapperType, qualifiedSourceName); srcWriter.println("interface %s extends ObjectMapper<LinkedHashSet<%s>> {}", linkedHashSetMapperType, qualifiedSourceName); srcWriter.println(); final String singleMapperField = qualifiedCamelCaseFieldName + "Mapper"; final String arrayListMapperField = qualifiedCamelCaseFieldName + "ArrayListMapper"; final String linkedListMapperField = qualifiedCamelCaseFieldName + "LinkedListMapper"; final String hashSetMapperField = qualifiedCamelCaseFieldName + "HashSetMapper"; final String linkedHashSetMapperField = qualifiedCamelCaseFieldName + "LinkedHashSetMapper"; final String treeSetMapperField = qualifiedCamelCaseFieldName + "TreeSetMapper"; // fields creating interfaces srcWriter.println("private final %s %s = GWT.create(%s.class);", singleMapperType, singleMapperField, singleMapperType); srcWriter.println("private final %s %s = GWT.create(%s.class);", arrayListMapperType, arrayListMapperField, arrayListMapperType); srcWriter.println("private final %s %s = GWT.create(%s.class);", linkedListMapperType, linkedListMapperField, linkedListMapperType); srcWriter.println("private final %s %s = GWT.create(%s.class);", hashSetMapperType, hashSetMapperField, hashSetMapperType); srcWriter.println("private final %s %s = GWT.create(%s.class);", linkedHashSetMapperType, linkedHashSetMapperField, linkedHashSetMapperType); srcWriter.println("private final %s %s = GWT.create(%s.class);", treeSetMapperType, treeSetMapperField, treeSetMapperType); srcWriter.println(); final String serdesField = qualifiedCamelCaseFieldName + "Serdes"; final String serdesType = "JsonObjectSerdes<" + qualifiedSourceName + ">"; // serializer field as anonymous class srcWriter.println("private final %s %s = new %s(%s.class) {", serdesType, serdesField, serdesType, qualifiedSourceName); srcWriter.println(); // static field to content-types srcWriter.println(" private final String[] PATTERNS = new String[]{ %s };", asStringCsv(annotation.value())); srcWriter.println(); // readJson srcWriter.println(" @Override"); srcWriter.println(" public %s readJson(JsonRecordReader r, DeserializationContext ctx) {", qualifiedSourceName); srcWriter.println(" return %s.read(Overlays.stringify(r));", singleMapperField); srcWriter.println(" }"); srcWriter.println(); // writeJson srcWriter.println(" @Override"); srcWriter.println(" public void writeJson(%s o, JsonRecordWriter w, SerializationContext ctx) {", qualifiedSourceName); srcWriter.println(" return;"); srcWriter.println(" }"); srcWriter.println(); // contentType srcWriter.println(" @Override"); srcWriter.println(" public String[] contentType() {"); srcWriter.println(" return PATTERNS;"); srcWriter.println(" }"); srcWriter.println(); // deserialize srcWriter.println(" @Override"); srcWriter.println(" public %s deserialize(String s, DeserializationContext ctx) {", qualifiedSourceName); srcWriter.println(" return %s.read(s);", singleMapperField); srcWriter.println(" }"); srcWriter.println(); // deserializeAsCollection srcWriter.println(" @Override"); srcWriter.println(" public <C extends Collection<%s>> C deserializeAsCollection(Class<C> c, " + "String s, DeserializationContext ctx) {", qualifiedSourceName); srcWriter.println(" if (c == List.class || c == ArrayList.class || c == Collection.class)"); srcWriter.println(" return (C) %s.read(s);", arrayListMapperField); srcWriter.println(" else if (c == LinkedList.class)"); srcWriter.println(" return (C) %s.read(s);", linkedListMapperField); srcWriter.println(" else if (c == Set.class || c == HashSet.class)"); srcWriter.println(" return (C) %s.read(s);", hashSetMapperField); srcWriter.println(" else if (c == TreeSet.class)"); srcWriter.println(" return (C) %s.read(s);", treeSetMapperField); srcWriter.println(" else if (c == LinkedHashSet.class)"); srcWriter.println(" return (C) %s.read(s);", linkedHashSetMapperField); srcWriter.println(" else"); srcWriter.println(" return super.deserializeAsCollection(c, s, ctx);"); srcWriter.println(" }"); // serialize srcWriter.println(" @Override"); srcWriter.println(" public String serialize(%s o, SerializationContext ctx) {", qualifiedSourceName); srcWriter.println(" return %s.write(o);", singleMapperField); srcWriter.println(" }"); srcWriter.println(); // serializeFromCollection srcWriter.println(" @Override"); srcWriter.println(" public String serializeFromCollection(Collection<%s> c, SerializationContext ctx) {", qualifiedSourceName); srcWriter.println(" if (c.getClass() == ArrayList.class)"); srcWriter.println(" return %s.write((ArrayList) c);", arrayListMapperField); srcWriter.println(" else if (c.getClass() == LinkedList.class)"); srcWriter.println(" return %s.write((LinkedList) c);", linkedListMapperField); srcWriter.println(" else if (c.getClass() == HashSet.class)"); srcWriter.println(" return %s.write((HashSet) c);", hashSetMapperField); srcWriter.println(" else if (c.getClass() == TreeSet.class)"); srcWriter.println(" return %s.write((TreeSet) c);", treeSetMapperField); srcWriter.println(" else if (c.getClass() == LinkedHashSet.class)"); srcWriter.println(" return %s.write((LinkedHashSet) c);", linkedHashSetMapperField); srcWriter.println(" else"); srcWriter.println(" return super.serializeFromCollection(c, ctx);"); srcWriter.println(" }"); // end anonymous class srcWriter.println("};"); srcWriter.println(); return serdesField; } private String asStringCsv(String[] array) { StringBuilder result = new StringBuilder(); for (String s : array) { result.append('"').append(s).append('"').append(", "); } result.replace(result.length() - 2, result.length(), ""); return result.toString(); } private String replaceDotByUpperCase(String s) { StringBuilder result = new StringBuilder(); for (int i = 0; i < s.length(); i++) { final char c = s.charAt(i); if (c == '.') { result.append(Character.toUpperCase(s.charAt(++i))); } else { result.append(c); } } return result.toString(); } private void generateFields(SourceWriter srcWriter) { // Initialize a field with binary name of the remote service interface srcWriter.println("private final ArrayList<Serdes<?>> serdesList = new ArrayList<Serdes<?>>();"); srcWriter.println(); } private void generateConstructor(SourceWriter srcWriter, ArrayList<String> serdes) { srcWriter.println("public GeneratedJsonSerdesImpl() {"); for (String s : serdes) { srcWriter.println(" serdesList.add(%s);", s); } srcWriter.println("}"); srcWriter.println(); } private void generateIteratorMethod(SourceWriter srcWriter) { srcWriter.println("@Override"); srcWriter.println("public Iterator<Serdes<?>> iterator() {"); srcWriter.println(" return serdesList.iterator();"); srcWriter.println("}"); srcWriter.println(); } }