View Javadoc

1   /**
2    *  MicroEmulator
3    *  Copyright (C) 2006-2007 Bartek Teodorczyk <barteo@barteo.net>
4    *  Copyright (C) 2006-2007 Vlad Skarzhevskyy
5    *
6    *  It is licensed under the following two licenses as alternatives:
7    *    1. GNU Lesser General Public License (the "LGPL") version 2.1 or any newer version
8    *    2. Apache License (the "AL") Version 2.0
9    *
10   *  You may not use this file except in compliance with at least one of
11   *  the above two licenses.
12   *
13   *  You may obtain a copy of the LGPL at
14   *      http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
15   *
16   *  You may obtain a copy of the AL at
17   *      http://www.apache.org/licenses/LICENSE-2.0
18   *
19   *  Unless required by applicable law or agreed to in writing, software
20   *  distributed under the License is distributed on an "AS IS" BASIS,
21   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22   *  See the LGPL or the AL for the specific language governing permissions and
23   *  limitations.
24   *
25   *  @version $Id: MIDletClassLoader.java 1881 2008-12-18 15:09:07Z vlads $
26   */
27  package org.microemu.app.classloader;
28  
29  import java.io.File;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.net.MalformedURLException;
33  import java.net.URL;
34  import java.net.URLClassLoader;
35  import java.security.AccessControlContext;
36  import java.security.AccessController;
37  import java.security.PrivilegedActionException;
38  import java.security.PrivilegedExceptionAction;
39  import java.util.HashSet;
40  import java.util.Iterator;
41  import java.util.Set;
42  import java.util.StringTokenizer;
43  
44  import org.microemu.app.util.IOUtils;
45  import org.microemu.log.Logger;
46  
47  /**
48   * Main features of this class loader Security aware - enables load and run app in Webstart. Proper class loading order.
49   * MIDlet classes loaded first then system and MicroEmulator classes Proper resource loading order. MIDlet resources
50   * only can be loaded. MIDlet Bytecode preprocessing/instrumentation
51   * 
52   * @author vlads
53   * 
54   */
55  public class MIDletClassLoader extends URLClassLoader {
56  
57  	// TODO make this configurable
58  
59  	public static boolean instrumentMIDletClasses = true;
60  
61  	public static boolean traceClassLoading = false;
62  
63  	public static boolean traceSystemClassLoading = false;
64  
65  	public static boolean enhanceCatchBlock = false;
66  
67  	private final static boolean debug = false;
68  
69  	private boolean delegatingToParent = false;
70  
71  	private boolean findPathInParent = false;
72  
73  	private InstrumentationConfig config;
74  
75  	private Set noPreporcessingNames;
76  
77  	/* The context to be used when loading classes and resources */
78  	private AccessControlContext acc;
79  
80  	private static class LoadClassByParentException extends ClassNotFoundException {
81  
82  		public LoadClassByParentException(String name) {
83  			super(name);
84  		}
85  
86  		private static final long serialVersionUID = 1L;
87  
88  	}
89  
90  	public MIDletClassLoader(ClassLoader parent) {
91  		super(new URL[] {}, parent);
92  		noPreporcessingNames = new HashSet();
93  		acc = AccessController.getContext();
94  		config = new InstrumentationConfig();
95  		config.setEnhanceCatchBlock(enhanceCatchBlock);
96  		config.setEnhanceThreadCreation(true);
97  	}
98  
99  	// public MIDletClassLoader(URL[] urls, ClassLoader parent) {
100 	// super(urls, parent);
101 	// noPreporcessingNames = new HashSet();
102 	// }
103 
104 	public void configure(MIDletClassLoaderConfig clConfig, boolean forJad) throws MalformedURLException {
105 		for (Iterator iter = clConfig.appclasspath.iterator(); iter.hasNext();) {
106 			String path = (String) iter.next();
107 			StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
108 			while (st.hasMoreTokens()) {
109 				this.addURL(new URL(IOUtils.getCanonicalFileClassLoaderURL(new File(st.nextToken()))));
110 			}
111 		}
112 		for (Iterator iter = clConfig.appclasses.iterator(); iter.hasNext();) {
113 			this.addClassURL((String) iter.next());
114 		}
115 		int delegationType = clConfig.getDelegationType(forJad);
116 		this.delegatingToParent = (delegationType == MIDletClassLoaderConfig.DELEGATION_DELEGATING);
117 		this.findPathInParent = (delegationType == MIDletClassLoaderConfig.DELEGATION_RELAXED);
118 	}
119 
120 	/**
121 	 * Appends the Class Location URL to the list of URLs to search for classes and resources.
122 	 * 
123 	 * @param Class
124 	 *            Name
125 	 */
126 	public void addClassURL(String className) throws MalformedURLException {
127 		String resource = getClassResourceName(className);
128 		URL url = getParent().getResource(resource);
129 		if (url == null) {
130 			url = this.getResource(resource);
131 		}
132 		if (url == null) {
133 			throw new MalformedURLException("Unable to find class " + className + " URL");
134 		}
135 		String path = url.toExternalForm();
136 		if (debug) {
137 			Logger.debug("addClassURL ", path);
138 		}
139 		addURL(new URL(path.substring(0, path.length() - resource.length())));
140 	}
141 
142 	static URL getClassURL(ClassLoader parent, String className) throws MalformedURLException {
143 		String resource = getClassResourceName(className);
144 		URL url = parent.getResource(resource);
145 		if (url == null) {
146 			throw new MalformedURLException("Unable to find class " + className + " URL");
147 		}
148 		String path = url.toExternalForm();
149 		return new URL(path.substring(0, path.length() - resource.length()));
150 	}
151 
152 	public void addURL(URL url) {
153 		if (debug) {
154 			Logger.debug("addURL ", url.toString());
155 		}
156 		super.addURL(url);
157 	}
158 
159 	/**
160 	 * Loads the class with the specified <a href="#name">binary name</a>.
161 	 * 
162 	 * <p>
163 	 * Search order is reverse to standard implemenation
164 	 * </p>
165 	 * 
166 	 * This implementation of this method searches for classes in the following order:
167 	 * 
168 	 * <p>
169 	 * <ol>
170 	 * 
171 	 * <li>
172 	 * <p>
173 	 * Invoke {@link #findLoadedClass(String)} to check if the class has already been loaded.
174 	 * </p>
175 	 * </li>
176 	 * 
177 	 * <li>
178 	 * <p>
179 	 * Invoke the {@link #findClass(String)} method to find the class in this class loader URLs.
180 	 * </p>
181 	 * </li>
182 	 * 
183 	 * <li>
184 	 * <p>
185 	 * Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method on the parent class loader. If the parent is
186 	 * <tt>null</tt> the class loader built-in to the virtual machine is used, instead.
187 	 * </p>
188 	 * </li>
189 	 * 
190 	 * </ol>
191 	 * 
192 	 */
193 	protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
194 		if (debug) {
195 			Logger.debug("loadClass", name);
196 		}
197 		// First, check if the class has already been loaded
198 		Class result = findLoadedClass(name);
199 		if (result == null) {
200 			try {
201 				result = findClass(name);
202 				if (debug && (result == null)) {
203 					Logger.debug("loadClass not found", name);
204 				}
205 			} catch (ClassNotFoundException e) {
206 
207 				if ((e instanceof LoadClassByParentException) || this.delegatingToParent) {
208 					if (traceSystemClassLoading) {
209 						Logger.info("Load system class", name);
210 					}
211 					// This will call our findClass again if Class is not found
212 					// in parent
213 					result = super.loadClass(name, false);
214 					if (result == null) {
215 						throw new ClassNotFoundException(name);
216 					}
217 				}
218 			}
219 		}
220 		if (resolve) {
221 			resolveClass(result);
222 		}
223 		return result;
224 	}
225 
226 	/**
227 	 * Finds the resource with the given name. A resource is some data (images, audio, text, etc) that can be accessed
228 	 * by class code in a way that is independent of the location of the code.
229 	 * 
230 	 * <p>
231 	 * The name of a resource is a '<tt>/</tt>'-separated path name that identifies the resource.
232 	 * 
233 	 * <p>
234 	 * Search order is reverse to standard implementation
235 	 * </p>
236 	 * 
237 	 * <p>
238 	 * This method will first use {@link #findResource(String)} to find the resource. That failing, this method will NOT
239 	 * invoke the parent class loader if delegatingToParent=false.
240 	 * </p>
241 	 * 
242 	 * @param name
243 	 *            The resource name
244 	 * 
245 	 * @return A <tt>URL</tt> object for reading the resource, or <tt>null</tt> if the resource could not be found or
246 	 *         the invoker doesn't have adequate privileges to get the resource.
247 	 * 
248 	 */
249 
250 	public URL getResource(final String name) {
251 		try {
252 			return (URL) AccessController.doPrivileged(new PrivilegedExceptionAction() {
253 				public Object run() {
254 					URL url = findResource(name);
255 					if ((url == null) && delegatingToParent && (getParent() != null)) {
256 						url = getParent().getResource(name);
257 					}
258 					return url;
259 				}
260 			}, acc);
261 		} catch (PrivilegedActionException e) {
262 			if (debug) {
263 				Logger.error("Unable to find resource " + name + " ", e);
264 			}
265 			return null;
266 		}
267 	}
268 
269 	/**
270 	 * Allow access to resources
271 	 */
272 	public InputStream getResourceAsStream(String name) {
273 		final URL url = getResource(name);
274 		if (url == null) {
275 			return null;
276 		}
277 
278 		try {
279 			return (InputStream) AccessController.doPrivileged(new PrivilegedExceptionAction() {
280 				public Object run() throws IOException {
281 					return url.openStream();
282 				}
283 			}, acc);
284 		} catch (PrivilegedActionException e) {
285 			if (debug) {
286 				Logger.debug("Unable to find resource for class " + name + " ", e);
287 			}
288 			return null;
289 		}
290 
291 	}
292 
293 	public boolean classLoadByParent(String className) {
294 		/* This java standard */
295 		if (className.startsWith("java.")) {
296 			return true;
297 		}
298 		/*
299 		 * This is required when Class.forName().newInstance() used to create instances with inheritance
300 		 */
301 		if (className.startsWith("sun.reflect.")) {
302 			return true;
303 		}
304 		/* No real device allow overloading this package */
305 		if (className.startsWith("javax.microedition.")) {
306 			return true;
307 		}
308 		if (className.startsWith("javax.")) {
309 			return true;
310 		}
311 		if (noPreporcessingNames.contains(className)) {
312 			return true;
313 		}
314 		return false;
315 	}
316 
317 	/**
318 	 * Special case for classes injected to MIDlet
319 	 * 
320 	 * @param klass
321 	 */
322 	public void disableClassPreporcessing(Class klass) {
323 		disableClassPreporcessing(klass.getName());
324 	}
325 
326 	public void disableClassPreporcessing(String className) {
327 		noPreporcessingNames.add(className);
328 	}
329 
330 	public static String getClassResourceName(String className) {
331 		return className.replace('.', '/').concat(".class");
332 	}
333 
334 	protected Class findClass(final String name) throws ClassNotFoundException {
335 		if (debug) {
336 			Logger.debug("findClass", name);
337 		}
338 		if (classLoadByParent(name)) {
339 			throw new LoadClassByParentException(name);
340 		}
341 		InputStream is;
342 		try {
343 			is = (InputStream) AccessController.doPrivileged(new PrivilegedExceptionAction() {
344 				public Object run() throws ClassNotFoundException {
345 					return getResourceAsStream(getClassResourceName(name));
346 				}
347 			}, acc);
348 
349 			// Relax ClassLoader behavior
350 			if ((is == null) && (this.findPathInParent)) {
351 				boolean classFound;
352 				try {
353 					addClassURL(name);
354 					classFound = true;
355 				} catch (MalformedURLException e) {
356 					classFound = false;
357 				}
358 				if (classFound) {
359 					is = (InputStream) AccessController.doPrivileged(new PrivilegedExceptionAction() {
360 						public Object run() throws ClassNotFoundException {
361 							return getResourceAsStream(getClassResourceName(name));
362 						}
363 					}, acc);
364 				}
365 			}
366 		} catch (PrivilegedActionException e) {
367 			if (debug) {
368 				Logger.debug("Unable to find resource for class " + name + " ", e);
369 			}
370 			throw new ClassNotFoundException(name, e.getCause());
371 		}
372 
373 		if (is == null) {
374 			if (debug) {
375 				Logger.debug("Unable to find resource for class", name);
376 			}
377 			throw new ClassNotFoundException(name);
378 		}
379 		byte[] byteCode;
380 		int byteCodeLength;
381 		try {
382 			if (traceClassLoading) {
383 				Logger.info("Load MIDlet class", name);
384 			}
385 			if (instrumentMIDletClasses) {
386 				byteCode = ClassPreprocessor.instrument(is, config);
387 				byteCodeLength = byteCode.length;
388 			} else {
389 				final int chunkSize = 1024 * 2;
390 				// No class or data object must be bigger than 16 Kilobyte
391 				final int maxClassSizeSize = 1024 * 16;
392 				byteCode = new byte[chunkSize];
393 				byteCodeLength = 0;
394 				do {
395 					int retrived;
396 					try {
397 						retrived = is.read(byteCode, byteCodeLength, byteCode.length - byteCodeLength);
398 					} catch (IOException e) {
399 						throw new ClassNotFoundException(name, e);
400 					}
401 					if (retrived == -1) {
402 						break;
403 					}
404 					if (byteCode.length + chunkSize > maxClassSizeSize) {
405 						throw new ClassNotFoundException(name, new ClassFormatError(
406 								"Class object is bigger than 16 Kilobyte"));
407 					}
408 					byteCodeLength += retrived;
409 					if (byteCode.length == byteCodeLength) {
410 						byte[] newData = new byte[byteCode.length + chunkSize];
411 						System.arraycopy(byteCode, 0, newData, 0, byteCode.length);
412 						byteCode = newData;
413 					} else if (byteCode.length < byteCodeLength) {
414 						throw new ClassNotFoundException(name, new ClassFormatError("Internal read error"));
415 					}
416 				} while (true);
417 			}
418 		} finally {
419 			try {
420 				is.close();
421 			} catch (IOException ignore) {
422 			}
423 		}
424 		if ((debug) && (instrumentMIDletClasses)) {
425 			Logger.debug("instrumented ", name);
426 		}
427 		return defineClass(name, byteCode, 0, byteCodeLength);
428 	}
429 }