/* * Copyright 2007 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.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.JPackage; 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.core.ext.typeinfo.NotFoundException; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.http.client.Request; import com.google.gwt.http.client.RequestBuilder; import com.google.gwt.user.client.rpc.AsyncCallback; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; /** * Validates the asynchronous version of * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} interface. */ class RemoteServiceAsyncValidator { static void logValidAsyncInterfaceDeclaration(TreeLogger logger, JClassType remoteService) { TreeLogger branch = logger.branch(TreeLogger.INFO, "A valid definition for the asynchronous version of interface '" + remoteService.getQualifiedSourceName() + "' would be:\n", null); branch.log(TreeLogger.ERROR, synthesizeAsynchronousInterfaceDefinition(remoteService), null); } private static String computeAsyncMethodSignature(JMethod syncMethod, JClassType asyncCallbackClass) { return computeInternalSignature(syncMethod) + "/" + asyncCallbackClass.getQualifiedSourceName(); } private static String computeInternalSignature(JMethod method) { StringBuffer sb = new StringBuffer(); sb.setLength(0); sb.append(method.getName()); JParameter[] params = method.getParameters(); for (JParameter param : params) { sb.append("/"); JType paramType = param.getType(); sb.append(paramType.getErasedType().getQualifiedSourceName()); } return sb.toString(); } /** * Builds a map of asynchronous method internal signatures to the * corresponding asynchronous {@link JMethod}. */ private static Map<String, JMethod> initializeAsyncMethodMap(JMethod[] asyncMethods) { Map<String, JMethod> sigs = new TreeMap<String, JMethod>(); for (JMethod asyncMethod : asyncMethods) { sigs.put(computeInternalSignature(asyncMethod), asyncMethod); } return sigs; } private static String synthesizeAsynchronousInterfaceDefinition(JClassType serviceIntf) { StringBuffer sb = new StringBuffer(); JPackage pkg = serviceIntf.getPackage(); if (pkg != null) { sb.append("\npackage "); sb.append(pkg.getName()); sb.append(";\n"); } sb.append("\npublic interface "); sb.append(serviceIntf.getSimpleSourceName()); sb.append("Async {\n"); JMethod[] methods = serviceIntf.getOverridableMethods(); for (JMethod method : methods) { assert (method != null); sb.append("\tvoid "); sb.append(method.getName()); sb.append("("); JParameter[] params = method.getParameters(); for (int paramIndex = 0; paramIndex < params.length; ++paramIndex) { JParameter param = params[paramIndex]; if (paramIndex > 0) { sb.append(", "); } sb.append(param.toString()); } if (params.length > 0) { sb.append(", "); } JType returnType = method.getReturnType(); sb.append(AsyncCallback.class.getName()); sb.append("<"); if (returnType instanceof JPrimitiveType) { sb.append(((JPrimitiveType) returnType).getQualifiedBoxedSourceName()); } else { sb.append(returnType.getParameterizedQualifiedSourceName()); } sb.append("> arg"); sb.append(Integer.toString(params.length + 1)); sb.append(");\n"); } sb.append("}"); return sb.toString(); } private static void validationFailed(TreeLogger branch, JClassType remoteService) throws UnableToCompleteException { logValidAsyncInterfaceDeclaration(branch, remoteService); throw new UnableToCompleteException(); } /** * {@link JClassType} for the {@link AsyncCallback} interface. */ private final JClassType asyncCallbackClass; /** * {@link JClassType} for the {@link RequestBuilder} class. */ private final JClassType requestBuilderType; /** * {@link JClassType} for the {@link Request} class. */ private final JClassType requestType; RemoteServiceAsyncValidator(TreeLogger logger, TypeOracle typeOracle) throws UnableToCompleteException { try { asyncCallbackClass = typeOracle.getType(AsyncCallback.class.getName()); requestType = typeOracle.getType(Request.class.getCanonicalName()); requestBuilderType = typeOracle.getType(RequestBuilder.class.getCanonicalName()); } catch (NotFoundException e) { logger.log(TreeLogger.ERROR, null, e); throw new UnableToCompleteException(); } } /** * Checks that for every method on the synchronous remote service interface * there is a corresponding asynchronous version in the asynchronous version * of the remote service. If the validation succeeds, a map of synchronous to * asynchronous methods is returned. * * @param logger * @param remoteService * @return map of synchronous method to asynchronous method * * @throws UnableToCompleteException if the asynchronous * {@link com.google.gwt.user.client.rpc.RemoteService * RemoteService} was not found, or if it does not have an * asynchronous method version of every synchronous one */ public Map<JMethod, JMethod> validate(TreeLogger logger, JClassType remoteService, JClassType remoteServiceAsync) throws UnableToCompleteException { TreeLogger branch = logger.branch(TreeLogger.DEBUG, "Checking the synchronous interface '" + remoteService.getQualifiedSourceName() + "' against its asynchronous version '" + remoteServiceAsync.getQualifiedSourceName() + "'", null); // Sync and async versions must have the same number of methods JMethod[] asyncMethods = remoteServiceAsync.getOverridableMethods(); JMethod[] syncMethods = remoteService.getOverridableMethods(); if (asyncMethods.length != syncMethods.length) { branch.branch(TreeLogger.ERROR, "The asynchronous version of " + remoteService.getQualifiedSourceName() + " has " + (asyncMethods.length > syncMethods.length ? "more" : "less") + " methods than the synchronous version", null); validationFailed(branch, remoteService); } // Check that for every sync method there is a corresponding async method boolean failed = false; Map<String, JMethod> asyncMethodMap = initializeAsyncMethodMap(asyncMethods); Map<JMethod, JMethod> syncMethodToAsyncMethodMap = new HashMap<JMethod, JMethod>(); for (JMethod syncMethod : syncMethods) { String asyncSig = computeAsyncMethodSignature(syncMethod, asyncCallbackClass); JMethod asyncMethod = asyncMethodMap.get(asyncSig); if (asyncMethod == null) { branch.branch(TreeLogger.ERROR, "Missing asynchronous version of the synchronous method '" + syncMethod.getReadableDeclaration() + "'", null); failed = true; } else { // TODO if async param is parameterized make sure that the sync return // type is assignable to the first type argument JType returnType = asyncMethod.getReturnType(); if (returnType != JPrimitiveType.VOID && returnType != requestType && returnType != requestBuilderType) { branch.branch(TreeLogger.ERROR, "The asynchronous version of the synchronous method '" + syncMethod.getReadableDeclaration() + "' must have a return type of 'void' or '" + Request.class.getCanonicalName() + "' or '" + RequestBuilder.class.getCanonicalName() + "'", null); failed = true; } else { syncMethodToAsyncMethodMap.put(syncMethod, asyncMethod); } } } if (failed) { validationFailed(branch, remoteService); } branch.log(TreeLogger.DEBUG, "Interfaces are in sync"); return syncMethodToAsyncMethodMap; } }