001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.user.rebind.rpc;
017:
018: import com.google.gwt.core.ext.TreeLogger;
019: import com.google.gwt.core.ext.UnableToCompleteException;
020: import com.google.gwt.core.ext.typeinfo.JClassType;
021: import com.google.gwt.core.ext.typeinfo.JMethod;
022: import com.google.gwt.core.ext.typeinfo.JPackage;
023: import com.google.gwt.core.ext.typeinfo.JParameter;
024: import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
025: import com.google.gwt.core.ext.typeinfo.JType;
026: import com.google.gwt.core.ext.typeinfo.NotFoundException;
027: import com.google.gwt.core.ext.typeinfo.TypeOracle;
028: import com.google.gwt.http.client.Request;
029: import com.google.gwt.user.client.rpc.AsyncCallback;
030:
031: import java.util.HashMap;
032: import java.util.Map;
033: import java.util.TreeMap;
034:
035: /**
036: * Validates the asynchronous version of
037: * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} interface.
038: */
039: class RemoteServiceAsyncValidator {
040: static void logValidAsyncInterfaceDeclaration(TreeLogger logger,
041: JClassType remoteService) {
042: TreeLogger branch = logger.branch(TreeLogger.INFO,
043: "A valid definition for the asynchronous version of interface '"
044: + remoteService.getQualifiedSourceName()
045: + "' would be:\n", null);
046: branch
047: .log(
048: TreeLogger.ERROR,
049: synthesizeAsynchronousInterfaceDefinition(remoteService),
050: null);
051: }
052:
053: private static String computeAsyncMethodSignature(
054: JMethod syncMethod, JClassType asyncCallbackClass) {
055: return computeInternalSignature(syncMethod) + "/"
056: + asyncCallbackClass.getQualifiedSourceName();
057: }
058:
059: private static String computeInternalSignature(JMethod method) {
060: StringBuffer sb = new StringBuffer();
061: sb.setLength(0);
062: sb.append(method.getName());
063: JParameter[] params = method.getParameters();
064: for (JParameter param : params) {
065: sb.append("/");
066: JType paramType = param.getType();
067: sb.append(paramType.getErasedType()
068: .getQualifiedSourceName());
069: }
070: return sb.toString();
071: }
072:
073: /**
074: * Builds a map of asynchronous method internal signatures to the
075: * corresponding asynchronous {@link JMethod}.
076: */
077: private static Map<String, JMethod> initializeAsyncMethodMap(
078: JMethod[] asyncMethods) {
079: Map<String, JMethod> sigs = new TreeMap<String, JMethod>();
080: for (JMethod asyncMethod : asyncMethods) {
081: sigs
082: .put(computeInternalSignature(asyncMethod),
083: asyncMethod);
084: }
085: return sigs;
086: }
087:
088: private static String synthesizeAsynchronousInterfaceDefinition(
089: JClassType serviceIntf) {
090: StringBuffer sb = new StringBuffer();
091: JPackage pkg = serviceIntf.getPackage();
092: if (pkg != null) {
093: sb.append("\npackage ");
094: sb.append(pkg.getName());
095: sb.append(";\n");
096: }
097:
098: sb.append("\npublic interface ");
099: sb.append(serviceIntf.getSimpleSourceName());
100: sb.append("Async {\n");
101:
102: JMethod[] methods = serviceIntf.getOverridableMethods();
103: for (JMethod method : methods) {
104: assert (method != null);
105:
106: sb.append("\tvoid ");
107: sb.append(method.getName());
108: sb.append("(");
109:
110: JParameter[] params = method.getParameters();
111: for (int paramIndex = 0; paramIndex < params.length; ++paramIndex) {
112: JParameter param = params[paramIndex];
113:
114: if (paramIndex > 0) {
115: sb.append(", ");
116: }
117:
118: sb.append(param.toString());
119: }
120:
121: if (params.length > 0) {
122: sb.append(", ");
123: }
124:
125: sb.append(AsyncCallback.class.getName());
126: sb.append(" arg");
127: sb.append(Integer.toString(params.length + 1));
128: sb.append(");\n");
129: }
130:
131: sb.append("}");
132:
133: return sb.toString();
134: }
135:
136: private static void validationFailed(TreeLogger branch,
137: JClassType remoteService) throws UnableToCompleteException {
138: logValidAsyncInterfaceDeclaration(branch, remoteService);
139: throw new UnableToCompleteException();
140: }
141:
142: /**
143: * {@link JClassType} for the {@link AsyncCallback} interface.
144: */
145: private final JClassType asyncCallbackClass;
146:
147: /**
148: * {@link JClassType} for the {@link Request} class.
149: */
150: private final JClassType requestType;
151:
152: RemoteServiceAsyncValidator(TreeLogger logger, TypeOracle typeOracle)
153: throws UnableToCompleteException {
154: try {
155: asyncCallbackClass = typeOracle.getType(AsyncCallback.class
156: .getName());
157: requestType = typeOracle.getType(Request.class
158: .getCanonicalName());
159: } catch (NotFoundException e) {
160: logger.log(TreeLogger.ERROR, null, e);
161: throw new UnableToCompleteException();
162: }
163: }
164:
165: /**
166: * Checks that for every method on the synchronous remote service interface
167: * there is a corresponding asynchronous version in the asynchronous version
168: * of the remote service. If the validation succeeds, a map of synchronous to
169: * asynchronous methods is returned.
170: *
171: * @param logger
172: * @param remoteService
173: * @return map of synchronous method to asynchronous method
174: *
175: * @throws UnableToCompleteException if the asynchronous
176: * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService}
177: * was not found, or if it does not have an asynchronous method
178: * version of every synchronous one
179: */
180: public Map<JMethod, JMethod> validate(TreeLogger logger,
181: JClassType remoteService, JClassType remoteServiceAsync)
182: throws UnableToCompleteException {
183: TreeLogger branch = logger.branch(TreeLogger.DEBUG,
184: "Checking the synchronous interface '"
185: + remoteService.getQualifiedSourceName()
186: + "' against its asynchronous version '"
187: + remoteServiceAsync.getQualifiedSourceName()
188: + "'", null);
189:
190: // Sync and async versions must have the same number of methods
191: JMethod[] asyncMethods = remoteServiceAsync
192: .getOverridableMethods();
193: JMethod[] syncMethods = remoteService.getOverridableMethods();
194: if (asyncMethods.length != syncMethods.length) {
195: branch
196: .branch(
197: TreeLogger.ERROR,
198: "The asynchronous version of "
199: + remoteService
200: .getQualifiedSourceName()
201: + " has "
202: + (asyncMethods.length > syncMethods.length ? "more"
203: : "less")
204: + " methods than the synchronous version",
205: null);
206: validationFailed(branch, remoteService);
207: }
208:
209: // Check that for every sync method there is a corresponding async method
210: boolean failed = false;
211: Map<String, JMethod> asyncMethodMap = initializeAsyncMethodMap(asyncMethods);
212: Map<JMethod, JMethod> syncMethodToAsyncMethodMap = new HashMap<JMethod, JMethod>();
213: for (JMethod syncMethod : syncMethods) {
214: String asyncSig = computeAsyncMethodSignature(syncMethod,
215: asyncCallbackClass);
216: JMethod asyncMethod = asyncMethodMap.get(asyncSig);
217: if (asyncMethod == null) {
218: branch.branch(TreeLogger.ERROR,
219: "Missing asynchronous version of the synchronous method '"
220: + syncMethod.getReadableDeclaration()
221: + "'", null);
222: failed = true;
223: } else {
224: // TODO if async param is parameterized make sure that the sync return
225: // type is assignable to the first type argument
226: JType returnType = asyncMethod.getReturnType();
227: if (returnType != JPrimitiveType.VOID
228: && returnType != requestType) {
229: branch
230: .branch(
231: TreeLogger.ERROR,
232: "The asynchronous version of the synchronous method '"
233: + syncMethod
234: .getReadableDeclaration()
235: + "' must have a return type of 'void' or '"
236: + Request.class
237: .getCanonicalName()
238: + "'", null);
239: failed = true;
240: } else {
241: syncMethodToAsyncMethodMap.put(syncMethod,
242: asyncMethod);
243: }
244: }
245: }
246:
247: if (failed) {
248: validationFailed(branch, remoteService);
249: }
250:
251: return syncMethodToAsyncMethodMap;
252: }
253: }
|