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

org.opalj.br.analyses.MethodDeclarationContext.scala Maven / Gradle / Ivy

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

import org.opalj.bi.VisibilityModifier
import org.opalj.bi.ACC_PUBLIC
import org.opalj.bi.ACC_PROTECTED
import org.opalj.bi.ACC_PRIVATE

/**
 * Encapsulates the information about a '''non-abstract''', '''non-static''' method which is
 * '''not an initializer''' (`<(cl)init>`) and which is required when determining potential call
 * targets.
 *
 * @note    A class may have -- w.r.t. a given package name -- at most one package
 *          visible method which has a specific name and descriptor combination.
 *          For methods with protected or public visibility a class always has at
 *          most one method with a given name and descriptor.
 *
 * @note    Equality is defined based on the name, descriptor and declaring package
 *          of a method (the concrete declaring class is not considered!).
 *
 * @author Michael Eichberg
 */
final class MethodDeclarationContext(val method: Method) extends Ordered[MethodDeclarationContext] {

    assert(!method.isStatic)
    assert(!method.isInitializer)

    def declaringClassType: ObjectType = method.declaringClassFile.thisType
    def packageName: String = declaringClassType.packageName
    def isPublic: Boolean = method.isPublic
    def name: String = method.name
    def descriptor: MethodDescriptor = method.descriptor

    override def equals(other: Any): Boolean = {
        other match {
            case that: MethodDeclarationContext =>
                this.packageName == that.packageName && {
                    val thisMethod = this.method
                    val thatMethod = that.method
                    thisMethod.name == thatMethod.name &&
                        thisMethod.descriptor == thatMethod.descriptor
                }
            case _ =>
                false
        }
    }

    /**
     * The hash code is equal to the ``"hashCode of the descriptor of the underlying method"
     * * 113 + "the hashCode of the package of the declaring class"``.
     */
    override def hashCode: Int = method.descriptor.hashCode * 113 + packageName.hashCode()

    override def toString: String = {
        val packageName = if (this.packageName == "") "" else this.packageName
        s"MethodDeclarationContext($packageName, ${method.signatureToJava()})"
    }

    /**
     * Compares this `MethodDeclarationContext` with the given one. Defines a total order w.r.t.
     * the name, descriptor and declaring package of a method. (The declaring class is not
     * considered and, therefore, two `MethodDeclarationContext`s may be considered equal
     * even though the underlying method is not the same one.)
     */
    def compare(that: MethodDeclarationContext): Int = {
        val result = this.method compare that.method
        if (result == 0)
            this.packageName compareTo that.packageName
        else
            result
    }

    def compareWithPublicMethod(thatMethod: Method): Int = {
        assert(thatMethod.isPublic, s"${thatMethod.toJava} is not public")
        this.method compare thatMethod
    }

    /**
     * Compares this method (declaration context) with an (implicit) method which has the
     * given `name` and `descriptor` and which is defined in the given `package`
     * unless this method is protected or public. In that case the
     * `packageName` is not compared and "0" (<=> equal) is returned.
     * Hence, this `compare` method is well suited to make a lookup for a matching
     * method declaration context in a sorted array of method declaration
     * contexts.
     */
    def compareAccessibilityAware(
        packageName: String, // only considered if name and descriptor already match...
        name:        String,
        descriptor:  MethodDescriptor
    ): Int = {
        val method = this.method
        val result = method.compare(name, descriptor)
        if (result == 0 && method.hasDefaultVisibility) {
            this.packageName compareTo packageName
        } else {
            result
        }
    }

    def compareAccessibilityAware(
        declaringClass: ObjectType,
        m:              Method
    ): Int = {
        compareAccessibilityAware(declaringClass.packageName, m.name, m.descriptor)
    }

    /**
     * Returns `true` if this method directly overrides the given method.
     *
     * (Note: indirect overriding can only be determined if all intermediate methods
     * are known; only in that case it is possible to test if, e.g., a public method
     * in package x indirectly overrides a package visible method in a package y.)
     *
     * @note    The overrides relation is reflexive as defined by the JVM specification
     *          (Section: "Overriding").
     *
     * @param   that The [[MethodDeclarationContext]] object of another method which is defined by
     *          the same class as this method or a superclass thereof. If the
     *          other method is defined by some other class with which this class
     *          is not in a sub-/supertype relation, the result is not defined.
     */
    def directlyOverrides(that: MethodDeclarationContext): Boolean = {
        // mc and ma are used as in the JVM spec
        val mc = this.method
        val ma = that.method

        mc == ma || (
            mc.name == ma.name &&
            mc.descriptor == ma.descriptor &&
            canDirectlyOverride(ma.visibilityModifier, that.packageName)
        )
    }

    /**
     * Returns true if a method with the same signature as this method that is defined in
     * the given package directly overrides this encapsultated method. This property
     * always holds if this method has public or protected visiblity. If this method
     * has package visibility, the other (implicit) method has to be defined in
     * this method's package.
     */
    def isDirectlyOverriddenBy(packageName: String): Boolean = {
        !this.method.hasDefaultVisibility || this.packageName == packageName
    }

    /**
     * Performs the accessibility check required when we need to determine
     * if this method (`mc`) overrides another method (`ma`).
     *
     * @note    This method must be defined by a class which is a subtype of the
     *          declaring class of the other method.
     */
    def canDirectlyOverride(
        visibility:  Option[VisibilityModifier],
        packageName: String
    ): Boolean = {
        visibility match {
            case Some(ACC_PUBLIC) | Some(ACC_PROTECTED) => true
            case Some(ACC_PRIVATE)                      => false
            case None                                   => this.packageName == packageName
        }
    }
}

/**
 * Definition of factory and extractor methods for [[MethodDeclarationContext]] objects.
 */
object MethodDeclarationContext {

    def apply(method: Method): MethodDeclarationContext = {
        new MethodDeclarationContext(method)
    }

    def unapply(mdc: MethodDeclarationContext): Some[Method] = Some(mdc.method)

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy