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.dev.shell;
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.TypeOracle;
022: import com.google.gwt.dev.jdt.ByteCodeCompiler;
023: import com.google.gwt.dev.jdt.CacheManager;
024: import com.google.gwt.util.tools.Utility;
025:
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.lang.reflect.Method;
029: import java.net.URL;
030: import java.util.ArrayList;
031: import java.util.HashMap;
032: import java.util.Map;
033:
034: /**
035: *
036: * TODO : we should refactor this class to move the getClassInfoByDispId,
037: * getDispId, getMethodDispatch and putMethodDispatch into a separate entity
038: * since they really do not interact with the CompilingClassLoader
039: * functionality.
040: */
041: public final class CompilingClassLoader extends ClassLoader {
042:
043: /**
044: * Oracle that can answer questions about
045: * {@link DispatchClassInfo DispatchClassInfos}.
046: */
047: private final class DispatchClassInfoOracle {
048:
049: /**
050: * Class identifier to DispatchClassInfo mapping.
051: */
052: private final ArrayList<DispatchClassInfo> classIdToClassInfo = new ArrayList<DispatchClassInfo>();
053:
054: /**
055: * Binary or source class name to DispatchClassInfo map.
056: */
057: private final Map<String, DispatchClassInfo> classNameToClassInfo = new HashMap<String, DispatchClassInfo>();
058:
059: /**
060: * Clears out the contents of this oracle.
061: */
062: public synchronized void clear() {
063: classIdToClassInfo.clear();
064: classNameToClassInfo.clear();
065: }
066:
067: /**
068: * Returns the {@link DispatchClassInfo} for a given dispatch id.
069: *
070: * @param dispId dispatch id
071: * @return DispatchClassInfo for the requested dispatch id
072: */
073: public synchronized DispatchClassInfo getClassInfoByDispId(
074: int dispId) {
075: int classId = extractClassIdFromDispId(dispId);
076:
077: return classIdToClassInfo.get(classId);
078: }
079:
080: /**
081: * Returns the dispatch id for a given member reference. Member references
082: * can be encoded as: "@class::field" or "@class::method(typesigs)".
083: *
084: * @param jsniMemberRef a string encoding a JSNI member to use
085: * @return integer encoded as ((classId << 16) | memberId)
086: */
087: public synchronized int getDispId(String jsniMemberRef) {
088: /*
089: * Map JS toString() onto the Java toString() method.
090: *
091: * TODO : is it true that tostring is valid in JavaScript? JavaScript is
092: * case sensitive.
093: */
094: if (jsniMemberRef.equals("toString")) {
095: jsniMemberRef = "@java.lang.Object::toString()";
096: }
097:
098: // References are of the form "@class::field" or
099: // "@class::method(typesigs)".
100: int endClassName = jsniMemberRef.indexOf("::");
101: if (endClassName == -1 || jsniMemberRef.length() < 1
102: || jsniMemberRef.charAt(0) != '@') {
103: logger.log(TreeLogger.WARN,
104: "Malformed JSNI reference '" + jsniMemberRef
105: + "'; expect subsequent failures",
106: new NoSuchFieldError(jsniMemberRef));
107: return -1;
108: }
109:
110: String className = jsniMemberRef.substring(1, endClassName);
111:
112: // Do the lookup by class name.
113: DispatchClassInfo dispClassInfo = getClassInfoFromClassName(className);
114: if (dispClassInfo != null) {
115: String memberName = jsniMemberRef
116: .substring(endClassName + 2);
117: int memberId = dispClassInfo.getMemberId(memberName);
118:
119: return synthesizeDispId(dispClassInfo.getClassId(),
120: memberId);
121: }
122:
123: logger
124: .log(
125: TreeLogger.WARN,
126: "Class '"
127: + className
128: + "' in JSNI reference '"
129: + jsniMemberRef
130: + "' could not be found; expect subsequent failures",
131: new ClassNotFoundException(className));
132: return -1;
133: }
134:
135: /**
136: * Extracts the class id from the dispatch id.
137: *
138: * @param dispId
139: * @return the classId encoded into this dispatch id
140: */
141: private int extractClassIdFromDispId(int dispId) {
142: return (dispId >> 16) & 0xffff;
143: }
144:
145: /**
146: * Returns the {@link java.lang.Class} instance for a given binary class
147: * name.
148: *
149: * @param binaryClassName the binary name of a class
150: * @return {@link java.lang.Class} instance or null if the given binary
151: * class name could not be found
152: */
153: private Class<?> getClassFromBinaryName(String binaryClassName) {
154: try {
155: return Class.forName(binaryClassName, true,
156: CompilingClassLoader.this );
157: } catch (ClassNotFoundException e) {
158: return null;
159: }
160: }
161:
162: /**
163: * Returns the {@link java.lang.Class} object for a class that matches the
164: * source or binary name given.
165: *
166: * @param className binary or source name
167: * @return {@link java.lang.Class} instance, if found, or null
168: */
169: private Class<?> getClassFromBinaryOrSourceName(String className) {
170: // Try the type oracle first
171: JClassType type = typeOracle.findType(className.replace(
172: '$', '.'));
173: if (type != null) {
174: // Use the type oracle to compute the exact binary name
175: String jniSig = type.getJNISignature();
176: jniSig = jniSig.substring(1, jniSig.length() - 1);
177: className = jniSig.replace('/', '.');
178: }
179: return getClassFromBinaryName(className);
180: }
181:
182: /**
183: * Returns the {@link DispatchClassInfo} associated with the class name.
184: * Since we allow both binary and source names to be used in JSNI class
185: * references, we need to be able to deal with the fact that multiple
186: * permutations of the class name with regards to source or binary forms map
187: * on the same {@link DispatchClassInfo}.
188: *
189: * @param className binary or source name for a class
190: * @return {@link DispatchClassInfo} associated with the binary or source
191: * class name; null if there is none
192: */
193: private DispatchClassInfo getClassInfoFromClassName(
194: String className) {
195:
196: DispatchClassInfo dispClassInfo = classNameToClassInfo
197: .get(className);
198: if (dispClassInfo != null) {
199: // return the cached value
200: return dispClassInfo;
201: }
202:
203: Class<?> cls = getClassFromBinaryOrSourceName(className);
204: if (cls == null) {
205: /*
206: * default to return null; mask the specific error and let the caller
207: * handle it
208: */
209: return null;
210: }
211:
212: /*
213: * we need to create a new DispatchClassInfo since we have never seen this
214: * class before under any source or binary class name
215: */
216: int classId = classIdToClassInfo.size();
217:
218: dispClassInfo = new DispatchClassInfo(cls, classId);
219: classIdToClassInfo.add(dispClassInfo);
220:
221: /*
222: * Whether we created a new DispatchClassInfo or not, we need to add a
223: * mapping for this name
224: */
225: classNameToClassInfo.put(className, dispClassInfo);
226:
227: return dispClassInfo;
228: }
229:
230: /**
231: * Synthesizes a dispatch identifier for the given class and member ids.
232: *
233: * @param classId class index
234: * @param memberId member index
235: * @return dispatch identifier for the given class and member ids
236: */
237: private int synthesizeDispId(int classId, int memberId) {
238: return (classId << 16) | memberId;
239: }
240: }
241:
242: private final ByteCodeCompiler compiler;
243:
244: private final DispatchClassInfoOracle dispClassInfoOracle = new DispatchClassInfoOracle();
245:
246: private final TreeLogger logger;
247:
248: private final Map<Method, Object> methodToDispatch = new HashMap<Method, Object>();
249:
250: private final TypeOracle typeOracle;
251:
252: public CompilingClassLoader(TreeLogger logger,
253: ByteCodeCompiler compiler, TypeOracle typeOracle)
254: throws UnableToCompleteException {
255: super (null);
256: this .logger = logger;
257: this .compiler = compiler;
258: this .typeOracle = typeOracle;
259:
260: // SPECIAL MAGIC: Prevents the compile process from ever trying to compile
261: // these guys from source, which is what we want, since they are special and
262: // neither of them would compile correctly from source.
263: //
264: // JavaScriptHost is special because its type cannot be known to the user.
265: // It is referenced only from generated code and GWT.create.
266: //
267: for (int i = 0; i < CacheManager.BOOTSTRAP_CLASSES.length; i++) {
268: Class<?> clazz = CacheManager.BOOTSTRAP_CLASSES[i];
269: String className = clazz.getName();
270: try {
271: String path = clazz.getName().replace('.', '/').concat(
272: ".class");
273: ClassLoader cl = Thread.currentThread()
274: .getContextClassLoader();
275: URL url = cl.getResource(path);
276: if (url != null) {
277: byte classBytes[] = getClassBytesFromStream(url
278: .openStream());
279: String loc = url.toExternalForm();
280: compiler.putClassBytes(logger, className,
281: classBytes, loc);
282: } else {
283: logger.log(TreeLogger.ERROR,
284: "Could not find required bootstrap class '"
285: + className + "' in the classpath",
286: null);
287: throw new UnableToCompleteException();
288: }
289: } catch (IOException e) {
290: logger
291: .log(TreeLogger.ERROR,
292: "Error reading class bytes for "
293: + className, e);
294: throw new UnableToCompleteException();
295: }
296: }
297: compiler.removeStaleByteCode(logger);
298: }
299:
300: /**
301: * Returns the {@link DispatchClassInfo} for a given dispatch id.
302: *
303: * @param dispId dispatch identifier
304: * @return {@link DispatchClassInfo} for a given dispatch id or null if one
305: * does not exist
306: */
307: public DispatchClassInfo getClassInfoByDispId(int dispId) {
308: return dispClassInfoOracle.getClassInfoByDispId(dispId);
309: }
310:
311: /**
312: * Returns the dispatch id for a JSNI member reference.
313: *
314: * @param jsniMemberRef a JSNI member reference
315: * @return dispatch id or -1 if the JSNI member reference could not be found
316: */
317: public int getDispId(String jsniMemberRef) {
318: return dispClassInfoOracle.getDispId(jsniMemberRef);
319: }
320:
321: public Object getMethodDispatch(Method method) {
322: synchronized (methodToDispatch) {
323: return methodToDispatch.get(method);
324: }
325: }
326:
327: public void putMethodDispatch(Method method, Object methodDispatch) {
328: synchronized (methodToDispatch) {
329: methodToDispatch.put(method, methodDispatch);
330: }
331: }
332:
333: @Override
334: protected synchronized Class<?> findClass(String className)
335: throws ClassNotFoundException {
336: if (className == null) {
337: throw new ClassNotFoundException("null class name",
338: new NullPointerException());
339: }
340:
341: // Don't mess with anything in the standard Java packages.
342: //
343: if (isInStandardJavaPackage(className)) {
344: // make my superclass load it
345: throw new ClassNotFoundException(className);
346: }
347:
348: // MAGIC: this allows JavaScriptHost (in user space) to bridge to the real
349: // class in host space.
350: //
351: if (className.equals(ShellJavaScriptHost.class.getName())) {
352: return ShellJavaScriptHost.class;
353: }
354:
355: // Get the bytes, compiling if necessary.
356: // Define the class from bytes.
357: //
358: try {
359: byte[] classBytes = compiler.getClassBytes(logger,
360: className);
361: return defineClass(className, classBytes, 0,
362: classBytes.length);
363: } catch (UnableToCompleteException e) {
364: throw new ClassNotFoundException(className);
365: }
366: }
367:
368: void clear() {
369: dispClassInfoOracle.clear();
370:
371: synchronized (methodToDispatch) {
372: methodToDispatch.clear();
373: }
374: }
375:
376: private byte[] getClassBytesFromStream(InputStream is)
377: throws IOException {
378: try {
379: byte classBytes[] = new byte[is.available()];
380: int read = 0;
381: while (read < classBytes.length) {
382: read += is.read(classBytes, read, classBytes.length
383: - read);
384: }
385: return classBytes;
386: } finally {
387: Utility.close(is);
388: }
389: }
390:
391: private boolean isInStandardJavaPackage(String className) {
392: if (className.startsWith("java.")) {
393: return true;
394: }
395:
396: if (className.startsWith("javax.")) {
397: return true;
398: }
399:
400: return false;
401: }
402: }
|