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;
   }
}