/*
* Copyright (c) 2004, Marco Petris
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - 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.
* - Neither the name of Marco Petris nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*/
package de.petris.dynamicaspects.util;
import java.io.ByteArrayInputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.generic.Type;
import de.petris.dynamicaspects.AspectException;
/**
* This class provides additional reflection utilities, because of the limited
* possibilities of the java reflection api.
*
* @author Marco Petris
*/
public class Reflection {
/**
* Returns the modifiers of the method defined by the given arguments. The modifiers
* are returned as a or-connected value, e. g. PUBLIC | FINAL
*
* @param className the name of the class containing the method
* @param methodName the name of the method
* @param methodSignature the signature of the method in java-internal format
* @return the or-connected modifier value
* @see java.lang.reflect.Modifier
*/
public static int getModifiers(
String className, String methodName, String methodSignature ) {
try {
Class targetClass = classForName( className );
// get the classnames of the arguments
String argClassNames[] =
Utility.methodSignatureArgumentTypes( methodSignature, false );
// get the classes of the arguments
Class[] argClasses = new Class[argClassNames.length];
for(
int classNameIdx= 0;
classNameIdx<argClassNames.length;
classNameIdx++ ) {
argClasses[classNameIdx] =
classForName( argClassNames[classNameIdx] );
}
// look up the constructor or method in the class or superclasses
if( methodName.equals( Constants.CONSTRUCTOR_NAME ) ) {
return lookUpConstructor(
targetClass, argClasses ).getModifiers();
}
else {
return lookUpMethod(
targetClass,
methodName,
argClasses ).getModifiers();
}
}
catch( Exception exc ) {
throw new AspectException(
"method getModifiers in "
+ Reflection.class.getName()
+ " caused an exception", exc );
}
}
/**
* Looks up the constructor defined by the given parameter types in the given
* class or superclass. This is a recursive search up the inheritance line.
*
* @param target the class containing the constructor
* @param paramTypes the parameter types of the constructor
* @return the constructor instance
*/
private static Constructor lookUpConstructor(
Class target, Class... paramTypes ) {
try {
return target.getDeclaredConstructor( paramTypes );
}
catch( NoSuchMethodException nme ) {
// the target class does not declare the method
// so we look up the method in its super class
return lookUpConstructor( target.getSuperclass(), paramTypes );
}
}
/**
* Looks up the method defined by the given methodname and the given argument types
* in the given class or superclass. This is a recursive search up the inheritance line.
*
* @param target the class containing the method
* @param methodName the method name
* @param paramTypes the argument types of the method
* @return the method instance
*/
private static Method lookUpMethod(
Class target, String methodName, Class... paramTypes ) {
try {
return target.getDeclaredMethod(
methodName,
paramTypes );
}
catch( NoSuchMethodException nme ) {
return lookUpMethod(
target.getSuperclass(), methodName, paramTypes );
}
}
/**
* Returns a string representation of the given modifiers.
* The string representation has a defined position for each modifier, i. e. :
* getModifierString( Modifier.PUBLIC | Modifier.FINAL )
* <=> public final <=>
* getModifierString( Modifier.FINAL | Modifier.PUBLIC )
*
* @param modifiers the or-connected modifiers
* @return the string representation of the modifiers
* @see java.lang.reflect.Modifier
*/
public static String getModifierString( int modifiers ) {
return Modifier.toString( modifiers);
}
/**
* Returns a string representation of the declaration of the method defined
* by the arguments.
* Example: <br>
* The following arguments <br>
* Classname: de.petris.dynamicaspects.test.targets.SimpleTarget <br>
* Methodname: doIt <br>
* MethodSignature: (DLjava/util/ArrayList;)J <br>
* become: <br>
* <code>
* private long de.petris.dynamicaspects.test.targets.SimpleTarget.doIt(double,java.util.ArrayList)
* </code><br>
*
* @param className the name of the class containing the method
* @param methodName the name of the method
* @param methodSignature the signature of the method
* @return a string representation of the declaration of the method
*/
public static String getMethodDeclaration(
String className, String methodName,
String methodSignature ) {
return getMethodDeclaration(
getModifiers( className, methodName, methodSignature ),
className, methodName, methodSignature, false );
}
/**
* Returns a string representation of the declaration of the method defined
* by the arguments.
* Example: <br>
* The following arguments <br>
* Classname: de.petris.dynamicaspects.test.targets.SimpleTarget <br>
* Methodname: doIt <br>
* MethodSignature: (DLjava/util/ArrayList;)J <br>
* become: <br>
* <code>
* private long de.petris.dynamicaspects.test.targets.SimpleTarget.doIt(double,java.util.ArrayList)
* </code><br>
* @param className the name of the class containing the method
* @param methodName the name of the method
* @param methodSignature the signature of the method
* @param chopClassName if true the classname is ommitted
* @return a string representation of the declaration of the method
*/
public static String getMethodDeclaration(
String className, String methodName,
String methodSignature, boolean chopClassName ) {
return getMethodDeclaration(
getModifiers( className, methodName, methodSignature ),
className, methodName, methodSignature, chopClassName );
}
/**
* Returns a string representation of the declaration of the method defined
* by the arguments.
* Example: <br>
* The following arguments <br>
* Modifiers: Modifier.PRIVATE <br>
* Classname: de.petris.dynamicaspects.test.targets.SimpleTarget <br>
* Methodname: doIt <br>
* MethodSignature: (DLjava/util/ArrayList;)J <br>
* become: <br>
*
* private long de.petris.dynamicaspects.test.targets.SimpleTarget.doIt(double,java.util.ArrayList)<br>
*
* @param modifiers the modifiers of the method
* @param className the name of the class containing the method
* @param methodName the name of the method
* @param methodSignature the signature of the method
* @param chopClassName if true the classname is ommitted
* @return a string representation of the declaration of the method
*/
public static String getMethodDeclaration(
int modifiers, String className, String methodName,
String methodSignature, boolean chopClassName ) {
// get the modifiers string
StringBuffer buffer =
new StringBuffer( getModifierString( modifiers ) );
String conc = "";
// handle return type
buffer.append( " " );
Type returnType = Type.getReturnType( methodSignature );
buffer.append( returnType.toString() );
buffer.append( " " );
// handle class name
if( !chopClassName ) {
buffer.append( className );
buffer.append( "." );
}
// handle method name
buffer.append( methodName );
// handle arguments
buffer.append( "(" );
for( Type arg : Type.getArgumentTypes( methodSignature ) ) {
buffer.append( conc );
buffer.append( arg );
conc = ",";
}
buffer.append( ")" );
return buffer.toString();
}
/**
* Return the dimensions of a class.
* For example boolean[][] returns 2.
*
* @param className the name of the class
* @return the dimension
*/
private static int getDimensions( String className ) {
int dimCounter = 0;
int index = -1;
while( (index = className.indexOf('[', index+1 )) != -1 ) {
dimCounter++;
}
return dimCounter;
}
/**
* A classForName which handles all classes including primitive types and arrays.
*
* @param className the name of the class.
* @return the instance of the desired class
*/
public static Class classForName( String className ) {
try {
if( className.contains( "[" ) ) {
return Array.newInstance(
classForName(
className.substring(0, className.indexOf('[') ) ),
new int[getDimensions(className)] ).getClass();
}
else if( className.equals("boolean") ) {
return boolean.class;
}
else if( className.equals("int") ) {
return int.class;
}
else if( className.equals("short") ) {
return short.class;
}
else if( className.equals("byte") ) {
return byte.class;
}
else if( className.equals("long") ) {
return long.class;
}
else if( className.equals("double") ) {
return double.class;
}
else if( className.equals("float") ) {
return float.class;
}
else if( className.equals("char") ) {
return char.class;
}
else {
return Class.forName( className );
}
}
catch( Exception exc ) {
throw new AspectException(
"method classForName in "
+ Reflection.class.getName()
+ " caused an exception", exc );
}
}
/**
* Returns a string representation of the given list.
*
* @param l the list
* @return the string representation
*/
public static String toString( List<Object> l ) {
StringBuffer buffer = new StringBuffer( "[" );
String conc = "";
Iterator<Object> iter = l.iterator();
while( iter.hasNext() ) {
buffer.append( conc );
buffer.append( iter.next() );
conc = ";";
}
buffer.append( "]" );
return buffer.toString();
}
/**
* Tests if the given type is a category 2 type ( see JVM Specification ).
* long and double return true, all other types return false
*
* @param t the type to test
* @return true for long and double, else false
*/
public static boolean isCat2Type( Type t ) {
return ( ( t.equals( Type.LONG ) )
|| ( t.equals( Type.DOUBLE ) ) );
}
/**
* Returns an instance of the inner class specified by the given
* enclosing class and the name of the inner class.
*
* @param innerClassName the name of the inner class
* @param declaringClass the delcaring or enclosing class
* @return an instance of the desired inner class
*/
public static Class getInnerClassOf(
String innerClassName, Class declaringClass ) {
for( Class innerClass : declaringClass.getDeclaredClasses() ) {
if( innerClass.getName().substring(
innerClass.getName().indexOf('$')+1 ).equals(
innerClassName ) ) {
return innerClass;
}
}
throw new AspectException(
new ClassNotFoundException( innerClassName ) );
}
/**
* Returns a string representation of a class which can be used in regular expressions.
* boolean[] becomes boolean\\[]
*
* @param className the name of the class
* @return the new string representation for regular exp.
*/
public static String classNameToPatternString( String className ) {
StringBuffer buf = new StringBuffer( className );
int index = -3;
while( (index = buf.indexOf("[", index+3 )) != -1 ) {
buf.insert( index, "\\" );
}
return buf.toString();
}
/**
* Returns the JavaClass for a given byte representation of a class and its given classname.
* @param classfileBuffer the byte representation of the class
* @param className the name of the class
* @return the extracted JavaClass object
*/
public static JavaClass classForByteArray(
byte[] classfileBuffer, String className ) {
ClassParser cp =
new ClassParser(
new ByteArrayInputStream(classfileBuffer),
className );
try {
return cp.parse();
}
catch( Throwable t ) {
throw new AspectException(
"tried to load class "
+ className + " from a byte array ",
t );
}
}
}