/*
* Copyright (C) 2001 Mika Riekkinen, Joni Suominen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package alt.jiapi.util;
import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.log4j.Category;
import alt.jiapi.InstrumentationContext;
import alt.jiapi.Runtime;
import alt.jiapi.reflect.JiapiClass;
import alt.jiapi.JiapiException;
/**
* A sample ClassLoader which can be used when instrumenting Java
* applications. It knows how to load classes from CLASSPATH.
* <p>
* NOTE: Should the jar files found from jre/lib/ext
* (System.getProperty("java.ext.dirs")) also be added to search path?
*
* @author Mika Riekkinen
* @author Joni Suominen
* @version $Revision: 1.5 $ $Date: 2004/08/04 08:58:04 $
*/
public class InstrumentingClassLoader extends URLClassLoader {
private static Category log = Runtime.getLogCategory(InstrumentingClassLoader.class);
protected Map classes;
protected InstrumentationContext ctx;
/**
* Creates a new InstrumentingClassloader.
*/
public static ClassLoader createClassLoader() {
return createClassLoader((InstrumentationContext)null);
}
/**
* Creates a new InstrumentingClassloader.
*/
public static ClassLoader createClassLoader(InstrumentationContextProvider icp) throws JiapiException {
return createClassLoader(icp.getInstrumentationContext());
}
/**
* Creates a new InstrumentingClassloader.
*/
public static ClassLoader createClassLoader(InstrumentationContextProvider icp, ClassLoader parent) throws JiapiException {
return createClassLoader(icp.getInstrumentationContext(), parent);
}
/**
* Creates a new InstrumentingClassloader.
*/
public static ClassLoader createClassLoader(InstrumentationContext ctx) {
return createClassLoader(ctx, getSystemClassLoader());
}
/**
* Creates a new InstrumentingClassloader.
*/
public static ClassLoader createClassLoader(InstrumentationContext ctx,
ClassLoader parent) {
URL urls[] = getClassPathUrls();
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkCreateClassLoader();
}
// System.out.println("Creating class loader with context " + ctx.getClass().getName() + ", " + ctx + ", parent: " + parent);
return new InstrumentingClassLoader(ctx, urls, parent);
}
protected InstrumentingClassLoader(InstrumentationContext ctx, URL[] urls,
ClassLoader parent) {
super(urls, parent);
this.ctx = ctx;
classes = Collections.synchronizedMap(new HashMap());
}
protected synchronized Class loadClass(String className, boolean resolve)
throws ClassNotFoundException {
//System.out.println("Loading " + className);
Class cl = null;
JiapiClass jiapiClass = null;
log.debug("loadClass(" + className + ")");
// System.out.println("loadClass(" + className + ")");
// Core Java classes and Jiapi classes are delegated to parent
// class loader.
if (className.startsWith("java.") || className.startsWith("javax.") ||
className.startsWith("sun.") || className.startsWith("alt.jiapi.")) {
// System.out.println("getParent().loadClass(" + className + ")" +
// getParent());
return getParent().loadClass(className);
}
if ((cl = (Class) classes.get(className)) == null) {
log.debug("cache miss: " + className);
// Check if we have permission to access the package.
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
int i = className.lastIndexOf('.');
if (i != -1) {
sm.checkPackageAccess(className.substring(0, i));
}
}
if (ctx == null) {
return bootstrap(className);
}
// Locate the class as a resource.
String path = className.replace('.', '/').concat(".class");
URL location = super.findResource(path);
if (location == null) {
// Delagate to parent.
return getParent().loadClass(className);
}
try {
jiapiClass = ctx.getLoader().loadClass(className, location);
}
catch(java.io.IOException ioe) {
throw new ClassNotFoundException(className);
}
ctx.instrument(jiapiClass);
byte[] bytes = jiapiClass.getByteCode();
if (System.getProperty("dump") != null) {
try {
jiapiClass.dump(new java.io.FileOutputStream(jiapiClass.getName() + ".dump"));
}
catch(Throwable t) {
}
}
if (bytes != null) {
CodeSource cs = createCodeSource(location);
cl = defineClass(className, bytes, 0, bytes.length, cs);
}
log.debug(cl + " was loaded with " + cl.getClassLoader());
// Should we cache this or not.
// Could there be a case, where we want to instrument
// a class differently each time.
// debug on, debug off...
classes.put(className, cl);
}
if (resolve) {
resolveClass(cl);
}
return cl;
}
/**
* Form a CodeSource for loaded class.
*
* @param location a location where the class was loaded from
*/
protected CodeSource createCodeSource(URL location) {
Certificate []certs = null;
if (location.getProtocol().equals("jar")) {
try {
JarURLConnection jarConnection =
(JarURLConnection) location.openConnection();
certs = jarConnection.getCertificates();
location = jarConnection.getJarFileURL();
} catch (IOException ioe) {
ioe.printStackTrace();
throw new RuntimeException(ioe.getMessage());
}
}
return new CodeSource(location, certs);
}
/**
* Creates an array of URLs which are used for seaching classes.
*/
private static URL []getClassPathUrls() {
String string = System.getProperty("java.class.path");
List urls = new ArrayList();
if (string != null) {
StringTokenizer st = new StringTokenizer(string,
File.pathSeparator);
while (st.hasMoreTokens()) {
try {
urls.add((new File(st.nextToken())).toURL());
} catch (IOException ioe) {
// Don't mind... CLASSPATH is supplied by user
// and can easily contain spelling mistakes etc.
}
}
}
return ((URL []) urls.toArray(new URL[0]));
}
/**
* Set the context for the ClassLoader.
*
* @param ctx an InstrumentationContext to be used
*/
public void setContext(InstrumentationContext ctx) {
this.ctx = ctx;
}
// should be private:
public Class bootstrap(String className) throws ClassNotFoundException {
String s = className.replace('.', '/') + ".class";
return findClass(s);
}
// This method is used only for bootstrapping the class.
byte [] byteBuffer = new byte[65000];
public Class findClass(String name) throws ClassNotFoundException {
log.debug("findClass(" + name + ")");
Class c = null;
//Class c = findClass(name);
try {
URL url = findResource(name);
if (url == null) {
throw new ClassNotFoundException(name);
}
java.io.InputStream is = url.openStream();
int count = 0;
while (is.available() > 0) {
int elemsRead = is.read(byteBuffer, count, is.available());
if (elemsRead == -1) {
break;
}
count += elemsRead;
}
c = defineClass(name.substring(0, name.lastIndexOf('.')),
byteBuffer, 0, count);
}
catch (java.io.IOException e) {
e.printStackTrace();
throw new ClassNotFoundException(name);
}
classes.put(c.getName(), c);
return c;
}
}