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