blob: 6eac4e12736b54f46940d27648e8cbe51dc43050 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* 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.web.bindery.autobean.gwt.rebind;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.impl.WeakMapping;
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.JEnumConstant;
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.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.editor.rebind.model.ModelUtils;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.web.bindery.autobean.gwt.client.impl.AbstractAutoBeanFactory;
import com.google.web.bindery.autobean.gwt.client.impl.ClientPropertyContext;
import com.google.web.bindery.autobean.gwt.client.impl.JsniCreatorMap;
import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanFactoryMethod;
import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanFactoryModel;
import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanMethod;
import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanType;
import com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanFactory;
import com.google.web.bindery.autobean.shared.AutoBeanUtils;
import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
import com.google.web.bindery.autobean.shared.Splittable;
import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean;
import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean.OneShotContext;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Generates implementations of AutoBeanFactory.
*/
public class AutoBeanFactoryGenerator extends Generator {
private GeneratorContext context;
private String simpleSourceName;
private TreeLogger logger;
private AutoBeanFactoryModel model;
@Override
public String generate(TreeLogger logger, GeneratorContext context, String typeName)
throws UnableToCompleteException {
this.context = context;
this.logger = logger;
TypeOracle oracle = context.getTypeOracle();
JClassType toGenerate = oracle.findType(typeName).isInterface();
if (toGenerate == null) {
logger.log(TreeLogger.ERROR, typeName + " is not an interface type");
throw new UnableToCompleteException();
}
String packageName = toGenerate.getPackage().getName();
simpleSourceName = toGenerate.getName().replace('.', '_') + "Impl";
PrintWriter pw = context.tryCreate(logger, packageName, simpleSourceName);
if (pw == null) {
return packageName + "." + simpleSourceName;
}
model = new AutoBeanFactoryModel(logger, toGenerate);
ClassSourceFileComposerFactory factory =
new ClassSourceFileComposerFactory(packageName, simpleSourceName);
factory.setSuperclass(AbstractAutoBeanFactory.class.getCanonicalName());
factory.addImplementedInterface(typeName);
SourceWriter sw = factory.createSourceWriter(context, pw);
for (AutoBeanType type : model.getAllTypes()) {
writeAutoBean(type);
}
writeDynamicMethods(sw);
writeEnumSetup(sw);
writeMethods(sw);
sw.commit(logger);
return factory.getCreatedClassName();
}
/**
* Flattens a parameterized type into a simple list of types.
*/
private void createTypeList(List<JType> accumulator, JType type) {
accumulator.add(type);
JParameterizedType hasParams = type.isParameterized();
if (hasParams != null) {
for (JClassType arg : hasParams.getTypeArgs()) {
createTypeList(accumulator, arg);
}
}
}
private String getBaseMethodDeclaration(JMethod jmethod) {
// Foo foo, Bar bar, Baz baz
StringBuilder parameters = new StringBuilder();
for (JParameter param : jmethod.getParameters()) {
parameters.append(",").append(ModelUtils.getQualifiedBaseSourceName(param.getType())).append(
" ").append(param.getName());
}
if (parameters.length() > 0) {
parameters = parameters.deleteCharAt(0);
}
StringBuilder throwsDeclaration = new StringBuilder();
if (jmethod.getThrows().length > 0) {
for (JType thrown : jmethod.getThrows()) {
throwsDeclaration.append(". ").append(ModelUtils.getQualifiedBaseSourceName(thrown));
}
throwsDeclaration.deleteCharAt(0);
throwsDeclaration.insert(0, "throws ");
}
String returnName = ModelUtils.getQualifiedBaseSourceName(jmethod.getReturnType());
assert !returnName.contains("extends");
return String.format("%s %s(%s) %s", returnName, jmethod.getName(), parameters,
throwsDeclaration);
}
/**
* Used by {@link #writeShim} to avoid duplicate declarations of Object
* methods.
*/
private boolean isObjectMethodImplementedByShim(JMethod jmethod) {
String methodName = jmethod.getName();
JParameter[] parameters = jmethod.getParameters();
switch (parameters.length) {
case 0:
return methodName.equals("hashCode") || methodName.equals("toString");
case 1:
return methodName.equals("equals")
&& parameters[0].getType().equals(context.getTypeOracle().getJavaLangObject());
}
return false;
}
private void writeAutoBean(AutoBeanType type) throws UnableToCompleteException {
PrintWriter pw = context.tryCreate(logger, type.getPackageNome(), type.getSimpleSourceName());
if (pw == null) {
// Previously-created
return;
}
ClassSourceFileComposerFactory factory =
new ClassSourceFileComposerFactory(type.getPackageNome(), type.getSimpleSourceName());
factory.setSuperclass(AbstractAutoBean.class.getCanonicalName() + "<"
+ type.getPeerType().getQualifiedSourceName() + ">");
SourceWriter sw = factory.createSourceWriter(context, pw);
writeShim(sw, type);
// Instance initializer code to set the shim's association
sw.println("{ %s.set(shim, %s.class.getName(), this); }", WeakMapping.class.getCanonicalName(),
AutoBean.class.getCanonicalName());
// Only simple wrappers have a default constructor
if (type.isSimpleBean()) {
// public FooIntfAutoBean(AutoBeanFactory factory) {}
sw.println("public %s(%s factory) {super(factory);}", type.getSimpleSourceName(),
AutoBeanFactory.class.getCanonicalName());
}
// Wrapping constructor
// public FooIntfAutoBean(AutoBeanFactory factory, FooIntfo wrapped) {
sw.println("public %s(%s factory, %s wrapped) {", type.getSimpleSourceName(),
AutoBeanFactory.class.getCanonicalName(), type.getPeerType().getQualifiedSourceName());
sw.indentln("super(wrapped, factory);");
sw.println("}");
// public FooIntf as() {return shim;}
sw.println("public %s as() {return shim;}", type.getPeerType().getQualifiedSourceName());
// public Class<Intf> getType() {return Intf.class;}
sw.println("public Class<%1$s> getType() {return %1$s.class;}", ModelUtils.ensureBaseType(
type.getPeerType()).getQualifiedSourceName());
if (type.isSimpleBean()) {
writeCreateSimpleBean(sw, type);
}
writeTraversal(sw, type);
sw.commit(logger);
}
/**
* For interfaces that consist of nothing more than getters and setters,
* create a map-based implementation that will allow the AutoBean's internal
* state to be easily consumed.
*/
private void writeCreateSimpleBean(SourceWriter sw, AutoBeanType type) {
sw.println("@Override protected %s createSimplePeer() {", type.getPeerType()
.getQualifiedSourceName());
sw.indent();
// return new FooIntf() {
sw.println("return new %s() {", type.getPeerType().getQualifiedSourceName());
sw.indent();
sw.println("private final %s data = %s.this.data;", Splittable.class.getCanonicalName(), type
.getQualifiedSourceName());
for (AutoBeanMethod method : type.getMethods()) {
JMethod jmethod = method.getMethod();
JType returnType = jmethod.getReturnType();
sw.println("public %s {", getBaseMethodDeclaration(jmethod));
sw.indent();
switch (method.getAction()) {
case GET: {
String castType;
if (returnType.isPrimitive() != null) {
castType = returnType.isPrimitive().getQualifiedBoxedSourceName();
// Boolean toReturn = Other.this.getOrReify("foo");
sw.println("%s toReturn = %s.this.getOrReify(\"%s\");", castType, type
.getSimpleSourceName(), method.getPropertyName());
// return toReturn == null ? false : toReturn;
sw.println("return toReturn == null ? %s : toReturn;", returnType.isPrimitive()
.getUninitializedFieldExpression());
} else if (returnType.equals(context.getTypeOracle().findType(
Splittable.class.getCanonicalName()))) {
sw.println("return data.isNull(\"%1$s\") ? null : data.get(\"%1$s\");", method
.getPropertyName());
} else {
// return (ReturnType) Outer.this.getOrReify(\"foo\");
castType = ModelUtils.getQualifiedBaseSourceName(returnType);
sw.println("return (%s) %s.this.getOrReify(\"%s\");", castType, type
.getSimpleSourceName(), method.getPropertyName());
}
}
break;
case SET:
case SET_BUILDER: {
JParameter param = jmethod.getParameters()[0];
// Other.this.setProperty("foo", parameter);
sw.println("%s.this.setProperty(\"%s\", %s);", type.getSimpleSourceName(), method
.getPropertyName(), param.getName());
if (JBeanMethod.SET_BUILDER.equals(method.getAction())) {
sw.println("return this;");
}
break;
}
case CALL:
// return com.example.Owner.staticMethod(Outer.this, param,
// param);
JMethod staticImpl = method.getStaticImpl();
if (!returnType.equals(JPrimitiveType.VOID)) {
sw.print("return ");
}
sw.print("%s.%s(%s.this", staticImpl.getEnclosingType().getQualifiedSourceName(),
staticImpl.getName(), type.getSimpleSourceName());
for (JParameter param : jmethod.getParameters()) {
sw.print(", %s", param.getName());
}
sw.println(");");
break;
default:
throw new RuntimeException();
}
sw.outdent();
sw.println("}");
}
sw.outdent();
sw.println("};");
sw.outdent();
sw.println("}");
}
/**
* Write an instance initializer block to populate the creators map.
*/
private void writeDynamicMethods(SourceWriter sw) {
List<JClassType> privatePeers = new ArrayList<JClassType>();
sw.println("@Override protected void initializeCreatorMap(%s map) {", JsniCreatorMap.class
.getCanonicalName());
sw.indent();
for (AutoBeanType type : model.getAllTypes()) {
if (type.isNoWrap()) {
continue;
}
String classLiteralAccessor;
JClassType peer = type.getPeerType();
String peerName = ModelUtils.ensureBaseType(peer).getQualifiedSourceName();
if (peer.isPublic()) {
classLiteralAccessor = peerName + ".class";
} else {
privatePeers.add(peer);
classLiteralAccessor = "classLit_" + peerName.replace('.', '_') + "()";
}
// map.add(Foo.class, getConstructors_com_foo_Bar());
sw.println("map.add(%s, getConstructors_%s());", classLiteralAccessor, peerName.replace('.',
'_'));
}
sw.outdent();
sw.println("}");
/*
* Create a native method for each peer type that isn't public since Java
* class literal references are scoped.
*/
for (JClassType peer : privatePeers) {
String peerName = ModelUtils.ensureBaseType(peer).getQualifiedSourceName();
sw.println("private native Class<?> classLit_%s() /*-{return @%s::class;}-*/;", peerName
.replace('.', '_'), peerName);
}
/*
* Create a method that returns an array containing references to the
* constructors.
*/
String factoryJNIName =
context.getTypeOracle().findType(AutoBeanFactory.class.getCanonicalName())
.getJNISignature();
for (AutoBeanType type : model.getAllTypes()) {
String peerName = ModelUtils.ensureBaseType(type.getPeerType()).getQualifiedSourceName();
String peerJNIName = ModelUtils.ensureBaseType(type.getPeerType()).getJNISignature();
/*-
* private native JsArray<JSO> getConstructors_com_foo_Bar() {
* return [
* BarProxyImpl::new(ABFactory),
* BarProxyImpl::new(ABFactory, DelegateType)
* ];
* }
*/
sw.println("private native %s<%s> getConstructors_%s() /*-{", JsArray.class
.getCanonicalName(), JavaScriptObject.class.getCanonicalName(), peerName
.replace('.', '_'));
sw.indent();
sw.println("return [");
if (type.isSimpleBean()) {
sw.indentln("@%s::new(%s),", type.getQualifiedSourceName(), factoryJNIName);
} else {
sw.indentln(",");
}
sw.indentln("@%s::new(%s%s)", type.getQualifiedSourceName(), factoryJNIName, peerJNIName);
sw.println("];");
sw.outdent();
sw.println("}-*/;");
}
}
private void writeEnumSetup(SourceWriter sw) {
// Make the deobfuscation model
Map<String, List<JEnumConstant>> map = new HashMap<String, List<JEnumConstant>>();
for (Map.Entry<JEnumConstant, String> entry : model.getEnumTokenMap().entrySet()) {
List<JEnumConstant> list = map.get(entry.getValue());
if (list == null) {
list = new ArrayList<JEnumConstant>();
map.put(entry.getValue(), list);
}
list.add(entry.getKey());
}
sw.println("@Override protected void initializeEnumMap() {");
sw.indent();
for (Map.Entry<JEnumConstant, String> entry : model.getEnumTokenMap().entrySet()) {
// enumToStringMap.put(Enum.FOO, "FOO");
sw.println("enumToStringMap.put(%s.%s, \"%s\");", entry.getKey().getEnclosingType()
.getQualifiedSourceName(), entry.getKey().getName(), entry.getValue());
}
for (Map.Entry<String, List<JEnumConstant>> entry : map.entrySet()) {
String listExpr;
if (entry.getValue().size() == 1) {
JEnumConstant e = entry.getValue().get(0);
// Collections.singletonList(Enum.FOO)
listExpr =
String.format("%s.<%s<?>> singletonList(%s.%s)", Collections.class.getCanonicalName(),
Enum.class.getCanonicalName(), e.getEnclosingType().getQualifiedSourceName(), e
.getName());
} else {
// Arrays.asList(Enum.FOO, OtherEnum.FOO, ThirdEnum,FOO)
StringBuilder sb = new StringBuilder();
boolean needsComma = false;
sb.append(String.format("%s.<%s<?>> asList(", Arrays.class.getCanonicalName(), Enum.class
.getCanonicalName()));
for (JEnumConstant e : entry.getValue()) {
if (needsComma) {
sb.append(",");
}
needsComma = true;
sb.append(e.getEnclosingType().getQualifiedSourceName()).append(".").append(e.getName());
}
sb.append(")");
listExpr = sb.toString();
}
sw.println("stringsToEnumsMap.put(\"%s\", %s);", entry.getKey(), listExpr);
}
sw.outdent();
sw.println("}");
}
private void writeMethods(SourceWriter sw) throws UnableToCompleteException {
for (AutoBeanFactoryMethod method : model.getMethods()) {
AutoBeanType autoBeanType = method.getAutoBeanType();
// public AutoBean<Foo> foo(FooSubtype wrapped) {
sw.println("public %s %s(%s) {", method.getReturnType().getQualifiedSourceName(), method
.getName(), method.isWrapper()
? (method.getWrappedType().getQualifiedSourceName() + " wrapped") : "");
if (method.isWrapper()) {
sw.indent();
// AutoBean<Foo> toReturn = AutoBeanUtils.getAutoBean(wrapped);
sw.println("%s toReturn = %s.getAutoBean(wrapped);", method.getReturnType()
.getParameterizedQualifiedSourceName(), AutoBeanUtils.class.getCanonicalName());
sw.println("if (toReturn != null) {return toReturn;}");
// return new FooAutoBean(Factory.this, wrapped);
sw.println("return new %s(%s.this, wrapped);", autoBeanType.getQualifiedSourceName(),
simpleSourceName);
sw.outdent();
} else {
// return new FooAutoBean(Factory.this);
sw.indentln("return new %s(%s.this);", autoBeanType.getQualifiedSourceName(),
simpleSourceName);
}
sw.println("}");
}
}
private void writeReturnWrapper(SourceWriter sw, AutoBeanType type, AutoBeanMethod method)
throws UnableToCompleteException {
if (!method.isValueType() && !method.isNoWrap()) {
JMethod jmethod = method.getMethod();
JClassType returnClass = jmethod.getReturnType().isClassOrInterface();
AutoBeanType peer = model.getPeer(returnClass);
sw.println("if (toReturn != null) {");
sw.indent();
sw.println("if (%s.this.isWrapped(toReturn)) {", type.getSimpleSourceName());
sw.indentln("toReturn = %s.this.getFromWrapper(toReturn);", type.getSimpleSourceName());
sw.println("} else {");
sw.indent();
if (peer != null) {
// toReturn = new FooAutoBean(getFactory(), toReturn).as();
sw.println("toReturn = new %s(getFactory(), toReturn).as();", peer.getQualifiedSourceName());
}
sw.outdent();
sw.println("}");
sw.outdent();
sw.println("}");
}
// Allow return values to be intercepted
JMethod interceptor = type.getInterceptor();
if (interceptor != null) {
// toReturn = FooCategory.__intercept(FooAutoBean.this, toReturn);
sw.println("toReturn = %s.%s(%s.this, toReturn);", interceptor.getEnclosingType()
.getQualifiedSourceName(), interceptor.getName(), type.getSimpleSourceName());
}
}
/**
* Create the shim instance of the AutoBean's peer type that lets us hijack
* the method calls. Using a shim type, as opposed to making the AutoBean
* implement the peer type directly, means that there can't be any conflicts
* between methods in the peer type and methods declared in the AutoBean
* implementation.
*/
private void writeShim(SourceWriter sw, AutoBeanType type) throws UnableToCompleteException {
// private final FooImpl shim = new FooImpl() {
sw.println("private final %1$s shim = new %1$s() {", type.getPeerType()
.getQualifiedSourceName());
sw.indent();
for (AutoBeanMethod method : type.getMethods()) {
JMethod jmethod = method.getMethod();
if (jmethod.isStatic()) {
continue;
}
String methodName = jmethod.getName();
JParameter[] parameters = jmethod.getParameters();
if (isObjectMethodImplementedByShim(jmethod)) {
// Skip any methods declared on Object, since we have special handling
continue;
}
// foo, bar, baz
StringBuilder arguments = new StringBuilder();
{
for (JParameter param : parameters) {
arguments.append(",").append(param.getName());
}
if (arguments.length() > 0) {
arguments = arguments.deleteCharAt(0);
}
}
sw.println("public %s {", getBaseMethodDeclaration(jmethod));
sw.indent();
switch (method.getAction()) {
case GET:
/*
* The getter call will ensure that any non-value return type is
* definitely wrapped by an AutoBean instance.
*/
String getValueType = ModelUtils.getQualifiedBaseSourceName(jmethod.getReturnType());
sw.println("%s toReturn = (%s) %s.this.getWrapped().%s();", getValueType, getValueType,
type.getSimpleSourceName(), methodName);
// Non-value types might need to be wrapped
writeReturnWrapper(sw, type, method);
sw.println("return toReturn;");
break;
case SET:
case SET_BUILDER:
// getWrapped().setFoo(foo);
sw.println("%s.this.getWrapped().%s(%s);", type.getSimpleSourceName(), methodName,
parameters[0].getName());
// FooAutoBean.this.set("setFoo", foo);
sw.println("%s.this.set(\"%s\", %s);", type.getSimpleSourceName(), methodName,
parameters[0].getName());
if (JBeanMethod.SET_BUILDER.equals(method.getAction())) {
sw.println("return this;");
}
break;
case CALL:
// XXX How should freezing and calls work together?
// sw.println("checkFrozen();");
if (JPrimitiveType.VOID.equals(jmethod.getReturnType())) {
// getWrapped().doFoo(params);
sw.println("%s.this.getWrapped().%s(%s);", type.getSimpleSourceName(), methodName,
arguments);
// call("doFoo", null, params);
sw.println("%s.this.call(\"%s\", null%s %s);", type.getSimpleSourceName(), methodName,
arguments.length() > 0 ? "," : "", arguments);
} else {
// Type toReturn = (Type) getWrapped().doFoo(params);
String callValueType = ModelUtils.ensureBaseType(jmethod.getReturnType()).getQualifiedSourceName();
sw.println("%s toReturn = (%s) %s.this.getWrapped().%s(%s);", callValueType, callValueType,
type.getSimpleSourceName(), methodName, arguments);
// Non-value types might need to be wrapped
writeReturnWrapper(sw, type, method);
// call("doFoo", toReturn, params);
sw.println("%s.this.call(\"%s\", toReturn%s %s);", type.getSimpleSourceName(),
methodName, arguments.length() > 0 ? "," : "", arguments);
sw.println("return toReturn;");
}
break;
default:
throw new RuntimeException();
}
sw.outdent();
sw.println("}");
}
// HACK: Iterator#remove was changed to a default method in Java 8,
// and those are hidden to generators
if (type.getPeerType().getQualifiedSourceName().equals(Iterator.class.getCanonicalName())) {
sw.println("@Override public void remove() {");
sw.indent();
sw.println("%s.this.getWrapped().remove();", type.getSimpleSourceName());
sw.println("%s.this.call(\"remove\", null);", type.getSimpleSourceName());
sw.outdent();
sw.println("}");
}
// Delegate equals(), hashCode(), and toString() to wrapped object
sw.println("@Override public boolean equals(Object o) {");
sw.indentln("return this == o || getWrapped().equals(o);");
sw.println("}");
sw.println("@Override public int hashCode() {");
sw.indentln("return getWrapped().hashCode();");
sw.println("}");
sw.println("@Override public String toString() {");
sw.indentln("return getWrapped().toString();");
sw.println("}");
// End of shim field declaration and assignment
sw.outdent();
sw.println("};");
}
/**
* Generate traversal logic.
*/
private void writeTraversal(SourceWriter sw, AutoBeanType type) {
List<AutoBeanMethod> referencedSetters = new ArrayList<AutoBeanMethod>();
sw.println("@Override protected void traverseProperties(%s visitor, %s ctx) {",
AutoBeanVisitor.class.getCanonicalName(), OneShotContext.class.getCanonicalName());
sw.indent();
sw.println("%s bean;", AbstractAutoBean.class.getCanonicalName());
sw.println("Object value;");
sw.println("%s propertyContext;", ClientPropertyContext.class.getCanonicalName());
// Local variable ref cleans up emitted js
sw.println("%1$s as = as();", type.getPeerType().getQualifiedSourceName());
for (AutoBeanMethod method : type.getMethods()) {
if (!method.getAction().equals(JBeanMethod.GET)) {
continue;
}
AutoBeanMethod setter = null;
// If it's not a simple bean type, try to find a real setter method
if (!type.isSimpleBean()) {
for (AutoBeanMethod maybeSetter : type.getMethods()) {
boolean isASetter =
maybeSetter.getAction().equals(JBeanMethod.SET)
|| maybeSetter.getAction().equals(JBeanMethod.SET_BUILDER);
if (isASetter && maybeSetter.getPropertyName().equals(method.getPropertyName())) {
setter = maybeSetter;
break;
}
}
}
// The type of property influences the visitation
String valueExpression =
String.format("bean = (%1$s) %2$s.getAutoBean(as.%3$s());", AbstractAutoBean.class
.getCanonicalName(), AutoBeanUtils.class.getCanonicalName(), method.getMethod()
.getName());
String visitMethod;
String visitVariable = "bean";
if (method.isCollection()) {
visitMethod = "Collection";
} else if (method.isMap()) {
visitMethod = "Map";
} else if (method.isValueType()) {
valueExpression = String.format("value = as.%s();", method.getMethod().getName());
visitMethod = "Value";
visitVariable = "value";
} else {
visitMethod = "Reference";
}
sw.println(valueExpression);
// Map<List<Foo>, Bar> --> Map, List, Foo, Bar
List<JType> typeList = new ArrayList<JType>();
createTypeList(typeList, method.getMethod().getReturnType());
assert typeList.size() > 0;
/*
* Make the PropertyContext that lets us call the setter. We allow
* multiple methods to be bound to the same property (e.g. to allow JSON
* payloads to be interpreted as different types). The leading underscore
* allows purely numeric property names, which are valid JSON map keys.
*/
// propertyContext = new CPContext(.....);
sw.println("propertyContext = new %s(", ClientPropertyContext.class.getCanonicalName());
sw.indent();
// The instance on which the context is nominally operating
sw.println("as,");
// Produce a JSNI reference to a setter function to call
{
if (setter != null) {
// Call a method that returns a JSNI reference to the method to call
// setFooMethodReference(),
sw.println("%sMethodReference(as),", setter.getMethod().getName());
referencedSetters.add(setter);
} else {
// Create a function that will update the values map
// CPContext.beanSetter(FooBeanImpl.this, "foo");
sw.println("%s.beanSetter(%s.this, \"%s\"),", ClientPropertyContext.Setter.class
.getCanonicalName(), type.getSimpleSourceName(), method.getPropertyName());
}
}
if (typeList.size() == 1) {
sw.println("%s.class", ModelUtils.ensureBaseType(typeList.get(0)).getQualifiedSourceName());
} else {
// Produce the array of parameter types
sw.print("new Class<?>[] {");
boolean first = true;
for (JType lit : typeList) {
if (first) {
first = false;
} else {
sw.print(", ");
}
sw.print("%s.class", ModelUtils.ensureBaseType(lit).getQualifiedSourceName());
}
sw.println("},");
// Produce the array of parameter counts
sw.print("new int[] {");
first = true;
for (JType lit : typeList) {
if (first) {
first = false;
} else {
sw.print(", ");
}
JParameterizedType hasParam = lit.isParameterized();
if (hasParam == null) {
sw.print("0");
} else {
sw.print(String.valueOf(hasParam.getTypeArgs().length));
}
}
sw.println("}");
}
sw.outdent();
sw.println(");");
// if (visitor.visitReferenceProperty("foo", value, ctx))
sw.println("if (visitor.visit%sProperty(\"%s\", %s, propertyContext)) {", visitMethod, method
.getPropertyName(), visitVariable);
if (!method.isValueType()) {
// Cycle-detection in AbstractAutoBean.traverse
sw.indentln("if (bean != null) { bean.traverse(visitor, ctx); }");
}
sw.println("}");
// visitor.endVisitorReferenceProperty("foo", value, ctx);
sw.println("visitor.endVisit%sProperty(\"%s\", %s, propertyContext);", visitMethod, method
.getPropertyName(), visitVariable);
}
sw.outdent();
sw.println("}");
for (AutoBeanMethod method : referencedSetters) {
JMethod jmethod = method.getMethod();
assert jmethod.getParameters().length == 1;
/*-
* Setter setFooMethodReference(Object instance) {
* return instance.@com.example.Blah::setFoo(Lcom/example/Foo;);
* }
*/
sw.println("public static native %s %sMethodReference(Object instance) /*-{",
ClientPropertyContext.Setter.class.getCanonicalName(), jmethod.getName());
sw.indentln("return instance.@%s::%s(%s);", jmethod.getEnclosingType()
.getQualifiedSourceName(), jmethod.getName(), jmethod.getParameters()[0].getType()
.getJNISignature());
sw.println("}-*/;");
}
}
}