blob: 97c6f7082379a6fdf7b6f9091eb32dca472f3681 [file] [log] [blame]
/*
* 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;
}
}