KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > dev > shell > JsniInjector


1 /*
2  * Copyright 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.dev.shell;
17
18 import com.google.gwt.core.ext.TreeLogger;
19 import com.google.gwt.core.ext.UnableToCompleteException;
20 import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
21 import com.google.gwt.core.ext.typeinfo.JClassType;
22 import com.google.gwt.core.ext.typeinfo.JMethod;
23 import com.google.gwt.core.ext.typeinfo.JParameter;
24 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
25 import com.google.gwt.core.ext.typeinfo.JType;
26 import com.google.gwt.core.ext.typeinfo.NotFoundException;
27 import com.google.gwt.core.ext.typeinfo.TypeOracle;
28 import com.google.gwt.dev.jdt.CompilationUnitProviderWithAlternateSource;
29 import com.google.gwt.dev.js.ast.JsBlock;
30 import com.google.gwt.dev.util.Jsni;
31 import com.google.gwt.dev.util.StringCopier;
32
33 import java.util.ArrayList JavaDoc;
34 import java.util.Arrays JavaDoc;
35 import java.util.IdentityHashMap JavaDoc;
36 import java.util.List JavaDoc;
37 import java.util.Map JavaDoc;
38
39 /**
40  * Adapts compilation units containing JSNI-accessible code by rewriting the
41  * source.
42  */

43 public class JsniInjector {
44
45   /**
46    * A consolidated way to get all expected types and succeed or fail
47    * atomically.
48    */

49   private class CoreTypes {
50     static final String JavaDoc PKG_JSOBJECT = "com.google.gwt.core.client";
51     static final String JavaDoc CLS_JSOBJECT = "JavaScriptObject";
52     static final String JavaDoc PKG_STRING = "java.lang";
53     static final String JavaDoc CLS_STRING = "String";
54
55     public final JClassType javaLangString;
56
57     public final JClassType javaScriptObject;
58
59     public CoreTypes(TreeLogger logger) throws UnableToCompleteException {
60       javaScriptObject = getCoreType(logger, PKG_JSOBJECT, CLS_JSOBJECT);
61       javaLangString = getCoreType(logger, PKG_STRING, CLS_STRING);
62     }
63
64     private JClassType getCoreType(TreeLogger logger, String JavaDoc pkg, String JavaDoc cls)
65         throws UnableToCompleteException {
66       try {
67         return oracle.getType(pkg, cls);
68       } catch (NotFoundException e) {
69         String JavaDoc msg = "Unable to find core type '" + pkg + "." + cls + "'";
70         logger.log(TreeLogger.ERROR, msg, e);
71         throw new UnableToCompleteException();
72       }
73     }
74   }
75
76   /**
77    * A chunk of replacement text and where to put it.
78    */

79   private static class Replacement implements Comparable JavaDoc {
80     public final int end;
81
82     public final int start;
83
84     public final char[] text;
85
86     public Replacement(int start, int end, char[] text) {
87       this.start = start;
88       this.end = end;
89       this.text = text;
90     }
91
92     public int compareTo(Object JavaDoc o) {
93       Replacement other = (Replacement) o;
94       if (start < other.start) {
95         assert (end <= other.start) : "Overlapping changes not supported";
96         return -1;
97       } else if (start > other.start) {
98         assert (start >= other.end) : "Overlapping changes not supported";
99         return 1;
100       } else {
101         return 0;
102       }
103     }
104   }
105
106   private final TypeOracle oracle;
107
108   private CoreTypes coreTypes;
109
110   private final Map JavaDoc parsedJsByMethod = new IdentityHashMap JavaDoc();
111
112   public JsniInjector(TypeOracle oracle) {
113     this.oracle = oracle;
114   }
115
116   public CompilationUnitProvider inject(TreeLogger logger,
117       CompilationUnitProvider cup) throws UnableToCompleteException {
118
119     logger = logger.branch(TreeLogger.SPAM,
120         "Checking for JavaScript native methods", null);
121
122     // Make sure the core types exist.
123
//
124
if (coreTypes == null) {
125       coreTypes = new CoreTypes(logger);
126     }
127
128     // Analyze the source and build a list of changes.
129
//
130
char[] source = cup.getSource();
131     List JavaDoc changes = new ArrayList JavaDoc();
132     rewriteCompilationUnit(logger, source, changes, cup);
133
134     // Sort and apply the changes.
135
//
136
int n = changes.size();
137     if (n > 0) {
138       Replacement[] repls = (Replacement[]) changes.toArray(new Replacement[n]);
139       Arrays.sort(repls);
140       StringCopier copier = new StringCopier(source);
141       for (int i = 0; i < n; ++i) {
142         Replacement repl = repls[i];
143         copier.commit(repl.text, repl.start, repl.end);
144       }
145
146       char[] results = copier.finish();
147
148       return new CompilationUnitProviderWithAlternateSource(cup, results);
149     } else {
150       // No changes were made, so we return the original.
151
//
152
logger.log(TreeLogger.SPAM, "No JavaScript native methods were found",
153           null);
154       return cup;
155     }
156   }
157
158   /**
159    * Static initialization: generate one call to 'JavaScriptHost.createNative()'
160    * for each native method, to define the JavaScript code that will be invoked
161    * later.
162    */

163   private char[] genInitializerBlock(String JavaDoc file, char[] source,
164       JMethod[] methods) {
165
166     String JavaDoc escapedFile = Jsni.escapeQuotesAndSlashes(file);
167
168     StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
169     sb.append(" static {");
170     for (int i = 0; i < methods.length; ++i) {
171       JMethod method = methods[i];
172
173       JsBlock jsniBody = (JsBlock) parsedJsByMethod.get(method);
174       if (jsniBody == null) {
175         // Not a JSNI method.
176
//
177
continue;
178       }
179
180       JParameter[] params = method.getParameters();
181       String JavaDoc paramNamesArray = getParamNamesArrayExpr(params);
182
183       final String JavaDoc jsTry = "try ";
184       final String JavaDoc jsCatch = " catch (e) {\\n"
185           + " __static[\\\"@"
186           + Jsni.JAVASCRIPTHOST_NAME
187           + "::exceptionCaught"
188           + "(ILjava/lang/String;Ljava/lang/String;)\\\"]"
189           + "((e && e.number) || 0, (e && e.name) || null , (e && e.message) || null);\\n"
190           + "}\\n";
191
192       // Surround the original JS body statements with a try/catch so that
193
// we can map JavaScript exceptions back into Java.
194
// Note that the method body itself will print curly braces, so we don't
195
// need them around the try/catch.
196
//
197
String JavaDoc js = jsTry + Jsni.generateEscapedJavaScriptForHostedMode(jsniBody)
198           + jsCatch;
199       String JavaDoc jsniSig = Jsni.getJsniSignature(method);
200
201       // figure out starting line number
202
int bodyStart = method.getBodyStart();
203       int line = Jsni.countNewlines(source, 0, bodyStart) + 1;
204
205       sb.append(" " + Jsni.JAVASCRIPTHOST_NAME + ".createNative(\""
206           + escapedFile + "\", " + line + ", " + "\"@" + jsniSig + "\", "
207           + paramNamesArray + ", \"" + js + "\");");
208     }
209     sb.append("}");
210     return sb.toString().toCharArray();
211   }
212
213   /**
214    * Create a legal Java method call that will result in a JSNI invocation.
215    *
216    * @param method
217    * @param expectedHeaderLines
218    * @param expectedBodyLines
219    * @return a String of the Java code to call a JSNI method, using
220    * JavaScriptHost.invokeNative*
221    */

222   private String JavaDoc genNonNativeVersionOfJsniMethod(JMethod method,
223       int expectedHeaderLines, int expectedBodyLines) {
224     StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
225
226     // Add extra lines at the start to match comments + declaration
227
//
228
for (int i = 0; i < expectedHeaderLines; ++i) {
229       sb.append('\n');
230     }
231
232     String JavaDoc methodDecl = method.getReadableDeclaration(false, true, false,
233         false, false);
234
235     sb.append(methodDecl + " {");
236     // wrap the call in a try-catch block
237
sb.append("try {");
238
239     // Write the Java call to the property invoke method, adding
240
// downcasts where necessary.
241
//
242
JType returnType = method.getReturnType();
243     boolean isJavaScriptObject = isJavaScriptObject(returnType);
244     JPrimitiveType primType;
245     if (isJavaScriptObject) {
246       // Add a downcast from Handle to the originally-declared type.
247
//
248
String JavaDoc returnTypeName = returnType.getQualifiedSourceName();
249       sb.append("return (" + returnTypeName + ")" + Jsni.JAVASCRIPTHOST_NAME
250           + ".invokeNativeHandle");
251     } else if (null != (primType = returnType.isPrimitive())) {
252       // Primitives have special overloads.
253
//
254
char[] primTypeSuffix = primType.getSimpleSourceName().toCharArray();
255       primTypeSuffix[0] = Character.toUpperCase(primTypeSuffix[0]);
256       String JavaDoc invokeMethodName = "invokeNative" + String.valueOf(primTypeSuffix);
257       if (primType != JPrimitiveType.VOID) {
258         sb.append("return ");
259       }
260       sb.append(Jsni.JAVASCRIPTHOST_NAME);
261       sb.append(".");
262       sb.append(invokeMethodName);
263     } else if (returnType == coreTypes.javaLangString) {
264       sb.append("return ");
265       sb.append(Jsni.JAVASCRIPTHOST_NAME);
266       sb.append(".invokeNativeString");
267     } else {
268       // Some reference type.
269
// We need to add a downcast to the originally-declared type.
270
//
271
String JavaDoc returnTypeName = returnType.getQualifiedSourceName();
272       sb.append("return (");
273       sb.append(returnTypeName);
274       sb.append(")");
275       sb.append(Jsni.JAVASCRIPTHOST_NAME);
276       sb.append(".invokeNativeObject");
277     }
278
279     // Write the argument list for the invoke call.
280
//
281
sb.append("(\"@");
282     String JavaDoc jsniSig = Jsni.getJsniSignature(method);
283     sb.append(jsniSig);
284     if (method.isStatic()) {
285       sb.append("\", null, ");
286     } else {
287       sb.append("\", this, ");
288     }
289
290     if (isJavaScriptObject) {
291       // Handle-oriented calls also need the return type as an argument.
292
//
293
String JavaDoc returnTypeName = returnType.getQualifiedSourceName();
294       sb.append(returnTypeName);
295       sb.append(".class, ");
296     }
297
298     // Build an array of classes that tells the invoker how to adapt the
299
// incoming arguments for calling into JavaScript.
300
//
301
sb.append(Jsni.buildTypeList(method));
302     sb.append(',');
303
304     // Build an array containing the arguments based on the names of the
305
// parameters.
306
//
307
sb.append(Jsni.buildArgList(method));
308     sb.append(");");
309
310     // Catch exceptions; rethrow if the exception is RTE or declared.
311
sb.append("} catch (java.lang.Throwable __gwt_exception) {");
312     sb.append("if (__gwt_exception instanceof java.lang.RuntimeException) throw (java.lang.RuntimeException) __gwt_exception;");
313     JType[] throwTypes = method.getThrows();
314     for (int i = 0; i < throwTypes.length; ++i) {
315       String JavaDoc typeName = throwTypes[i].getQualifiedSourceName();
316       sb.append("if (__gwt_exception instanceof " + typeName + ") throw (" + typeName
317           + ") __gwt_exception;");
318     }
319     sb.append("throw new java.lang.RuntimeException(\"Undeclared checked exception thrown out of JavaScript; web mode behavior may differ.\", __gwt_exception);");
320     sb.append("}");
321
322     sb.append("}");
323
324     // Add extra lines at the end to match JSNI body.
325
//
326
for (int i = 0; i < expectedBodyLines; ++i) {
327       sb.append('\n');
328     }
329
330     return sb.toString();
331   }
332
333   private String JavaDoc getParamNamesArrayExpr(JParameter[] params) {
334     StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
335     sb.append("new String[] {");
336     for (int i = 0, n = params.length; i < n; ++i) {
337       if (i > 0) {
338         sb.append(", ");
339       }
340
341       JParameter param = params[i];
342       sb.append('\"');
343       sb.append(param.getName());
344       sb.append('\"');
345     }
346     sb.append("}");
347     return sb.toString();
348   }
349
350   private boolean isJavaScriptObject(JType type) {
351     JClassType classType = type.isClass();
352     if (classType == null) {
353       return false;
354     }
355
356     if (classType.isAssignableTo(coreTypes.javaScriptObject)) {
357       return true;
358     } else {
359       return false;
360     }
361   }
362
363   private void rewriteCompilationUnit(TreeLogger logger, char[] source,
364       List JavaDoc changes, CompilationUnitProvider cup)
365       throws UnableToCompleteException {
366
367     // Hit all the types in the compilation unit.
368
//
369
JClassType[] types = oracle.getTypesInCompilationUnit(cup);
370     for (int i = 0; i < types.length; i++) {
371       JClassType type = types[i];
372       rewriteType(logger, source, changes, type);
373     }
374   }
375
376   private void rewriteType(TreeLogger logger, char[] source, List JavaDoc changes,
377       JClassType type) throws UnableToCompleteException {
378
379     String JavaDoc loc = type.getCompilationUnit().getLocation();
380
381     // Examine each method for JSNIness.
382
//
383
List JavaDoc patchedMethods = new ArrayList JavaDoc();
384     JMethod[] methods = type.getMethods();
385     for (int i = 0; i < methods.length; i++) {
386       JMethod method = methods[i];
387       if (method.isNative()) {
388         Jsni.Interval interval = Jsni.findJsniSource(method);
389         if (interval != null) {
390           // The method itself needs to be replaced.
391
//
392

393           // Parse it.
394
//
395
String JavaDoc js = new String JavaDoc(source, interval.start, interval.end
396               - interval.start);
397           int startLine = Jsni.countNewlines(source, 0, interval.start) + 1;
398           JsBlock body = Jsni.parseAsFunctionBody(logger, js, loc, startLine);
399
400           // Remember this as being a valid JSNI method.
401
//
402
parsedJsByMethod.put(method, body);
403
404           // Replace the method.
405
final int declStart = method.getDeclStart();
406           final int declEnd = method.getDeclEnd();
407
408           int expectedHeaderLines = Jsni.countNewlines(source, declStart,
409               interval.start);
410           int expectedBodyLines = Jsni.countNewlines(source, interval.start,
411               interval.end);
412           String JavaDoc newDecl = genNonNativeVersionOfJsniMethod(method,
413               expectedHeaderLines, expectedBodyLines);
414
415           final char[] newSource = newDecl.toCharArray();
416           changes.add(new Replacement(declStart, declEnd, newSource));
417           patchedMethods.add(method);
418         } else {
419           // report error
420
String JavaDoc msg = "No JavaScript body found for native method '" + method
421               + "' in type '" + type + "'";
422           logger.log(TreeLogger.ERROR, msg, null);
423           throw new UnableToCompleteException();
424         }
425       }
426     }
427
428     if (!patchedMethods.isEmpty()) {
429       JMethod[] patched = new JMethod[patchedMethods.size()];
430       patched = (JMethod[]) patchedMethods.toArray(patched);
431
432       TreeLogger branch = logger.branch(TreeLogger.SPAM, "Patched methods in '"
433           + type.getQualifiedSourceName() + "'", null);
434
435       for (int i = 0; i < patched.length; i++) {
436         branch.log(TreeLogger.SPAM, patched[i].getReadableDeclaration(), null);
437       }
438
439       // Insert an initializer block immediately after the opening brace of the
440
// class.
441
//
442
char[] block = genInitializerBlock(loc, source, patched);
443
444       // If this is a non-static inner class, actually put the initializer block
445
// in the first enclosing static or top-level class instead.
446
while (type.getEnclosingType() != null && !type.isStatic()) {
447         type = type.getEnclosingType();
448       }
449
450       int bodyStart = type.getBodyStart();
451       changes.add(new Replacement(bodyStart, bodyStart, block));
452     }
453   }
454 }
455
Popular Tags