KickJava   Java API By Example, From Geeks To Geeks.

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


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
21 import java.lang.reflect.Constructor JavaDoc;
22 import java.lang.reflect.InvocationTargetException JavaDoc;
23 import java.lang.reflect.Method JavaDoc;
24 import java.lang.reflect.Modifier JavaDoc;
25
26 /**
27  * The interface to the low-level browser, this class serves as a 'domain' for a
28  * module, loading all of its classes in a separate, isolated class loader. This
29  * allows us to run multiple modules, both in succession and simultaneously.
30  */

31 public abstract class ModuleSpace implements ShellJavaScriptHost {
32
33   private static ThreadLocal JavaDoc sCaughtJavaExceptionObject = new ThreadLocal JavaDoc();
34
35   private static ThreadLocal JavaDoc sLastThrownJavaException = new ThreadLocal JavaDoc();
36
37   private static ThreadLocal JavaDoc sThrownJavaExceptionObject = new ThreadLocal JavaDoc();
38
39   /**
40    * Logger is thread local.
41    */

42   private static ThreadLocal JavaDoc threadLocalLogger = new ThreadLocal JavaDoc();
43
44   public static void setThrownJavaException(Throwable JavaDoc t) {
45     Throwable JavaDoc was = (Throwable JavaDoc) sLastThrownJavaException.get();
46     if (was != t) {
47       // avoid logging the same exception twice
48
getLogger().log(TreeLogger.WARN, "Exception thrown into JavaScript", t);
49       sLastThrownJavaException.set(t);
50     }
51     sThrownJavaExceptionObject.set(t);
52   }
53
54   protected static RuntimeException JavaDoc createJavaScriptException(ClassLoader JavaDoc cl,
55       String JavaDoc name, String JavaDoc desc) {
56     Exception JavaDoc caught;
57     try {
58       Class JavaDoc javaScriptExceptionClass = Class.forName(
59           "com.google.gwt.core.client.JavaScriptException", true, cl);
60       Class JavaDoc string = String JavaDoc.class;
61       Constructor JavaDoc ctor = javaScriptExceptionClass.getDeclaredConstructor(new Class JavaDoc[] {
62           string, string});
63       return (RuntimeException JavaDoc) ctor.newInstance(new Object JavaDoc[] {name, desc});
64     } catch (InstantiationException JavaDoc e) {
65       caught = e;
66     } catch (IllegalAccessException JavaDoc e) {
67       caught = e;
68     } catch (SecurityException JavaDoc e) {
69       caught = e;
70     } catch (ClassNotFoundException JavaDoc e) {
71       caught = e;
72     } catch (NoSuchMethodException JavaDoc e) {
73       caught = e;
74     } catch (IllegalArgumentException JavaDoc e) {
75       caught = e;
76     } catch (InvocationTargetException JavaDoc e) {
77       caught = e;
78     }
79     throw new RuntimeException JavaDoc("Error creating JavaScriptException", caught);
80   }
81
82   protected static TreeLogger getLogger() {
83     return (TreeLogger) threadLocalLogger.get();
84   }
85
86   /**
87    * Tricky one, this. Reaches over into this modules's JavaScriptHost class and
88    * sets its static 'host' field to be the specified ModuleSpace instance
89    * (which will either be this ModuleSpace or null).
90    *
91    * @param moduleSpace the ModuleSpace instance to store using
92    * JavaScriptHost.setHost().
93    * @see JavaScriptHost
94    */

95   private static void setJavaScriptHost(ModuleSpace moduleSpace, ClassLoader JavaDoc cl) {
96     // Find the application's JavaScriptHost interface.
97
//
98
Throwable JavaDoc caught;
99     try {
100       final String JavaDoc jsHostClassName = JavaScriptHost.class.getName();
101       Class JavaDoc jsHostClass = Class.forName(jsHostClassName, true, cl);
102       final Class JavaDoc[] paramTypes = new Class JavaDoc[] {ShellJavaScriptHost.class};
103       Method JavaDoc setHostMethod = jsHostClass.getMethod("setHost", paramTypes);
104       setHostMethod.invoke(jsHostClass, new Object JavaDoc[] {moduleSpace});
105       return;
106     } catch (ClassNotFoundException JavaDoc e) {
107       caught = e;
108     } catch (SecurityException JavaDoc e) {
109       caught = e;
110     } catch (NoSuchMethodException JavaDoc e) {
111       caught = e;
112     } catch (IllegalArgumentException JavaDoc e) {
113       caught = e;
114     } catch (IllegalAccessException JavaDoc e) {
115       caught = e;
116     } catch (InvocationTargetException JavaDoc e) {
117       caught = e.getTargetException();
118     }
119     throw new RuntimeException JavaDoc("Error initializing JavaScriptHost", caught);
120   }
121
122   private final ModuleSpaceHost host;
123
124   private final Object JavaDoc key;
125
126   private final String JavaDoc moduleName;
127
128   protected ModuleSpace(ModuleSpaceHost host, String JavaDoc moduleName, Object JavaDoc key) {
129     this.host = host;
130     this.moduleName = moduleName;
131     this.key = key;
132     TreeLogger hostLogger = host.getLogger();
133     threadLocalLogger.set(hostLogger);
134   }
135
136   public void dispose() {
137     // Tell the user-space JavaScript host object that we're done
138
//
139
clearJavaScriptHost();
140
141     // Clear out the exception field, it may be holding a user-space object
142
sLastThrownJavaException.set(null);
143
144     // Clear out the class loader's cache
145
host.getClassLoader().clear();
146   }
147
148   public void exceptionCaught(int number, String JavaDoc name, String JavaDoc message) {
149     Throwable JavaDoc thrown = (Throwable JavaDoc) sThrownJavaExceptionObject.get();
150
151     if (thrown != null) {
152       // See if the caught exception was thrown by us
153
if (isExceptionSame(thrown, number, name, message)) {
154         sCaughtJavaExceptionObject.set(thrown);
155         sThrownJavaExceptionObject.set(null);
156         return;
157       }
158     }
159
160     sCaughtJavaExceptionObject.set(createJavaScriptException(
161         getIsolatedClassLoader(), name, message));
162   }
163   
164   /**
165    * Get the unique key for this module.
166    *
167    * @return the unique key
168    */

169   public Object JavaDoc getKey() {
170     return key;
171   }
172
173   /**
174    * Get the module name.
175    *
176    * @return the module name
177    */

178   public String JavaDoc getModuleName() {
179     return moduleName;
180   }
181
182   public boolean invokeNativeBoolean(String JavaDoc name, Object JavaDoc jthis, Class JavaDoc[] types,
183       Object JavaDoc[] args) throws Throwable JavaDoc {
184     JsValue result = invokeNative(name, jthis, types, args);
185     Boolean JavaDoc value = (Boolean JavaDoc) JsValueGlue.get(result, Boolean JavaDoc.class,
186         "invokeNativeBoolean(" + name + ")");
187     return value.booleanValue();
188   }
189
190   public byte invokeNativeByte(String JavaDoc name, Object JavaDoc jthis, Class JavaDoc[] types,
191       Object JavaDoc[] args) throws Throwable JavaDoc {
192     JsValue result = invokeNative(name, jthis, types, args);
193     Byte JavaDoc value = (Byte JavaDoc) JsValueGlue.get(result, Byte JavaDoc.class, "invokeNativeByte("
194         + name + ")");
195     return value.byteValue();
196   }
197
198   public char invokeNativeChar(String JavaDoc name, Object JavaDoc jthis, Class JavaDoc[] types,
199       Object JavaDoc[] args) throws Throwable JavaDoc {
200     JsValue result = invokeNative(name, jthis, types, args);
201     Character JavaDoc value = (Character JavaDoc) JsValueGlue.get(result, Character JavaDoc.class,
202         "invokeNativeCharacter(" + name + ")");
203     return value.charValue();
204   }
205
206   public double invokeNativeDouble(String JavaDoc name, Object JavaDoc jthis, Class JavaDoc[] types,
207       Object JavaDoc[] args) throws Throwable JavaDoc {
208     JsValue result = invokeNative(name, jthis, types, args);
209     Double JavaDoc value = (Double JavaDoc) JsValueGlue.get(result, Double JavaDoc.class,
210         "invokeNativeDouble(" + name + ")");
211     return value.doubleValue();
212   }
213
214   public float invokeNativeFloat(String JavaDoc name, Object JavaDoc jthis, Class JavaDoc[] types,
215       Object JavaDoc[] args) throws Throwable JavaDoc {
216     JsValue result = invokeNative(name, jthis, types, args);
217     Float JavaDoc value = (Float JavaDoc) JsValueGlue.get(result, Float JavaDoc.class,
218         "invokeNativeFloat(" + name + ")");
219     return value.floatValue();
220   }
221
222   public Object JavaDoc invokeNativeHandle(String JavaDoc name, Object JavaDoc jthis, Class JavaDoc returnType,
223       Class JavaDoc[] types, Object JavaDoc[] args) throws Throwable JavaDoc {
224
225     JsValue result = invokeNative(name, jthis, types, args);
226     return JsValueGlue.get(result, returnType, "invokeNativeHandle(" + name
227         + ")");
228   }
229
230   public int invokeNativeInt(String JavaDoc name, Object JavaDoc jthis, Class JavaDoc[] types,
231       Object JavaDoc[] args) throws Throwable JavaDoc {
232     JsValue result = invokeNative(name, jthis, types, args);
233     Integer JavaDoc value = (Integer JavaDoc) JsValueGlue.get(result, Integer JavaDoc.class,
234         "invokeNativeInteger(" + name + ")");
235     return value.intValue();
236   }
237
238   public long invokeNativeLong(String JavaDoc name, Object JavaDoc jthis, Class JavaDoc[] types,
239       Object JavaDoc[] args) throws Throwable JavaDoc {
240     JsValue result = invokeNative(name, jthis, types, args);
241     Long JavaDoc value = (Long JavaDoc) JsValueGlue.get(result, Long JavaDoc.class, "invokeNativeLong("
242         + name + ")");
243     return value.longValue();
244   }
245
246   public Object JavaDoc invokeNativeObject(String JavaDoc name, Object JavaDoc jthis, Class JavaDoc[] types,
247       Object JavaDoc[] args) throws Throwable JavaDoc {
248     JsValue result = invokeNative(name, jthis, types, args);
249     return JsValueGlue.get(result, Object JavaDoc.class, "invokeNativeObject(" + name
250         + ")");
251   }
252
253   public short invokeNativeShort(String JavaDoc name, Object JavaDoc jthis, Class JavaDoc[] types,
254       Object JavaDoc[] args) throws Throwable JavaDoc {
255     JsValue result = invokeNative(name, jthis, types, args);
256     Short JavaDoc value = (Short JavaDoc) JsValueGlue.get(result, Short JavaDoc.class,
257         "invokeNativeShort(" + name + ")");
258     return value.shortValue();
259   }
260
261   public String JavaDoc invokeNativeString(String JavaDoc name, Object JavaDoc jthis, Class JavaDoc[] types,
262       Object JavaDoc[] args) throws Throwable JavaDoc {
263     JsValue result = invokeNative(name, jthis, types, args);
264     return (String JavaDoc) JsValueGlue.get(result, String JavaDoc.class, "invokeNativeString("
265         + name + ")");
266   }
267
268   public void invokeNativeVoid(String JavaDoc name, Object JavaDoc jthis, Class JavaDoc[] types,
269       Object JavaDoc[] args) throws Throwable JavaDoc {
270     JsValue result = invokeNative(name, jthis, types, args);
271     if (!result.isUndefined()) {
272       getLogger().log(
273           TreeLogger.WARN,
274           "JSNI method '" + name + "' returned a value of type "
275               + result.getTypeString() + "; should not have returned a value",
276           null);
277     }
278   }
279
280   /**
281    * Allows client-side code to log to the tree logger.
282    */

283   public void log(String JavaDoc message, Throwable JavaDoc e) {
284     TreeLogger logger = host.getLogger();
285     TreeLogger.Type type = TreeLogger.INFO;
286     if (e != null) {
287       type = TreeLogger.ERROR;
288     }
289     logger.log(type, message, e);
290   }
291
292   /**
293    * Runs the module's user startup code.
294    */

295   public final void onLoad(TreeLogger logger) throws UnableToCompleteException {
296     // Tell the host we're ready for business.
297
//
298
host.onModuleReady(this);
299
300     // Tell the user-space JavaScript host object how to get back here.
301
//
302
setJavaScriptHost();
303
304     // Make sure we can resolve JSNI references to static Java names.
305
//
306
try {
307       Object JavaDoc staticDispatch = getStaticDispatcher();
308       createNative("initializeStaticDispatcher", 0, "__defineStatic",
309           new String JavaDoc[] {"__arg0"}, "window.__static = __arg0;");
310       invokeNativeVoid("__defineStatic", null, new Class JavaDoc[] {Object JavaDoc.class},
311           new Object JavaDoc[] {staticDispatch});
312     } catch (Throwable JavaDoc e) {
313       logger.log(TreeLogger.ERROR, "Unable to initialize static dispatcher", e);
314       throw new UnableToCompleteException();
315     }
316
317     // Actually run user code.
318
//
319
String JavaDoc entryPointTypeName = null;
320     try {
321       String JavaDoc[] entryPoints = host.getEntryPointTypeNames();
322       if (entryPoints.length > 0) {
323         for (int i = 0; i < entryPoints.length; i++) {
324           entryPointTypeName = entryPoints[i];
325           Class JavaDoc clazz = loadClassFromSourceName(entryPointTypeName);
326           Method JavaDoc onModuleLoad = null;
327           try {
328             onModuleLoad = clazz.getMethod("onModuleLoad", null);
329             if (!Modifier.isStatic(onModuleLoad.getModifiers())) {
330               // it's non-static, so we need to rebind the class
331
onModuleLoad = null;
332             }
333           } catch (NoSuchMethodException JavaDoc e) {
334             // okay, try rebinding it; maybe the rebind result will have one
335
}
336           Object JavaDoc module = null;
337           if (onModuleLoad == null) {
338             module = rebindAndCreate(entryPointTypeName);
339             onModuleLoad = module.getClass().getMethod("onModuleLoad", null);
340           }
341           onModuleLoad.setAccessible(true);
342           onModuleLoad.invoke(module, null);
343         }
344       } else {
345         logger.log(
346             TreeLogger.WARN,
347             "The module has no entry points defined, so onModuleLoad() will never be called",
348             null);
349       }
350     } catch (Throwable JavaDoc e) {
351       Throwable JavaDoc caught = e;
352
353       if (e instanceof InvocationTargetException JavaDoc) {
354         caught = ((InvocationTargetException JavaDoc) e).getTargetException();
355       }
356
357       if (caught instanceof ExceptionInInitializerError JavaDoc) {
358         caught = ((ExceptionInInitializerError JavaDoc) caught).getException();
359       }
360
361       String JavaDoc unableToLoadMessage = "Unable to load module entry point class "
362           + entryPointTypeName;
363       if (caught != null) {
364         unableToLoadMessage += " (see associated exception for details)";
365       }
366       logger.log(TreeLogger.ERROR, unableToLoadMessage, caught);
367       throw new UnableToCompleteException();
368     }
369   }
370
371   public Object JavaDoc rebindAndCreate(String JavaDoc requestedClassName)
372       throws UnableToCompleteException {
373     Throwable JavaDoc caught = null;
374     String JavaDoc msg = null;
375     String JavaDoc resultName = null;
376     try {
377       // Rebind operates on source-level names.
378
//
379
String JavaDoc sourceName = requestedClassName.replace('$', '.');
380       resultName = rebind(sourceName);
381       Class JavaDoc resolvedClass = loadClassFromSourceName(resultName);
382       if (Modifier.isAbstract(resolvedClass.getModifiers())) {
383         msg = "Deferred binding result type '" + resultName
384             + "' should not be abstract";
385       } else {
386         Constructor JavaDoc ctor = resolvedClass.getDeclaredConstructor(null);
387         ctor.setAccessible(true);
388         return ctor.newInstance(null);
389       }
390     } catch (ClassNotFoundException JavaDoc e) {
391       msg = "Could not load deferred binding result type '" + resultName + "'";
392       caught = e;
393     } catch (InstantiationException JavaDoc e) {
394       caught = e;
395     } catch (IllegalAccessException JavaDoc e) {
396       caught = e;
397     } catch (ExceptionInInitializerError JavaDoc e) {
398       caught = e.getException();
399     } catch (NoSuchMethodException JavaDoc e) {
400       msg = "Rebind result '" + resultName
401           + "' has no default (zero argument) constructors.";
402       caught = e;
403     } catch (InvocationTargetException JavaDoc e) {
404       caught = e.getTargetException();
405     }
406
407     // Always log here because sometimes this method gets called from static
408
// initializers and other unusual places, which can obscure the problem.
409
//
410
if (msg == null) {
411       msg = "Failed to create an instance of '" + requestedClassName
412           + "' via deferred binding ";
413     }
414     host.getLogger().log(TreeLogger.ERROR, msg, caught);
415     throw new UnableToCompleteException();
416   }
417
418   protected String JavaDoc createNativeMethodInjector(String JavaDoc jsniSignature,
419       String JavaDoc[] paramNames, String JavaDoc js) {
420     String JavaDoc newScript = "window[\"" + jsniSignature + "\"] = function(";
421
422     for (int i = 0; i < paramNames.length; ++i) {
423       if (i > 0) {
424         newScript += ", ";
425       }
426
427       newScript += paramNames[i];
428     }
429
430     newScript += ") { " + js + " };\n";
431     return newScript;
432   }
433
434   /**
435    * Invokes a native JavaScript function.
436    *
437    * @param name the name of the function to invoke
438    * @param jthis the function's 'this' context
439    * @param types the type of each argument
440    * @param args the arguments to be passed
441    * @return the return value as a Variant.
442    */

443   protected abstract JsValue doInvoke(String JavaDoc name, Object JavaDoc jthis, Class JavaDoc[] types,
444       Object JavaDoc[] args) throws Throwable JavaDoc;
445
446   protected CompilingClassLoader getIsolatedClassLoader() {
447     return host.getClassLoader();
448   }
449
450   /**
451    * Injects the magic needed to resolve JSNI references from module-space.
452    */

453   protected abstract Object JavaDoc getStaticDispatcher();
454
455   /**
456    * Invokes a native JavaScript function.
457    *
458    * @param name the name of the function to invoke
459    * @param jthis the function's 'this' context
460    * @param types the type of each argument
461    * @param args the arguments to be passed
462    * @return the return value as a Variant.
463    */

464   protected final JsValue invokeNative(String JavaDoc name, Object JavaDoc jthis,
465       Class JavaDoc[] types, Object JavaDoc[] args) throws Throwable JavaDoc {
466     // Whenever a native method is invoked, release any enqueued cleanup objects
467
JsValue.mainThreadCleanup();
468     JsValue result = doInvoke(name, jthis, types, args);
469     // Is an exception active?
470
Throwable JavaDoc thrown = (Throwable JavaDoc) sCaughtJavaExceptionObject.get();
471     if (thrown == null) {
472       return result;
473     }
474     sCaughtJavaExceptionObject.set(null);
475
476     /*
477      * The stack trace on the stored exception will not be very useful due to
478      * how it was created. Using fillInStackTrace() resets the stack trace to
479      * this moment in time, which is usually far more useful.
480      */

481     thrown.fillInStackTrace();
482     throw thrown;
483   }
484
485   protected boolean isExceptionSame(Throwable JavaDoc original, int number,
486       String JavaDoc name, String JavaDoc message) {
487     // For most platforms, the null exception means we threw it.
488
// IE overrides this.
489
return (name == null && message == null);
490   }
491
492   protected String JavaDoc rebind(String JavaDoc sourceName) throws UnableToCompleteException {
493     try {
494       String JavaDoc result = host.rebind(host.getLogger(), sourceName);
495       if (result != null) {
496         return result;
497       } else {
498         return sourceName;
499       }
500     } catch (UnableToCompleteException e) {
501       String JavaDoc msg = "Deferred binding failed for '" + sourceName
502           + "'; expect subsequent failures";
503       host.getLogger().log(TreeLogger.ERROR, msg, e);
504       throw new UnableToCompleteException();
505     }
506   }
507
508   /**
509    * Clear the module's JavaScriptHost 'host' field.
510    */

511   private void clearJavaScriptHost() {
512     setJavaScriptHost(null, getIsolatedClassLoader());
513   }
514
515   /**
516    * Handles loading a class that might be nested given a source type name.
517    */

518   private Class JavaDoc loadClassFromSourceName(String JavaDoc sourceName)
519       throws ClassNotFoundException JavaDoc {
520     String JavaDoc toTry = sourceName;
521     while (true) {
522       try {
523         return Class.forName(toTry, true, getIsolatedClassLoader());
524       } catch (ClassNotFoundException JavaDoc e) {
525         // Assume that the last '.' should be '$' and try again.
526
//
527
int i = toTry.lastIndexOf('.');
528         if (i == -1) {
529           throw e;
530         }
531
532         toTry = toTry.substring(0, i) + "$" + toTry.substring(i + 1);
533       }
534     }
535   }
536
537   /**
538    * Set the module's JavaScriptHost 'host' field to this ModuleSpace instance.
539    */

540   private void setJavaScriptHost() {
541     setJavaScriptHost(this, getIsolatedClassLoader());
542   }
543
544 }
545
Popular Tags