All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opalj.br.ClassFile.scala Maven / Gradle / Ivy

The newest version!
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package br

import scala.annotation.tailrec
import org.opalj.log.OPALLogger
import org.opalj.collection.immutable.UShortPair
import org.opalj.bi.ACC_ABSTRACT
import org.opalj.bi.ACC_ANNOTATION
import org.opalj.bi.ACC_PRIVATE
import org.opalj.bi.ACC_ENUM
import org.opalj.bi.ACC_FINAL
import org.opalj.bi.ACC_INTERFACE
import org.opalj.bi.ACC_MODULE
import org.opalj.bi.ACC_PUBLIC
import org.opalj.bi.ACC_SUPER
import org.opalj.bi.AccessFlags
import org.opalj.bi.AccessFlagsContexts
import org.opalj.bi.AccessFlagsMatcher
import org.opalj.bi.VisibilityModifier
import org.opalj.collection.{binarySearch, insertedAt}

import scala.collection.immutable.ArraySeq

/**
 * Represents a single class file which either defines a class type or an interface type.
 * (`Annotation` types are also interface types and `Enum`s are class types.)
 *
 * @param   version A pair of unsigned short values identifying the class file version number.
 *          `UShortPair(minorVersion, majorVersion)`.
 * @param   accessFlags The access flags of this class. To further analyze the access flags
 *          either use the corresponding convenience methods (e.g., isEnumDeclaration())
 *          or the class [[org.opalj.bi.AccessFlagsIterator]] or the classes which
 *          inherit from [[org.opalj.bi.AccessFlag]].
 * @param   thisType The type implemented by this class file.
 * @param   superclassType The class type from which this class inherits. `None` if this
 *          class file defines `java.lang.Object` or a module.
 * @param   interfaceTypes The set of implemented interfaces. May be empty.
 * @param   fields The declared fields. May be empty. The list is sorted by name.
 * @param   methods The declared methods. May be empty. The list is first sorted by name,
 *          and then by method descriptor.
 * @param   attributes This class file's reified attributes. Which attributes
 *          are reified depends on the configuration of the class file reader; e.g.,
 *          [[org.opalj.br.reader.Java8Framework]].
 *          The JVM specification defines the following attributes:
 *           - ''InnerClasses''
 *           - ''EnclosingMethod''
 *           - ''Synthetic''
 *           - ''Signature''
 *           - ''SourceFile''
 *           - ''SourceDebugExtension''
 *           - ''Deprecated''
 *           - ''RuntimeVisibleAnnotations''
 *           - ''RuntimeInvisibleAnnotations''
 *          In case of Java 9 ([[org.opalj.br.reader.Java9Framework]]) the following
 *          attributes are added:
 *           - ''Module''
 *           - ''ModuleMainClass''
 *           - ''ModulePackages''
 *
 *          The ''BootstrapMethods'' attribute, which is also defined by the JVM specification,
 *          may, however, be resolved and is then no longer part of the attributes table of
 *          the class file.
 *          The ''BootstrapMethods'' attribute is basically the container for the bootstrap
 *          methods referred to by the [[org.opalj.br.instructions.INVOKEDYNAMIC]]
 *          instructions.
 *
 * @note    Equality of `ClassFile` objects is reference based and a class file's hash code
 *          is the same as the underlying [[ObjectType]]'s hash code; i.e., ' `thisType`'s hash code.
 *
 * @author  Michael Eichberg
 */
final class ClassFile private (
        val version:        UShortPair,
        val accessFlags:    Int,
        val thisType:       ObjectType,
        val superclassType: Option[ObjectType],
        val interfaceTypes: ObjectTypes,
        val fields:         Fields,
        val methods:        Methods,
        val attributes:     Attributes
) extends ConcreteSourceElement {

    methods.foreach { m => assert(m.declaringClassFile == null); m.declaringClassFile = this }
    fields.foreach { f => assert(f.declaringClassFile == null); f.declaringClassFile = this }

    /**
     * Compares this class file with the given one; returns (the first) differences if any. The
     * comparison tries to be stable in the presence of difference that are not runtime relevant.
     * For example, the precise structure of the constant pool is completely irrelevant.
     * Additionally, some variance in the bytecode (e.g., `bipush(2)` vs `iconst_2`) is generally
     * irrelevant and also the order in which [[Attribute]]s are found.
     *
     * The degree to which the two class files have to be similar can be configured using
     * a [[SimilarityTestConfiguration]] object. By default, all parts will be compared and have to
     * be equal except of irrelevant differences.
     * The default ([[CompareAllConfiguration]]) compares all parts.
     *
     * @return `None` if this class file and the other are equal - i.e., if both
     *          effectively implement the same class.
     */
    def findDissimilarity(
        other:  ClassFile,
        config: SimilarityTestConfiguration = CompareAllConfiguration
    ): Option[AnyRef] = {

        if (this.version != other.version) {
            return Some(("class file version", this.version, other.version));
        }

        if (this.accessFlags != other.accessFlags) {
            return Some(("class file access flags", this.accessFlags, other.accessFlags));
        }

        if (this.thisType != other.thisType) {
            return Some(("declared type", this.thisType.toJava, other.thisType.toJava));
        }

        if (this.superclassType != other.superclassType) {
            return Some(("declared supertype", this.superclassType, other.superclassType));
        }

        if (this.interfaceTypes != other.interfaceTypes) {
            return Some(("inherited interface types", this.interfaceTypes, other.interfaceTypes));
        }

        val (thisFields, otherFields) = config.compareFields(this, this.fields, other.fields)
        if (thisFields.size != otherFields.size) {
            val message = "number of (filtered) fields differ"
            return Some((message, thisFields.size, otherFields.size));
        }
        // RECALL The fields are always strictly ordered in the same way!
        val thisFieldIt = thisFields.iterator
        val otherFieldIt = otherFields.iterator
        while (thisFieldIt.hasNext) {
            val thisField = thisFieldIt.next()
            val otherField = otherFieldIt.next()
            if (!thisField.similar(otherField, config)) {
                return Some(("the fields are different", thisField, otherField));
            }
        }

        val (thisMethods, otherMethods) = config.compareMethods(this, this.methods, other.methods)
        if (thisMethods.size != otherMethods.size) {
            val message = "number of (filtered) methods differ"
            return Some((message, thisMethods.size, otherMethods.size));
        }
        // RECALL The methods are always strictly ordered in the same way!
        val thisMethodIt = thisMethods.iterator
        val otherMethodIt = otherMethods.iterator
        while (thisMethodIt.hasNext) {
            val thisMethod = thisMethodIt.next()
            val otherMethod = otherMethodIt.next()
            if (!thisMethod.similar(otherMethod, config)) {
                return Some(("the methods are different", thisMethod, otherMethod));
            }
        }

        compareAttributes(other.attributes, config)
    }

    /**
     * Compares this class file with the given one to check if both define the same class modulo
     * those parts which are not considered relevant.
     *
     * @see [[findDissimilarity]] for further information.
     *
     * @param config Configures which parts of the class files should be compared.
     */
    def similar(
        other:  ClassFile,
        config: SimilarityTestConfiguration = CompareAllConfiguration
    ): Boolean = {
        findDissimilarity(other, config).isEmpty
    }

    /**
     * Creates a deep copy of this class file object which also copies the methods and fields.
     *
     * @note If the requirements of `unsafeReplaceMethod` are met you should use that method!
     */
    def copy(
        version:        UShortPair         = this.version,
        accessFlags:    Int                = this.accessFlags,
        thisType:       ObjectType         = this.thisType,
        superclassType: Option[ObjectType] = this.superclassType,
        interfaceTypes: ObjectTypes        = this.interfaceTypes,
        fields:         FieldTemplates     = this.fields.map[FieldTemplate](f => f.copy()),
        methods:        MethodTemplates    = this.methods.map[MethodTemplate](m => m.copy()),
        attributes:     Attributes         = this.attributes
    ): ClassFile = {
        ClassFile(
            version.minor, version.major, accessFlags,
            thisType, superclassType, interfaceTypes,
            fields,
            methods,
            attributes
        )
    }

    /**
     * Creates a new class file object which has the specified attributes.
     *
     * '''The old class file object must not be used after this call; if this cannot be guaranteed
     * `copy` has to be used; otherwise the back-references (field -> class file and method ->
     * class file) are broken!'''
     *
     * @note This method is primarily intended to be used to perform load-time transformations!
     */
    def _UNSAFE_replaceAttributes(newAttributes: Attributes): ClassFile = {
        val newMethods = this.methods
        newMethods.foreach(m => m.detach())

        val newFields = this.fields
        newFields.foreach(f => f.detach())

        new ClassFile(
            this.version,
            this.accessFlags,
            this.thisType,
            this.superclassType,
            this.interfaceTypes,
            newFields,
            newMethods,
            newAttributes
        )
    }

    /**
     * Creates a new class file object where the method `oldMethod` is replaced by the `newMethod`.
     * Hence, the old method must be defined by this class file!
     *
     * '''Both methods have to have the same name and descriptor!'''
     *
     * '''The old class file object must not be used after this call; if this cannot be guaranteed
     * `copy` has to be used; otherwise the back-references (field -> class file and method ->
     * class file) are broken!'''
     *
     * @note This method is primarily intended to be used to perform load-time transformations!
     */
    def _UNSAFE_replaceMethod(oldMethod: Method, newMethod: MethodTemplate): this.type = {
        assert(oldMethod.name == newMethod.name)
        assert(oldMethod.descriptor == newMethod.descriptor)

        val index = binarySearch[Method, JVMMethod](this.methods, oldMethod)
        val newPreparedMethod: Method = newMethod.prepareClassFileAttachement()
        newPreparedMethod.declaringClassFile = this
        val methods = this.methods.unsafeArray.asInstanceOf[Array[AnyRef]]
        methods(index) = newPreparedMethod

        oldMethod.detach(); // TO BE SURE THAT THE OLD METHOD NO LONGER REFERENCES THIS CLASS FILE

        this
    }

    /**
     * Creates a new class file object with the given method.
     *
     * '''This class file must not contain a method with the same name and descriptor!'''
     *
     * '''The old class file object must not be used after this call; if this cannot be guaranteed
     * `copy` has to be used; otherwise the back-references (field -> class file and method ->
     * class file) are broken!'''
     *
     * @note This method is primarily intended to be used to perform load-time transformations!
     */
    def _UNSAFE_addMethod(methodTemplate: MethodTemplate): ClassFile = {
        val newMethod = methodTemplate.prepareClassFileAttachement()

        assert(this.findMethod(newMethod.name, newMethod.descriptor).isEmpty)

        val index = binarySearch[Method, JVMMethod](this.methods, newMethod)
        if (index >= 0)
            throw new IllegalArgumentException(
                s"$this: a method with the given name and descriptor already exists: $newMethod"
            )
        val insertionPoint = -index - 1
        var newMethods = this.methods
        newMethods.foreach(m => m.detach())
        newMethods = insertedAt(newMethods, insertionPoint, newMethod)

        val newFields = this.fields
        newFields.foreach(f => f.detach())

        new ClassFile(
            this.version,
            this.accessFlags,
            this.thisType,
            this.superclassType,
            this.interfaceTypes,
            newFields,
            newMethods,
            this.attributes
        )
    }

    def methodsWithBody: Iterator[Method] = methods.iterator.filter(_.body.isDefined)

    def methodBodies: Iterator[Code] = methods.iterator.flatMap(_.body)

    import ClassFile._

    def minorVersion: UShort = version.minor

    def majorVersion: UShort = version.major

    def jdkVersion: String = org.opalj.bi.jdkVersion(majorVersion)

    override def isClass: Boolean = true

    override def asClassFile: this.type = this

    def asVirtualClass: VirtualClass = VirtualClass(thisType)

    /**
     * The unique id associated with the type defined by this class file.
     */
    def id = thisType.id

    /**
     * The fully qualified name of the type defined by this class file.
     */
    def fqn: String = thisType.fqn

    def isAbstract: Boolean = (ACC_ABSTRACT.mask & accessFlags) != 0

    def isFinal: Boolean = (ACC_FINAL.mask & accessFlags) != 0

    /**
     * Returns `true` if the class is final or if it only defines private constructors and it
     * is therefore not possible to inherit from this class.
     *
     * An abstract type (abstract classes and interfaces) is never effectively final.
     */
    def isEffectivelyFinal: Boolean = {
        isFinal || (
            !isAbstract && (constructors forall { _.isPrivate })
        )
    }

    /**
     * `true` if the class file has public visibility. If `false` the method `isPackageVisible`
     * will return `true`.
     *
     * @note There is no private or protected visibility.
     */
    def isPublic: Boolean = (ACC_PUBLIC.mask & accessFlags) != 0

    /**
     * `true` if the class file has package visibility. If `false` the method `isPublic`
     * will return `true`.
     *
     * @note    A class file cannot have private or protected visibility.
     */
    def isPackageVisible: Boolean = !isPublic

    def isClassDeclaration: Boolean = (accessFlags & classCategoryMask) == 0

    def isEnumDeclaration: Boolean = (accessFlags & ACC_ENUM.mask) == ACC_ENUM.mask

    // JVM 9 Specification:
    // If ACC_MODULE is set in ClassFile.access_flags, then no other flag in
    // ClassFile.access_flags may be set.
    def isModuleDeclaration: Boolean = accessFlags == ACC_MODULE.mask

    /**
     * Returns true if this class file represents an interface.
     *
     * @note From the JVM point-of-view annotations are also interfaces!
     *
     * @see [[org.opalj.br.analyses.Project]] to determine if this interface declaration is a
     *      functional interface.
     */
    def isInterfaceDeclaration: Boolean = (accessFlags & ACC_INTERFACE.mask) == ACC_INTERFACE.mask

    def isAnnotationDeclaration: Boolean = (accessFlags & classCategoryMask) == annotationMask

    def isInnerClass: Boolean = innerClasses.exists(_.exists(_.innerClassType == thisType))

    /**
     * Returns `true` if this class file has no direct representation in the source code.
     *
     * @see [[VirtualTypeFlag]] for further information.
     */
    def isVirtualType: Boolean = attributes.contains(VirtualTypeFlag)

    /**
     * Returns Java 9's module attribute if defined.
     */
    def module: Option[Module] = { attributes collectFirst { case m: Module => m } }

    def enclosingMethod: Option[EnclosingMethod] = {
        attributes collectFirst { case em: EnclosingMethod => em }
    }

    /**
     * Returns this class file's bootstrap method table.
     *
     * @note    A class file's bootstrap method table may be removed at load time if
     *          the corresponding [[org.opalj.br.instructions.INVOKEDYNAMIC]] instructions
     *          are rewritten.
     */
    def bootstrapMethodTable: Option[BootstrapMethodTable] = {
        attributes collectFirst { case bmt: BootstrapMethodTable => bmt }
    }

    /**
     * Returns OPAL's [[SynthesizedClassFiles]] attribute if it is defined.
     */
    def synthesizedClassFiles: Option[SynthesizedClassFiles] = {
        attributes collectFirst { case scf: SynthesizedClassFiles => scf }
    }

    /**
     * Returns the `inner classes attribute`, if defined.
     *
     * @note The inner classes attribute contains (for inner classes) also a reference
     *      to its outer class. Furthermore, it contains references to other inner
     *      classes that are not an inner class of this class.
     *      If you are just interested in the inner classes
     *      of this class, use the method nested classes.
     * @see [[nestedClasses]]
     */
    def innerClasses: Option[InnerClasses] = {
        attributes collectFirst { case InnerClassTable(ice) => ice }
    }

    /**
     * Returns `true` if this class file defines an anonymous inner class.
     *
     * This method relies on the inner classes attribute to identify anonymous inner
     * classes.
     */
    def isAnonymousInnerClass: Boolean = {
        /*
        isClassDeclaration && innerClasses.isDefined &&  innerClasses.get.exists { i =>
                    i.innerClassType == thisType && {
                        if (i.innerName.isEmpty) true else return false;
                    }
            }
        */
        isClassDeclaration && innerClasses.isDefined && !innerClasses.get.forall { i =>
            i.innerClassType != thisType || i.innerName.nonEmpty
        }
    }

    /**
     * Returns the set of all immediate nested classes of this class. I.e., returns those
     * nested classes that are not defined in the scope of a nested class of this
     * class.
     */
    def nestedClasses(implicit classFileRepository: ClassFileRepository): Seq[ObjectType] = {

        import classFileRepository.logContext

        // From the Java __8__ specification:
        // - every inner class must have an inner class attribute (at least for itself)
        // - every class that has inner classes must have an innerclasses attribute
        //   and the inner classes array must contain an entry
        // - the InnerClasses attribute only encodes information about its immediate
        //   inner classes
        var outerClassType: Option[ObjectType] = enclosingMethod.map(_.clazz)
        var isInnerType = false

        def isThisType(innerClass: InnerClass): Boolean = {
            if (innerClass.innerClassType eq thisType) {
                if (innerClass.outerClassType.isDefined)
                    outerClassType = innerClass.outerClassType
                isInnerType = true
                true
            } else
                false
        }

        val nestedClassesCandidates =
            innerClasses.map { innerClasses =>
                innerClasses
                    .filter(innerClass =>
                        // it does not describe this class:
                        (!isThisType(innerClass)) &&
                            // it does not give information about an outer class:
                            (!this.fqn.startsWith(innerClass.innerClassType.fqn)) &&
                            // it does not give information about some other inner class of this type:
                            (
                                innerClass.outerClassType.isEmpty ||
                                (innerClass.outerClassType.get eq thisType)
                            ))
                    .map[ObjectType](_.innerClassType)
            }.getOrElse {
                ArraySeq.empty
            }

        // THE FOLLOWING CODE IS NECESSARY TO COPE WITH BYTECODE GENERATED
        // BY OLD JAVA COMPILERS (IN PARTICULAR JAVA 1.1);
        // IT BASICALLY TRIES TO RECREATE THE INNER-OUTERCLASSES STRUCTURE
        if (isInnerType && outerClassType.isEmpty) {
            // let's try to find the outer class that refers to this class
            val thisFQN = thisType.fqn
            val innerTypeNameStartIndex = thisFQN.indexOf('$')
            if (innerTypeNameStartIndex == -1) {
                OPALLogger.warn(
                    "processing bytecode",
                    "the inner class "+thisType.toJava+
                        " does not use the standard naming schema"+
                        "; the inner classes information may be incomplete"
                )

                return nestedClassesCandidates.filter(_.fqn.startsWith(this.fqn));
            }
            val outerFQN = thisFQN.substring(0, innerTypeNameStartIndex)
            classFileRepository.classFile(ObjectType(outerFQN)) match {
                case Some(outerClass) =>

                    def directNestedClasses(objectTypes: Iterable[ObjectType]): Set[ObjectType] = {
                        var nestedTypes: Set[ObjectType] = Set.empty
                        objectTypes.foreach { objectType =>
                            classFileRepository.classFile(objectType) match {
                                case Some(classFile) =>
                                    nestedTypes ++= classFile.nestedClasses(classFileRepository)
                                case None =>
                                    OPALLogger.warn(
                                        "class file reader",
                                        "cannot get informaton about "+objectType.toJava+
                                            "; the inner classes information may be incomplete"
                                    )
                            }
                        }
                        nestedTypes
                    }

                    // let's filter those classes that are known innerclasses of this type's
                    // (indirect) outertype (they cannot be innerclasses of this class..)
                    var nestedClassesOfOuterClass = outerClass.nestedClasses(classFileRepository)
                    while (nestedClassesOfOuterClass.nonEmpty &&
                        !nestedClassesOfOuterClass.contains(thisType) &&
                        !nestedClassesOfOuterClass.exists(nestedClassesCandidates.contains)) {
                        // We are still lacking sufficient information to make a decision
                        // which class is a nested class of which other class
                        // e.g., we might have the following situation:
                        // class X {
                        //  class Y {                                // X$Y
                        //      void m(){
                        //          new Listener(){                  // X$Listener$1
                        //              void event(){
                        //                  new Listener(){...}}}}}} // X$Listener$2
                        nestedClassesOfOuterClass =
                            directNestedClasses(nestedClassesOfOuterClass).toSeq
                    }
                    val filteredNestedClasses =
                        nestedClassesCandidates.filterNot(nestedClassesOfOuterClass.contains)
                    return filteredNestedClasses;
                case None =>
                    val disclaimer = "; the inner classes information may be incomplete"
                    OPALLogger.warn(
                        "project configuration",
                        s"cannot identify the outer type of ${thisType.toJava}$disclaimer"
                    )

                    return nestedClassesCandidates.filter(_.fqn.startsWith(this.fqn));
            }
        }

        nestedClassesCandidates
    }

    /**
     * Iterates over '''all ''direct'' and ''indirect'' nested classes''' of this class file.
     *
     * @example To collect all nested types:
     * {{{
     *   var allNestedTypes: Set[ObjectType] = Set.empty
     *   foreachNestedClasses(innerclassesProject, { nc => allNestedTypes += nc.thisType })
     * }}}
     */
    def foreachNestedClass(
        f: (ClassFile) => Unit
    )(
        implicit
        classFileRepository: ClassFileRepository
    ): Unit = {
        nestedClasses(classFileRepository) foreach { nestedType =>
            classFileRepository.classFile(nestedType) foreach { nestedClassFile =>
                f(nestedClassFile)
                nestedClassFile.foreachNestedClass(f)
            }
        }
    }

    /**
     * Each class has at most one explicit, direct outer type. Note that a local
     * class (a class defined in the scope of a method) or an anonymous class
     * do not specify an outer type.
     *
     * @return The object type of the outer type as well as the access flags of this
     *      inner class.
     */
    def outerType: Option[(ObjectType, Int)] = {
        innerClasses flatMap { innerClasses =>
            innerClasses collectFirst {
                case InnerClass(`thisType`, Some(outerType), _, accessFlags) =>
                    (outerType, accessFlags)
            }
        }
    }

    /**
     * Each class file optionally defines a class signature.
     */
    def classSignature: Option[ClassSignature] = {
        attributes collectFirst { case s: ClassSignature => s }
    }

    /**
     * The SourceFile attribute is an optional attribute [...]. There can be
     * at most one `SourceFile` attribute.
     */
    def sourceFile: Option[String] = attributes collectFirst { case SourceFile(s) => s }

    /**
     * The SourceDebugExtension attribute is an optional attribute [...]. There can be
     * at most one `SourceDebugExtension` attribute. The data (which is modified UTF8
     * String may, however, not be representable using a String object (see the
     * spec. for further details.)
     *
     * The returned Array must not be mutated.
     */
    def sourceDebugExtension: Option[Array[Byte]] = {
        attributes collectFirst { case SourceDebugExtension(s) => s }
    }

    /**
     * All constructors/instance initialization methods (``) defined by this class.
     *
     * (This does not include the static initializer.)
     */
    def constructors: Iterator[Method] = new Iterator[Method] {
        private[this] var i = -1

        private[this] def gotoNextConstructor(): Unit = {
            i += 1
            if (i >= methods.size) {
                i = -1
            } else {
                val methodName = methods(i).name
                val r = methodName.compareTo("")
                if (r < 0 /*methodName < ""*/ )
                    gotoNextConstructor()
                else if (r > 0 /*methodName > ""*/ )
                    i = -1;
            }
        }
        gotoNextConstructor()

        def hasNext: Boolean = i >= 0
        def next(): Method = { val m = methods(i); gotoNextConstructor(); m }
    }

    /**
     * Returns `true` if this class defines a so-called default constructor. A
     * default constructor needs to be present, e.g., when the class is serializable.
     *
     * The default constructor is the constructor that takes no parameters.
     *
     * @note The result is recomputed.
     */
    def hasDefaultConstructor: Boolean = constructors exists { _.descriptor.parametersCount == 0 }

    /**
     * All defined instance methods. I.e., all methods that are not static,
     * constructors, or static initializers.
     */
    def instanceMethods: Iterator[Method] = {
        methods.iterator.filterNot { m => m.isStatic || m.isConstructor || m.isStaticInitializer }
    }

    /**
     * The static initializer of this class.
     *
     * @note The way how the static initializer is identified has changed
     *       with Java 7. In a class file whose version number is 51.0 or above, the
     *       method must have its ACC_STATIC flag set. Other methods named <clinit>
     *       in a class file are of no consequence.
     */
    def staticInitializer: Option[Method] = {
        // The set of methods is sorted - hence, the static initializer should
        // be (among) the first method(s).
        val methodsCount = methods.size
        val noArgsAndReturnVoidDescriptor = MethodDescriptor.NoArgsAndReturnVoid
        var i = 0
        while (i < methodsCount) {
            val method = methods(i)
            val methodNameComparison = method.name.compareTo("")

            if (methodNameComparison == 0 &&
                method.descriptor == noArgsAndReturnVoidDescriptor &&
                (majorVersion < 51 || method.isStatic))
                return Some(method);

            else if (methodNameComparison < 0)
                return None;

            i += 1
        }
        None
    }

    /**
     * Returns the field with the given name, if any.
     *
     * @note The complexity is O(log2 n); this algorithm uses binary search.
     */
    def findField(name: String): List[Field] = {
        @tailrec @inline def findField(low: Int, high: Int): List[Field] = {
            if (high < low)
                return List.empty;

            val mid = (low + high) / 2 // <= will never overflow...(there are at most 65535 fields)
            val field = fields(mid)
            val fieldNameComparison = field.name.compareTo(name)
            if (fieldNameComparison == 0) {
                var theFields = List(field)
                var d = mid - 1
                while (low <= d && fields(d).name.equals(name)) {
                    theFields ::= fields(d)
                    d -= 1
                }
                var u = mid + 1
                while (u <= high && fields(u).name.equals(name)) {
                    theFields ::= fields(u)
                    u += 1
                }
                theFields
            } else if (fieldNameComparison < 0) {
                findField(mid + 1, high)
            } else {
                findField(low, mid - 1)
            }
        }

        findField(0, fields.size - 1)
    }

    /**
     * Returns the field with the given name and type.
     */
    def findField(name: String, fieldType: FieldType): Option[Field] = {
        findField(name).find(f => f.fieldType eq fieldType)
    }

    /**
     * Returns the methods (including constructors and static initializers) with the given name,
     * if any.
     *
     * @note The complexity is O(log2 n); this algorithm uses binary search.
     */
    def findMethod(name: String): List[Method] = {
        @tailrec @inline def findMethod(low: Int, high: Int): List[Method] = {
            if (high < low)
                return List.empty;

            val mid = (low + high) / 2 // <= will never overflow...(there are at most 65535 methods)
            val method = methods(mid)
            val methodName = method.name
            val methodNameComparison = methodName.compareTo(name)
            if (methodNameComparison == 0) {
                var theMethods = List(method)
                var d = mid - 1
                while (low <= d && methods(d).name.equals(name)) {
                    theMethods ::= methods(d)
                    d -= 1
                }
                var u = mid + 1
                while (u <= high && methods(u).name.equals(name)) {
                    theMethods ::= methods(u)
                    u += 1
                }
                theMethods
            } else if (methodNameComparison < 0) {
                findMethod(mid + 1, high)
            } else {
                findMethod(low, mid - 1)
            }
        }

        findMethod(0, methods.size - 1)
    }

    /**
     * Returns the method with the given name and descriptor that is declared by
     * this class file.
     *
     * @note The complexity is O(log2 n); this algorithm uses a binary search algorithm.
     */
    def findMethod(name: String, descriptor: MethodDescriptor): Option[Method] = {
        @tailrec @inline def findMethod(low: Int, high: Int): Option[Method] = {
            if (high < low)
                return None;

            val mid = (low + high) / 2 // <= will never overflow...(there are at most 65535 methods)
            val method = methods(mid)
            val nameComparison = method.name.compareTo(name)
            if (nameComparison == 0) {
                val methodDescriptorComparison = method.descriptor.compare(descriptor)
                if (methodDescriptorComparison < 0)
                    findMethod(mid + 1, high)
                else if (methodDescriptorComparison == 0)
                    Some(method)
                else
                    findMethod(low, mid - 1)
            } else if (nameComparison < 0) {
                findMethod(mid + 1, high)
            } else {
                findMethod(low, mid - 1)
            }
        }

        findMethod(0, methods.size - 1)
    }

    /**
     * Returns the method which directly overrides a method with the given properties. The result
     * is `Success()`` if we can find a method; `Empty` if no method can be found and
     * `Failure` if a method is found which supposedly overrides the specified method,
     * but which is less visible.
     *
     * @note    This method is only defined for proper virtual methods. I.e., asking for
     *          overridings of a private methods is not supported.
     */
    def findDirectlyOverridingMethod(
        packageName: String,
        visibility:  Option[VisibilityModifier],
        name:        String,
        descriptor:  MethodDescriptor
    ): Result[Method] = {
        assert(visibility.isEmpty || visibility.get != ACC_PRIVATE)

        findMethod(name, descriptor).filter(m => !m.isStatic) match {

            case Some(candidateMethod) =>
                import VisibilityModifier.isAtLeastAsVisibleAs
                if (Method.canDirectlyOverride(thisType.packageName, visibility, packageName) &&
                    isAtLeastAsVisibleAs(candidateMethod.visibilityModifier, visibility))
                    Success(candidateMethod)
                else
                    Failure

            case None =>
                Empty
        }

    }

    final def findDirectlyOverridingMethod(
        packageName: String,
        method:      Method
    ): Result[Method] = {
        findDirectlyOverridingMethod(
            packageName,
            method.visibilityModifier,
            method.name,
            method.descriptor
        )
    }

    def findMethod(
        name:       String,
        descriptor: MethodDescriptor,
        matcher:    AccessFlagsMatcher
    ): Option[Method] = {
        findMethod(name, descriptor) filter { m => matcher.unapply(m.accessFlags) }
    }

    /**
     * This class file's `hasCode`. The `hashCode` is (by purpose) identical to
     * the id of the `ObjectType` it implements.
     */
    override def hashCode: Int = thisType.id

    override def equals(other: Any): Boolean = {
        other match {
            case that: ClassFile => that eq this
            case _               => false
        }
    }

    override def toString: String = {
        val superIntefaces =
            if (interfaceTypes.nonEmpty)
                interfaceTypes.iterator.map[String](_.toJava).mkString("\t\twith ", " with ", "\n")
            else
                ""

        "ClassFile(\n\t"+
            AccessFlags.toStrings(accessFlags, AccessFlagsContexts.CLASS).mkString("", " ", " ") +
            thisType.toJava+"\n"+
            superclassType.map("\textends "+_.toJava+"\n").getOrElse("") +
            superIntefaces +
            annotationsToJava(runtimeVisibleAnnotations, "\t@{ ", " }\n") +
            annotationsToJava(runtimeInvisibleAnnotations, "\t@{ ", " }\n")+
            "\t[version="+majorVersion+"."+minorVersion+"]\n)"

    }

}
/**
 * Defines factory and extractor methods for `ClassFile` objects as well as related
 * constants.
 *
 * @author Michael Eichberg
 */
object ClassFile {

    val classCategoryMask: Int = {
        ACC_INTERFACE.mask | ACC_ANNOTATION.mask | ACC_ENUM.mask | ACC_MODULE.mask
    }

    val annotationMask: Int = ACC_INTERFACE.mask | ACC_ANNOTATION.mask

    /**
     * @note   The default version is equivalent to Java 5, i.e.,
     *         no StackMapTable attribute is required.
     * @param  accessFlags This class' access flags, by default: PUBLIC and SUPER
     *         (always need to be set)
     * @param  superclassType The class from which this class/interface inherits from. By default
     *         `java.lang.Object`.
     */
    def apply(
        minorVersion:   Int                = 0,
        majorVersion:   Int                = 50,
        accessFlags:    Int                = { ACC_PUBLIC.mask | ACC_SUPER.mask },
        thisType:       ObjectType,
        superclassType: Option[ObjectType] = Some(ObjectType.Object),
        interfaceTypes: Interfaces         = NoInterfaces,
        fields:         FieldTemplates     = NoFieldTemplates,
        methods:        MethodTemplates    = NoMethodTemplates,
        attributes:     Attributes         = NoAttributes
    ): ClassFile = {
        new ClassFile(
            UShortPair(minorVersion, majorVersion),
            accessFlags,
            thisType, superclassType, interfaceTypes,
            fields.sorted[JVMField].map[Field](f => f.prepareClassFileAttachement()),
            methods.sorted[JVMMethod].map[Method](f => f.prepareClassFileAttachement()),
            attributes
        )
    }

    // This method is only intended to be called by the ClassFileReader/ClassFileBinding!
    protected[br] def reify(
        minorVersion:   Int                = 0,
        majorVersion:   Int                = 50,
        accessFlags:    Int                = { ACC_PUBLIC.mask | ACC_SUPER.mask },
        thisType:       ObjectType,
        superclassType: Option[ObjectType] = Some(ObjectType.Object),
        interfaceTypes: Interfaces         = NoInterfaces,
        fields:         Fields             = NoFields,
        methods:        Methods            = NoMethods,
        attributes:     Attributes         = NoAttributes
    ): ClassFile = {
        new ClassFile(
            UShortPair(minorVersion, majorVersion),
            accessFlags,
            thisType, superclassType, interfaceTypes,
            fields.sorted[JVMField],
            methods.sorted[JVMMethod],
            attributes
        )
    }

    def unapply(
        classFile: ClassFile
    ): Option[(Int, ObjectType, Option[ObjectType], Seq[ObjectType])] = {
        import classFile._
        Some((accessFlags, thisType, superclassType, interfaceTypes))
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy