KickJava   Java API By Example, From Geeks To Geeks.

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


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.JClassType;
21 import com.google.gwt.core.ext.typeinfo.TypeOracle;
22 import com.google.gwt.dev.jdt.ByteCodeCompiler;
23 import com.google.gwt.dev.jdt.CacheManager;
24 import com.google.gwt.util.tools.Utility;
25
26 import java.io.IOException JavaDoc;
27 import java.io.InputStream JavaDoc;
28 import java.lang.reflect.Method JavaDoc;
29 import java.net.URL JavaDoc;
30 import java.util.ArrayList JavaDoc;
31 import java.util.HashMap JavaDoc;
32 import java.util.Map JavaDoc;
33
34 /**
35  *
36  * TODO : we should refactor this class to move the getClassInfoByDispId,
37  * getDispId, getMethodDispatch and putMethodDispatch into a separate entity
38  * since they really do not interact with the CompilingClassLoader
39  * functionality.
40  */

41 public final class CompilingClassLoader extends ClassLoader JavaDoc {
42
43   /**
44    * Oracle that can answer questions about
45    * {@link DispatchClassInfo DispatchClassInfos}.
46    */

47   private final class DispatchClassInfoOracle {
48
49     /**
50      * Class identifier to DispatchClassInfo mapping.
51      */

52     private final ArrayList JavaDoc classIdToClassInfo = new ArrayList JavaDoc();
53
54     /**
55      * Binary or source class name to DispatchClassInfo map.
56      */

57     private final Map JavaDoc classNameToClassInfo = new HashMap JavaDoc();
58
59     /**
60      * Clears out the contents of this oracle.
61      */

62     public synchronized void clear() {
63       classIdToClassInfo.clear();
64       classNameToClassInfo.clear();
65     }
66
67     /**
68      * Returns the {@link DispatchClassInfo} for a given dispatch id.
69      *
70      * @param dispId dispatch id
71      * @return DispatchClassInfo for the requested dispatch id
72      */

73     public synchronized DispatchClassInfo getClassInfoByDispId(int dispId) {
74       int classId = extractClassIdFromDispId(dispId);
75
76       return (DispatchClassInfo) classIdToClassInfo.get(classId);
77     }
78
79     /**
80      * Returns the dispatch id for a given member reference. Member references
81      * can be encoded as: "@class::field" or "@class::method(typesigs)".
82      *
83      * @param jsniMemberRef a string encoding a JSNI member to use
84      * @return integer encoded as ((classId << 16) | memberId)
85      */

86     public synchronized int getDispId(String JavaDoc jsniMemberRef) {
87       /*
88        * Map JS toString() onto the Java toString() method.
89        *
90        * TODO : is it true that tostring is valid in JavaScript? JavaScript is
91        * case sensitive.
92        */

93       if (jsniMemberRef.equals("toString")) {
94         jsniMemberRef = "@java.lang.Object::toString()";
95       }
96
97       // References are of the form "@class::field" or
98
// "@class::method(typesigs)".
99
int endClassName = jsniMemberRef.indexOf("::");
100       if (endClassName == -1 || jsniMemberRef.length() < 1
101           || jsniMemberRef.charAt(0) != '@') {
102         logger.log(TreeLogger.WARN, "Malformed JSNI reference '"
103             + jsniMemberRef + "'; expect subsequent failures",
104             new NoSuchFieldError JavaDoc(jsniMemberRef));
105         return -1;
106       }
107
108       String JavaDoc className = jsniMemberRef.substring(1, endClassName);
109
110       // Do the lookup by class name.
111
DispatchClassInfo dispClassInfo = getClassInfoFromClassName(className);
112       if (dispClassInfo != null) {
113         String JavaDoc memberName = jsniMemberRef.substring(endClassName + 2);
114         int memberId = dispClassInfo.getMemberId(memberName);
115
116         return synthesizeDispId(dispClassInfo.getClassId(), memberId);
117       }
118
119       logger.log(TreeLogger.WARN, "Class '" + className
120           + "' in JSNI reference '" + jsniMemberRef
121           + "' could not be found; expect subsequent failures",
122           new ClassNotFoundException JavaDoc(className));
123       return -1;
124     }
125
126     /**
127      * Extracts the class id from the dispatch id.
128      *
129      * @param dispId
130      * @return the classId encoded into this dispatch id
131      */

132     private int extractClassIdFromDispId(int dispId) {
133       return (dispId >> 16) & 0xffff;
134     }
135
136     /**
137      * Returns the {@link java.lang.Class} instance for a given binary class
138      * name.
139      *
140      * @param binaryClassName the binary name of a class
141      * @return {@link java.lang.Class} instance or null if the given binary
142      * class name could not be found
143      */

144     private Class JavaDoc getClassFromBinaryName(String JavaDoc binaryClassName) {
145       try {
146         return Class.forName(binaryClassName, true, CompilingClassLoader.this);
147       } catch (ClassNotFoundException JavaDoc e) {
148         return null;
149       }
150     }
151
152     /**
153      * Returns the {@link java.lang.Class} object for a class that matches the
154      * source or binary name given.
155      *
156      * @param className binary or source name
157      * @return {@link java.lang.Class} instance, if found, or null
158      */

159     private Class JavaDoc getClassFromBinaryOrSourceName(String JavaDoc className) {
160       // Try the type oracle first
161
JClassType type = typeOracle.findType(className.replace('$', '.'));
162       if (type != null) {
163         // Use the type oracle to compute the exact binary name
164
String JavaDoc jniSig = type.getJNISignature();
165         jniSig = jniSig.substring(1, jniSig.length() - 1);
166         className = jniSig.replace('/', '.');
167       }
168       return getClassFromBinaryName(className);
169     }
170
171     /**
172      * Returns the {@link DispatchClassInfo} associated with the class name.
173      * Since we allow both binary and source names to be used in JSNI class
174      * references, we need to be able to deal with the fact that multiple
175      * permutations of the class name with regards to source or binary forms map
176      * on the same {@link DispatchClassInfo}.
177      *
178      * @param className binary or source name for a class
179      * @return {@link DispatchClassInfo} associated with the binary or source
180      * class name; null if there is none
181      */

182     private DispatchClassInfo getClassInfoFromClassName(String JavaDoc className) {
183
184       DispatchClassInfo dispClassInfo = (DispatchClassInfo) classNameToClassInfo.get(className);
185       if (dispClassInfo != null) {
186         // return the cached value
187
return dispClassInfo;
188       }
189
190       Class JavaDoc cls = getClassFromBinaryOrSourceName(className);
191       if (cls == null) {
192         /*
193          * default to return null; mask the specific error and let the caller
194          * handle it
195          */

196         return null;
197       }
198
199       if (dispClassInfo == null) {
200         /*
201          * we need to create a new DispatchClassInfo since we have never seen
202          * this class before under any source or binary class name
203          */

204         int classId = classIdToClassInfo.size();
205
206         dispClassInfo = new DispatchClassInfo(cls, classId);
207         classIdToClassInfo.add(dispClassInfo);
208       }
209
210       /*
211        * Whether we created a new DispatchClassInfo or not, we need to add a
212        * mapping for this name
213        */

214       classNameToClassInfo.put(className, dispClassInfo);
215
216       return dispClassInfo;
217     }
218
219     /**
220      * Synthesizes a dispatch identifier for the given class and member ids.
221      *
222      * @param classId class index
223      * @param memberId member index
224      * @return dispatch identifier for the given class and member ids
225      */

226     private int synthesizeDispId(int classId, int memberId) {
227       return (classId << 16) | memberId;
228     }
229   }
230
231   private final ByteCodeCompiler compiler;
232
233   private final DispatchClassInfoOracle dispClassInfoOracle = new DispatchClassInfoOracle();
234
235   private final TreeLogger logger;
236
237   private final Map JavaDoc methodToDispatch = new HashMap JavaDoc();
238
239   private final TypeOracle typeOracle;
240
241   public CompilingClassLoader(TreeLogger logger, ByteCodeCompiler compiler,
242       TypeOracle typeOracle) throws UnableToCompleteException {
243     super(null);
244     this.logger = logger;
245     this.compiler = compiler;
246     this.typeOracle = typeOracle;
247
248     // SPECIAL MAGIC: Prevents the compile process from ever trying to compile
249
// these guys from source, which is what we want, since they are special and
250
// neither of them would compile correctly from source.
251
//
252
// JavaScriptHost is special because its type cannot be known to the user.
253
// It is referenced only from generated code and GWT.create.
254
//
255
for (int i = 0; i < CacheManager.BOOTSTRAP_CLASSES.length; i++) {
256       Class JavaDoc clazz = CacheManager.BOOTSTRAP_CLASSES[i];
257       String JavaDoc className = clazz.getName();
258       try {
259         String JavaDoc path = clazz.getName().replace('.', '/').concat(".class");
260         ClassLoader JavaDoc cl = Thread.currentThread().getContextClassLoader();
261         URL JavaDoc url = cl.getResource(path);
262         if (url != null) {
263           byte classBytes[] = getClassBytesFromStream(url.openStream());
264           String JavaDoc loc = url.toExternalForm();
265           compiler.putClassBytes(logger, className, classBytes, loc);
266         } else {
267           logger.log(TreeLogger.ERROR,
268               "Could not find required bootstrap class '" + className
269                   + "' in the classpath", null);
270           throw new UnableToCompleteException();
271         }
272       } catch (IOException JavaDoc e) {
273         logger.log(TreeLogger.ERROR, "Error reading class bytes for "
274             + className, e);
275         throw new UnableToCompleteException();
276       }
277     }
278     compiler.removeStaleByteCode(logger);
279   }
280
281   /**
282    * Returns the {@link DispatchClassInfo} for a given dispatch id.
283    *
284    * @param dispId dispatch identifier
285    * @return {@link DispatchClassInfo} for a given dispatch id or null if one
286    * does not exist
287    */

288   public DispatchClassInfo getClassInfoByDispId(int dispId) {
289     return dispClassInfoOracle.getClassInfoByDispId(dispId);
290   }
291
292   /**
293    * Returns the dispatch id for a JSNI member reference.
294    *
295    * @param jsniMemberRef a JSNI member reference
296    * @return dispatch id or -1 if the JSNI member reference could not be found
297    */

298   public int getDispId(String JavaDoc jsniMemberRef) {
299     return dispClassInfoOracle.getDispId(jsniMemberRef);
300   }
301
302   public Object JavaDoc getMethodDispatch(Method JavaDoc method) {
303     synchronized (methodToDispatch) {
304       return methodToDispatch.get(method);
305     }
306   }
307
308   public void putMethodDispatch(Method JavaDoc method, Object JavaDoc methodDispatch) {
309     synchronized (methodToDispatch) {
310       methodToDispatch.put(method, methodDispatch);
311     }
312   }
313
314   protected synchronized Class JavaDoc findClass(String JavaDoc className)
315       throws ClassNotFoundException JavaDoc {
316     if (className == null) {
317       throw new ClassNotFoundException JavaDoc("null class name",
318           new NullPointerException JavaDoc());
319     }
320
321     // Don't mess with anything in the standard Java packages.
322
//
323
if (isInStandardJavaPackage(className)) {
324       // make my superclass load it
325
throw new ClassNotFoundException JavaDoc(className);
326     }
327
328     // MAGIC: this allows JavaScriptHost (in user space) to bridge to the real
329
// class in host space.
330
//
331
if (className.equals(ShellJavaScriptHost.class.getName())) {
332       return ShellJavaScriptHost.class;
333     }
334
335     // Get the bytes, compiling if necessary.
336
// Define the class from bytes.
337
//
338
try {
339       byte[] classBytes = compiler.getClassBytes(logger, className);
340       return defineClass(className, classBytes, 0, classBytes.length);
341     } catch (UnableToCompleteException e) {
342       throw new ClassNotFoundException JavaDoc(className);
343     }
344   }
345
346   void clear() {
347     dispClassInfoOracle.clear();
348
349     synchronized (methodToDispatch) {
350       methodToDispatch.clear();
351     }
352   }
353
354   private byte[] getClassBytesFromStream(InputStream JavaDoc is) throws IOException JavaDoc {
355     try {
356       byte classBytes[] = new byte[is.available()];
357       int read = 0;
358       while (read < classBytes.length) {
359         read += is.read(classBytes, read, classBytes.length - read);
360       }
361       return classBytes;
362     } finally {
363       Utility.close(is);
364     }
365   }
366
367   private boolean isInStandardJavaPackage(String JavaDoc className) {
368     if (className.startsWith("java.")) {
369       return true;
370     }
371
372     if (className.startsWith("javax.")) {
373       return true;
374     }
375
376     return false;
377   }
378 }
379
Popular Tags