
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