ClassInfo.java
/*
** Module : ClassInfo.java
** Abstract : Container for class definitions.
**
** Copyright (c) 2012-2017, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------Description-------------------------------
** 001 SVL 20120108 Created the initial version.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.classloader;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.*;
import org.apache.bcel.generic.Type;
import java.lang.reflect.*;
import java.util.*;
/**
* Container for class definitions. Can store definitions obtained using
* either BCEL or class loaders. Created in order to support ability to read
* the definitions of the target class and its superclasses using BCEL
* (class loaders provide the complete class definitions, see
* {@link java.lang.ClassLoader#loadClass(String)} for example, but with BCEL
* you have to manually load superclasses).<br>
* Consider we have the following class and interface hierarchy:<pre>
* TargetClass
* | impl. | impl. | ext.
* Iface1 Iface2 SuperClass1
* | ext. |ext. | impl. | impl.
* Iface3 | Iface4 Iface5
* |ext. | | ext.
* | | | loaded with BCEL
* ----------- | | | ------------------------
* | | | loaded with class loader
* Iface6 SuperClass2 Iface7
* |ext. |impl. |ext.
* Iface8 Iface9 SuperClass3 </pre><br>
*
* These classes and interfaces will be divided between internal
* <tt>ClassInfo</tt> structures:<br>
* <tt>superclassChain</tt>: <tt>TargetClass</tt>, <tt>SuperClass1</tt><br>
* <tt>rootSuperclass</tt>: <tt>SuperClass2</tt><br>
* <tt>bcelInterfaces</tt>: <tt>Iface1</tt>, <tt>Iface2</tt>, <tt>Iface3</tt>,
* <tt>Iface4</tt>, <tt>Iface5</tt><br>
* <tt>loaderIterfaces</tt>: <tt>Iface6</tt>, <tt>Iface7</tt><br>
* <tt>Iface8</tt>, <tt>Iface9</tt> and <tt>SuperClass3</tt> aren't stored
* can be access through their parent classes/interfaces.
*/
public class ClassInfo
{
/**
* The list of superclasses. If not <code>null</code> then the zero element
* contains definitions of the target class. If <code>null</code> then the
* target class is stored into <code>rootSuperclass</code> variable.
*/
private List<JavaClass> superclassChain = null;
/**
* The root superclass. May be <code>null</code> if all superclasses were
* loaded using BCEL.
*/
private Class rootSuperclass = null;
/**
* The set of names of the interfaces and their super interfaces which are
* implemented by the target class and were loaded using BCEL.
*/
private Set<String> bcelInterfaces = null;
/**
* The set of the interfaces which are implemented by the target class and
* were loaded using class loader.
*/
private Set<Class> loaderIterfaces = null;
/**
* Creates the class definitions container for the target class.
*
* @param targetClass
* <tt>Class</tt> object which represents the target class.
*/
public ClassInfo(Class targetClass)
{
rootSuperclass = targetClass;
}
/**
* Creates the class definitions container for the target class.
*
* @param targetClass
* <tt>JavaClass</tt> object which represents the target class.
*/
public ClassInfo(JavaClass targetClass)
{
addSuperclass(targetClass);
}
/**
* Compares names, return types and argument types of two methods.
*
* @param method1
* First method to compare. Can be
* <tt>java.lang.reflect.Method</tt> or
* <tt>org.apache.bcel.classfile.Method</tt>.
* @param method2
* Second method to compare. Can be
* <tt>java.lang.reflect.Method</tt> or
* <tt>org.apache.bcel.classfile.Method</tt>.
*
* @return <code>true</code> if methods match.
*/
public static boolean compareMethods(Object method1,
Object method2)
{
boolean bcel1 = method1 instanceof Method;
boolean bcel2 = method2 instanceof Method;
if (bcel1 && bcel2)
return compareMethods((Method) method1, (Method) method2);
else if (!bcel1 && !bcel2)
return compareMethods((java.lang.reflect.Method) method1,
(java.lang.reflect.Method) method2);
else if (!bcel1 && bcel2)
return compareMethods((Method) method2,
(java.lang.reflect.Method) method1);
else
return compareMethods((Method) method1,
(java.lang.reflect.Method) method2);
}
/**
* Determines whether the specified method is abstract.
*
* @param method
* Method to me checked. Can be <tt>java.lang.reflect.Method</tt>
* or <tt>org.apache.bcel.classfile.Method</tt>.
*
* @return <code>true</code> if the method is abstract.
*/
public static boolean isAbstractMethod(Object method)
{
return method instanceof Method ?
((Method) method).isAbstract() :
Modifier.isAbstract(
((java.lang.reflect.Method) method).getModifiers());
}
/**
* Set the root superclass.
*
* @param rootSuperclass
* Root superclass to be set.
*/
public void setRootSuperclass(Class rootSuperclass)
{
this.rootSuperclass = rootSuperclass;
}
/**
* Add definitions of a superclass.
*
* @param superclass
* Superclass definitions to be added.
*/
public void addSuperclass(JavaClass superclass)
{
if (superclassChain == null)
superclassChain = new ArrayList<JavaClass>();
superclassChain.add(superclass);
}
/**
* Determines whether the class implements the target interface.
*
* @param iface
* Interface to be checked.
*
* @return <code>true</code> if the class implements the target interface.
*/
public boolean isImplements(Class iface)
{
if (bcelInterfaces != null)
{
if (bcelInterfaces.contains(iface.getName()))
return true;
}
if (loaderIterfaces != null)
{
for (Class cls : loaderIterfaces)
{
if (iface.isAssignableFrom(cls))
return true;
}
}
return rootSuperclass != null && iface.isAssignableFrom(rootSuperclass);
}
/**
* Get the names of the interfaces which are directly implemented by the
* superclasses (including the target class) which were loaded using BCEL.
*
* @return See above.
*/
public String[] getFirstLevelBcelInterfaces()
{
if (superclassChain == null)
return new String[0];
Set<String> ifaces = new HashSet<String>();
for (JavaClass cls : superclassChain)
{
ifaces.addAll(Arrays.asList(cls.getInterfaceNames()));
}
return ifaces.toArray(new String[ifaces.size()]);
}
/**
* Determines whether the class "statically implements" the target
* interface, i.e. it has matching public static function for each function
* defined by the interface.
*
* @param iface
* Interface to be checked.
*
* @return <code>true</code> if the class "statically implements" the
* target interface.
*
* @throws NoClassDefFoundError
* If methods of the target interface cannot be resolved.
*/
public boolean isStaticallyImplements(Class iface)
throws NoClassDefFoundError
{
java.lang.reflect.Method[] ifaceMethods = iface.getMethods();
Object[] implMethods = getPublicStaticMethods();
outer:
for (java.lang.reflect.Method ifaceMethod : ifaceMethods)
{
for (Object implMethod : implMethods)
{
if (isAbstractMethod(implMethod))
continue; // skip abstract implementation methods
if (compareMethods(ifaceMethod, implMethod))
continue outer;
}
return false;
}
return true;
}
/**
* Get the class name.
*
* @return class name.
*/
public String getClassName()
{
if (superclassChain != null)
return superclassChain.get(0).getClassName();
return rootSuperclass.getName();
}
/**
* Determines whether the class is public.
*
* @return <code>true</code> if the class is public.
*/
public boolean isPublic()
{
return superclassChain != null ?
superclassChain.get(0).isPublic() :
Modifier.isPublic(rootSuperclass.getModifiers());
}
/**
* Determines whether this class object represents an interface type.
*
* @return <code>true</code> if this class object represents an interface
* type.
*/
public boolean isInterface()
{
return superclassChain != null ?
superclassChain.get(0).isInterface() :
rootSuperclass.isInterface();
}
/**
* Determines whether this class is abstract.
*
* @return <code>true</code> if this class is abstract.
*/
public boolean isAbstract()
{
return superclassChain != null ?
superclassChain.get(0).isAbstract() :
Modifier.isAbstract(rootSuperclass.getModifiers());
}
/**
* Get the list of all public methods.
*
* @return the list of all public methods, can be either
* <tt>java.lang.reflect.Method</tt> or
* <tt>org.apache.bcel.classfile.Method</tt> objects.
*/
public Object[] getPublicMethods()
{
List<Object> res = new ArrayList<Object>();
if (superclassChain != null)
{
for (JavaClass cls : superclassChain)
{
Method[] methods = cls.getMethods();
for (Method method : methods)
{
if (method.isPublic())
res.add(method);
}
}
}
if (rootSuperclass != null)
{
try
{
res.addAll(Arrays.asList(rootSuperclass.getMethods()));
}
catch (NoClassDefFoundError e)
{
// cannot load methods, just ignore it
}
}
return res.toArray(new Object[res.size()]);
}
/**
* Get the list of all public static methods.
*
* @return the list of all public static methods, can be either
* <tt>java.lang.reflect.Method</tt> or
* <tt>org.apache.bcel.classfile.Method</tt> objects.
*/
public Object[] getPublicStaticMethods()
{
List<Object> res = new ArrayList<Object>();
if (superclassChain != null)
{
for (JavaClass cls : superclassChain)
{
Method[] methods = cls.getMethods();
for (Method method : methods)
{
if (method.isPublic() && method.isStatic())
res.add(method);
}
}
}
if (rootSuperclass != null)
{
try
{
java.lang.reflect.Method[] methods = rootSuperclass.getMethods();
for (java.lang.reflect.Method method : methods)
{
if (Modifier.isStatic(method.getModifiers()))
res.add(method);
}
}
catch (NoClassDefFoundError e)
{
// cannot load methods, just ignore it
}
}
return res.toArray(new Object[res.size()]);
}
/**
* Add to the list of the interfaces implemented by this class the
* specified interfaces (loaded by class loader).
*
* @param iface
* Interface to be added
*/
public void addInterface(Class iface)
{
if (loaderIterfaces == null)
{
loaderIterfaces = new HashSet<Class>();
}
loaderIterfaces.add(iface);
}
/**
* Add to the list of the interfaces implemented by this class the
* specified interfaces (loaded by BCEL).
*
* @param iface
* The name of the interface to be added.
*/
public void addBcelInterface(String iface)
{
if (bcelInterfaces == null)
{
bcelInterfaces = new HashSet<String>();
}
bcelInterfaces.add(iface);
}
/**
* Get the string representation of the container.
*
* @return string representation of the container.
*/
public String toString()
{
return getClassName();
}
/**
* Compares names, return types and argument types of two methods.
*
* @param method1
* First method to compare.
* @param method2
* Second method to compare.
*
* @return <code>true</code> if methods match.
*/
private static boolean compareMethods(Method method1,
java.lang.reflect.Method method2)
{
if (!method1.getName().equals(method2.getName()))
return false;
Type ret1 = method1.getReturnType();
Type ret2 = Type.getType(method2.getReturnType());
if (!ret1.equals(ret2))
return false;
Type[] params1 = method1.getArgumentTypes();
Class[] params2 = method2.getParameterTypes();
if (params1.length != params2.length)
return false;
for (int i = 0; i < params1.length; i++)
{
if (!params1[i].equals(Type.getType(params2[i])))
return false;
}
return true;
}
/**
* Compares names, return types and argument types of two methods.
*
* @param method1
* First method to compare.
* @param method2
* Second method to compare.
*
* @return <code>true</code> if methods match.
*/
private static boolean compareMethods(java.lang.reflect.Method method1,
java.lang.reflect.Method method2)
{
if (!method1.getName().equals(method2.getName()))
return false;
Class ret1 = method1.getReturnType();
Class ret2 = method2.getReturnType();
if (!ret1.equals(ret2))
return false;
Class[] params1 = method1.getParameterTypes();
Class[] params2 = method2.getParameterTypes();
if (params1.length != params2.length)
return false;
for (int i = 0; i < params1.length; i++)
{
if (!params1[i].equals(params2[i]))
return false;
}
return true;
}
/**
* Compares names, return types and argument types of two methods.
*
* @param method1
* First method to compare.
* @param method2
* Second method to compare.
*
* @return <code>true</code> if methods match.
*/
private static boolean compareMethods(Method method1,
Method method2)
{
if (!method1.getName().equals(method2.getName()))
return false;
Type ret1 = method1.getReturnType();
Type ret2 = method2.getReturnType();
if (!ret1.equals(ret2))
return false;
Type[] params1 = method1.getArgumentTypes();
Type[] params2 = method2.getArgumentTypes();
if (params1.length != params2.length)
return false;
for (int i = 0; i < params1.length; i++)
{
if (!params1[i].equals(params2[i]))
return false;
}
return true;
}
}