// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.rebind; 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.JMethod; import com.google.gwt.core.ext.typeinfo.JParameter; import com.google.gwt.core.ext.typeinfo.JPrimitiveType; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.RpcRequestBuilder; import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.rebind.rpc.ServiceInterfaceProxyGenerator; import java.io.PrintWriter; /** * Service proxy generator that can be used instead of * {@link ServiceInterfaceProxyGenerator} to generate client proxies for remote * services. * * <p>The generated proxies implement the * {@link com.google.appinventor.client.ExtendedServiceProxy} interface * and delegate all additional calls to "normal" proxies generated by * {@link ServiceInterfaceProxyGenerator}. * */ public class ExtendedServiceProxyGenerator extends Generator { // Suffix that is appended to the name of the service interface to build the // name of the proxy class private static final String PROXY_SUFFIX = "_ExtendedProxy"; // Suffix that is appended to the name of the service interface to build the // name of the asynchronous service interface private static final String ASYNC_SUFFIX = "Async"; // Delegate generator to generate "normal" proxies private static final Generator PROXY_GENERATOR = new ServiceInterfaceProxyGenerator(); @Override public String generate(TreeLogger logger, GeneratorContext context, String typeName) throws UnableToCompleteException { // Delegate the creation of the "normal" proxy String proxyTypeName = PROXY_GENERATOR.generate(logger, context, typeName); // Wrap the generated proxy in an extended proxy return generateExtendedProxy(logger, context, typeName, proxyTypeName); } /** * Generates a wrapper around the proxy generated by * {@link ServiceInterfaceProxyGenerator}. * * @param logger log interface * @param context generator context * @param typeName name of the interface that was passed to * {@link com.google.gwt.core.client.GWT#create(Class)} * @param proxyTypeName the name of the wrapped proxy class * @return the name of the extended proxy class */ private String generateExtendedProxy(TreeLogger logger, GeneratorContext context, String typeName, String proxyTypeName) { JClassType type = context.getTypeOracle().findType(typeName); String packageName = type.getPackage().getName(); String className = type.getSimpleSourceName() + PROXY_SUFFIX; String asyncName = typeName + ASYNC_SUFFIX; String classNameExtendedServiceProxy = "com.google.appinventor.client.ExtendedServiceProxy"; // The generator can be invoked for the same class name more than once. // In this case the GeneratorContext.tryCreate method will return null to // indicate that the file already exists. This is not an error. PrintWriter out = context.tryCreate(logger, packageName, className); if (out != null) { out.println("package " + packageName + ";"); out.println("class " + className); out.println(" extends " + classNameExtendedServiceProxy + "<" + typeName + ">"); out.println(" implements " + ServiceDefTarget.class.getName() + ", " + asyncName + " {"); out.println(" private " + proxyTypeName + " proxy = new " + proxyTypeName + "();"); out.println(" public String getServiceEntryPoint() {"); out.println(" return proxy.getServiceEntryPoint();"); out.println(" }"); out.println(" public void setRpcRequestBuilder(" + RpcRequestBuilder.class.getName() + " builder) {"); out.println(" proxy.setRpcRequestBuilder(builder);"); out.println(" }"); out.println(" public void setServiceEntryPoint(String address) {"); out.println(" proxy.setServiceEntryPoint(address);"); out.println(" }"); out.println(" public String getSerializationPolicyName() {"); out.println(" return proxy.getSerializationPolicyName();"); out.println(" }"); for (JMethod method : type.getMethods()) { printMethod(out, method, typeName); } out.println("}"); context.commit(logger, out); } return packageName + "." + className; } /** * Generate the implementation of a single method. * * @param out where to print the method to * @param method the method * @param typeName type name of the containing proxy class */ private void printMethod(PrintWriter out, JMethod method, String typeName) { // Build parameter lists int i = 0; StringBuilder actualParamsBuilder = new StringBuilder(); StringBuilder formalParamsBuilder = new StringBuilder(); for (JParameter param : method.getParameters()) { if (i != 0) { actualParamsBuilder.append(", "); formalParamsBuilder.append(", "); } String paramType = param.getType().getParameterizedQualifiedSourceName(); String paramName = "p" + i++; actualParamsBuilder.append(paramName); formalParamsBuilder.append(paramType + " " + paramName); } String actualParams = actualParamsBuilder.toString(); String formalParams = formalParamsBuilder.toString(); // Information about the return type JType returnType = method.getReturnType(); boolean hasReturnValue = !returnType.getSimpleSourceName().equals("void"); JPrimitiveType primitiveReturnType = returnType.isPrimitive(); String resultType = primitiveReturnType != null ? primitiveReturnType.getQualifiedBoxedSourceName() : returnType.getParameterizedQualifiedSourceName(); String callbackType = AsyncCallback.class.getName() + "<" + resultType + ">"; // Print method out.println(" public void " + method.getName() + "(" + formalParams + (formalParams.isEmpty() ? "" : ", ") + "final " + callbackType + " callback" + ") {"); out.println(" fireStart(\"" + method.getName() + "\"" + (actualParams.isEmpty() ? "" : ", ") + actualParams + ");"); out.println(" proxy." + method.getName() + "(" + actualParams + (actualParams.isEmpty() ? "" : ", ") + "new " + callbackType + "() {"); out.println(" public void onSuccess(" + resultType + " result) {"); out.println(" " + outcome(method, "Success", "result")); out.println(" }"); out.println(" public void onFailure(Throwable caught) {"); out.println(" " + outcome(method, "Failure", "caught")); out.println(" }"); out.println(" });"); out.println(" }"); } /** * Generate code to handle the outcome of an RPC. * * @param method the RPC method that was called * @param outcome the outcome: "Success" or "Failure" * @param result the result of the RPC call or null for void methods * @return the generated code */ private String outcome(JMethod method, String outcome, String result) { String callListener = "fire" + outcome + "(\"" + method.getName() + "\", " + result + ");"; String callCallback = "callback.on" + outcome + "(" + result + ");"; return callListener + ' ' + callCallback; } }