/*
* 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.reflect;
import java.io.IOException;
import java.io.OutputStream;
import java.io.ByteArrayInputStream;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import java.util.Map;
import org.apache.log4j.Category;
import alt.jiapi.InstrumentationContext;
import alt.jiapi.JiapiException;
import alt.jiapi.Runtime;
import alt.jiapi.file.Attribute;
import alt.jiapi.file.ClassFile;
import alt.jiapi.file.ConstantPool;
import alt.jiapi.file.Field;
import alt.jiapi.file.Interface;
import alt.jiapi.file.Method;
/**
* This class represents a bytecode of a Class.
* It contains methods to add and remove <code>JiapiMethod</code>s and
* <code>JiapiField</code>s.
* <p>
* JiapiClass is an abstract class. Concrete classes are loaded
* using Loader.
* <blockquote>
* <code>
* Loader loader = new Loader();
* JiapiClass clazz = loader.loadClass(className);
* </code>
* </blockquote>
*
* @author Mika Riekkinen
* @author Joni Suominen
* @version $Revision: 1.30 $ $Date: 2005/05/24 12:10:26 $
* @see Loader
*/
public class JiapiClass {
// Used in Collection.toArray(fieldArrayTemplate)
private static final JiapiField[] fieldArrayTemplate = new JiapiField[0];
private static final JiapiMethod[] methodArrayTemplate =new JiapiMethod[0];
private static Category log = Runtime.getLogCategory(JiapiClass.class);
private Loader loader; // Loader that loaded this JiapiClass
private ClassBuilder builder;
private ClassFile clazz;
private List jiapiMethods;
private List jiapiFields;
// Used for testing
public static void main(String[] args) throws Exception {
Loader l = new Loader();
JiapiClass jc = l.loadClass(args[0]);
System.out.println(jc);
}
/**
* Create empty JiapiClass.
* Created JiapiClass has all internal datastructures initialized so,
* that when dumped to byte[], it is a valid java class file.
* It has not methods, fields etc.
*
* @param name Name of the JiapiClass to create
* @return an instance of JiapiClass
*/
public static JiapiClass createClass(String name) {
ClassFile cf = new ClassFile(name);
return new JiapiClass(cf);
}
/**
* Parses byte[] for a class definition.
*/
public static JiapiClass parseClass(byte[] bytes) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ClassFile cf = ClassFile.parse(bais);
return new JiapiClass(cf);
}
private JiapiClass() {
}
JiapiClass(ClassFile classFile) {
this.clazz = classFile;
this.builder = new ClassBuilder(classFile);
this.loader = new Loader();
jiapiMethods = new LinkedList();
jiapiFields = new LinkedList();
List methods = clazz.getMethods();
Iterator i = methods.iterator();
while(i.hasNext()) {
Method m = (Method)i.next();
JiapiMethod jm = new JiapiMethod(m);
jm.setDeclaringClass(this);
jiapiMethods.add(jm);
}
List fields = clazz.getFields();
i = fields.iterator();
while(i.hasNext()) {
Field f = (Field)i.next();
JiapiField jf = new JiapiField(f);
jf.setDeclaringClass(this);
jiapiFields.add(jf);
}
}
/**
* Dumps bytecodes of this JiapiClass to OutputStream given.
* @param out OutputStream to use
*/
public void dump(OutputStream out) throws IOException {
out.write(getByteCode());
}
/**
* Gets the Loader, that loaded this JiapiClass.
*
* @return Loader
*/
public Loader getLoader() {
return loader;
}
/**
* Get name of a class.
*
* @return name of a class
*/
public String getName() {
return clazz.getClassName();
}
/**
* Get the package where this class is defined.
*
* @return a package name of a class, or null if this class does
* not belong to any package.
*/
public String getPackageName() {
String cName = getName();
int idx = cName.lastIndexOf('.'); // NOTE: InnerClasses ???
if (idx == -1) {
return null;
}
return cName.substring(0, idx);
}
/**
* Get the byte-code of this class.
*
* @return an array of bytecodes
*/
public byte[] getByteCode() {
// Loop methods for changed bytecode
JiapiMethod[] methods = getDeclaredMethods();
for(int i = 0; i < methods.length; i++) {
methods[i].update();
}
return clazz.toBytes();
}
/**
* Get all the methods of this JiapiClass and its superclasses.
*
* @return an array of methods in this class
* @see #getDeclaredMethods() getDeclaredMethods() for further info
* @exception ClassNotFoundException is thrown if superclass could
* not be loaded
*/
public JiapiMethod[] getMethods() throws ClassNotFoundException {
ArrayList al = new ArrayList();
JiapiClass jc = this;
do {
al.addAll(jc.jiapiMethods);
}
while((jc = jc.getSuperclass()) != null);
return (JiapiMethod[])al.toArray(methodArrayTemplate);
}
/**
* Get all the methods of this JiapiClass. If this JiapiClass
* has no methods, an array of length 0 is returned. A new array is created
* each time this method is called, so adding and removing items from array
* does not reflect such changes back into JiapiClass.
*
* @return an array of methods in this class
*/
public JiapiMethod[] getDeclaredMethods() {
ArrayList al = new ArrayList();
al.addAll(jiapiMethods);
return (JiapiMethod[])al.toArray(methodArrayTemplate);
}
/**
* Get a specific method from a class. Parameter type names are given
* in simple form. This would be the same as used when programming java
* code. Some examples of simple form: <code>java.lang.Object</code>,
* <code>int</code>, <code>int[]</code>, <code>my.stuff.Thing[]</code><p>
* Example:
* <pre>
* JiapiClass clazz = ...;
* String[] paramTypeNames = new String[] {"java.lang.Object[]", "int"};
* JiapiMethod m = clazz.getMethod("foo", paramTypeNames);
* </pre>
* Would return a method <code>foo(Object[], int)</code>
*
* @param name Name of the method
* @param parameterTypeNames Type names of the parameters
* @return a method which matches the given name and parameter types
* @exception NoSuchMethodException if matching method is not found
*/
public JiapiMethod getDeclaredMethod(String name,
String[] parameterTypeNames) throws NoSuchMethodException {
Signature otherSignature = new Signature("void", parameterTypeNames);
Iterator i = jiapiMethods.iterator();
while (i.hasNext()) {
JiapiMethod jm = (JiapiMethod)i.next();
if (jm.getName().equals(name)) {
if (jm.getSignature().equals(otherSignature)) {
return jm;
}
}
}
throw new NoSuchMethodException(name + otherSignature.toString());
}
/**
* Get a specific method from a class.
*
* @param parameterTypes An array of JiapiClass, where each element
* represents a parameter.
* @return a method which matches the given name and parameter types
* @exception NoSuchMethodException if matching method is not found
*/
public JiapiMethod getDeclaredMethod(String name,
JiapiClass[] parameterTypes)
throws NoSuchMethodException {
String[] parameterTypeNames = null;
if (parameterTypes != null) {
parameterTypeNames = new String[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
parameterTypeNames[i] = parameterTypes[i].getName();
}
}
return getDeclaredMethod(name, parameterTypeNames);
}
/**
* Get a specific method from a class or any of its superclasses.
* Parameter type names are given in simple form. This would be the
* same as used when programming java
* code. Some examples of simple form: <code>java.lang.Object</code>,
* <code>int</code>, <code>int[]</code>, <code>my.stuff.Thing[]</code><p>
* Example:
* <pre>
* JiapiClass clazz = ...;
* String[] paramTypeNames = new String[] {"java.lang.Object[]", "int"};
* JiapiMethod m = clazz.getMethod("foo", paramTypeNames);
* </pre>
* Would return a method <code>foo(Object[], int)</code>
*
* @param name Name of the method
* @param parameterTypeNames Type names of the parameters
* @return a method which matches the given name and parameter types
* @exception NoSuchMethodException if matching method is not found
* @exception ClassNotFoundException is thrown if superclass could
* not be loaded
*/
public JiapiMethod getMethod(String name, String[] parameterTypeNames)
throws NoSuchMethodException, ClassNotFoundException {
JiapiClass jc = this;
JiapiMethod method = null;
do {
try {
method = jc.getDeclaredMethod(name, parameterTypeNames);
return method;
} catch (NoSuchMethodException nsme) {
// Don't mind.
}
}
while ((jc = jc.getSuperclass()) != null);
throw new NoSuchMethodException(name);
}
/**
* Get a specific method from a class or any of its superclasses.
*
* @param parameterTypes An array of JiapiClass, where each element
* represents a parameter.
* @return a method which matches the given name and parameter types
* @exception NoSuchMethodException if matching method is not found
* @exception ClassNotFoundException is thrown if superclass could
* not be loaded
*/
public JiapiMethod getMethod(String name, JiapiClass[] parameterTypes)
throws NoSuchMethodException, ClassNotFoundException {
JiapiClass jc = this;
JiapiMethod method = null;
do {
try {
method = jc.getDeclaredMethod(name, parameterTypes);
return method;
} catch (NoSuchMethodException nsme) {
// Don't mind.
}
}
while ((jc = jc.getSuperclass()) != null);
throw new NoSuchMethodException(name);
}
/**
* Get all the Fields of this JiapiClass and its superclasses.
* All of the fields in this class and superclasses are returned.
*
* @return an array of fields in this class and its superclasses
* @see #getDeclaredFields getDeclaredFields() for further info
* @exception ClassNotFoundException is thrown if superclass could
* not be loaded
*/
public JiapiField[] getFields() throws ClassNotFoundException {
ArrayList al = new ArrayList();
JiapiClass jc = this;
do {
al.addAll(jc.jiapiFields);
}
while ((jc = jc.getSuperclass()) != null);
return (JiapiField[])al.toArray(fieldArrayTemplate);
}
/**
* Get all the fields declared by this JiapiClass. If this JiapiClass
* has no fields, an array of length 0 is returned. A new array is created
* each time this method is called, so adding and removing items from array
* does not reflect such changes back into JiapiClass.
*
* @return an array of fields in this class
*/
public JiapiField[] getDeclaredFields() {
ArrayList al = new ArrayList();
al.addAll(jiapiFields);
return (JiapiField[])al.toArray(fieldArrayTemplate);
}
/**
* Get a declared field of a class.
*
* @param name a name of a field
* @return a field with a given name
* @exception NoSuchFieldException if matching field is not found
*/
public JiapiField getDeclaredField(String name) throws NoSuchFieldException {
Iterator i = jiapiFields.iterator();
while(i.hasNext()) {
JiapiField f = (JiapiField)i.next();
if (f.getName().equals(name)) {
return f;
}
}
throw new NoSuchFieldException(name);
}
/**
* Get a field from a class or from any of its superclasses.
*
* @param name a name of a field
* @return a field with a given name
* @exception NoSuchFieldException if matching field is not found
* @exception ClassNotFoundException is thrown if superclass could
* not be loaded
*/
public JiapiField getField(String name) throws NoSuchFieldException, ClassNotFoundException {
JiapiClass jc = this;
JiapiField field = null;
do {
try {
field = jc.getDeclaredField(name);
return field;
} catch (NoSuchFieldException nsfe) {
// Don't mind.
}
}
while ((jc = jc.getSuperclass()) != null);
throw new NoSuchFieldException(name);
}
/**
* Gets the super class of this JiapiClass. If this JiapiClass
* represents java.lang.Object, a null is returned.
*
* @return super class of the class represented by this JiapiClass
* @exception ClassNotFoundException is thrown, if superclass could not
* be loaded
*/
public JiapiClass getSuperclass() throws ClassNotFoundException {
String scName = clazz.getSuperclassName();
JiapiClass superClass = null;
if (scName != null) {
try {
superClass = loader.loadClass(scName);
}
catch(IOException ioe) {
log.error("", ioe);
}
}
return superClass;
}
/**
* Get modifiers of this JiapiClass.
*
* @return access modifiers of a class
* @see java.lang.reflect.Modifier
*/
public int getModifiers() {
return clazz.getAccessFlags();
}
/**
* Adds a named Field to this JiapiClass. Field will be <code>
* public</code> and of type <code>java.lang.Object</code>
*
* @param name Name of the field to be added
* @return JiapiField which was added
* @exception FieldExistsException if a field with a same name already
* exists
*/
public JiapiField addField(String name) throws FieldExistsException {
return addField((short)Modifier.PUBLIC, "java.lang.Object", name);
}
/**
* Adds a new Field to this JiapiClass.
*
* @param modifiers Modifiers of the field
* @param type Type of the field to be added. Type is given in
* simple form, like 'java.lang.Object' or 'int'
* @param name Name of the field to be added
* @return JiapiField which was added
* @exception FieldExistsException if a field with a same name already
* exists
*/
public JiapiField addField(int modifiers, String type, String name) throws FieldExistsException {
JiapiField jf = builder.addField((short)modifiers, type, name);
jf.setDeclaringClass(this);
jiapiFields.add(jf);
return jf;
}
/**
* Gets all the interfaces, that this class directly implements.
*
* @return names of the implemented interfaces
*/
public String[] getInterfaceNames() {
ConstantPool cp = clazz.getConstantPool();
List l = clazz.getInterfaces();
String[] iNames = new String[l.size()];
for (int i = 0; i < iNames.length; i++) {
Interface iFace = (Interface)l.get(i);
iNames[i] = iFace.getName();
}
return iNames;
}
/**
* Gets all the interfaces, that this class directly implements.
*
* @return names of the implemented interfaces
*/
public JiapiClass[] getInterfaceTypes() throws ClassNotFoundException {
String[] iNames = getInterfaceNames();
JiapiClass[] interfaces = new JiapiClass[iNames.length];
for (int i = 0; i < interfaces.length; i++) {
try {
interfaces[i] = getLoader().loadClass(iNames[i]);
}
catch(java.io.IOException ioe) {
throw new ClassNotFoundException(iNames[i]);
}
}
return interfaces;
}
/**
* Adds an interface for a class.
* If current class is an interface, it extends the added interface.
* Otherwise it implements it.
*
* @param interfaceType the type of an interface
*/
public void addInterface(String interfaceType) {
clazz.addInterface(interfaceType);
}
/**
* Adds an interface for a class.
* If current class is an interface, it extends the added interface.
* Otherwise it implements it.
*
* @param interfaceType the type of an interface
* @exception IllegalArgumentException is thrown, if interfaceType is
* not an interface.
*/
public void addInterface(JiapiClass interfaceType) throws IllegalArgumentException {
if (!interfaceType.isInterface()) {
throw new IllegalArgumentException("Not an interface: " +
interfaceType.getName());
}
addInterface(interfaceType.getName());
}
/**
* Adds a new method to this class. The signature is derived
* from given methods signature.
*
* @param m JiapiMethod to be used as method signature
* @return JiapiMethod which was added
* @exception MethodExistsException if a method with a same name and
* parameter signature already exists
*/
public JiapiMethod addMethod(JiapiMethod m) throws MethodExistsException {
return addMethod((int)m.getModifiers(), m.getName(), m.getSignature());
}
/**
* Adds a new method to this class. Method, that is added, do not
* contain any bytecode. It is the responsibility of the developer
* to provide later some meaningful method body.
*
* @param modifiers access modifiers of a class
* @param methodName name of a method
* @param signature signature of a method
* @return JiapiMethod which was added
* @exception MethodExistsException if a method with a same name and
* parameter signature already exists
* @see java.lang.reflect.Modifier
*/
public JiapiMethod addMethod(int modifiers, String methodName,
Signature signature) throws MethodExistsException {
JiapiMethod jm = null;
// Check, that the method don't exist
Iterator i = jiapiMethods.iterator();
while(i.hasNext()) {
JiapiMethod method = (JiapiMethod)i.next();
if (method.getName().equals(methodName) &&
method.getSignature().getDescriptor().equals(signature.getDescriptor())) {
throw new MethodExistsException(method);
}
}
try {
jm = builder.addMethod((short)modifiers, methodName, signature);
}
catch(MethodExistsException mee) {
jm = mee.getMethod();
jm.setDeclaringClass(this);
throw mee;
}
jm.setDeclaringClass(this);
jiapiMethods.add(jm);
return jm;
}
/**
* Tests, whether this JiapiClass is an interface or not.
*
* @return true, if this JiapiClass represents an interface.
*/
public boolean isInterface() {
return Modifier.isInterface(getModifiers());
}
public String toString() {
StringBuffer sb = new StringBuffer();
// clear ACC_SUPER(0x0020 -> 0xffdf), otherwise 'synchronized' would
// be returned by java.lang.reflect.Modifier
sb.append(Modifier.toString(getModifiers() & 0xffdf));
sb.append(" JiapiClass ");
sb.append(getName());
String[] interfaces = getInterfaceNames();
if (interfaces.length > 0) {
sb.append(" implements ");
for (int i = 0; i < interfaces.length; i++) {
sb.append(interfaces[i]);
if (i < interfaces.length - 1) {
sb.append(", ");
}
}
}
sb.append("\nFields:");
JiapiField[] fields = getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
sb.append("\n ");
sb.append(fields[i].toString());
}
sb.append("\nMethods:");
JiapiMethod[] methods = getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
sb.append("\n ");
sb.append(methods[i].toString());
// sb.append('\n');
// sb.append(methods[i].getInstructionList());
}
return sb.toString();
}
// Package access -----------------
void setLoader(Loader l) {
this.loader = l;
}
// Made public for alt.jiapi.tool.Javap
// public ClassFile getClassFile() {
// return clazz;
// }
/**
* Get the ConstantPool related to this JiapiClass.
*
* @return ConstantPool of this JiapiClass
*/
public ConstantPool getConstantPool() {
return clazz.getConstantPool();
}
}