View Javadoc

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