Mercurial > hg > blitz_condensed
diff src/com/go/trove/util/ClassInjector.java @ 0:3dc0c5604566
Initial checkin of blitz 2.0 fcs - no installer yet.
author | Dan Creswell <dan.creswell@gmail.com> |
---|---|
date | Sat, 21 Mar 2009 11:00:06 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/go/trove/util/ClassInjector.java Sat Mar 21 11:00:06 2009 +0000 @@ -0,0 +1,430 @@ +/* ==================================================================== + * Trove - Copyright (c) 1997-2000 Walt Disney Internet Group + * ==================================================================== + * The Tea Software License, Version 1.1 + * + * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Walt Disney Internet Group (http://opensource.go.com/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact opensource@dig.com. + * + * 5. Products derived from this software may not be called "Tea", + * "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet", + * "Kettle", "Trove" or "BeanDoc" appear in their name, without prior + * written permission of the Walt Disney Internet Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * For more information about Tea, please see http://opensource.go.com/. + */ + +package com.go.trove.util; + +import java.io.OutputStream; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.ref.*; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.util.*; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/****************************************************************************** + * A special ClassLoader that allows classes to be defined by directly + * injecting the bytecode. All classes other than those injected are loaded + * from the parent ClassLoader, which is the ClassLoader that loaded this + * class. If a directory is passed in, the ClassInjector looks there for + * non-injected class files before asking the parent ClassLoader for a class. + * + * @author Brian S O'Neill + * @version + * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 7/30/01 <!-- $--> + */ +public class ClassInjector extends ClassLoader { + private static Map cShared = new NullKeyMap(new IdentityMap()); + + /** + * Returns a shared ClassInjector instance. + */ + public static ClassInjector getInstance() { + return getInstance(null); + } + + /** + * Returns a shared ClassInjector instance for the given ClassLoader. + */ + public static ClassInjector getInstance(ClassLoader loader) { + ClassInjector injector = null; + + synchronized (cShared) { + Reference ref = (Reference)cShared.get(loader); + if (ref != null) { + injector = (ClassInjector)ref.get(); + } + if (injector == null) { + injector = new ClassInjector(loader); + cShared.put(loader, new WeakReference(injector)); + } + return injector; + } + } + + // Parent ClassLoader, used to load classes that aren't defined by this. + private ClassLoader mSuperLoader; + + private File[] mRootClassDirs; + private String mRootPackage; + + // A set of all the classes defined by the ClassInjector. + private Map mDefined = Collections.synchronizedMap(new HashMap()); + + // A map to store raw bytecode for future use in getResourceAsStream(). + private Map mGZippedBytecode; + + private URLStreamHandler mFaker; + + /** + * Construct a ClassInjector that uses the ClassLoader that loaded this + * class as a parent, and it has no root class directory or root package. + */ + public ClassInjector() { + this(null, (File[])null, null); + } + + /** + * Construct a ClassInjector that has no root class directory or root + * package. + * + * @param parent optional parent ClassLoader to default to when a class + * cannot be loaded with this ClassInjector. + */ + public ClassInjector(ClassLoader parent) { + this(parent, (File[])null, null); + } + + /** + * Construct a ClassInjector that uses the ClassLoader that loaded this + * class as a parent. + * + * @param rootClassDir optional directory to look for non-injected classes + * @param rootPackage optional package name for the root directory + */ + public ClassInjector(File rootClassDir, String rootPackage) { + this(null, (rootClassDir == null) ? null : new File[]{rootClassDir}, + rootPackage); + } + + /** + * @param parent optional parent ClassLoader to default to when a class + * cannot be loaded with this ClassInjector. + * @param rootClassDir optional directory to look for non-injected classes + * @param rootPackage optional package name for the root directory + */ + public ClassInjector(ClassLoader parent, + File rootClassDir, String rootPackage) { + this(parent, (rootClassDir == null) ? null : new File[]{rootClassDir}, + rootPackage); + } + + /** + * Construct a ClassInjector that uses the ClassLoader that loaded this + * class as a parent. + * + * @param rootClassDirs optional directories to look for non-injected + * classes + * @param rootPackage optional package name for the root directory + */ + public ClassInjector(File[] rootClassDirs, String rootPackage) { + this(null, rootClassDirs, rootPackage); + } + + /** + * @param parent optional parent ClassLoader to default to when a class + * cannot be loaded with this ClassInjector. + * @param rootClassDirs optional directories to look for non-injected + * classes + * @param rootPackage optional package name for the root directory + */ + public ClassInjector(ClassLoader parent, + File[] rootClassDirs, + String rootPackage) { + this(parent, rootClassDirs, rootPackage, false); + } + + /** + * @param parent optional parent ClassLoader to default to when a class + * cannot be loaded with this ClassInjector. + * @param rootClassDirs optional directories to look for non-injected + * classes + * @param rootPackage optional package name for the root directory + * @param keepRawBytecode if true, will cause the ClassInjector to store + * the raw bytecode of defined classes. + */ + public ClassInjector(ClassLoader parent, + File[] rootClassDirs, + String rootPackage, + boolean keepRawBytecode) { + super(); + if (parent == null) { + parent = getClass().getClassLoader(); + } + mSuperLoader = parent; + if (rootClassDirs != null) { + mRootClassDirs = (File[])rootClassDirs.clone(); + } + if (rootPackage != null && !rootPackage.endsWith(".")) { + rootPackage += '.'; + } + mRootPackage = rootPackage; + + if (keepRawBytecode) { + mGZippedBytecode = Collections.synchronizedMap(new HashMap()); + } + } + + /** + * Get a stream used to define a class. Close the stream to finish the + * definition. + * + * @param the fully qualified name of the class to be defined. + */ + public OutputStream getStream(String name) { + return new Stream(name); + } + + public URL getResource(String name) { + + if (mGZippedBytecode != null) { + if (mGZippedBytecode.containsKey(name)) { + try { + return new URL("file", null, -1, name, getURLFaker()); + } + catch (Exception e) { + e.printStackTrace(); + } + System.out.println("created URL for " + name); + } + } + return mSuperLoader.getResource(name); + } + + private URLStreamHandler getURLFaker() { + if (mFaker == null) { + mFaker = new URLFaker(); + } + return mFaker; + } + + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + + Class clazz = findLoadedClass(name); + + if (clazz == null) { + synchronized (this) { + clazz = findLoadedClass(name); + + if (clazz == null) { + clazz = loadFromFile(name); + + if (clazz == null) { + if (mSuperLoader != null) { + clazz = mSuperLoader.loadClass(name); + } + else { + clazz = findSystemClass(name); + } + + if (clazz == null) { + throw new ClassNotFoundException(name); + } + } + } + } + } + + if (resolve) { + resolveClass(clazz); + } + + return clazz; + } + + protected void define(String name, byte[] data) { + defineClass(name, data, 0, data.length); + if (mGZippedBytecode != null) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + GZIPOutputStream gz = new GZIPOutputStream(baos); + gz.write(data,0,data.length); + gz.close(); + mGZippedBytecode.put(name.replace('.','/') + ".class", + baos.toByteArray()); + } + catch (IOException ioe) { + ioe.printStackTrace(); + } + } + } + + private Class loadFromFile(String name) throws ClassNotFoundException { + if (mRootClassDirs == null) { + return null; + } + + String fileName = name; + + if (mRootPackage != null) { + if (fileName.startsWith(mRootPackage)) { + fileName = fileName.substring(mRootPackage.length()); + } + else { + return null; + } + } + + fileName = fileName.replace('.', File.separatorChar); + ClassNotFoundException error = null; + + for (int i=0; i<mRootClassDirs.length; i++) { + File file = new File(mRootClassDirs[i], fileName + ".class"); + + if (file.exists()) { + try { + byte[] buffer = new byte[(int)file.length()]; + int avail = buffer.length; + int offset = 0; + InputStream in = new FileInputStream(file); + + int len = -1; + while ( (len = in.read(buffer, offset, avail)) > 0 ) { + offset += len; + + if ( (avail -= len) <= 0 ) { + avail = buffer.length; + byte[] newBuffer = new byte[avail * 2]; + System.arraycopy(buffer, 0, newBuffer, 0, avail); + buffer = newBuffer; + } + } + + in.close(); + + return defineClass(name, buffer, 0, offset); + } + catch (IOException e) { + if (error == null) { + error = new ClassNotFoundException + (fileName + ": " + e.toString()); + } + } + } + } + + if (error != null) { + throw error; + } + else { + return null; + } + } + + private class Stream extends ByteArrayOutputStream { + private String mName; + + public Stream(String name) { + super(1024); + mName = name; + } + + public void close() { + synchronized (mDefined) { + if (mDefined.get(mName) == null) { + define(mName, toByteArray()); + mDefined.put(mName, mName); + } + } + } + } + + private class URLFaker extends URLStreamHandler { + + protected URLConnection openConnection(URL u) throws IOException { + return new ClassInjector.ResourceConnection(u); + } + } + + private class ResourceConnection extends URLConnection { + + String resourceName; + public ResourceConnection(URL u) { + super(u); + resourceName = u.getFile(); + } + + // not really needed here but it was abstract. + public void connect() {} + + public InputStream getInputStream() throws IOException { + + try { + if (mGZippedBytecode != null) { + + if (mGZippedBytecode.get(resourceName) != null) { + return new GZIPInputStream(new ByteArrayInputStream + ((byte[])mGZippedBytecode.get(resourceName))); + } + else { + System.out.println(resourceName + " not found in bytecode map."); + } + } + else { + System.out.println("no bytecode map configured in "+ ClassInjector.this); + } + } + catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + } +}