| /* |
| * 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("}-*/;"); |
| } |
| } |
| } |