com.google.web.bindery.autobean.gwt.rebind.AutoBeanFactoryGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.google.web.bindery.autobean.gwt.rebind.AutoBeanFactoryGenerator.java

Source

/*
 * 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.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 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 java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
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();
            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.
                 */
                sw.println("%s toReturn = %s.this.getWrapped().%s();",
                        ModelUtils.getQualifiedBaseSourceName(jmethod.getReturnType()), 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 = getWrapped().doFoo(params);
                    sw.println("%s toReturn = %s.this.getWrapped().%s(%s);",
                            ModelUtils.ensureBaseType(jmethod.getReturnType()).getQualifiedSourceName(),
                            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("}");
        }

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