com.google.gwt.user.rebind.rpc.ProxyCreator.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.user.rebind.rpc.ProxyCreator.java

Source

/*
 * Copyright 2008 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.gwt.user.rebind.rpc;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.impl.Impl;
import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.ConfigurationProperty;
import com.google.gwt.core.ext.GeneratorContextExt;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
import com.google.gwt.core.ext.linker.GeneratedResource;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JPackage;
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.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.generator.NameFactory;
import com.google.gwt.dev.javac.rebind.CachedClientDataMap;
import com.google.gwt.dev.javac.rebind.CachedPropertyInformation;
import com.google.gwt.dev.javac.rebind.CachedRebindResult;
import com.google.gwt.dev.javac.rebind.RebindResult;
import com.google.gwt.dev.javac.rebind.RebindStatus;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
import com.google.gwt.user.client.rpc.RpcToken;
import com.google.gwt.user.client.rpc.RpcToken.RpcTokenImplementation;
import com.google.gwt.user.client.rpc.RpcTokenException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.google.gwt.user.client.rpc.impl.ClientSerializationStreamWriter;
import com.google.gwt.user.client.rpc.impl.FailedRequest;
import com.google.gwt.user.client.rpc.impl.FailingRequestBuilder;
import com.google.gwt.user.client.rpc.impl.RemoteServiceProxy;
import com.google.gwt.user.client.rpc.impl.RequestCallbackAdapter.ResponseReader;
import com.google.gwt.user.client.rpc.impl.RpcStatsContext;
import com.google.gwt.user.linker.rpc.RpcLogArtifact;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.user.server.rpc.SerializationPolicyLoader;
import com.google.gwt.user.server.rpc.impl.TypeNameObfuscator;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Creates a client-side proxy for a
 * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} interface
 * as well as the necessary type and field serializers.
 */
public class ProxyCreator {
    /**
     * Some keys for storing cached information for use with generator result
     * caching.
     */
    public static final String CACHED_PROPERTY_INFO_KEY = "cached-property-info";
    public static final String CACHED_TYPE_INFO_KEY = "cached-type-info";

    /**
     * The directory within which RPC manifests are placed for individual
     * permutations.
     */
    public static final String MANIFEST_ARTIFACT_DIR = "rpcPolicyManifest/manifests";

    /**
     * Properties which need to be checked to determine cacheability.
     */
    private static final Collection<String> configPropsToCheck = Arrays
            .asList(TypeSerializerCreator.GWT_ELIDE_TYPE_NAMES_FROM_RPC, Shared.RPC_ENHANCED_CLASSES);
    private static final Collection<String> selectionPropsToCheck = Arrays
            .asList(Shared.RPC_PROP_SUPPRESS_NON_STATIC_FINAL_FIELD_WARNINGS);

    private static final String PROXY_SUFFIX = "_Proxy";

    private static final Map<JPrimitiveType, ResponseReader> JPRIMITIVETYPE_TO_RESPONSEREADER = new HashMap<JPrimitiveType, ResponseReader>();
    static {
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.BOOLEAN, ResponseReader.BOOLEAN);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.BYTE, ResponseReader.BYTE);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.CHAR, ResponseReader.CHAR);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.DOUBLE, ResponseReader.DOUBLE);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.FLOAT, ResponseReader.FLOAT);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.INT, ResponseReader.INT);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.LONG, ResponseReader.LONG);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.SHORT, ResponseReader.SHORT);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.VOID, ResponseReader.VOID);
    }

    /**
     * Adds a root type for each type that appears in the RemoteService interface
     * methods.
     */
    private static void addRemoteServiceRootTypes(TreeLogger logger, TypeOracle typeOracle,
            SerializableTypeOracleBuilder typesSentFromBrowser, SerializableTypeOracleBuilder typesSentToBrowser,
            JClassType remoteService) throws NotFoundException, UnableToCompleteException {
        logger = logger.branch(TreeLogger.DEBUG,
                "Analyzing '" + remoteService.getParameterizedQualifiedSourceName() + "' for serializable types",
                null);

        JMethod[] methods = remoteService.getOverridableMethods();

        JClassType exceptionClass = typeOracle.getType(Exception.class.getName());

        JClassType rteType = typeOracle.getType(RpcTokenException.class.getName());
        JClassType rpcTokenClass = typeOracle.getType(RpcToken.class.getName());
        RpcTokenImplementation tokenClassToUse = remoteService
                .findAnnotationInTypeHierarchy(RpcTokenImplementation.class);
        if (tokenClassToUse != null) {
            // only include serializer for the specified class literal
            JClassType rpcTokenType = typeOracle.getType(tokenClassToUse.value());
            if (rpcTokenType.isAssignableTo(rpcTokenClass)) {
                typesSentFromBrowser.addRootType(logger, rpcTokenType);
                typesSentToBrowser.addRootType(logger, rteType);
            } else {
                logger.branch(TreeLogger.ERROR, "RPC token class " + tokenClassToUse.value() + " must implement "
                        + RpcToken.class.getName(), null);
                throw new UnableToCompleteException();
            }
        } else {
            JClassType[] rpcTokenSubclasses = rpcTokenClass.getSubtypes();
            for (JClassType rpcTokenSubclass : rpcTokenSubclasses) {
                typesSentFromBrowser.addRootType(logger, rpcTokenSubclass);
            }
            if (rpcTokenSubclasses.length > 0) {
                typesSentToBrowser.addRootType(logger, rteType);
            }
        }

        TreeLogger validationLogger = logger.branch(TreeLogger.DEBUG, "Analyzing methods:", null);
        for (JMethod method : methods) {
            TreeLogger methodLogger = validationLogger.branch(TreeLogger.DEBUG, method.toString(), null);
            JType returnType = method.getReturnType();
            if (returnType != JPrimitiveType.VOID) {
                TreeLogger returnTypeLogger = methodLogger.branch(TreeLogger.DEBUG,
                        "Return type: " + returnType.getParameterizedQualifiedSourceName(), null);
                typesSentToBrowser.addRootType(returnTypeLogger, returnType);
            }

            JParameter[] params = method.getParameters();
            for (JParameter param : params) {
                TreeLogger paramLogger = methodLogger.branch(TreeLogger.DEBUG, "Parameter: " + param.toString(),
                        null);
                JType paramType = param.getType();
                typesSentFromBrowser.addRootType(paramLogger, paramType);
            }

            JType[] exs = method.getThrows();
            if (exs.length > 0) {
                TreeLogger throwsLogger = methodLogger.branch(TreeLogger.DEBUG, "Throws:", null);
                for (JType ex : exs) {
                    if (!exceptionClass.isAssignableFrom(ex.isClass())) {
                        throwsLogger = throwsLogger.branch(TreeLogger.WARN,
                                "'" + ex.getQualifiedSourceName()
                                        + "' is not a checked exception; only checked exceptions may be used",
                                null);
                    }

                    typesSentToBrowser.addRootType(throwsLogger, ex);
                }
            }
        }
    }

    /**
     * Add the implicit root types that are needed to make RPC work. These would
     * be {@link String} and {@link IncompatibleRemoteServiceException}.
     */
    private static void addRequiredRoots(TreeLogger logger, TypeOracle typeOracle,
            SerializableTypeOracleBuilder stob) throws NotFoundException {
        logger = logger.branch(TreeLogger.DEBUG, "Analyzing implicit types");

        // String is always instantiable.
        JClassType stringType = typeOracle.getType(String.class.getName());
        stob.addRootType(logger, stringType);

        // IncompatibleRemoteServiceException is always serializable
        JClassType icseType = typeOracle.getType(IncompatibleRemoteServiceException.class.getName());
        stob.addRootType(logger, icseType);
    }

    /**
     * Take the union of two type arrays, and then sort the results
     * alphabetically.
     */
    private static JType[] unionOfTypeArrays(JType[]... types) {
        Set<JType> typesList = new HashSet<JType>();
        for (JType[] a : types) {
            typesList.addAll(Arrays.asList(a));
        }
        JType[] serializableTypes = typesList.toArray(new JType[typesList.size()]);
        Arrays.sort(serializableTypes, SerializableTypeOracleBuilder.JTYPE_COMPARATOR);
        return serializableTypes;
    }

    protected JClassType serviceIntf;

    private boolean elideTypeNames;

    /**
     * The possibly obfuscated type signatures used to represent a type.
     */
    private Map<JType, String> typeStrings;

    private Set<JType> customSerializersUsed;
    private Set<JType> typesNotUsingCustomSerializers;

    public ProxyCreator(JClassType serviceIntf) {
        assert (serviceIntf.isInterface() != null);
        this.serviceIntf = serviceIntf;
    }

    /**
     * Creates the client-side proxy class.
     * 
     * @throws UnableToCompleteException
     */
    public RebindResult create(TreeLogger logger, GeneratorContextExt context) throws UnableToCompleteException {
        TypeOracle typeOracle = context.getTypeOracle();

        JClassType serviceAsync = typeOracle.findType(serviceIntf.getQualifiedSourceName() + "Async");
        if (serviceAsync == null) {
            logger.branch(TreeLogger.ERROR, "Could not find an asynchronous version for the service interface "
                    + serviceIntf.getQualifiedSourceName(), null);
            RemoteServiceAsyncValidator.logValidAsyncInterfaceDeclaration(logger, serviceIntf);
            throw new UnableToCompleteException();
        }

        if (checkAlreadyGenerated(typeOracle, serviceIntf)) {
            return new RebindResult(RebindStatus.USE_EXISTING, getProxyQualifiedName());
        }

        // Make sure that the async and synchronous versions of the RemoteService
        // agree with one another
        //
        RemoteServiceAsyncValidator rsav = new RemoteServiceAsyncValidator(logger, typeOracle);
        Map<JMethod, JMethod> syncMethToAsyncMethMap = rsav.validate(logger, serviceIntf, serviceAsync);

        final PropertyOracle propertyOracle = context.getPropertyOracle();

        // Load the blacklist/whitelist
        TypeFilter blacklistTypeFilter = new BlacklistTypeFilter(logger, propertyOracle);

        // Determine the set of serializable types
        Event event = SpeedTracerLogger.start(CompilerEventType.GENERATOR_RPC_STOB);
        SerializableTypeOracle typesSentFromBrowser;
        SerializableTypeOracle typesSentToBrowser;
        String rpcLog;
        try {
            SerializableTypeOracleBuilder typesSentFromBrowserBuilder = new SerializableTypeOracleBuilder(logger,
                    propertyOracle, context);
            typesSentFromBrowserBuilder.setTypeFilter(blacklistTypeFilter);
            SerializableTypeOracleBuilder typesSentToBrowserBuilder = new SerializableTypeOracleBuilder(logger,
                    propertyOracle, context);
            typesSentToBrowserBuilder.setTypeFilter(blacklistTypeFilter);

            addRoots(logger, typeOracle, typesSentFromBrowserBuilder, typesSentToBrowserBuilder);

            // Decide what types to send in each direction.
            // Log the decisions to a string that will be written later in this method
            {
                StringWriter stringWriter = new StringWriter();
                PrintWriter writer = new PrintWriter(stringWriter);

                typesSentFromBrowserBuilder.setLogOutputWriter(writer);
                typesSentToBrowserBuilder.setLogOutputWriter(writer);

                writer.write("====================================\n");
                writer.write("Types potentially sent from browser:\n");
                writer.write("====================================\n\n");
                writer.flush();
                typesSentFromBrowser = typesSentFromBrowserBuilder.build(logger);

                writer.write("===================================\n");
                writer.write("Types potentially sent from server:\n");
                writer.write("===================================\n\n");
                writer.flush();
                typesSentToBrowser = typesSentToBrowserBuilder.build(logger);

                writer.close();
                rpcLog = stringWriter.toString();
            }
        } finally {
            event.end();
        }

        // Check previous cached result, to see if we can return now
        if (checkCachedGeneratorResultValid(logger, context, typesSentFromBrowser, typesSentToBrowser)) {
            logger.log(TreeLogger.TRACE, "Reusing all cached artifacts for " + getProxyQualifiedName());
            return new RebindResult(RebindStatus.USE_ALL_CACHED, getProxyQualifiedName());
        }

        try {
            ConfigurationProperty prop = context.getPropertyOracle()
                    .getConfigurationProperty(TypeSerializerCreator.GWT_ELIDE_TYPE_NAMES_FROM_RPC);
            elideTypeNames = Boolean.parseBoolean(prop.getValues().get(0));
        } catch (BadPropertyValueException e) {
            logger.log(TreeLogger.ERROR,
                    "Configuration property " + TypeSerializerCreator.GWT_ELIDE_TYPE_NAMES_FROM_RPC
                            + " is not defined. Is RemoteService.gwt.xml inherited?");
            throw new UnableToCompleteException();
        }

        SourceWriter srcWriter = getSourceWriter(logger, context, serviceAsync);
        if (srcWriter == null) {
            // don't expect this to occur, but could happen if an instance was
            // recently generated but not yet committed
            return new RebindResult(RebindStatus.USE_EXISTING, getProxyQualifiedName());
        }

        generateTypeHandlers(logger, context, typesSentFromBrowser, typesSentToBrowser);

        String serializationPolicyStrongName = writeSerializationPolicyFile(logger, context, typesSentFromBrowser,
                typesSentToBrowser);

        String remoteServiceInterfaceName = elideTypeNames ? TypeNameObfuscator.SERVICE_INTERFACE_ID
                : SerializationUtils.getRpcTypeName(serviceIntf);
        generateProxyFields(srcWriter, typesSentFromBrowser, serializationPolicyStrongName,
                remoteServiceInterfaceName);

        generateProxyContructor(srcWriter);

        generateProxyMethods(srcWriter, typesSentFromBrowser, typeOracle, syncMethToAsyncMethMap);

        generateStreamWriterOverride(srcWriter);

        generateCheckRpcTokenTypeOverride(srcWriter, typeOracle, typesSentFromBrowser);

        srcWriter.commit(logger);

        if (context.isProdMode() || logger.isLoggable(TreeLogger.DEBUG)) {
            // Create an artifact explaining STOB's decisions. It will be emitted by
            // RpcLogLinker
            context.commitArtifact(logger, new RpcLogArtifact(serviceIntf.getQualifiedSourceName(),
                    serializationPolicyStrongName, rpcLog));
        }

        if (checkGeneratorResultCacheability(context)) {
            // Remember the type info that we care about for cacheability testing.
            CachedClientDataMap clientData = new CachedClientDataMap();
            CachedRpcTypeInformation cti = new CachedRpcTypeInformation(typesSentFromBrowser, typesSentToBrowser,
                    customSerializersUsed, typesNotUsingCustomSerializers);
            clientData.put(CACHED_TYPE_INFO_KEY, cti);
            CachedPropertyInformation cpi = new CachedPropertyInformation(logger, context.getPropertyOracle(),
                    selectionPropsToCheck, configPropsToCheck);
            clientData.put(CACHED_PROPERTY_INFO_KEY, cpi);

            /*
             * Return with RebindStatus.USE_PARTIAL_CACHED, since we are allowing
             * reuse of cached results for field serializers, when available, but
             * all other types have been newly generated.
             */
            return new RebindResult(RebindStatus.USE_PARTIAL_CACHED, getProxyQualifiedName(), clientData);
        } else {
            return new RebindResult(RebindStatus.USE_ALL_NEW_WITH_NO_CACHING, getProxyQualifiedName());
        }
    }

    protected void addRoots(TreeLogger logger, TypeOracle typeOracle,
            SerializableTypeOracleBuilder typesSentFromBrowserBuilder,
            SerializableTypeOracleBuilder typesSentToBrowserBuilder) throws UnableToCompleteException {
        try {
            addRequiredRoots(logger, typeOracle, typesSentFromBrowserBuilder);
            addRequiredRoots(logger, typeOracle, typesSentToBrowserBuilder);

            addRemoteServiceRootTypes(logger, typeOracle, typesSentFromBrowserBuilder, typesSentToBrowserBuilder,
                    serviceIntf);
        } catch (NotFoundException e) {
            logger.log(TreeLogger.ERROR, "Unable to find type referenced from remote service", e);
            throw new UnableToCompleteException();
        }
    }

    protected String computeTypeNameExpression(JType paramType) {
        String typeName;
        if (typeStrings.containsKey(paramType)) {
            typeName = typeStrings.get(paramType);
        } else {
            typeName = SerializationUtils.getRpcTypeName(paramType);
        }
        return typeName == null ? null : ('"' + typeName + '"');
    }

    protected void generateCheckRpcTokenTypeOverride(SourceWriter srcWriter, TypeOracle typeOracle,
            SerializableTypeOracle typesSentFromBrowser) {
        JClassType rpcTokenType = typeOracle.findType(RpcToken.class.getName());
        JClassType[] rpcTokenSubtypes = rpcTokenType.getSubtypes();
        String rpcTokenImplementation = "";
        for (JClassType rpcTokenSubtype : rpcTokenSubtypes) {
            if (typesSentFromBrowser.isSerializable(rpcTokenSubtype)) {
                if (rpcTokenImplementation.length() > 0) {
                    // >1 implematation of RpcToken, bail
                    rpcTokenImplementation = "";
                    break;
                } else {
                    rpcTokenImplementation = rpcTokenSubtype.getQualifiedSourceName();
                }
            }
        }
        if (rpcTokenImplementation.length() > 0) {
            srcWriter.println("@Override");
            srcWriter.println("protected void checkRpcTokenType(RpcToken token) {");
            srcWriter.indent();
            srcWriter.println("if (!(token instanceof " + rpcTokenImplementation + ")) {");
            srcWriter.indent();
            srcWriter.println("throw new RpcTokenException(\"Invalid RpcToken type: " + "expected '"
                    + rpcTokenImplementation + "' but got '\" + " + "token.getClass() + \"'\");");
            srcWriter.outdent();
            srcWriter.println("}");
            srcWriter.outdent();
            srcWriter.println("}");
        }
    }

    /**
     * Generate the proxy constructor and delegate to the superclass constructor
     * using the default address for the
     * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService}.
     */
    protected void generateProxyContructor(SourceWriter srcWriter) {
        srcWriter.println("public " + getProxySimpleName() + "() {");
        srcWriter.indent();
        srcWriter.println("super(GWT.getModuleBaseURL(),");
        srcWriter.indent();
        srcWriter.println(getRemoteServiceRelativePath() + ", ");
        srcWriter.println("SERIALIZATION_POLICY, ");
        srcWriter.println("SERIALIZER);");
        srcWriter.outdent();
        srcWriter.outdent();
        srcWriter.println("}");
    }

    /**
     * Generate any fields required by the proxy.
     * 
     * @param serializableTypeOracle the type oracle
     */
    protected void generateProxyFields(SourceWriter srcWriter, SerializableTypeOracle serializableTypeOracle,
            String serializationPolicyStrongName, String remoteServiceInterfaceName) {
        // Initialize a field with binary name of the remote service interface
        srcWriter.println("private static final String REMOTE_SERVICE_INTERFACE_NAME = " + "\""
                + remoteServiceInterfaceName + "\";");
        srcWriter.println(
                "private static final String SERIALIZATION_POLICY =\"" + serializationPolicyStrongName + "\";");
        String typeSerializerName = SerializationUtils.getTypeSerializerQualifiedName(serviceIntf);
        srcWriter.println(
                "private static final " + typeSerializerName + " SERIALIZER = new " + typeSerializerName + "();");
        srcWriter.println();
    }

    /**
     * Generates the client's asynchronous proxy method.
     * 
     * @param serializableTypeOracle the type oracle
     */
    protected void generateProxyMethod(SourceWriter w, SerializableTypeOracle serializableTypeOracle,
            TypeOracle typeOracle, JMethod syncMethod, JMethod asyncMethod) {

        w.println();

        // Write the method signature
        JType asyncReturnType = asyncMethod.getReturnType().getErasedType();
        w.print("public ");
        w.print(asyncReturnType.getQualifiedSourceName());
        w.print(" ");
        w.print(asyncMethod.getName() + "(");

        boolean needsComma = false;
        NameFactory nameFactory = new NameFactory();
        JParameter[] asyncParams = asyncMethod.getParameters();
        for (int i = 0; i < asyncParams.length; ++i) {
            JParameter param = asyncParams[i];

            if (needsComma) {
                w.print(", ");
            } else {
                needsComma = true;
            }

            /*
             * Ignoring the AsyncCallback parameter, if any method requires a call to
             * SerializationStreamWriter.writeObject we need a try catch block
             */
            JType paramType = param.getType();
            paramType = paramType.getErasedType();

            w.print(paramType.getQualifiedSourceName());
            w.print(" ");

            String paramName = param.getName();
            nameFactory.addName(paramName);
            w.print(paramName);
        }

        w.println(") {");
        w.indent();

        String helperName = nameFactory.createName("helper");
        String helperClassName = RemoteServiceProxy.ServiceHelper.class.getCanonicalName();
        w.println("%s %s = new %s(\"%s\", \"%s\");", helperClassName, helperName, helperClassName,
                getProxySimpleName(), syncMethod.getName());

        w.println("try {");
        w.indent();

        // Write the parameter count followed by the parameter values
        JParameter[] syncParams = syncMethod.getParameters();

        String streamWriterName = nameFactory.createName("streamWriter");
        w.println("%s %s = %s.start(REMOTE_SERVICE_INTERFACE_NAME, %s);",
                SerializationStreamWriter.class.getSimpleName(), streamWriterName, helperName, syncParams.length);

        for (JParameter param : syncParams) {
            JType paramType = param.getType().getErasedType();
            String typeNameExpression = computeTypeNameExpression(paramType);
            assert typeNameExpression != null : "Could not compute a type name for "
                    + paramType.getQualifiedSourceName();
            w.println(streamWriterName + ".writeString(" + typeNameExpression + ");");
        }

        // Encode all of the arguments to the asynchronous method, but exclude the
        // last argument which is the callback instance.
        //
        for (int i = 0; i < asyncParams.length - 1; ++i) {
            JParameter asyncParam = asyncParams[i];
            w.print(streamWriterName + ".");
            w.print(Shared.getStreamWriteMethodNameFor(asyncParam.getType()));
            w.println("(" + asyncParam.getName() + ");");
        }

        /*
         * Depending on the return type for the async method, return a
         * RequestBuilder, a Request, or nothing at all.
         */
        JParameter callbackParam = asyncParams[asyncParams.length - 1];
        JType returnType = syncMethod.getReturnType();
        String callbackName = callbackParam.getName();

        if (asyncReturnType == JPrimitiveType.VOID) {
            w.println("%s.finish(%s, ResponseReader.%s);", helperName, callbackName,
                    getResponseReaderFor(returnType).name());
        } else if (asyncReturnType.getQualifiedSourceName().equals(RequestBuilder.class.getName())) {
            w.println("return %s.finishForRequestBuilder(%s, ResponseReader.%s);", helperName, callbackName,
                    getResponseReaderFor(returnType).name());
        } else if (asyncReturnType.getQualifiedSourceName().equals(Request.class.getName())) {
            w.println("return %s.finish(%s, ResponseReader.%s);", helperName, callbackName,
                    getResponseReaderFor(returnType).name());
        } else {
            // This method should have been caught by RemoteServiceAsyncValidator
            throw new RuntimeException("Unhandled return type " + asyncReturnType.getQualifiedSourceName());
        }

        w.outdent();
        w.print("} catch (SerializationException ");
        String exceptionName = nameFactory.createName("ex");
        w.println(exceptionName + ") {");
        w.indent();
        if (!asyncReturnType.getQualifiedSourceName().equals(RequestBuilder.class.getName())) {
            /*
             * If the method returns void or Request, signal the serialization error
             * immediately. If the method returns RequestBuilder, the error will be
             * signaled whenever RequestBuilder.send() is invoked.
             */
            w.println(callbackName + ".onFailure(" + exceptionName + ");");
        }
        if (asyncReturnType.getQualifiedSourceName().equals(RequestBuilder.class.getName())) {
            w.println("return new " + FailingRequestBuilder.class.getName() + "(" + exceptionName + ", "
                    + callbackName + ");");
        } else if (asyncReturnType.getQualifiedSourceName().equals(Request.class.getName())) {
            w.println("return new " + FailedRequest.class.getName() + "();");
        } else {
            assert asyncReturnType == JPrimitiveType.VOID;
        }
        w.outdent();
        w.println("}");

        w.outdent();
        w.println("}");
    }

    protected void generateProxyMethods(SourceWriter w, SerializableTypeOracle serializableTypeOracle,
            TypeOracle typeOracle, Map<JMethod, JMethod> syncMethToAsyncMethMap) {
        JMethod[] syncMethods = serviceIntf.getOverridableMethods();
        for (JMethod syncMethod : syncMethods) {

            JMethod asyncMethod = syncMethToAsyncMethMap.get(syncMethod);
            assert (asyncMethod != null);

            JClassType enclosingType = syncMethod.getEnclosingType();
            JParameterizedType isParameterizedType = enclosingType.isParameterized();
            if (isParameterizedType != null) {
                JMethod[] methods = isParameterizedType.getMethods();
                for (int i = 0; i < methods.length; ++i) {
                    if (methods[i] == syncMethod) {
                        /*
                         * Use the generic version of the method to ensure that the server
                         * can find the method using the erasure of the generic signature.
                         */
                        syncMethod = isParameterizedType.getBaseType().getMethods()[i];
                    }
                }
            }

            generateProxyMethod(w, serializableTypeOracle, typeOracle, syncMethod, asyncMethod);
        }
    }

    /**
     * @param syncMethod
     * @param asyncMethod
     * @param statsContextName
     */
    protected void generateRpcStatsContext(SourceWriter w, JMethod syncMethod, JMethod asyncMethod,
            String statsContextName) {
        w.println("RpcStatsContext " + statsContextName + " = new RpcStatsContext();");
    }

    protected void generateStreamWriterOverride(SourceWriter srcWriter) {
        srcWriter.println("@Override");
        srcWriter.println("public SerializationStreamWriter createStreamWriter() {");
        srcWriter.indent();
        /*
         * Need an explicit cast since we've widened the declaration of the method
         * in RemoteServiceProxy.
         */
        srcWriter.println("ClientSerializationStreamWriter toReturn =");
        srcWriter.indentln("(ClientSerializationStreamWriter) super.createStreamWriter();");
        if (elideTypeNames) {
            srcWriter.println("toReturn.addFlags(ClientSerializationStreamWriter." + "FLAG_ELIDE_TYPE_NAMES);");
        }
        srcWriter.println("if (getRpcToken() != null) {");
        srcWriter.indent();
        srcWriter.println("toReturn.addFlags(ClientSerializationStreamWriter." + "FLAG_RPC_TOKEN_INCLUDED);");
        srcWriter.outdent();
        srcWriter.println("}");
        srcWriter.println("return toReturn;");
        srcWriter.outdent();
        srcWriter.println("}");
    }

    protected void generateTypeHandlers(TreeLogger logger, GeneratorContextExt context,
            SerializableTypeOracle typesSentFromBrowser, SerializableTypeOracle typesSentToBrowser)
            throws UnableToCompleteException {
        Event event = SpeedTracerLogger.start(CompilerEventType.GENERATOR_RPC_TYPE_SERIALIZER);
        TypeSerializerCreator tsc = new TypeSerializerCreator(logger, typesSentFromBrowser, typesSentToBrowser,
                context, SerializationUtils.getTypeSerializerQualifiedName(serviceIntf),
                SerializationUtils.getTypeSerializerSimpleName(serviceIntf));
        tsc.realize(logger);
        event.end();

        typeStrings = new HashMap<JType, String>(tsc.getTypeStrings());
        typeStrings.put(serviceIntf, TypeNameObfuscator.SERVICE_INTERFACE_ID);

        customSerializersUsed = tsc.getCustomSerializersUsed();
        typesNotUsingCustomSerializers = tsc.getTypesNotUsingCustomSerializers();
    }

    protected String getProxySimpleName() {
        String[] name = Shared.synthesizeTopLevelClassName(serviceIntf, PROXY_SUFFIX);
        return name[1];
    }

    protected Class<? extends RemoteServiceProxy> getProxySupertype() {
        return RemoteServiceProxy.class;
    }

    protected String getRemoteServiceRelativePath() {
        RemoteServiceRelativePath moduleRelativeURL = serviceIntf.getAnnotation(RemoteServiceRelativePath.class);
        if (moduleRelativeURL != null) {
            return "\"" + moduleRelativeURL.value() + "\"";
        }

        return null;
    }

    protected Class<? extends SerializationStreamWriter> getStreamWriterClass() {
        return ClientSerializationStreamWriter.class;
    }

    protected String writeSerializationPolicyFile(TreeLogger logger, GeneratorContextExt ctx,
            SerializableTypeOracle serializationSto, SerializableTypeOracle deserializationSto)
            throws UnableToCompleteException {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            OutputStreamWriter osw = new OutputStreamWriter(baos,
                    SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING);
            TypeOracle oracle = ctx.getTypeOracle();
            PrintWriter pw = new PrintWriter(osw);

            JType[] serializableTypes = unionOfTypeArrays(serializationSto.getSerializableTypes(),
                    deserializationSto.getSerializableTypes(), new JType[] { serviceIntf });

            for (int i = 0; i < serializableTypes.length; ++i) {
                JType type = serializableTypes[i];
                String binaryTypeName = SerializationUtils.getRpcTypeName(type);
                pw.print(binaryTypeName);
                pw.print(", " + Boolean.toString(deserializationSto.isSerializable(type)));
                pw.print(", " + Boolean.toString(deserializationSto.maybeInstantiated(type)));
                pw.print(", " + Boolean.toString(serializationSto.isSerializable(type)));
                pw.print(", " + Boolean.toString(serializationSto.maybeInstantiated(type)));
                pw.print(", " + typeStrings.get(type));

                /*
                 * Include the serialization signature to bump the RPC file name if
                 * obfuscated identifiers are used.
                 */
                pw.print(", " + SerializationUtils.getSerializationSignature(oracle, type));
                pw.print('\n');

                /*
                 * Emit client-side field information for classes that may be enhanced
                 * on the server. Each line consists of a comma-separated list
                 * containing the keyword '@ClientFields', the class name, and a list of
                 * all potentially serializable client-visible fields.
                 */
                if ((type instanceof JClassType) && ((JClassType) type).isEnhanced()) {
                    JField[] fields = ((JClassType) type).getFields();
                    JField[] rpcFields = new JField[fields.length];
                    int numRpcFields = 0;
                    for (JField f : fields) {
                        if (f.isTransient() || f.isStatic() || f.isFinal()) {
                            continue;
                        }
                        rpcFields[numRpcFields++] = f;
                    }

                    pw.print(SerializationPolicyLoader.CLIENT_FIELDS_KEYWORD);
                    pw.print(',');
                    pw.print(binaryTypeName);
                    for (int idx = 0; idx < numRpcFields; idx++) {
                        pw.print(',');
                        pw.print(rpcFields[idx].getName());
                    }
                    pw.print('\n');
                }
            }

            // Closes the wrapped streams.
            pw.close();

            byte[] serializationPolicyFileContents = baos.toByteArray();
            String serializationPolicyName = Util.computeStrongName(serializationPolicyFileContents);

            String serializationPolicyFileName = SerializationPolicyLoader
                    .getSerializationPolicyFileName(serializationPolicyName);
            OutputStream os = ctx.tryCreateResource(logger, serializationPolicyFileName);
            if (os != null) {
                os.write(serializationPolicyFileContents);
                GeneratedResource resource = ctx.commitResource(logger, os);

                /*
                 * Record which proxy class created the resource. A manifest will be
                 * emitted by the RpcPolicyManifestLinker.
                 */
                emitPolicyFileArtifact(logger, ctx, resource.getPartialPath());
            } else {
                if (logger.isLoggable(TreeLogger.TRACE)) {
                    logger.log(TreeLogger.TRACE, "SerializationPolicy file for RemoteService '"
                            + serviceIntf.getQualifiedSourceName() + "' already exists; no need to rewrite it.",
                            null);
                }
            }

            return serializationPolicyName;
        } catch (UnsupportedEncodingException e) {
            logger.log(TreeLogger.ERROR,
                    SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING + " is not supported", e);
            throw new UnableToCompleteException();
        } catch (IOException e) {
            logger.log(TreeLogger.ERROR, null, e);
            throw new UnableToCompleteException();
        }
    }

    private boolean checkAlreadyGenerated(TypeOracle typeOracle, JClassType serviceAsync) {
        JPackage serviceIntfPkg = serviceAsync.getPackage();
        String packageName = serviceIntfPkg == null ? "" : serviceIntfPkg.getName();
        return typeOracle.findType(packageName, getProxySimpleName()) != null;
    }

    private boolean checkCachedGeneratorResultValid(TreeLogger logger, GeneratorContextExt ctx,
            SerializableTypeOracle typesSentFromBrowser, SerializableTypeOracle typesSentToBrowser) {

        CachedRebindResult lastResult = ctx.getCachedGeneratorResult();
        if (lastResult == null || !ctx.isGeneratorResultCachingEnabled()) {
            return false;
        }

        CachedPropertyInformation cpi = (CachedPropertyInformation) lastResult
                .getClientData(CACHED_PROPERTY_INFO_KEY);
        if (cpi == null) {
            return false;
        }

        CachedRpcTypeInformation cti = (CachedRpcTypeInformation) lastResult.getClientData(CACHED_TYPE_INFO_KEY);
        if (cti == null) {
            return false;
        }

        if (!cti.checkTypeInformation(logger, ctx.getTypeOracle(), typesSentFromBrowser, typesSentToBrowser)) {
            return false;
        }

        if (!cpi.checkPropertiesWithPropertyOracle(logger, ctx.getPropertyOracle())) {
            return false;
        }

        return true;
    }

    private boolean checkGeneratorResultCacheability(GeneratorContextExt context) {
        /*
         * Currently not supporting caching for implementations which sub-class this
         * class, such as {@link RpcProxyCreator}, which implements deRPC.
         */
        if (!this.getClass().equals(ProxyCreator.class)) {
            return false;
        }

        return context.isGeneratorResultCachingEnabled();
    }

    private void emitPolicyFileArtifact(TreeLogger logger, GeneratorContextExt context, String partialPath)
            throws UnableToCompleteException {
        try {
            String qualifiedSourceName = serviceIntf.getQualifiedSourceName();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Writer writer;
            writer = new OutputStreamWriter(baos, SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING);
            writer.write("serviceClass: " + qualifiedSourceName + "\n");
            writer.write("path: " + partialPath + "\n");
            writer.close();

            byte[] manifestBytes = baos.toByteArray();
            String md5 = Util.computeStrongName(manifestBytes);
            OutputStream os = context.tryCreateResource(logger, MANIFEST_ARTIFACT_DIR + "/" + md5 + ".txt");
            os.write(manifestBytes);

            GeneratedResource resource = context.commitResource(logger, os);
            // TODO: change to Deploy when possible
            resource.setVisibility(Visibility.LegacyDeploy);
        } catch (UnsupportedEncodingException e) {
            logger.log(TreeLogger.ERROR,
                    SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING + " is not supported", e);
            throw new UnableToCompleteException();
        } catch (IOException e) {
            logger.log(TreeLogger.ERROR, null, e);
            throw new UnableToCompleteException();
        }
    }

    private String getProxyQualifiedName() {
        String[] name = Shared.synthesizeTopLevelClassName(serviceIntf, PROXY_SUFFIX);
        return name[0].length() == 0 ? name[1] : name[0] + "." + name[1];
    }

    private ResponseReader getResponseReaderFor(JType returnType) {
        if (returnType.isPrimitive() != null) {
            return JPRIMITIVETYPE_TO_RESPONSEREADER.get(returnType.isPrimitive());
        }

        if (returnType.getQualifiedSourceName().equals(String.class.getCanonicalName())) {
            return ResponseReader.STRING;
        }

        return ResponseReader.OBJECT;
    }

    private SourceWriter getSourceWriter(TreeLogger logger, GeneratorContextExt ctx, JClassType serviceAsync) {
        JPackage serviceIntfPkg = serviceAsync.getPackage();
        String packageName = serviceIntfPkg == null ? "" : serviceIntfPkg.getName();
        PrintWriter printWriter = ctx.tryCreate(logger, packageName, getProxySimpleName());
        if (printWriter == null) {
            return null;
        }

        ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName,
                getProxySimpleName());

        String[] imports = new String[] { getProxySupertype().getCanonicalName(),
                getStreamWriterClass().getCanonicalName(), SerializationStreamWriter.class.getCanonicalName(),
                GWT.class.getCanonicalName(), ResponseReader.class.getCanonicalName(),
                SerializationException.class.getCanonicalName(), RpcToken.class.getCanonicalName(),
                RpcTokenException.class.getCanonicalName(), Impl.class.getCanonicalName(),
                RpcStatsContext.class.getCanonicalName() };
        for (String imp : imports) {
            composerFactory.addImport(imp);
        }

        composerFactory.setSuperclass(getProxySupertype().getSimpleName());
        composerFactory.addImplementedInterface(serviceAsync.getErasedType().getQualifiedSourceName());

        return composerFactory.createSourceWriter(ctx, printWriter);
    }
}