/*
* Copyright 2001-2008 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
* $Id: ClassBytesLoader.java 3918 2008-04-14 17:35:35Z gbevin $
*/
package com.uwyn.rife.tools;
import com.uwyn.rife.tools.exceptions.FileUtilsErrorException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import sun.misc.Resource;
import sun.misc.URLClassPath;
/**
* Utility class to load the bytes of class files.
* <p>
* After instantiating it, the {@link #setupSunByteLoading} method can
* optionally be called to let the class detect if it's possible to
* interface with a private API that's specific to the Sun JVM. This interface
* can improve class byte loading performance by 40%.
*
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
* @version $Revision: 3918 $
* @since 1.6
*/
public class ClassBytesLoader
{
private ClassLoader mClassLoader = null;
private Object mSunByteLoading = null;
private Method mSunByteLoadingMethod = null;
/**
* Instantiates a new bytes loader for class files.
*
* @param classLoader the classloader that should be used to search for the
* classes
* @since 1.6
*/
public ClassBytesLoader(ClassLoader classLoader)
{
mClassLoader = classLoader;
}
/**
* Tries to setup an interaction with a private Sun JVM interface that can
* speed up class bytes loading by 40%, when available.
*
* @return {@code true} when the interaction with the private Sun JVM
* interface could be setup; or<p>
* {@code false} otherwise
*/
public boolean setupSunByteLoading()
{
// this is an ugly hack to be able to use Sun's implementation of URLClassLoader for
// speed increases if it's available, otherwise we'll fallback to a regular method of
// reading the class bytes
try
{
Field ucp_field = URLClassLoader.class.getDeclaredField("ucp");
ucp_field.setAccessible(true);
Object ucp = ucp_field.get(mClassLoader);
if (ucp.getClass().getName().equals("sun.misc.URLClassPath"))
{
Class urlclasspath_class = mClassLoader.loadClass("sun.misc.URLClassPath");
Class byteloading_class = mClassLoader.loadClass(getClass().getName()+"$SunByteLoading");
Constructor byteloading_constructor = byteloading_class.getDeclaredConstructor(urlclasspath_class);
byteloading_constructor.setAccessible(true);
Object byteloading = byteloading_constructor.newInstance(ucp);
Method byteloading_method = byteloading_class.getDeclaredMethod("getBytes", String.class);
byteloading_method.setAccessible(true);
mSunByteLoading = byteloading;
mSunByteLoadingMethod = byteloading_method;
}
}
catch (Throwable e)
{
mSunByteLoading = null;
mSunByteLoadingMethod = null;
}
return isUsingSunByteLoading();
}
/**
* Retrieves a byte array that contains the bytecode for a specific Java
* class.
*
* @param classFileName the file name of the class whose bytes should be
* loaded, note that this is not the Java FQN ({@code com.uwyn.rife.Version}),
* but the real name of the file resource ({@code com/uwyn/rife/Version.java})
* @return an array with the bytes of the class; or<p>
* {@code null} if no bytes could be loaded
* @throws FileUtilsErrorException if an error occurred during the loading
* of the class bytes
* @see #getClassBytes(String, URL)
* @since 1.6
*/
public byte[] getClassBytes(String classFileName) throws ClassNotFoundException
{
return getClassBytes(classFileName, null);
}
/**
* Retrieves a byte array that contains the bytecode for a specific Java
* class.
*
* @param classFileName the file name of the class whose bytes should be
* loaded, note that this is not the Java FQN ({@code com.uwyn.rife.Version}),
* but the real name of the file resource ({@code com/uwyn/rife/Version.java})
* @param classResource the resource that can be used to load the class
* bytes from if it couldn't be obtained by using the file name, if no
* resource is provided and the bytes couldn't be loaded by simply using
* the class' file name, a resource will be looked up for the file name
* through the class loader that was provided to the constructor
* @return an array with the bytes of the class; or<p>
* {@code null} if no bytes could be loaded
* @throws FileUtilsErrorException if an error occurred during the loading
* of the class bytes
* @see #getClassBytes(String)
* @since 1.6
*/
public byte[] getClassBytes(String classFileName, URL classResource) throws ClassNotFoundException
{
byte[] raw_bytes = null;
// this is a hack to be able to use Sun's implementation of URLClassLoader for
// speed increases if it's available, otherwise we'll fallback to a regular method of
// reading the class bytes
if (classFileName != null &&
isUsingSunByteLoading())
{
try
{
raw_bytes = (byte[])mSunByteLoadingMethod.invoke(mSunByteLoading, classFileName);
}
catch (IllegalAccessException e)
{
raw_bytes = null;
}
catch (IllegalArgumentException e)
{
raw_bytes = null;
}
catch (InvocationTargetException e)
{
raw_bytes = null;
}
}
// get the class bytes through a regular method that works on any JVM
if (null == raw_bytes)
{
if (null == classResource &&
classFileName != null)
{
classResource = mClassLoader.getResource(classFileName);
}
if (classResource != null)
{
try
{
raw_bytes = FileUtils.readBytes(classResource);
}
catch (FileUtilsErrorException e)
{
throw new ClassNotFoundException("Unexpected error while reading the bytes of the class resource '"+classResource+"'.", e);
}
}
}
return raw_bytes;
}
/**
* Indicates whether this class is using the private Sun JVM interface to
* speed up class bytes loading.
*
* @return {@code true} when this class is using the private Sun JVM
* interface; or<p>
* {@code false} if this is not the case
* @since 1.6
*/
public boolean isUsingSunByteLoading()
{
return mSunByteLoading != null;
}
@SuppressWarnings("unused")
private static class SunByteLoading
{
URLClassPath mUrlClassPath = null;
private SunByteLoading(URLClassPath ucp)
{
mUrlClassPath = ucp;
}
private byte[] getBytes(String path)
{
Resource resource = mUrlClassPath.getResource(path, false);
if (resource != null)
{
try
{
return resource.getBytes();
}
catch (IOException e)
{
return null;
}
}
else
{
return null;
}
}
}
}