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

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

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

import scala.language.implicitConversions
import scala.annotation.tailrec

import java.io.InputStream
import java.util.concurrent.locks.ReentrantReadWriteLock

import scala.collection.mutable
import scala.jdk.CollectionConverters._
import scala.concurrent.Await.{result => await}
import scala.concurrent.Future
import scala.concurrent.duration.Duration.Inf
import scala.concurrent.ExecutionContext
import scala.io.BufferedSource

import it.unimi.dsi.fastutil.ints.Int2IntArrayMap
import org.opalj.control.foreachNonNullValue
import org.opalj.graphs.Node
import org.opalj.io.process
import org.opalj.io.processSource

import org.opalj.log.GlobalLogContext
import org.opalj.log.LogContext
import org.opalj.log.OPALLogger
import org.opalj.log.Warn
import org.opalj.concurrent.OPALUnboundedExecutionContext
import org.opalj.collection.ForeachRefIterator
import org.opalj.collection.EqualSets
import org.opalj.collection.IntIterator
import org.opalj.collection.StrictSubset
import org.opalj.collection.StrictSuperset
import org.opalj.collection.UncomparableSets
import org.opalj.collection.immutable.UIDSet
import org.opalj.collection.immutable.UIDSet1
import org.opalj.collection.CompleteCollection
import org.opalj.collection.IncompleteCollection
import org.opalj.collection.QualifiedCollection
import org.opalj.br.ObjectType.Object

/**
 * Represents '''a project's class hierarchy'''. The class hierarchy only contains
 * information about those classes that were explicitly added to it. Hence, the class hierarchy
 * may contain holes. However, the type `java.lang.Object` is always part of the class hierarchy.
 *
 * ==Thread safety==
 * This class is effectively immutable; concurrent access to the class hierarchy is supported.
 *
 * @note    Java 9 module definitions are completely ignored.
 *
 * @note    Unless explicitly documented, it is an error to pass an instance of `ObjectType`
 *          to any method if the `ObjectType` was not previously added. If in doubt, first
 *          check if the type is known (`isKnown`/`ifKnown`).
 *
 * @param   knownTypesMap A mapping between the id of an object type and the object type;
 *          implicitly encodes which types are known.
 *
 * @param   isInterfaceTypeMap `true` iff the type is an interface otherwise `false`;
 *          '''only defined for those types that are known'''.
 *
 * @param   isKnownToBeFinalMap `true` if the class is known to be `final`. I.e.,
 *          if the class is final `isFinal(ClassFile(objectType)) =>
 *          isFinal(classHierarchy(objectType))`.
 *
 * @param   superclassTypeMap Contains type information about a type's immediate superclass.
 *          This value is always defined (i.e., not null) unless the key identifies the
 *          object type `java.lang.Object` or when the respective class file was not
 *          analyzed and the respective type was only seen in the declaration of another class.
 *
 * @param   superinterfaceTypesMap Contains type information about a type's directly
 *          implemented interfaces; if any.
 *
 * @param   subclassTypesMap Contains type information about a type's subclasses; if any.
 *
 * @param   subinterfaceTypesMap Contains type information about a type's subinterfaces.
 *          They only ''class type'' that is allowed to have a non-empty set of subinterfaces
 *          is `java.lang.Object`.
 *
 * @param   rootTypes  The set of ''all types'' which have no supertypes or for which we have
 *          no further supertype information because of an incomplete project.
 *          If the class hierarchy is complete, then this set contains exactly one element and
 *          that element must identify `java.lang.Object`.
 *
 *          If we load an application and all the jars used to implement it or a library
 *          and all the libraries it depends on, then the class hierarchy '''should not'''
 *          contain multiple root types. However, the (complete) JDK already contains
 *          some references to Eclipse classes which are not part of the JDK.
 *
 * @param   leafTypes The set of all types which have no subtypes.
 *
 * @param   isSupertypeInformationCompleteMap A map which stores for each ''known type''
 *          if its supertype information is complete.
 *
 * @param   supertypeInformationMap Contains for each object type the set of class types and
 *          interface types it inherits from. This set is computed on a best-effort basis.
 *
 *          In some cases the supertype information may be incomplete, because the project
 *          as such is incomplete. Whether the type information is complete for a given type
 *          or not can be checked using `isSupertypeInformationComplete`.
 *
 * @author Michael Eichberg
 */
class ClassHierarchy private (
        // the case "java.lang.Object" is handled explicitly!
        private[this] val knownTypesMap:       Array[ObjectType],
        private[this] val isKnownTypeMap:      Array[Boolean],
        private[this] val isInterfaceTypeMap:  Array[Boolean],
        private[this] val isKnownToBeFinalMap: Array[Boolean],

        // The element is null for types for which we have no complete information
        // (unless it is java.lang.Object)!
        private[this] val superclassTypeMap:      Array[ObjectType],
        private[this] val superinterfaceTypesMap: Array[UIDSet[ObjectType]],

        // In the following all elements are non-null for each known type!
        private[this] val subclassTypesMap:     Array[UIDSet[ObjectType]],
        private[this] val subinterfaceTypesMap: Array[UIDSet[ObjectType]],

        // DERIVED INFORMATION
        val rootTypes:                                       UIDSet[ObjectType],
        val leafTypes:                                       UIDSet[ObjectType],
        private[this] val isSupertypeInformationCompleteMap: Array[Boolean],

        private[this] val supertypeInformationMap: Array[SupertypeInformation],
        private[this] val subtypeInformationMap:   Array[SubtypeInformation]
)(
        implicit
        val logContext: LogContext
) {

    def updatedLogContext(newLogContext: LogContext): ClassHierarchy = {
        new ClassHierarchy(
            knownTypesMap,
            isKnownTypeMap,
            isInterfaceTypeMap,
            isKnownToBeFinalMap,
            superclassTypeMap,
            superinterfaceTypesMap,
            subclassTypesMap,
            subinterfaceTypesMap,
            rootTypes,
            leafTypes,
            isSupertypeInformationCompleteMap,
            supertypeInformationMap,
            subtypeInformationMap
        )(
            newLogContext
        )
    }

    // TODO Use all subTypes/subclassTypes/subinterfaceTypes
    // TODO Use all supertypes/superclassTypes/superinterfaceTypes
    // TODO Precompute all subTypesCF/subclassTypesCF/subinterfaceTypesCF
    // TODO Precompute all supertypesCF/superclassTypesCF/superinterfaceTypesCF

    /**
     * The set of ''all class types'' (excluding interfaces) which have no super type or
     * for which the supertype information is incomplete; that is all (pseudo) root types.
     * If the class hierarchy is complete, then this set contains exactly one element and
     * that element must identify `java.lang.Object`.
     *
     * @note    The returned root class types are not necessarily a subset of `rootTypes`.
     *          A class which has an unknown super class, but implements a known interface
     *          is considered to belong to be a root class type  but is not a member of
     *          `rootTypes` because some supertype information exists!
     *
     * @note    If we load an application and all the jars used to implement it or a library
     *          and all the library it depends on, then the class hierarchy '''should not'''
     *          contain multiple root types. However, the (complete) JDK contains some references
     *          to Eclipse classes which are not part of the JDK.
     */
    def rootClassTypesIterator: Iterator[ObjectType] = {
        knownTypesMap.iterator filter { objectType =>
            (objectType ne null) && {
                val oid = objectType.id
                (superclassTypeMap(oid) eq null) && !isInterfaceTypeMap(oid)
            }
        }
    }

    def leafClassTypesIterator: Iterator[ObjectType] = {
        leafTypes.iterator filterNot { objectType => isInterfaceTypeMap(objectType.id) }
    }

    /**
     * Iterates over all interfaces which only inherit from `java.lang.Object` and adds the
     * types to the given `Growable` collection. I.e., iterates
     * over all interfaces which are at the top of the interface inheritance hierarchy.
     */
    def rootInterfaceTypes(collection: mutable.Growable[ObjectType]): collection.type = {
        superinterfaceTypesMap.iterator.zipWithIndex foreach { si =>
            val (superinterfaceTypes, id) = si
            if (superinterfaceTypes != null && superinterfaceTypes.isEmpty && isInterface(id)) {
                collection += knownTypesMap(id)
            }
        }
        collection
    }

    /**
     * A dump of the class hierarchy information in TSV format.
     * The following information will be dumped:
     *  - type
     *  - id
     *  - (is) interface
     *  - (is) final
     *  - (is) root type
     *  - (is) leaf type
     *  - (is) supertype information complete
     *  - super class
     *  - super interfaces
     *  - sub classes
     *  - sub interfaces
     */
    def asTSV: String = {

        implicit def objectTypeToString(ot: ObjectType): String = {
            if (ot ne null) ot.toJava else "N/A"
        }

        implicit def objectTypesToString(ots: UIDSet[ObjectType]): String = {
            if (ots ne null) ots.map(_.toJava).mkString("{", ",", "}") else "N/A"
        }

        case class TypeInfo(
                objectType:                     String,
                objectTypeId:                   Int,
                isInterface:                    Boolean,
                isFinal:                        Boolean,
                isRootType:                     Boolean,
                isLeafType:                     Boolean,
                isSupertypeInformationComplete: Boolean,
                superclassType:                 String,
                superinterfaceTypes:            String,
                subclassTypes:                  String,
                subinterfaceTypes:              String
        ) {
            override def toString: String = {
                s"$objectType \t$objectTypeId \t$isInterface \t$isFinal \t$isRootType \t$isLeafType \t"+
                    s"$isSupertypeInformationComplete \t$superclassType \t$superinterfaceTypes \t"+
                    s"$subclassTypes \t$subinterfaceTypes"
            }
        }

        val rootTypes = this.rootTypes.toSet
        val typeInfos =
            (0 until knownTypesMap.length) filter { i => knownTypesMap(i) ne null } map { i =>
                val t = knownTypesMap(i)
                TypeInfo(
                    t,
                    t.id,
                    isInterfaceTypeMap(i),
                    isKnownToBeFinalMap(i),
                    rootTypes.contains(t),
                    leafTypes.contains(t),
                    isSupertypeInformationCompleteMap(i),
                    superclassTypeMap(i),
                    superinterfaceTypesMap(i),
                    subclassTypesMap(i),
                    subinterfaceTypesMap(i)
                ).toString
            }

        val header =
            "type \tid \tinterface \tfinal"+
                " \troot type \tleaf type \tsupertype information complete"+
                " \tsuper class \tsuper interfaces \tsub classes \tsub interfaces\n"
        typeInfos.sorted.mkString(header, "\n", "\n")
    }

    //
    //
    // IMPLEMENTS THE MAPPING BETWEEN AN ObjectType AND IT'S ID
    //
    //

    private[this] var objectTypesMap: Array[ObjectType] = new Array(ObjectType.objectTypesCount)
    private[this] final val objectTypesMapRWLock = new ReentrantReadWriteLock()

    private[this] final def objectTypesCreationListener(objectType: ObjectType): Unit = {
        val id = objectType.id
        val writeLock = objectTypesMapRWLock.writeLock()
        writeLock.lock()
        try {
            val thisObjectTypesMap = objectTypesMap
            if (id >= thisObjectTypesMap.length) {
                val newLength = Math.max(ObjectType.objectTypesCount, id) + 100
                val newObjectTypesMap = new Array[ObjectType](newLength)
                Array.copy(thisObjectTypesMap, 0, newObjectTypesMap, 0, thisObjectTypesMap.length)
                newObjectTypesMap(id) = objectType
                objectTypesMap = newObjectTypesMap
            } else {
                thisObjectTypesMap(id) = objectType
            }
        } finally {
            writeLock.unlock()
        }
    }

    ObjectType.setObjectTypeCreationListener(objectTypesCreationListener)

    /**
     * Returns the `ObjectType` with the given Id. The id has to be the id of a valid
     * ObjectType.
     */
    final def getObjectType(objectTypeId: Int): ObjectType = {
        val readLock = objectTypesMapRWLock.readLock()
        readLock.lock()
        try {
            val ot = objectTypesMap(objectTypeId)
            if (ot eq null) {
                throw new IllegalArgumentException("ObjectType id invalid: "+objectTypeId)
            }
            ot
        } finally {
            readLock.unlock()
        }
    }

    //
    //
    // REGULAR METHODS
    //
    //

    /**
     * Returns the supertype information if the given type is known. If the given type is unknown
     * `None` is returned.
     */
    def supertypeInformation(objectType: ObjectType): Option[SupertypeInformation] = {
        val oid = objectType.id
        if (isKnown(oid)) {
            Some(supertypeInformationMap(oid))
        } else {
            None
        }
    }

    /**
     * Returns the subtype information if the given type is known. If the given type is unknown
     * `None` is returned.
     */
    def subtypeInformation(objectType: ObjectType): Option[SubtypeInformation] = {
        val oid = objectType.id
        if (isKnown(oid)) {
            Some(subtypeInformationMap(oid))
        } else {
            None
        }
    }

    /**
     * Returns `true` if the class hierarchy has some information about the given
     * type.
     *
     * @note    Consider using isKnown(objectTypeId : Int) if you need the object ids anyway.
     */
    @inline final def isKnown(objectType: ObjectType): Boolean = isKnown(objectType.id)

    @inline final def isKnown(objectTypeId: Int): Boolean = {
        val isKnownTypeMap = this.isKnownTypeMap
        objectTypeId < isKnownTypeMap.length && isKnownTypeMap(objectTypeId)
    }

    /**
     * Returns `true` if the type is unknown. This is `true` for all types that are
     * referred to in the body of a method, but which are not referred to in the
     * declarations of the class files that were analyzed.
     *
     * @note    Consider using isUnknown(objectTypeId : Int) if you need the object ids anyway.
     */
    @inline final def isUnknown(objectType: ObjectType): Boolean = isUnknown(objectType.id)

    @inline final def isUnknown(objectTypeId: Int): Boolean = {
        val isKnownTypeMap = this.isKnownTypeMap
        objectTypeId >= knownTypesMap.length || !isKnownTypeMap(objectTypeId)
    }

    /**
     * Tests if the given objectType is known and if so executes the given function.
     *
     * @example
     * {{{
     * ifKnown(ObjectType.Serializable){isDirectSupertypeInformationComplete}
     * }}}
     */
    @inline final def ifKnown[T](objectType: ObjectType)(f: ObjectType => T): Option[T] = {
        if (isKnown(objectType))
            Some(f(objectType))
        else
            None
    }

    /**
     * Calls the given function `f` for each type that is known to the class hierarchy.
     */
    def foreachKnownType[T](f: ObjectType => T): Unit = {
        foreachNonNullValue(knownTypesMap)((_ /*index*/ , t) => f(t))
    }

    /**
     * Returns `true` if the given type is `final`. I.e., the declaring class
     * was explicitly declared `final` and no subtypes exist.
     *
     * @note No explicit `isKnown` check is required.
     *
     * @return  `false` is returned if:
     *  - the object type is unknown,
     *  - the object type is known not to be final or
     *  - the information is incomplete
     */
    @inline def isKnownToBeFinal(objectType: ObjectType): Boolean = {
        isKnownToBeFinal(objectType.id)
    }

    @inline def isKnownToBeFinal(objectTypeId: Int): Boolean = {
        isKnown(objectTypeId) && isKnownToBeFinalMap(objectTypeId)
    }

    /**
     * Returns `true` if the given type is known and is `final`. I.e., the declaring class
     * was explicitly declared final or – if the type identifies an array type –
     * the component type is either known to be final or is a primitive/base type.
     *
     * @note No explicit `isKnown` check is required.
     *
     * @return `false` is returned if:
     *  - the object type/component type is unknown,
     *  - the object type/component type is known not to be final or
     *  - the information about the object type/component type is incomplete
     */
    @inline def isKnownToBeFinal(referenceType: ReferenceType): Boolean = {
        referenceType match {
            case objectType: ObjectType =>
                isKnownToBeFinal(objectType)
            case arrayType: ArrayType =>
                val elementType = arrayType.elementType
                elementType.isBaseType || isKnownToBeFinal(elementType.asObjectType)
        }
    }

    /**
     * Returns `true` if the given `objectType` is known and defines an interface type.
     *
     * @note No explicit `isKnown` check is required.
     *
     * @param objectType An `ObjectType`.
     */
    @inline def isInterface(objectType: ObjectType): Answer = {
        val oid = objectType.id
        if (isUnknown(oid))
            Unknown
        else
            Answer(isInterfaceTypeMap(oid))
    }

    /** Returns  `true` if and only if the given type is known to define an interface! */
    @inline private[this] def isInterface(objectTypeId: Int): Boolean = {
        isKnown(objectTypeId) && isInterfaceTypeMap(objectTypeId)
    }

    @inline private[br] def unsafeIsInterface(objectTypeId: Int): Boolean = {
        isInterfaceTypeMap(objectTypeId)
    }

    /**
     * Returns `true` if the type hierarchy information related to the given type's
     * supertypes is complete.
     *
     * @note No explicit `isKnown` check is required.
     */
    @inline def isDirectSuperclassTypeInformationComplete(objectType: ObjectType): Boolean = {
        (objectType eq Object) || {
            val oid = objectType.id
            isKnown(oid) && superclassTypeMap(oid) != null
        }
    }

    /**
     * Returns `true` if the type hierarchy has complete information about all supertypes
     * of the given type.
     *
     * @note No explicit `isKnown` check is required.
     */
    @inline final def isSupertypeInformationComplete(objectType: ObjectType): Boolean = {
        val oid = objectType.id
        isKnown(oid) && isSupertypeInformationCompleteMap(oid)
    }

    /**
     * Returns `Yes` if the class hierarchy contains subtypes of the given type and `No` if
     * it contains no subtypes. `Unknown` is returned if the given type is not known.
     *
     * Please note, that the answer will be `No` even though the (running) project contains
     * (in)direct subtypes of the given type, but  the class hierarchy is not
     * complete. I.e., not all class files (libraries) used by the project are analyzed.
     * A second case is that some class files are generated at runtime that inherit from
     * the given `ObjectType`.
     *
     * @note    No explicit `isKnown` check is required.
     * @param   objectType Some `ObjectType`.
     */
    def hasSubtypes(objectType: ObjectType): Answer = {
        val oid = objectType.id
        if (isUnknown(oid)) {
            Unknown
        } else {
            Answer(subclassTypesMap(oid).nonEmpty || subinterfaceTypesMap(oid).nonEmpty)
        }
    }

    /**
     * The set of all class- and interface-types that (directly or indirectly)
     * inherit from the given type.
     *
     * @note    No explicit `isKnown` check is required; if the type is unknown the
     *          returned set will be empty unless `reflexive` is true.
     * @note    If you don't need the set, it is more efficient to use `foreachSubtype`.
     * @note    If the type hierarchy is not complete, the answer may also be incomplete.
     *          E.g., if x inherits from y and y inherits from z, but y is not known to the
     *          class hierarchy then x will not be in the set of all (known) subtypes of z.
     *
     * @param   objectType An `ObjectType`.
     * @param   reflexive If `true` the given type is also included in the returned
     *          set.
     * @return  The set of all direct and indirect subtypes of the given type.
     */
    def allSubtypes(objectType: ObjectType, reflexive: Boolean): Set[ObjectType] = {
        val oid = objectType.id
        if (isUnknown(oid))
            return if (reflexive) UIDSet1(objectType) else UIDSet.empty

        if (reflexive)
            subtypeInformationMap(oid).allTypes + objectType
        else
            subtypeInformationMap(oid).allTypes
    }

    def allSubtypesIterator(objectType: ObjectType, reflexive: Boolean): Iterator[ObjectType] = {
        val oid = objectType.id
        if (isUnknown(oid))
            return if (reflexive) Iterator(objectType) else Iterator.empty;

        if (reflexive)
            subtypeInformationMap(oid).iterator ++ Iterator(objectType)
        else
            subtypeInformationMap(oid).iterator
    }

    def allSubtypesForeachIterator(
        objectType: ObjectType,
        reflexive:  Boolean
    ): ForeachRefIterator[ObjectType] = {
        val oid = objectType.id
        if (isUnknown(oid))
            return {
                if (reflexive)
                    ForeachRefIterator.single(objectType)
                else
                    ForeachRefIterator.empty
            };

        if (reflexive)
            subtypeInformationMap(oid).foreachIterator ++ ForeachRefIterator.single(objectType)
        else
            subtypeInformationMap(oid).foreachIterator
    }

    /**
     * Enables the guided processing of all subtypes of the given type. This function enables you
     * to compute some value based on the subtypes of the given type and – at the same time – to
     * control the process of traversing the subtypes.
     *
     * @param initial The initial value of the computation. This is also the value that
     *        will be returned if the given type has no subtypes and `reflexive` is `false`.
     * @param f A function that computes the new value given the current value and which also
     *        returns the information whether the computation should be aborted using the
     *        current value of type `t` or if the subtypes of the current subtype should be
     *        traversed. The commented signature of `f` is:
     *        {{{
     *        f: (T, ObjectType) => (T /*result*/ , Boolean /*skip subtypes*/ , Boolean /*abort*/ )
     *        }}}
     */
    def processSubtypes[@specialized(Boolean) T](
        objectType: ObjectType,
        reflexive:  Boolean    = false
    )(
        initial: T
    )(
        f: (T, ObjectType) => (T /*result*/ , Boolean /*skip subtypes*/ , Boolean /*abort*/ )
    ): T = {
        if (isUnknown(objectType))
            return initial;

        var processed = UIDSet.empty[ObjectType]

        def forallSubtypes(initial: T, objectType: ObjectType): (T, Boolean /*continue*/ ) = {
            val oid = objectType.id
            var t: T = initial
            val continue = {
                subclassTypesMap(oid).forall { subtype =>
                    val (newT, continue) = process(t, subtype); t = newT; continue
                } &&
                    subinterfaceTypesMap(oid).forall { subtype =>
                        val (newT, continue) = process(t, subtype); t = newT; continue
                    }
            }
            (t, continue)
        }

        def process(t: T, objectType: ObjectType): (T, Boolean /*continue*/ ) = {
            if (processed.contains(objectType))
                return (t, true);

            processed += objectType

            val (newT, skipSubtypes, abort) = f(t, objectType)
            if (abort) (newT, false)
            else if (skipSubtypes) (newT, true)
            else forallSubtypes(newT, objectType)
        }

        val (newResult, _) = {
            if (reflexive) {
                process(initial, objectType)
            } else {
                forallSubtypes(initial, objectType)
            }
        }
        newResult
    }

    /**
     * Calls the function `f` for each known (direct or indirect) subtype of the given type.
     *
     * @note    No explicit `isKnown` check is required; if the type is unknown nothing
     *          will happen.
     * @note    For details regarding incomplete class hierarchies see [[allSubtypes]].
     * *
     * @param   objectType An `ObjectType`.
     */
    def foreachSubtype(objectType: ObjectType)(f: ObjectType => Unit): Unit = {
        val oid = objectType.id
        if (isKnown(oid)) {
            subtypeInformationMap(oid).foreach(f)
        }
    }

    /**
     * Iterates over all subtypes of the given type, by first iterating over the subclass types
     * and then iterating over the subinterface types (if the given object type defines an
     * interface type or identifies `java.lang.Object`).
     *
     * @param    process The process function will be called for each subtype of the given type.
     *           If process returns false, subtypes of the current type will no longer be traversed.
     *           However, if a subtype of the current type is reachable via another path (by means
     *           of interface inheritance) then that subtype may be processed.
     *
     * @note    Classes are always traversed first.
     */
    def foreachSubtype(
        objectType: ObjectType,
        reflexive:  Boolean    = false
    )(
        process: ObjectType => Boolean
    ): Unit = {
        var processed = UIDSet.empty[ObjectType]
        def foreachSubtype(objectType: ObjectType): Unit = {
            if (processed.contains(objectType))
                return ;

            processed += objectType

            if (process(objectType)) {
                val oid = objectType.id
                subclassTypesMap(oid) foreach { foreachSubtype }
                subinterfaceTypesMap(oid) foreach { foreachSubtype }
            }
        }

        if (objectType == ObjectType.Object) {
            if (reflexive) {
                if (!process(ObjectType.Object))
                    return ;
            };

            rootTypes foreach { rootType =>
                if (rootType ne ObjectType.Object) {
                    foreachSubtype(rootType)
                } else {
                    // java.lang.Object is always known ...
                    subclassTypesMap(ObjectType.ObjectId) foreach { foreachSubtype }
                    subinterfaceTypesMap(ObjectType.ObjectId) foreach { foreachSubtype }
                }
            }

            return ;
        }

        if (isUnknown(objectType))
            return ;

        if (reflexive)
            foreachSubtype(objectType)
        else {
            val oid = objectType.id
            subclassTypesMap(oid) foreach { foreachSubtype }
            subinterfaceTypesMap(oid) foreach { foreachSubtype }
        }
    }

    def foreachSubtypeCF(
        objectType: ObjectType,
        reflexive:  Boolean    = false
    )(
        process: ClassFile => Boolean
    )(
        implicit
        project: ClassFileRepository
    ): Unit = {
        foreachSubtype(objectType, reflexive) { subtype =>
            project.classFile(subtype) match {
                case Some(classFile) => process(classFile)
                case _ /* None */    => true
            }
        }
    }

    /**
     * Executes the given function `f` for each subclass of the given `ObjectType`.
     * In this case the subclass relation is '''not reflexive'''. Furthermore, it may be
     * possible that f is invoked multiple times using the same `ClassFile` object if
     * the given objectType identifies an interface.
     *
     * Subtypes for which no `ClassFile` object is available are ignored.
     *
     * @note    No explicit `isKnown` check is required; if the type is unknown nothing
     *          will happen.
     * @note    For details regarding incomplete class hierarchies see `foreachSubtype`.
     */
    def foreachSubclass(
        objectType: ObjectType,
        project:    ClassFileRepository
    )(
        f: ClassFile => Unit
    ): Unit = {
        foreachSubtype(objectType) { subtype => project.classFile(subtype).foreach(f) }
    }

    /**
     * Returns all (direct and indirect) subclass types of the given class type.
     *
     * @note    No explicit `isKnown` check is required; if the type is unknown, an empty
     *          iterator is returned.
     */
    def allSubclassTypes(objectType: ObjectType, reflexive: Boolean): Iterator[ObjectType] = {
        val oid = objectType.id
        if (isUnknown(oid))
            return Iterator.empty;

        val subclassTypesIterator = subtypeInformationMap(oid).classTypes.iterator
        if (reflexive)
            Iterator(objectType) ++ subclassTypesIterator
        else
            subclassTypesIterator
    }

    /**
     * Executes the given function `f` for each known direct subclass of the given `ObjectType`.
     * In this case the subclass relation is '''not reflexive''' and interfaces inheriting from
     * the given object type are ignored.
     *
     * Subtypes for which no `ClassFile` object is available are ignored.
     *
     * @note    No explicit `isKnown` check is required; if the type is unknown nothing
     *          will happen.
     */
    def foreachDirectSubclassType[T](
        objectType: ObjectType,
        project:    ClassFileRepository
    )(
        f: ClassFile => T
    ): Unit = {
        val oid = objectType.id
        if (isUnknown(oid))
            return ;

        import project.classFile
        subclassTypesMap(oid) foreach { subtype => classFile(subtype).foreach(f) }
    }

    def directSubtypesCount(objectType: ObjectType): Int = {
        directSubtypesCount(objectType.id)
    }

    def directSubtypesCount(objectTypeId: Int): Int = {
        if (isUnknown(objectTypeId))
            return 0;

        subclassTypesMap(objectTypeId).size + subinterfaceTypesMap(objectTypeId).size
    }

    /**
     * Tests if a subtype of the given `ObjectType` exists that satisfies the given predicate.
     * In this case the subtype relation is '''not reflexive'''.
     *
     * Subtypes for which no `ClassFile` object is available are ignored.
     *
     * @note    No explicit `isKnown` check is required; if the type is unknown nothing
     *          will happen.
     */
    def existsSubclass(
        objectType: ObjectType,
        project:    ClassFileRepository
    )(
        p: ClassFile => Boolean
    ): Boolean = {
        foreachSubtype(objectType) { objectType =>
            val cfOption = project.classFile(objectType) // IMPROVE implement Project.classFile(ObjectTypeID:Int)
            if (cfOption.isDefined && p(cfOption.get))
                return true;
        }
        false
    }

    def foreachDirectSupertypeCF[U](
        objectType: ObjectType
    )(
        f: ClassFile => U
    )(
        implicit
        project: ClassFileRepository
    ): Unit = {
        val oid = objectType.id
        if (isUnknown(oid))
            return ;

        val superinterfaceTypes = superinterfaceTypesMap(oid)
        if (superinterfaceTypes ne null) {
            superinterfaceTypes foreach { t => project.classFile(t).foreach(f) }
        }

        val superclassType = superclassTypeMap(oid)
        if (superclassType ne null) project.classFile(superclassType).foreach(f)

    }

    def foreachDirectSupertype(objectType: ObjectType)(f: ObjectType => Unit): Unit = {
        if (isUnknown(objectType))
            return ;

        val oid = objectType.id
        val superinterfaceTypes = superinterfaceTypesMap(oid)
        if (superinterfaceTypes ne null) superinterfaceTypes.foreach(f)
        val superclassType = superclassTypeMap(oid)
        if (superclassType ne null) f(superclassType)
    }

    /**
     * Calls the given function `f` for each of the given type's supertypes.
     */
    def foreachSupertype(
        ot:        ObjectType,
        reflexive: Boolean    = false
    )(
        f: ObjectType => Unit
    ): Unit = {
        val oid = ot.id
        if (reflexive) f(ot)
        if (isKnown(oid)) {
            supertypeInformationMap(oid).foreach(f)
        }
    }

    /**
     * Returns the list of all super types in initialization order.
     * I.e., it will return the top level super class first - i.e., `java.lang.Object`
     * and then all sub class types.
     *
     * If the given type is `java.lang.Object`, the empty list is returned.
     *
     * Interfaces are not further considered, because they generally don't need any instance
     * initialization. If the given type is an interface type, the returned list will hence only
     * contain `java.lang.Object`.
     *
     * @note    If the class hierarchy is not complete, it may happen that the super class chain
     *             is not complete. In this case an [[org.opalj.collection.IncompleteCollection]]
     *          will be returned.
     */
    def allSuperclassTypesInInitializationOrder(
        objectType: ObjectType
    ): QualifiedCollection[List[ObjectType]] = {
        val oid = objectType.id

        if (oid == ObjectType.ObjectId)
            return CompleteCollection(List());

        if (isUnknown(oid))
            return IncompleteCollection(List());

        var allTypes: List[ObjectType] = List.empty

        val superclassTypeMap = this.superclassTypeMap
        var superclassType = superclassTypeMap(oid)
        while (superclassType ne null) {
            allTypes ::= superclassType
            superclassType = superclassTypeMap(superclassType.id)
        }
        if (allTypes.head eq ObjectType.Object)
            CompleteCollection(allTypes)
        else
            IncompleteCollection(allTypes)
    }

    def directSupertypes(objectType: ObjectType): UIDSet[ObjectType] = {
        val oid = objectType.id
        if (oid == ObjectType.ObjectId || isUnknown(oid)) {
            UIDSet.empty
        } else {
            val superinterfaceTypes: UIDSet[ObjectType] = {
                val superinterfaceTypes = superinterfaceTypesMap(oid)
                if (superinterfaceTypes ne null)
                    superinterfaceTypes
                else
                    UIDSet.empty
            }
            val superclassType = superclassTypeMap(oid)
            if (superclassType ne null)
                superinterfaceTypes + superclassType
            else
                superinterfaceTypes
        }
    }

    /**
     * The set of all supertypes of the given type.
     *
     * @note  Whenever possible, one of the higher-order functions should be used to avoid the
     *        creation of intermediate data-structures.
     *
     * @param reflexive If `true`, the returned set will also contain the given type.
     */
    def allSupertypes(objectType: ObjectType, reflexive: Boolean = false): UIDSet[ObjectType] = {
        val oid = objectType.id
        if (isUnknown(oid))
            return UIDSet.empty;

        var supertypeInformation = supertypeInformationMap(oid)
        if (supertypeInformation == null) {
            // The following is thread-safe, because we will always compute the same
            // information!
            // This happens ONLY in case of broken projects where
            // the sub-supertype information is totally broken;
            // e.g., a sub type `extends C` but C is an interface.
            supertypeInformation = interpolateSupertypeInformation(objectType)
            supertypeInformationMap(oid) = supertypeInformation
        }
        val ts = supertypeInformation.allTypes
        if (reflexive)
            ts + objectType
        else
            ts
    }

    /**
     * Returns the set of all interfaces directly or indirectly implemented by the given type.
     *
     * @param reflexive If `true` the returned set will also contain the given type if
     *      it is an interface type.
     * @throws NullPointerException if the project is very broken (e.g., if a class states
     *         that it inherits from class C, but class C is actually an interface).
     */
    def allSuperinterfacetypes(
        objectType: ObjectType,
        reflexive:  Boolean    = false
    ): UIDSet[ObjectType] = {
        val oid = objectType.id
        if (isUnknown(oid))
            return UIDSet.empty

        var supertypeInformation = supertypeInformationMap(oid)
        if (supertypeInformation == null) {
            // The following is thread-safe, because we will always compute the same information!
            // This happens ONLY in case of broken projects where the sub-supertype information is
            // totally broken; e.g., a sub type `extends C` but C is an interface.
            supertypeInformation = interpolateSupertypeInformation(objectType)
            supertypeInformationMap(oid) = supertypeInformation
        }
        val superinterfacetypes = supertypeInformation.interfaceTypes
        if (reflexive && isInterfaceTypeMap(oid))
            superinterfacetypes + objectType
        else
            superinterfacetypes
    }

    private[this] def interpolateSupertypeInformation(o: ObjectType): SupertypeInformation = {
        val allClassTypes = UIDSet.empty[ObjectType] ++ allSuperclassTypesInInitializationOrder(o).s

        var allInterfaceTypes = UIDSet.empty[ObjectType]
        foreachSuperinterfaceType(o) { supertype => allInterfaceTypes += supertype; true }

        SupertypeInformation.forSubtypesOfObject(
            isKnownTypeMap,
            isInterfaceTypeMap,
            allClassTypes,
            allInterfaceTypes,
            UIDSet.empty
        )
    }

    /**
     * Calls the function `f` for each supertype of the given object type for
     * which the classfile is available.
     *
     * It is possible that the class file of the same super interface type `I`
     * is passed multiple times to `f` when `I` is implemented multiple times
     * by the given type's supertypes.
     *
     * The algorithm first iterates over the type's super classes
     * before it iterates over the super interfaces.
     *
     * @note See [[foreachSupertype]] for details.
     */
    def foreachSuperclass(
        objectType: ObjectType,
        project:    ClassFileRepository
    )(
        f: ClassFile => Unit
    ): Unit = {
        foreachSupertype(objectType) { supertype => project.classFile(supertype).foreach(f) }
    }

    /**
     * Returns the set of all classes/interfaces from which the given type inherits
     * and for which the respective class file is available.
     *
     * @return  An `Iterable` over all class files of all super types of the given
     *          `objectType` that pass the given filter and for which the class file
     *          is available.
     * @note    It may be more efficient to use `foreachSuperclass(ObjectType,
     *          ObjectType => Option[ClassFile])(ClassFile => Unit)`
     */
    def superclasses(
        objectType: ObjectType,
        project:    ClassFileRepository
    )(
        classFileFilter: ClassFile => Boolean = { _ => true }
    ): Iterable[ClassFile] = {
        // We want to make sure that every class file is returned only once,
        // but we want to avoid equals calls on `ClassFile` objects.
        var classFiles = Map[ObjectType, ClassFile]()
        foreachSuperclass(objectType, project) { classFile =>
            if (classFileFilter(classFile))
                classFiles = classFiles.updated(classFile.thisType, classFile)
        }
        classFiles.values
    }

    /**
     * Efficient, best-effort iterator over all super types of the given type.
     */
    def allSuperclassesIterator(
        ot:        ObjectType,
        reflexive: Boolean    = false
    )(
        implicit
        project: ClassFileRepository
    ): Iterator[ClassFile] = {
        val oid = ot.id

        val baseTypes = if (isKnown(oid)) {
            supertypeInformationMap(oid).iterator
        } else {
            Iterator.empty
        }

        val allTypes = if (reflexive) baseTypes ++ Iterator(ot) else baseTypes

        allTypes
            .filter(t => project.classFile(t).isDefined)
            .map(t => project.classFile(t).get)
    }

    /**
     * Returns `Some()` if this type is known and information about the
     * supertypes is available. I.e., if this type is not known, `None` is returned;
     * if the given type's superinterfaces are known (even if this class does not
     * implement (directly or indirectly) any interface) `Some(UIDSet())` is
     * returned.
     */
    // TODO Rename => directSuperinterfacetypes
    def superinterfaceTypes(objectType: ObjectType): Option[UIDSet[ObjectType]] = {
        val oid = objectType.id
        if (isUnknown(oid))
            return None;

        val superinterfaceTypes = superinterfaceTypesMap(oid)
        if (superinterfaceTypes ne null)
            Some(superinterfaceTypes)
        else
            None
    }

    /**
     * Returns the immediate superclass of the given object type, if the given
     * type is known and if it has a superclass. I.e., in case of `java.lang.Object` None is
     * returned.
     */
    def superclassType(objectType: ObjectType): Option[ObjectType] = {
        val oid = objectType.id
        if (isUnknown(oid))
            return None;

        val superclassType = superclassTypeMap(oid)
        if (superclassType ne null)
            Some(superclassType)
        else
            None
    }

    /**
     * Returns the supertype of the object type identified by the given object type id or `null`
     * if the type is unknown or if the type has no supertype.
     */
    def superclassType(objectTypeId: Int): ObjectType = {
        if (isKnown(objectTypeId))
            superclassTypeMap(objectTypeId) // may also be null
        else
            null
    }

    // TODO Rename => directSupertypes
    def supertypes(objectType: ObjectType): UIDSet[ObjectType] = {
        superinterfaceTypes(objectType) match {
            case None =>
                superclassType(objectType).map(UIDSet1.apply).getOrElse(UIDSet.empty)
            case Some(superinterfaceTypes) =>
                superinterfaceTypes ++ superclassType(objectType)
        }
    }

    def foreachDirectSubtypeOf[U](objectType: ObjectType)(f: ObjectType => U): Unit = {
        val oid = objectType.id
        if (isUnknown(oid))
            return ;

        this.subclassTypesMap(oid).foreach(f)
        this.subinterfaceTypesMap(oid).foreach(f)
    }

    /**
     * The direct subtypes of the given type (not reflexive).
     */
    def directSubtypesOf(objectType: ObjectType): Iterator[ObjectType] = {
        val oid = objectType.id
        if (isUnknown(oid))
            return Iterator.empty;

        this.subclassTypesMap(oid).iterator ++ this.subinterfaceTypesMap(oid).iterator
    }

    def directSubclassesOf(objectType: ObjectType): UIDSet[ObjectType] = {
        val oid = objectType.id
        if (isUnknown(oid))
            return UIDSet.empty;

        this.subclassTypesMap(oid)
    }

    def directSubinterfacesOf(objectType: ObjectType): UIDSet[ObjectType] = {
        val oid = objectType.id
        if (isUnknown(oid))
            return UIDSet.empty;

        this.subinterfaceTypesMap(oid)
    }

    def directSuperinterfacesOf(objectType: ObjectType): UIDSet[ObjectType] = {
        val oid = objectType.id
        if (isUnknown(oid))
            return UIDSet.empty;

        this.superinterfaceTypesMap(oid)
    }

    /**
     * Iterates over all subinterfaces of the given interface type (or java.lang.Object) until
     * the callback function returns "false".
     */
    def foreachSubinterfaceType(interfaceType: ObjectType)(f: ObjectType => Boolean): Unit = {
        var processedTypes = UIDSet.empty[ObjectType]
        var typesToProcess = directSubinterfacesOf(interfaceType)
        while (typesToProcess.nonEmpty) {
            val subInterfaceType = typesToProcess.head
            typesToProcess = typesToProcess.tail
            processedTypes += subInterfaceType
            if (f(subInterfaceType)) {
                directSubinterfacesOf(subInterfaceType) foreach { i =>
                    if (!processedTypes.contains(i))
                        typesToProcess += i
                }
            }
        }
    }

    /**
     * Iterates over all direct and indirect (also by means of super classes) superinterfaces
     * of the type  until the callback function returns "false".
     */
    def foreachSuperinterfaceType(t: ObjectType)(f: ObjectType => Boolean): Unit = {
        if (isUnknown(t))
            return ;

        var processedTypes = UIDSet.empty[ObjectType]
        var typesToProcess = directSuperinterfacesOf(t) ++ superclassType(t)
        while (typesToProcess.nonEmpty) {
            val superType = typesToProcess.head
            typesToProcess = typesToProcess.tail
            processedTypes += superType
            if (!isInterface(superType.id) || f(superType)) {
                val superInterfaces: Iterator[ObjectType] =
                    if (directSuperinterfacesOf(superType) eq null) {
                        Iterator.empty
                    } else {
                        directSuperinterfacesOf(superType).iterator
                    }
                (superInterfaces ++ superclassType(superType).iterator) foreach { i =>
                    if (!processedTypes.contains(i))
                        typesToProcess += i
                }
            }
        }
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    //
    // "HIGHER-LEVEL" QUERIES RELATED TO THE JVM/JAVA SPECIFICATION
    //
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    /**
     * Determines if `subtype` is a subtype of `supertype` using this class hierarchy.
     *
     * This method can be used as a foundation for implementing the logic of the JVM's
     * `instanceof` and `classcast` instructions. But, in both cases additional logic
     * for handling `null` values and for considering the runtime type needs to be
     * implemented by the caller of this method.
     *
     * @note    The answer `false` does not necessarily imply that two '''runtime values''' for
     *          which the given types are only upper bounds are not (w.r.t. '''their
     *          runtime types''') in a subtype relation. E.g., if `subtype` denotes the type
     *          `java.util.List` and `supertype` denotes the type `java.util.ArrayList` then
     *          the answer is clearly `false`. But, at runtime, this may not be the case. I.e.,
     *          only the answer `true` is conclusive. In case of `false` further information
     *          needs to be taken into account by the caller to determine what it means that
     *          the (upper) type (bounds) of the underlying values are not in an inheritance
     *          relation.
     * @param   subtype Any class, interface  or array type.
     * @param   supertype Any class, interface or array type.
     * @return  `true` if `subtype` is indeed a subtype of the given `supertype`. `false` is
     *          returned if the typing relation is unknown OR if `subtype` is definitively not
     *          a subtype of `supertype`.
     */
    @tailrec final def isSubtypeOf(subtype: ReferenceType, supertype: ReferenceType): Boolean = {
        // necessary to get reasonable answers in case of incomplete type hierarchies
        if (subtype eq supertype)
            return true;

        if (subtype.isObjectType) {
            if (supertype.isObjectType)
                isSubtypeOf(subtype.asObjectType, supertype.asObjectType)
            else
                // the supertype is an array type..
                false
        } else {
            // the subtype is an array type
            if (supertype.isObjectType) {
                (supertype eq ObjectType.Object) ||
                    (supertype eq ObjectType.Serializable) || (supertype eq ObjectType.Cloneable)
            } else {
                // ... and the supertype is also an array type
                // The case:
                //    `componentType eq superComponentType`
                // is already handled by the very first test `subtype eq supertype` because
                // ArrayTypes are internalized.
                val componentType = subtype.asArrayType.componentType
                val superComponentType = supertype.asArrayType.componentType
                if (superComponentType.isBaseType || componentType.isBaseType)
                    false
                else
                    isSubtypeOf(componentType.asReferenceType, superComponentType.asReferenceType)
            }
        }
    }

    /**
     * Returns `true` if subtype is a subtype of `supertype`; `false` is returned if
     * the subtyping relationship is unknown OR `subtype` is not a subtype of `supertype`.
     * `isSubtypeOf` is reflexive.
     *
     * See `isASubtypeOf` if more precise information about the subtyping relationship is required.
     */
    def isSubtypeOf(subtype: ObjectType, theSupertype: ObjectType): Boolean = {
        if (subtype eq theSupertype)
            return true;

        val theSupertypeId = theSupertype.id
        if (isUnknown(theSupertypeId))
            return false;

        subtypeInformationMap(theSupertypeId).contains(subtype)
    }

    /**
     * Determines if the type described by the first set of upper type bounds is
     * a subtype of the second type. I.e., it checks, if for all types of the
     * `subtypes` upper type bound, a type in the `supertypes` type exists that is a
     * supertype of the respective subtype. If `subtypes` is empty, `true` will be
     * returned; an empty upper type bound is expected to model `null`.
     */
    def isSubtypeOf(
        subtypes:   UIDSet[_ <: ReferenceType],
        supertypes: UIDSet[_ <: ReferenceType]
    ): Boolean = {
        if (subtypes.isEmpty /*the upper type bound of "null" values*/ || subtypes == supertypes)
            return true;

        supertypes forall { supertype: ReferenceType =>
            subtypes exists { subtype: ReferenceType =>
                this.isSubtypeOf(subtype, supertype)
            }
        }
    }

    /**
     * Returns `true` if the subtype is a subtype of '''all''' given supertypes. Hence,
     * supertypes should not contain more than one class type.
     */
    def isSubtypeOf(subtype: ReferenceType, supertypes: UIDSet[_ <: ReferenceType]): Boolean = {
        if (supertypes.isEmpty /*the upper type bound of "null" values*/ )
            return false;

        supertypes forall { supertype: ReferenceType => isSubtypeOf(subtype, supertype) }
    }

    def isSubtypeOf(subtypes: UIDSet[_ <: ReferenceType], supertype: ReferenceType): Boolean = {
        if (subtypes.isEmpty) /*the upper type bound of "null" values*/
            return true;

        subtypes exists { subtype: ReferenceType => this.isSubtypeOf(subtype, supertype) }
    }

    /**
     * Determines if a value of type `elementValueType` can be stored in an array of
     * type `arrayType`. E.g., a value of type `IntegerType` can be stored in an
     * array (one-dimensional) of type `ArrayType(IntegerType)`. This method takes
     * the fact that a type may just model an upper type bound into account.
     *
     * @param   elementValueType The type of the value that should be stored in the
     *          array. This type is compared against the component type of the array.
     * @param   elementValueTypeIsPrecise Specifies if the type information is precise;
     *          i.e., whether `elementValueType` models the precise runtime type (`true`)
     *          or just an upper bound (`false`). If the `elementValueType` is a base/
     *          primitive type, then this value should be `true`; but actually it is
     *          ignored.
     * @param   arrayType The type of the array.
     * @param   arrayTypeIsPrecise Specifies if the type information is precise;
     *          i.e., whether arrayType models the precise runtime type (`true`)
     *          or just an upper bound (`false`).
     */
    @tailrec final def canBeStoredIn(
        elementValueType:          FieldType,
        elementValueTypeIsPrecise: Boolean,
        arrayType:                 ArrayType,
        arrayTypeIsPrecise:        Boolean
    ): Answer = {
        if (elementValueType.isBaseType) {
            Answer(elementValueType eq arrayType.componentType)
        } else if (elementValueType.isArrayType) {
            if (arrayType.componentType.isArrayType) {
                canBeStoredIn(
                    elementValueType.asArrayType.componentType,
                    elementValueTypeIsPrecise,
                    arrayType.componentType.asArrayType,
                    arrayTypeIsPrecise
                )
            } else if (arrayType.componentType.isBaseType) {
                No
            } else /*arrayType.componentType.isObjectType*/ {
                val componentObjectType = arrayType.componentType.asObjectType
                // Recall that `isSubtypeOf` completely handles all cases that make
                // it possible to store an array in a value of type ObjectType.
                isASubtypeOf(elementValueType.asArrayType, componentObjectType) match {
                    case Yes => if (arrayTypeIsPrecise) Yes else Unknown
                    case No  => No
                    case _   => throw new AssertionError("some array type <: some object type failed")
                }
            }
        } else /* the type of the element value is an ObjectType*/ {
            if (arrayType.elementType.isBaseType) {
                No
            } else {
                val elementValueObjectType = elementValueType.asObjectType
                val arrayComponentReferenceType = arrayType.componentType.asReferenceType
                isASubtypeOf(elementValueObjectType, arrayComponentReferenceType) match {
                    case Yes =>
                        if (arrayTypeIsPrecise || isKnownToBeFinal(elementValueObjectType))
                            Yes
                        else
                            Unknown
                    case No =>
                        if (elementValueTypeIsPrecise && arrayTypeIsPrecise)
                            No
                        else
                            Unknown
                    case unknown => unknown
                }
            }
        }
    }

    /**
     * Determines if the given class or interface type `subtype` is actually a subtype
     * of the class or interface type `supertype`.
     *
     * This method can be used as a foundation for implementing the logic of the JVM's
     * `instanceof` and `checkcast` instructions. But, in that case additional logic
     * for handling `null` values and for considering the runtime type needs to be
     * implemented by the caller of this method.
     *
     * @note    No explicit `isKnown` check is required.
     * @param   subtype Any `ObjectType`.
     * @param   theSupertype Any `ObjectType`.
     * @return  `Yes` if `subtype` is a subtype of the given `supertype`. `No`
     *          if `subtype` is not a subtype of `supertype` and `Unknown` if the analysis is
     *          not conclusive. The latter can happen if the class hierarchy is not
     *          complete and hence precise information about a type's supertypes
     *          is not available.
     *
     */
    def isASubtypeOf(subtype: ObjectType, theSupertype: ObjectType): Answer = {
        if (subtype eq theSupertype)
            return Yes;

        val Object = ObjectType.Object
        if (theSupertype eq Object)
            return Yes;

        if (subtype eq Object /* && theSupertype != ObjectType.Object is already handled */ )
            return No;

        val subtypeId = subtype.id
        val theSupertypeId = theSupertype.id

        if (isKnownToBeFinal(theSupertypeId))
            return No;

        if (isUnknown(subtypeId))
            return Unknown;

        if (isUnknown(theSupertypeId)) {
            return if (isSupertypeInformationCompleteMap(subtypeId)) No else Unknown;
        }

        val isInterfaceTypeMap = this.isInterfaceTypeMap
        val subtypeIsInterface = isInterfaceTypeMap(subtypeId)
        val supertypeIsInterface = isInterfaceTypeMap(theSupertypeId)

        if (subtypeIsInterface && !supertypeIsInterface)
            // An interface always (only) directly inherits from java.lang.Object
            // and this is already checked before.
            return No;

        val supertypeInformationMap = this.supertypeInformationMap
        if (supertypeInformationMap(subtypeId).containsId(theSupertypeId))
            Yes
        else if (isSupertypeInformationCompleteMap(subtypeId))
            No
        else if (supertypeInformationMap(theSupertypeId).containsId(subtypeId))
            No
        else
            Unknown
    }

    /**
     * Determines if `subtype` is a subtype of `supertype` using this class hierarchy.
     *
     * This method can be used as a foundation for implementing the logic of the JVM's
     * `instanceof` and `classcast` instructions. But, in both cases additional logic
     * for handling `null` values and for considering the runtime type needs to be
     * implemented by the caller of this method.
     *
     * @note    The answer `No` does not necessarily imply that two '''runtime values''' for
     *          which the given types are only upper bounds are not (w.r.t. '''their
     *          runtime types''') in a subtype relation. E.g., if `subtype` denotes the type
     *          `java.util.List` and `supertype` denotes the type `java.util.ArrayList` then
     *          the answer is clearly `No`. But, at runtime, this may not be the case. I.e.,
     *          only the answer `Yes` is conclusive. In case of `No` further information
     *          needs to be taken into account by the caller to determine what it means that
     *          the (upper) type (bounds) of the underlying values are not in an inheritance
     *          relation.
     * @param   subtype Any class, interface  or array type.
     * @param   supertype Any class, interface or array type.
     * @return  `Yes` if `subtype` is indeed a subtype of the given `supertype`. `No`
     *          if `subtype` is not a subtype of `supertype` and `Unknown` if the analysis is
     *          not conclusive. The latter can happen if the class hierarchy is not
     *          completely available and hence precise information about a type's supertypes
     *          is not available.
     */
    @tailrec final def isASubtypeOf(subtype: ReferenceType, supertype: ReferenceType): Answer = {

        // The following two tests are particularly relevant in case of incomplete
        // class hierarchies since they allow to give definitive answers in some
        // cases of missing type information.
        if ((subtype eq supertype) || (supertype eq Object))
            return Yes;

        if (subtype eq Object)
            return No; // the given supertype has to be a subtype...

        if (subtype.isObjectType) {
            if (supertype.isArrayType)
                No
            else
                // The analysis is conclusive iff we can get all supertypes
                // for the given type (ot) up until "java/lang/Object"; i.e.,
                // if there are no holes.
                isASubtypeOf(subtype.asObjectType, supertype.asObjectType)
        } else {
            // ... subtype is an ArrayType
            if (supertype.isObjectType) {
                Answer(
                    (supertype eq ObjectType.Serializable) || (supertype eq ObjectType.Cloneable)
                )
            } else {
                val componentType = subtype.asArrayType.componentType
                val superComponentType = supertype.asArrayType.componentType
                if (superComponentType.isBaseType || componentType.isBaseType)
                    // Recall that the case:
                    //
                    //    `componentType eq superComponentType`
                    //
                    // is already handled by the very first test `subtype eq supertype` because
                    // ArrayTypes are internalized.
                    No
                else
                    isASubtypeOf(componentType.asReferenceType, superComponentType.asReferenceType)
            }
        }
    }

    /**
     * Determines if the type described by the first set of upper type bounds is
     * a subtype of the second type. I.e., it checks, if for all types of the
     * `subtypes` upper type bound, a type in the `supertypes` type exists that is a
     * supertype of the respective subtype. If `subtypes` is empty, `Yes` will be
     * returned; an empty upper type bound is expected to model `null`.
     */
    def isASubtypeOf(
        subtypes:   UIDSet[_ <: ReferenceType],
        supertypes: UIDSet[_ <: ReferenceType]
    ): Answer = {
        if (subtypes.isEmpty /* <=> upper type bound of "null" values */ || subtypes == supertypes)
            return Yes;

        Answer(
            supertypes forall { supertype: ReferenceType =>
                var subtypingRelationUnknown = false
                val subtypeExists =
                    subtypes exists { subtype: ReferenceType =>
                        val isSubtypeOf = this.isASubtypeOf(subtype, supertype)
                        isSubtypeOf match {
                            case Yes     => true
                            case Unknown => { subtypingRelationUnknown = true; false /* continue */ }
                            case No      => false
                        }
                    }
                if (subtypeExists)
                    true
                else if (subtypingRelationUnknown)
                    return Unknown;
                else
                    false
            }
        )
    }

    /**
     * Returns `Yes` if the subtype is a subtype of '''all''' given supertypes. Hence,
     * supertypes should not contain more than one class type.
     */
    def isASubtypeOf(subtype: ReferenceType, supertypes: UIDSet[_ <: ReferenceType]): Answer = {
        if (supertypes.isEmpty /* <=> upper type bound of "null" values */ )
            return No;

        supertypes foreach { supertype: ReferenceType =>
            isASubtypeOf(subtype, supertype) match {
                case Yes     => /*Nothing to do*/
                case Unknown => return Unknown; // FIXME No should have precedence over Unknown even if some supertypes are Unknown...
                case No      => return No;
            }
        }
        // subtype is a subtype of all supertypes
        Yes
    }

    def isASubtypeOf(subtypes: UIDSet[_ <: ReferenceType], supertype: ReferenceType): Answer = {
        if (subtypes.isEmpty) /* <=> upper type bound of "null" values */
            return Yes;

        var subtypeRelationUnknown = false
        val subtypeExists =
            subtypes exists { subtype: ReferenceType =>
                this.isASubtypeOf(subtype, supertype) match {
                    case Yes     => true
                    case Unknown => { subtypeRelationUnknown = true; false /* continue search */ }
                    case No      => false
                }
            }
        if (subtypeExists)
            Yes
        else if (subtypeRelationUnknown)
            Unknown
        else
            No

    }

    /**
     * Computes the set of types which are subtypes (reflexive) of all types identified by the
     * given `upper type bound`. E.g., the class X which implements I and J,
     * would be a direct subtype of the upper type bound consisting of I and J.
     * If the bound consists of only one type, then the bound is returned.
     *
     * @param   upperTypeBound A set of types that are in no inheritance relationship.
     *          `upperTypeBound` must not be empty.
     */
    def directSubtypesOf(upperTypeBound: UIDSet[ObjectType]): UIDSet[ObjectType] = {
        if (upperTypeBound.isSingletonSet)
            return upperTypeBound;

        val firstType = upperTypeBound.head
        val remainingTypeBounds = upperTypeBound.tail

        // Basic Idea: Let's do a breadth-first search and for every candidate type
        // we check whether the type is a subtype of all types in the bound.
        // If so, the type is added to the result set and the search terminates
        // for this particular type.

        // The analysis is complicated by the fact that an interface may be
        // implemented multiple times, e.g.,:
        // interface I
        // interface J extends I
        // class X implements I,J
        // class Y implements J

        var directSubtypes = UIDSet.empty[ObjectType]
        var processedTypes = UIDSet.empty[ObjectType]
        val typesToProcess = new mutable.Queue ++= directSubtypesOf(firstType)
        while (typesToProcess.nonEmpty) {
            val candidateType = typesToProcess.dequeue()
            processedTypes += candidateType
            val isCommonSubtype =
                remainingTypeBounds.forall { otherTypeBound: ObjectType =>
                    isASubtypeOf(candidateType, otherTypeBound).isYesOrUnknown
                }
            if (isCommonSubtype) {
                directSubtypes =
                    directSubtypes.filter { candidateDirectSubtype =>
                        isASubtypeOf(candidateDirectSubtype, candidateType).isNoOrUnknown
                    } +
                        candidateType
            } else {
                directSubtypesOf(candidateType).foreach { candidateType =>
                    if (!processedTypes.contains(candidateType))
                        typesToProcess += candidateType
                }
            }
        }

        directSubtypes
    }

    //
    //
    // SUBTYPE RELATION W.R.T. GENERIC TYPES
    //
    //

    /*
     * This is a helper method only. TypeArguments are just a part of a generic
     * `ClassTypeSignature`. Hence, it makes no
     * sense to check subtype relation of incomplete information.
     *
     * @note At the comparison of two [[GenericTypeArgument]]s without [[VarianceIndicator]]s
     * we have to check two different things. First compare the [[ObjectType]]s, if they are equal
     * we still have to care about the [[TypeArgument]]s since we are dealing with generics.
     */
    private[this] def isASubtypeOfByTypeArgument(
        subtype:   TypeArgument,
        supertype: TypeArgument
    )(
        implicit
        p: ClassFileRepository
    ): Answer = {
        (subtype, supertype) match {
            case (ConcreteTypeArgument(et), ConcreteTypeArgument(superEt)) => Answer(et eq superEt)
            case (ConcreteTypeArgument(et), UpperTypeBound(superEt))       => isASubtypeOf(et, superEt)
            case (ConcreteTypeArgument(et), LowerTypeBound(superEt))       => isASubtypeOf(superEt, et)
            case (_, Wildcard)                                             => Yes
            case (GenericTypeArgument(varInd, cts), GenericTypeArgument(supVarInd, supCts)) =>
                (varInd, supVarInd) match {
                    case (None, None) =>
                        if (cts.objectType eq supCts.objectType) isASubtypeOf(cts, supCts) else No
                    case (None, Some(CovariantIndicator)) =>
                        isASubtypeOf(cts, supCts)
                    case (None, Some(ContravariantIndicator)) =>
                        isASubtypeOf(supCts, cts)
                    case (Some(CovariantIndicator), Some(CovariantIndicator)) =>
                        isASubtypeOf(cts, supCts)
                    case (Some(ContravariantIndicator), Some(ContravariantIndicator)) =>
                        isASubtypeOf(supCts, cts)
                    case _ => No
                }
            case (UpperTypeBound(et), UpperTypeBound(superEt)) => isASubtypeOf(et, superEt)
            case (LowerTypeBound(et), LowerTypeBound(superEt)) => isASubtypeOf(superEt, et)
            case _                                             => No
        }
    }

    @inline @tailrec private[this] final def compareTypeArguments(
        subtypeArgs:   List[TypeArgument],
        supertypeArgs: List[TypeArgument]
    )(
        implicit
        p: ClassFileRepository
    ): Answer = {

        (subtypeArgs, supertypeArgs) match {
            case (Nil, Nil)          => Yes
            case (Nil, _) | (_, Nil) => No
            case (arg :: tail, supArg :: supTail) =>
                // IMPROVE Consider implementing an "isSubtypeOfByTypeArgument:Boolean" method.
                val isASubtypeOf = isASubtypeOfByTypeArgument(arg, supArg)
                if (isASubtypeOf.isNoOrUnknown)
                    isASubtypeOf
                else
                    compareTypeArguments(tail, supTail)
        }
    }

    /**
     * Determines whether the given [[ClassSignature]] of `subtype` implements or extends
     * the given `supertype`.
     * In case that the `subtype` does implement or extend the `supertype`, an `Option` of
     * [[ClassTypeSignature]] is returned. Otherwise None will be returned.
     *
     * @example
     *  subtype: [[ClassSignature]] from class A where A extends List
     *  supertype: List as [[ObjectType]]
     *
     *  This method scans all super classes and super interfaces of A in order to find
     *  the concrete class declaration of List where it is bound to String. The above example
     *  would yield the [[ClassTypeSignature]] of List.
     *
     * @param subtype Any type or interface.
     * @param supertype Any type or interface.
     * @return `Option` of [[ClassTypeSignature]] if the `subtype` extends or implements
     *          the given `supertype`, `None` otherwise.
     */
    def getSupertypeDeclaration(
        subtype:   ClassSignature,
        supertype: ObjectType
    )(
        implicit
        project: ClassFileRepository
    ): Option[ClassTypeSignature] = {

        val signaturesToCheck = subtype.superClassSignature :: subtype.superInterfacesSignature
        for {
            cts <- signaturesToCheck if cts.objectType eq supertype
        } { return Some(cts) }

        for {
            cts <- signaturesToCheck
            superCs <- getClassSignature(cts.objectType)
            matchingType <- getSupertypeDeclaration(superCs, supertype)
        } { return Some(matchingType) }

        None
    }

    /**
     * Returns the object type's class signature if the class files is available and
     * a class signature is defined.
     */
    @inline private[this] final def getClassSignature(
        ot: ObjectType
    )(
        implicit
        p: ClassFileRepository
    ): Option[ClassSignature] = {
        p.classFile(ot).flatMap(cf => cf.classSignature)
    }

    /**
     * Determines if the given class or interface type encoded by the
     * [[ClassTypeSignature]] `subtype` is actually a subtype
     * of the class or interface type encoded in the [[ClassTypeSignature]] of the
     * `supertype`.
     *
     * @note This method relies – in case of a comparison of non generic types – on
     *       `isSubtypeOf(org.opalj.br.ObjectType,org.opalj.br.ObjectType)` of `Project` which
     *        performs an upwards search only. E.g., given the following
     *      type hierarchy:
     *      `class D inherits from C`
     *      `class E inherits from D`
     *      and the query isSubtypeOf(D,E) the answer will be `Unknown` if `C` is
     *      `Unknown` and `No` otherwise.
     * @param subtype Any `ClassTypeSignature`.
     * @param supertype Any `ClassTypeSignature`.
     * @return `Yes` if `subtype` is a subtype of the given `supertype`. `No`
     *      if `subtype` is not a subtype of `supertype` and `Unknown` if the analysis is
     *      not conclusive. The latter can happen if the class hierarchy is not
     *      complete and hence precise information about a type's supertypes
     *      is not available.
     * @example =========  Introduction ==========
     *
     *  Before looking in some examples, we have to set up the terminology.
     *
     *  Type definition: List
     *
     *  ContainerType - A ContainerType is a type with parameters. In the previous type definition
     *                  is `List` the ContainerType.
     *  TypeArgument - A [[TypeArgument]] is one of the parameters of the ContainerType. The above type
     *                  definition has three [[TypeArgument]]s. (String, ? extends Number and ?)
     *  VarianceIndicator - A [[VarianceIndicator]] is defined in the context of [[TypeArgument]]s. There
     *                      is a [[CovariantIndicator]] which can be defined in the type definition by using the
     *                      `extends` keyword. (? extends Number is a covariant [[TypeArgument]]). The other
     *                      one is the [[ContravariantIndicator]] which is defined using the `super` keyword.
     * @example ========= 1 ==========
     *
     *                instance // definition
     *      subtype: List // List
     *      supertype: List // List
     *
     *      If the ContainerType of the `subtype` is equal to the ContainerType of the `supertype` and non of the
     *      [[TypeArgument]]s has a [[VarianceIndicator]], then exists a subtype relation if and only if all of the
     *      [[TypeArgument]]s are equal.
     * @example ========= 2 =========
     *
     * subtype:     SomeClass // SomeClass extends SomeInterface
     * supertype:   SomeInterface // SomeInterface
     *
     * Is the `subtype` a [[ConcreteType]] without [[org.opalj.br.FormalTypeParameter]]s and the `supertype` is a [[GenericType]] then
     * we first have to check whether the `subtype` is a subtype of the given `supertype`. If not, then the `subtype` is not an actual
     * subtype of the given `supertype`. Otherwise we have to find the definition of the `supertype` in the type definition
     * or the type definition of a super class or a super interface (interface definition of SomeInterface).
     * Once found the `supertype`, we can compare all [[TypeArgument]]s of the supertype definition of the `subtype`
     * and the given `supertype`. (We are comparing String and String in this example)
     * If all of them are equal, `subtype` is an actual subtype of the `supertype`.
     * @example ========= 3 =========
     *
     * subtype:     Foo // Foo extends Bar
     * supertype:   Bar // Bar
     *
     * Does the `subtype` and `supertype` have [[FormalTypeParameter]]s and the ContainerType of the `subtype`
     * is a subtype of the ContainerType of the `supertype`, we have to compare the shared [[TypeArgument]]s. In
     * our example the subtype Foo has two [[FormalTypeParameter]] (T,E) and the supertype Bar has only one
     * [[FormalTypeParameter]] (E). Since both of them specify E in the [[ClassSignature]] of Foo, they share E as
     * [[FormalTypeParameter]]. So it is necessary to check whether the actual bound [[TypeArgument]] at the
     * position of E is equal. At first we have to locate the shared parameter in the [[ClassSignature]], so it is possible
     * to find the correct [[TypeArgument]]s. The above example shows that the shared parameter E is in the second position
     * of the [[FormalTypeParameter]]s of Foo and at the first position of the [[FormalTypeParameter]]s of Bar. Second and last
     * we know can compare the according [[TypeArgument]]s. All other parameters can be ignored because they are no important
     * to decide the subtype relation.
     */
    def isASubtypeOf(
        subtype:   ClassTypeSignature,
        supertype: ClassTypeSignature
    )(
        implicit
        project: ClassFileRepository
    ): Answer = {
        def compareTypeArgumentsOfClassSuffixes(
            suffix:      List[SimpleClassTypeSignature],
            superSuffix: List[SimpleClassTypeSignature]
        ): Answer = {
            if (suffix.isEmpty && superSuffix.isEmpty)
                return Yes;

            suffix.zip(superSuffix).foldLeft(Yes: Answer)((acc, value) =>
                (acc, compareTypeArguments(value._1.typeArguments, value._2.typeArguments)) match {
                    case (_, Unknown)     => return Unknown;
                    case (x, y) if x ne y => No
                    case (x, _ /*x*/ )    => x
                })
        }
        if (subtype.objectType eq supertype.objectType) {
            (subtype, supertype) match {
                case (ConcreteType(_), ConcreteType(_)) =>
                    Yes

                case (GenericType(_, _), ConcreteType(_)) =>
                    isASubtypeOf(subtype.objectType, supertype.objectType)

                case (GenericType(_, elements), GenericType(_, superElements)) =>
                    compareTypeArguments(elements, superElements)

                case (GenericTypeWithClassSuffix(_, elements, suffix), GenericTypeWithClassSuffix(_, superElements, superSuffix)) => {
                    compareTypeArguments(elements, superElements) match {
                        case Yes    => compareTypeArgumentsOfClassSuffixes(suffix, superSuffix)
                        case answer => answer
                    }
                }

                case _ => No
            }
        } else {
            val isASubtype = isASubtypeOf(subtype.objectType, supertype.objectType)
            if (isASubtype.isYes) {

                def haveSameTypeBinding(
                    subtype:            ObjectType,
                    supertype:          ObjectType,
                    supertypeArguments: List[TypeArgument],
                    isInnerClass:       Boolean            = false
                ): Answer = {
                    getClassSignature(subtype).map { cs =>
                        getSupertypeDeclaration(cs, supertype).map { matchingType =>
                            val classSuffix = matchingType.classTypeSignatureSuffix
                            if (isInnerClass && classSuffix.nonEmpty)
                                compareTypeArguments(classSuffix.last.typeArguments, supertypeArguments)
                            else
                                compareTypeArguments(matchingType.simpleClassTypeSignature.typeArguments, supertypeArguments)
                        } getOrElse No
                    } getOrElse Unknown
                }
                def compareSharedTypeArguments(
                    subtype:            ObjectType,
                    typeArguments:      List[TypeArgument],
                    supertype:          ObjectType,
                    supertypeArguments: List[TypeArgument]
                ): Answer = {

                    val cs = getClassSignature(subtype)
                    val superCs = getClassSignature(supertype)
                    if (cs.isEmpty || superCs.isEmpty)
                        return Unknown;

                    val ftp = cs.get.formalTypeParameters
                    val superFtp = superCs.get.formalTypeParameters
                    var typeArgs = List.empty[TypeArgument]
                    var supertypeArgs = List.empty[TypeArgument]

                    var i = 0
                    while (i < ftp.size) {
                        val index = superFtp.indexOf(ftp(i))
                        if (index >= 0) {
                            typeArgs = typeArguments(i) :: typeArgs
                            supertypeArgs = supertypeArguments(index) :: supertypeArgs
                        }
                        i = i + 1
                    }

                    if (typeArgs.isEmpty) {
                        if (cs.get.superClassSignature.classTypeSignatureSuffix.nonEmpty)
                            Yes
                        else
                            haveSameTypeBinding(subtype, supertype, supertypeArguments)
                    } else {
                        compareTypeArguments(typeArgs, supertypeArgs)
                    }

                }
                (subtype, supertype) match {
                    case (ConcreteType(_), ConcreteType(_))   => Yes
                    case (GenericType(_, _), ConcreteType(_)) => Yes

                    case (ConcreteType(_), GenericType(_, supertypeArguments)) =>
                        haveSameTypeBinding(subtype.objectType, supertype.objectType, supertypeArguments)

                    case (GenericType(containerType, elements), GenericType(superContainerType, superElements)) =>
                        compareSharedTypeArguments(containerType, elements, superContainerType, superElements)

                    case (GenericTypeWithClassSuffix(_, _, _), ConcreteType(_)) => Yes

                    case (GenericTypeWithClassSuffix(containerType, elements, _), GenericType(superContainerType, superElements)) =>
                        compareSharedTypeArguments(containerType, elements, superContainerType, superElements)

                    case (GenericTypeWithClassSuffix(containerType, _ /*typeArguments*/ , suffix), GenericTypeWithClassSuffix(superContainerType, _ /*supertypeArguments*/ , superSuffix)) => {
                        compareSharedTypeArguments(containerType, subtype.classTypeSignatureSuffix.last.typeArguments,
                            superContainerType, supertype.classTypeSignatureSuffix.last.typeArguments) match {
                            case Yes => compareTypeArgumentsOfClassSuffixes(suffix.dropRight(1), superSuffix.dropRight(1)) match {
                                case Yes if suffix.last.typeArguments.isEmpty && superSuffix.last.typeArguments.isEmpty => Yes
                                case Yes if suffix.last.typeArguments.isEmpty && superSuffix.last.typeArguments.nonEmpty => {
                                    val ss = getClassSignature(containerType).flatMap { cs => getSupertypeDeclaration(cs, superContainerType) }
                                    if (ss.get.classTypeSignatureSuffix.last.typeArguments.collectFirst { case x @ ProperTypeArgument(_, TypeVariableSignature(_)) => x }.size > 0)
                                        compareTypeArgumentsOfClassSuffixes(List(subtype.simpleClassTypeSignature), List(superSuffix.last))
                                    else compareTypeArgumentsOfClassSuffixes(List(ss.get.classTypeSignatureSuffix.last), List(superSuffix.last))
                                }
                                case Yes    => compareTypeArgumentsOfClassSuffixes(List(suffix.last), List(superSuffix.last))
                                case answer => answer
                            }
                            case answer => answer
                        }
                    }
                    case _ => No
                }
            } else isASubtype
        }
    }

    /**
     * Determines if the given class or interface type encoded in a [[ClassTypeSignature]]
     * `subtype` is actually a subtype of the class, interface or intersection type encoded
     * in the [[FormalTypeParameter]] of the `supertype` parameter. The subtype relation is
     * fulfilled if the subtype is a subtype of the class bound and/or all interface types
     * that are prescribed by the formal type specification.
     *
     * @note    This method does consider generics types specified within the
     *          [[FormalTypeParameter]].
     * @param   subtype Any `ClassTypeSignature`.
     * @param   supertype Any `FormalTypeParameter`.
     * @return `Yes` if `subtype` is a subtype of the given `supertype`. `No`
     *          if `subtype` is not a subtype of `supertype` and `Unknown` if the analysis is
     *          not conclusive. The latter can happen if the class hierarchy is not
     *          complete and hence precise information about a type's supertypes
     *          is not available.
     *
     */
    def isASubtypeOf(
        subtype:   ClassTypeSignature,
        supertype: FormalTypeParameter
    )(
        implicit
        p: ClassFileRepository
    ): Answer = {

        // IMPROVE Avoid creating the list by using an inner function (def).
        (supertype.classBound.toList ++ supertype.interfaceBound)
            .collect { case s: ClassTypeSignature => s }
            .foldLeft(Yes: Answer) { (a, superCTS) =>
                (a, isASubtypeOf(subtype, superCTS)) match {
                    case (_, Unknown)     => return Unknown;
                    case (x, y) if x ne y => No
                    case (x, _ /*x*/ )    => x
                }
            }
    }

    /**
     * Returns some statistical data about the class hierarchy.
     */
    def statistics: String = {
        "Class Hierarchy Statistics:"+
            "\n\tKnown types: "+knownTypesMap.count(_ != null)+
            "\n\tInterface types: "+isInterfaceTypeMap.count(isInterface => isInterface)+
            "\n\tIdentified Superclasses: "+superclassTypeMap.count(_ != null)+
            "\n\tSuperinterfaces: "+
            superinterfaceTypesMap.filter(_ != null).foldLeft(0)(_ + _.size)+
            "\n\tSubclasses: "+
            subclassTypesMap.filter(_ != null).foldLeft(0)(_ + _.size)+
            "\n\tSubinterfaces: "+
            subinterfaceTypesMap.filter(_ != null).foldLeft(0)(_ + _.size)
    }

    /**
     * Returns a view of the class hierarchy as a graph (which can then be transformed
     * into a dot representation [[http://www.graphviz.org Graphviz]]). This
     * graph can be a multi-graph if the class hierarchy contains holes.
     */
    def toGraph(): Node = new Node {

        private val nodes: mutable.Map[ObjectType, Node] = {
            val nodes = mutable.HashMap.empty[ObjectType, Node]

            foreachNonNullValue(knownTypesMap) { (id, aType) =>
                val entry: (ObjectType, Node) = (
                    aType,
                    new Node {
                        private val directSubtypes = directSubtypesOf(aType)
                        override def nodeId: Int = aType.id
                        override def toHRR: Option[String] = Some(aType.toJava)
                        override val visualProperties: Map[String, String] = {
                            Map("shape" -> "box") ++ (
                                if (isInterface(aType).isYes)
                                    Map("fillcolor" -> "aliceblue", "style" -> "filled")
                                else
                                    Map.empty
                            )
                        }
                        def foreachSuccessor(f: Node => Unit): Unit = {
                            directSubtypes foreach { subtype => f(nodes(subtype)) }
                        }
                        def hasSuccessors: Boolean = directSubtypes.nonEmpty
                    }
                )
                nodes += entry
            }
            nodes
        }

        // a virtual root node
        override def nodeId: Int = -1
        override def toHRR: Option[String] = None
        override def foreachSuccessor(f: Node => Unit): Unit = {
            /*
             * We may not see the class files of all classes that are referred
             * to in the class files that we did see. Hence, we have to be able
             * to handle partial class hierarchies.
             */
            val rootTypes = nodes filter { case (t, _) => superclassTypeMap(t.id) eq null }
            rootTypes.values.foreach(f)
        }
        override def hasSuccessors: Boolean = nodes.nonEmpty
    }

    // -----------------------------------------------------------------------------------
    //
    // COMMON FUNCTIONALITY TO CALCULATE THE MOST SPECIFIC COMMON SUPERTYPE OF TWO
    // TYPES / TWO UPPER TYPE BOUNDS
    //
    // -----------------------------------------------------------------------------------

    /**
     * Calculates the set of all supertypes of the given `types`.
     */
    def allSupertypesOf(types: UIDSet[ObjectType], reflexive: Boolean): UIDSet[ObjectType] = {
        var allSupertypesOf: UIDSet[ObjectType] = UIDSet.empty
        types foreach { t: ObjectType =>
            if (!allSupertypesOf.contains(t)) {
                if (isKnown(t))
                    allSupertypesOf ++= allSupertypes(t, reflexive)
                else if (reflexive)
                    // the project's class hierarchy is obviously not complete
                    // however, we do as much as we can...
                    allSupertypesOf += t
            }
        }
        allSupertypesOf
    }

    /**
     * Selects all types of the given set of types that '''do not have any subtype
     * in the given set'''. If the given set is empty, a set containing `java.lang.Object`
     * is returned. A set which contains only one type will directly be returned.
     *
     * @param   types A set of types that contains '''for each type stored in the
     *          set all direct and indirect supertypes or none'''. For example, the intersection
     *          of the sets of all supertypes (as returned, e.g., by
     *          `ClassHierarchy.allSupertypes`) of two (independent) types satisfies this
     *          condition. If `types` is empty, the returned leaf type is `ObjectType.Object`.
     *          which should always be a safe fallback.
     */
    def leafTypes(types: UIDSet[ObjectType]): UIDSet[ObjectType] = {
        if (types.isEmpty)
            return ClassHierarchy.JustObject;

        if (types.isSingletonSet)
            return types;

        types filter { aType =>
            isUnknown(aType) ||
                //!(directSubtypesOf(aType) exists { t => types.contains(t) })
                !(types exists { t => (t ne aType) && isSubtypeOf(t, aType) })
        }
    }

    /**
     * Calculates the most specific common supertype of the given types.
     * If `reflexive` is `false`, no two types across both sets have to be in
     * an inheritance relation; if in doubt use `true`.
     *
     * @param upperTypeBoundsB A list (set) of `ObjectType`s that are not in an
     *      inheritance relation if reflexive is `false`.
     * @example
     * {{{
     * /* Consider the following type hierarchy:
     *  *    Object <- Collection <- List
     *  *    Object <- Collection <- Set
     *  *    Object <- Externalizable
     *  *    Object <- Serializable
     *  */
     * Object o = new ...
     * if (...) {
     *      Set s = (Set) o;
     *      (( Externalizable)s).save(...)
     *      // => o(s) has to be a subtype of Set AND Externalizable
     * } else {
     *      List l = (List) l;
     *      ((Serializable)l).store(...)
     *      // => o(l) has to be a subtype of List AND Serializable
     * }
     * // Here, o is either a set or a list. Hence, it is at least a Collection,
     * // but we cannot deduce anything w.r.t. Serializable and Externalizable.
     * }}}
     */
    def joinUpperTypeBounds(
        upperTypeBoundsA: UIDSet[ObjectType],
        upperTypeBoundsB: UIDSet[ObjectType],
        reflexive:        Boolean
    ): UIDSet[ObjectType] = {

        assert(upperTypeBoundsA.nonEmpty)
        assert(upperTypeBoundsB.nonEmpty)

        upperTypeBoundsA.compare(upperTypeBoundsB) match {
            case StrictSubset   => upperTypeBoundsA
            case EqualSets      => upperTypeBoundsA /* or upperTypeBoundsB */
            case StrictSuperset => upperTypeBoundsB
            case UncomparableSets =>
                val allSupertypesOfA = allSupertypesOf(upperTypeBoundsA, reflexive)
                val allSupertypesOfB = allSupertypesOf(upperTypeBoundsB, reflexive)
                val commonSupertypes = allSupertypesOfA intersect allSupertypesOfB
                leafTypes(commonSupertypes)
        }
    }

    /**
     * Tries to calculate the most specific common supertype of the given types.
     * If `reflexive` is `false`, the given types do not have to be in an
     * inheritance relation.
     *
     * @param upperTypeBoundB A list (set) of `ObjectType`s that are not in a mutual
     *      inheritance relation.
     * @return (I) Returns (if reflexive is `true`) `upperTypeBoundA` if it is a supertype
     *      of at least one type of `upperTypeBoundB`.
     *      (II) Returns `upperTypeBoundB` if `upperTypeBoundA` is
     *      a subtype of all types of `upperTypeBoundB`. Otherwise a new upper type
     *      bound is calculated and returned.
     */
    def joinObjectTypes(
        upperTypeBoundA: ObjectType,
        upperTypeBoundB: UIDSet[ObjectType],
        reflexive:       Boolean
    ): UIDSet[ObjectType] = {

        if (upperTypeBoundB.isEmpty)
            return upperTypeBoundB;

        if (upperTypeBoundB.isSingletonSet) {
            val upperTypeBound =
                if (upperTypeBoundA eq upperTypeBoundB.head) {
                    if (reflexive)
                        upperTypeBoundB
                    else
                        directSupertypes(upperTypeBoundA)
                } else {
                    joinObjectTypes(upperTypeBoundA, upperTypeBoundB.head, reflexive)
                }
            return upperTypeBound;
        }

        if (upperTypeBoundB contains upperTypeBoundA) {
            // The upperTypeBoundB contains more than one type; hence, considering
            // "reflexive" is no longer necessary...
            // if (isKnownToBeFinal(upperTypeBoundA)) the upper type bound (hopefully)
            // deliberately contains types which are guaranteed to be in a super-/subtype
            // relation, but which are not part of the analyzed code base. Nevertheless,
            // we are performing a join and therefore, drop the information...
            return new UIDSet1(upperTypeBoundA);
        }

        if (isUnknown(upperTypeBoundA)) {
            OPALLogger.logOnce(Warn(
                "project configuration - class hierarchy", "type unknown: "+upperTypeBoundA.toJava
            ))
            // there is nothing that we can do...
            return ClassHierarchy.JustObject;
        }

        val allSupertypesOfA = allSupertypes(upperTypeBoundA, reflexive)
        val allSupertypesOfB = allSupertypesOf(upperTypeBoundB, reflexive)
        val commonSupertypes = allSupertypesOfA intersect allSupertypesOfB
        leafTypes(commonSupertypes)
    }

    def joinArrayType(
        upperTypeBoundA: ArrayType,
        upperTypeBoundB: UIDSet[_ <: ReferenceType]
    ): UIDSet[_ <: ReferenceType] = {
        upperTypeBoundB match {
            case UIDSet1(utbB: ArrayType) =>
                if (utbB eq upperTypeBoundA)
                    return upperTypeBoundB;
                else
                    joinArrayTypes(upperTypeBoundA, utbB) match {
                        case Left(newUTB)  => new UIDSet1(newUTB)
                        case Right(newUTB) => newUTB
                    }
            case UIDSet1(utbB: ObjectType) =>
                joinAnyArrayTypeWithObjectType(utbB)
            case _ =>
                val utbB = upperTypeBoundB.asInstanceOf[UIDSet[ObjectType]]
                joinAnyArrayTypeWithMultipleTypesBound(utbB)
        }
    }

    def joinReferenceType(
        upperTypeBoundA: ReferenceType,
        upperTypeBoundB: UIDSet[_ <: ReferenceType]
    ): UIDSet[_ <: ReferenceType] = {
        if (upperTypeBoundA.isArrayType)
            joinArrayType(upperTypeBoundA.asArrayType, upperTypeBoundB)
        else
            upperTypeBoundB match {
                case UIDSet1(_: ArrayType) =>
                    joinAnyArrayTypeWithObjectType(upperTypeBoundA.asObjectType)
                case UIDSet1(utbB: ObjectType) =>
                    joinObjectTypes(upperTypeBoundA.asObjectType, utbB, true)
                case _ =>
                    val utbB = upperTypeBoundB.asInstanceOf[UIDSet[ObjectType]]
                    joinObjectTypes(upperTypeBoundA.asObjectType, utbB, true)
            }
    }

    def joinReferenceTypes(
        upperTypeBoundA: UIDSet[_ <: ReferenceType],
        upperTypeBoundB: UIDSet[_ <: ReferenceType]
    ): UIDSet[_ <: ReferenceType] = {
        if ((upperTypeBoundA eq upperTypeBoundB) || upperTypeBoundA == upperTypeBoundB)
            return upperTypeBoundA;

        if (upperTypeBoundA.isEmpty)
            return upperTypeBoundB;

        if (upperTypeBoundB.isEmpty)
            return upperTypeBoundA;

        if (upperTypeBoundA.isSingletonSet)
            joinReferenceType(upperTypeBoundA.head, upperTypeBoundB)
        else if (upperTypeBoundB.isSingletonSet)
            joinReferenceType(upperTypeBoundB.head, upperTypeBoundA)
        else
            joinUpperTypeBounds(
                upperTypeBoundA.asInstanceOf[UIDSet[ObjectType]],
                upperTypeBoundB.asInstanceOf[UIDSet[ObjectType]],
                true
            ).asInstanceOf[UpperTypeBound]
    }

    /**
     * Tries to calculate the most specific common supertype of the two given types.
     * If `reflexive` is `false`, the two types do not have to be in an inheritance
     * relation.
     *
     * If the class hierarchy is not complete, a best guess is made.
     */
    def joinObjectTypes(
        upperTypeBoundA: ObjectType,
        upperTypeBoundB: ObjectType,
        reflexive:       Boolean
    ): UIDSet[ObjectType] = {

        assert(
            reflexive || (
                (upperTypeBoundA ne ObjectType.Object) && (upperTypeBoundB ne ObjectType.Object)
            )
        )

        if (upperTypeBoundA eq upperTypeBoundB) {
            if (reflexive)
                return new UIDSet1(upperTypeBoundA);
            else
                return directSupertypes(upperTypeBoundA /*or ...B*/ );
        }

        if (isSubtypeOf(upperTypeBoundB, upperTypeBoundA)) {
            if (reflexive)
                return new UIDSet1(upperTypeBoundA);
            else
                return directSupertypes(upperTypeBoundA);
        }

        if (isSubtypeOf(upperTypeBoundA, upperTypeBoundB)) {
            if (reflexive)
                return new UIDSet1(upperTypeBoundB);
            else
                return directSupertypes(upperTypeBoundB);
        }

        if (isUnknown(upperTypeBoundA) || isUnknown(upperTypeBoundB)) {
            // there is not too much that we can do...
            return ClassHierarchy.JustObject;
        }

        val allSupertypesOfA = allSupertypes(upperTypeBoundA, reflexive = false)
        val allSupertypesOfB = allSupertypes(upperTypeBoundB, reflexive = false)
        val commonSupertypes = allSupertypesOfA intersect allSupertypesOfB
        val mostSpecificCommonSupertype = leafTypes(commonSupertypes)
        mostSpecificCommonSupertype
    }

    /**
     * Calculates the most specific common supertype of any array type and some
     * class-/interface type.
     *
     * Recall that (Java) arrays implement `Cloneable` and `Serializable`.
     */
    def joinAnyArrayTypeWithMultipleTypesBound(
        thatUpperTypeBound: UIDSet[ObjectType]
    ): UIDSet[ObjectType] = {
        import ObjectType.Cloneable
        import ObjectType.Serializable
        import ObjectType.SerializableAndCloneable

        if (thatUpperTypeBound == SerializableAndCloneable)
            thatUpperTypeBound
        else {
            val isSerializable =
                thatUpperTypeBound exists { thatType => isSubtypeOf(thatType, Serializable) }
            val isCloneable =
                thatUpperTypeBound exists { thatType => isSubtypeOf(thatType, Cloneable) }
            if (isSerializable) {
                if (isCloneable)
                    SerializableAndCloneable
                else
                    new UIDSet1(Serializable)
            } else if (isCloneable) {
                new UIDSet1(Cloneable)
            } else {
                new UIDSet1(Object)
            }
        }
    }

    /**
     * Calculates the most specific common supertype of any array type and some
     * class-/interface type.
     *
     * Recall that (Java) arrays implement `Cloneable` and `Serializable`.
     */
    def joinAnyArrayTypeWithObjectType(thatUpperTypeBound: ObjectType): UIDSet[ObjectType] = {
        import ObjectType.Cloneable
        import ObjectType.Object
        import ObjectType.Serializable

        if ((thatUpperTypeBound eq Object) ||
            (thatUpperTypeBound eq Serializable) ||
            (thatUpperTypeBound eq Cloneable))
            new UIDSet1(thatUpperTypeBound)
        else {
            var newUpperTypeBound: UIDSet[ObjectType] = UIDSet.empty
            if (isSubtypeOf(thatUpperTypeBound, Serializable))
                newUpperTypeBound += Serializable
            if (isSubtypeOf(thatUpperTypeBound, Cloneable))
                newUpperTypeBound += Cloneable
            if (newUpperTypeBound.isEmpty)
                new UIDSet1(Object)
            else
                newUpperTypeBound
        }
    }

    /**
     * Calculates the most specific common supertype of two array types.
     *
     * @return `Left()` if the calculated type can be represented using
     *      an `ArrayType` and `Right(UIDList(ObjectType.Serializable, ObjectType.Cloneable))`
     *      if the two arrays do not have an `ArrayType` as a most specific common supertype.
     */
    def joinArrayTypes(
        thisUpperTypeBound: ArrayType,
        thatUpperTypeBound: ArrayType
    ): Either[ArrayType, UIDSet[ObjectType]] = {
        // We have ALSO to consider the following corner cases:
        // Foo[][] and Bar[][] => Object[][] (Object is the common super class)
        // Object[] and int[][] => Object[] (which may contain arrays of int values...)
        // Foo[] and int[][] => Object[]
        // int[] and Object[][] => SerializableAndCloneable

        import ObjectType.SerializableAndCloneable

        if (thisUpperTypeBound eq thatUpperTypeBound)
            return Left(thisUpperTypeBound);

        val thisUTBDim = thisUpperTypeBound.dimensions
        val thatUTBDim = thatUpperTypeBound.dimensions

        if (thisUTBDim < thatUTBDim) {
            if (thisUpperTypeBound.elementType.isBaseType) {
                if (thisUTBDim == 1)
                    Right(SerializableAndCloneable)
                else
                    Left(ArrayType(thisUTBDim - 1, ObjectType.Object))
            } else {
                Left(ArrayType(thisUTBDim, ObjectType.Object))
            }
        } else if (thisUTBDim > thatUTBDim) {
            if (thatUpperTypeBound.elementType.isBaseType) {
                if (thatUTBDim == 1)
                    Right(SerializableAndCloneable)
                else
                    Left(ArrayType(thatUTBDim - 1, ObjectType.Object))
            } else {
                Left(ArrayType(thatUTBDim, ObjectType.Object))
            }
        } else if (thisUpperTypeBound.elementType.isBaseType ||
            thatUpperTypeBound.elementType.isBaseType) {
            // => the number of dimensions is the same, but the elementType isn't
            //    (if the element type would be the same, both object reference would
            //    refer to the same object and this would have been handled the very
            //    first test)
            // Scenario:
            // E.g., imagine that we have a method that "just" wants to
            // serialize some data. In such a case the method may be passed
            // different arrays with different primitive values.
            if (thisUTBDim == 1 /* && thatUTBDim == 1*/ )
                Right(SerializableAndCloneable)
            else
                Left(ArrayType(thisUTBDim - 1, ObjectType.Object))
        } else {
            // When we reach this point, the dimensions are identical and both
            // elementTypes are reference types
            val thatElementType = thatUpperTypeBound.elementType.asObjectType
            val thisElementType = thisUpperTypeBound.elementType.asObjectType
            val elementType =
                joinObjectTypesUntilSingleUpperBound(thisElementType, thatElementType, true)
            Left(ArrayType(thisUTBDim, elementType))
        }
    }

    def joinObjectTypesUntilSingleUpperBound(
        upperTypeBoundA: ObjectType,
        upperTypeBoundB: ObjectType,
        reflexive:       Boolean
    ): ObjectType = {
        val newUpperTypeBound = joinObjectTypes(upperTypeBoundA, upperTypeBoundB, reflexive)
        val result =
            if (newUpperTypeBound.isSingletonSet)
                newUpperTypeBound.head
            else
                newUpperTypeBound reduce { (c, n) =>
                    // We are already one level up in the class hierarchy. Hence,
                    // we now certainly want to be reflexive!
                    joinObjectTypesUntilSingleUpperBound(c, n, true)
                }
        result
    }

    /**
     * Given an upper type bound '''a''' most specific type that is a common supertype
     * of the given types is determined.
     *
     * @see `joinObjectTypesUntilSingleUpperBound(upperTypeBoundA: ObjectType,
     *       upperTypeBoundB: ObjectType, reflexive: Boolean)` for further details.
     */
    def joinObjectTypesUntilSingleUpperBound(upperTypeBound: UIDSet[ObjectType]): ObjectType = {
        if (upperTypeBound.isSingletonSet)
            upperTypeBound.head
        else
            upperTypeBound reduce { (c, n) => joinObjectTypesUntilSingleUpperBound(c, n, true) }
    }

    def joinReferenceTypesUntilSingleUpperBound(
        upperTypeBound: UIDSet[_ <: ReferenceType]
    ): ReferenceType = {
        if (upperTypeBound.isSingletonSet)
            upperTypeBound.head
        else
            // Note that the upper type bound must never consist of more than one array type;
            // and that the type hierarchy related to arrays is "hardcoded"
            // ... (here) type erasure also has its benefits ...
            joinObjectTypesUntilSingleUpperBound(upperTypeBound.asInstanceOf[UIDSet[ObjectType]])
    }

    def joinUpperTypeBounds(
        utbA: UIDSet[_ <: ReferenceType],
        utbB: UIDSet[_ <: ReferenceType]
    ): UIDSet[_ <: ReferenceType] = {
        if (utbA == utbB)
            utbA
        else if (utbA.isEmpty)
            utbB
        else if (utbB.isEmpty)
            utbA
        else if (utbA.isSingletonSet && utbA.head.isArrayType) {
            if (utbB.isSingletonSet) {
                if (utbB.head.isArrayType) {
                    val joinedArrayType =
                        joinArrayTypes(
                            utbB.head.asInstanceOf[ArrayType],
                            utbA.head.asInstanceOf[ArrayType]
                        )
                    joinedArrayType match {
                        case Left(arrayType)       => new UIDSet1(arrayType)
                        case Right(upperTypeBound) => upperTypeBound
                    }
                } else {
                    joinAnyArrayTypeWithObjectType(utbB.head.asInstanceOf[ObjectType])
                }
            } else {
                joinAnyArrayTypeWithMultipleTypesBound(utbB.asInstanceOf[UIDSet[ObjectType]])
            }
        } else if (utbB.isSingletonSet) {
            if (utbB.head.isArrayType) {
                joinAnyArrayTypeWithMultipleTypesBound(utbA.asInstanceOf[UIDSet[ObjectType]])
            } else {
                joinObjectTypes(
                    utbB.head.asObjectType,
                    utbA.asInstanceOf[UIDSet[ObjectType]],
                    true
                )
            }
        } else {
            joinUpperTypeBounds(
                utbA.asInstanceOf[UIDSet[ObjectType]],
                utbB.asInstanceOf[UIDSet[ObjectType]],
                true
            )
        }
    }

    override def toString: String = {
        var s = "ClassHierarchy(\n\t"

        // Compute some fundamental class hierarchy statistics:

        // 1. compute subtype information
        {
            var i = 1 // let's skip java.lang.Object
            val subtypesToFrequency = new Int2IntArrayMap()
            while (i < subtypeInformationMap.length) {
                if (subtypeInformationMap(i) != null) {
                    val subtypesCount = subtypeInformationMap(i).size
                    subtypesToFrequency.put(
                        subtypesCount,
                        subtypesToFrequency.getOrDefault(subtypesCount, 0) + 1
                    )
                }
                i += 1
            }
            val subtypesCounts = subtypesToFrequency.keySet().asScala.toList.sorted
            var overallDepth: Double = 0d
            s += "subtype statistics=\n\t"
            s += " subtypes count   / frequency of occurrence (without java.lang.Object)\n\t"
            s +=
                subtypesCounts.map { subtypesCount =>
                    val i: Int = subtypesCount.intValue()
                    val frequency = subtypesToFrequency.get(i)
                    overallDepth += i * frequency
                    f"$subtypesCount%17d / $frequency"
                }.mkString("\n\t")
            s += "\n\t average number of subtypes: "
            s += s"${overallDepth / (subtypeInformationMap.count(_ != null) - 1)}\n\t"
        }
        // 2. compute supertype information
        {
            var i = 1 // let's skip java.lang.Object
            val supertypesToFrequency = new Int2IntArrayMap()
            while (i < supertypeInformationMap.length) {
                if (supertypeInformationMap(i) != null) {
                    val supertypesCount = supertypeInformationMap(i).size
                    supertypesToFrequency.put(
                        supertypesCount,
                        supertypesToFrequency.getOrDefault(supertypesCount, 0) + 1
                    )
                }
                i += 1
            }
            val supertypesCounts = supertypesToFrequency.keySet().asScala.toList.sorted
            var overallDepth: Double = 0d
            s += "supertype statistics=\n\t"
            s += " supertypes count / frequency of occurrence (without java.lang.Object)\n\t"
            s +=
                supertypesCounts.map { supertypesCount =>
                    val i: Int = supertypesCount.intValue()
                    val frequency = supertypesToFrequency.get(i)
                    overallDepth += i * frequency
                    f"$supertypesCount%17d / $frequency"
                }.mkString("\n\t")
            s += "\n\t average number of supertypes: "
            s += (overallDepth / (subtypeInformationMap.count(_ != null) - 1))
        }
        s += "\n)"
        s
    }

}

/**
 * Factory methods for creating `ClassHierarchy` objects.
 *
 * @author Michael Eichberg
 */
object ClassHierarchy {

    private[this] implicit val classHierarchyEC: ExecutionContext = OPALUnboundedExecutionContext

    final val JustObject: UIDSet[ObjectType] = new UIDSet1(ObjectType.Object)

    /**
     * Creates a `ClassHierarchy` that captures the type hierarchy related to
     * the exceptions thrown by specific Java bytecode instructions as well as
     * fundamental types such as Cloneable and Serializable and also those types
     * related to reflection..
     *
     * This class hierarchy is primarily useful for testing purposes.
     */
    lazy val PreInitializedClassHierarchy: ClassHierarchy = {
        apply(classFiles = Iterable.empty, defaultTypeHierarchyDefinitions())(GlobalLogContext)
    }

    def noDefaultTypeHierarchyDefinitions(): List[() => java.io.InputStream] = List.empty

    def defaultTypeHierarchyDefinitions(): List[() => java.io.InputStream] = List(
        () => { getClass.getResourceAsStream("ClassHierarchyJLS.ths") },
        () => { getClass.getResourceAsStream("ClassHierarchyJVMExceptions.ths") },
        () => { getClass.getResourceAsStream("ClassHierarchyJava7-java.lang.reflect.ths") }
    )

    def parseTypeHierarchyDefinition(
        in: InputStream
    )(
        implicit
        logContext: LogContext
    ): Seq[TypeDeclaration] = process(in) { in =>

        if (in eq null) {
            OPALLogger.error(
                "internal - class hierarchy",
                "loading the predefined class hierarchy failed; "+
                    "make sure that all resources are found in the correct folders and "+
                    "try to rebuild the project using \"sbt copyResources\""
            )
            Seq.empty;
        } else {
            val typeRegExp =
                """(class|interface)\s+(\S+)(\s+extends\s+(\S+)(\s+implements\s+(.+))?)?""".r
            processSource(new BufferedSource(in)) { source =>
                source.getLines().
                    map(_.trim).
                    filterNot { l => l.startsWith("#") || l.length == 0 }.
                    map { l =>
                        val typeRegExp(typeKind, theType, _, superclassType, _, superinterfaceTypes) = l
                        TypeDeclaration(
                            ObjectType(theType),
                            typeKind == "interface",
                            Option(superclassType).map(ObjectType(_)),
                            Option(superinterfaceTypes).map { superinterfaceTypes =>
                                UIDSet.fromSpecific[ObjectType](
                                    superinterfaceTypes.
                                        split(',').
                                        map(t => ObjectType(t.trim))
                                )
                            }.getOrElse(UIDSet.empty)
                        )
                    }.
                    toList
            }
        }
    }

    def apply(
        classFiles:               Iterable[ClassFile],
        typeHierarchyDefinitions: Seq[() => InputStream] = defaultTypeHierarchyDefinitions()
    )(
        implicit
        logContext: LogContext
    ): ClassHierarchy = {
        // We have to make sure that we have seen all types before we can generate
        // the arrays to store the information about the types!
        create(
            classFiles,
            typeHierarchyDefinitions flatMap { inputStreamFactory =>
                val in = inputStreamFactory.apply()
                parseTypeHierarchyDefinition(in)
            }
        )
    }

    /**
     * Creates the class hierarchy by analyzing the given class files, the predefined
     * type declarations, and the specified predefined class hierarchies.
     *
     * By default the class hierarchy related to the exceptions thrown by bytecode
     * instructions are predefined as well as the class hierarchy related to the main
     * classes of the JDK.
     * See the file `ClassHierarchyJVMExceptions.ths`, `ClassHierarchyJLS.ths` and
     * `ClassHierarchyJava7-java.lang.reflect.ths` (text files) for further details.
     *
     * Basically, only the part of a project's class hierarchy is reified that is referred
     * to in the ''class declarations'' of the analyzed classes  I.e., those classes
     * which are directly referred to in class declarations, but for which the respective
     * class file was not analyzed, are also considered to be visible and are integrated
     * in the class hierarchy.
     * However, types only referred to in the body of a method, but for which neither
     * the defining class file is analyzed nor a class exists that inherits from
     * them are not integrated.
     * For example, if the class file of the class `java.util.ArrayList` is analyzed,
     * then the class hierarchy will have some information about, e.g., `java.util.List`
     * from which `ArrayList` inherits. However, the information about `List` is incomplete
     * and `List` will be a boundary class unless we also analyze the class file that
     * defines `java.util.List`.
     */
    def create(
        classFiles:       Iterable[ClassFile],
        typeDeclarations: Iterable[TypeDeclaration]
    )(
        implicit
        logContext: LogContext
    ): ClassHierarchy = {

        val objectTypesCount = ObjectType.objectTypesCount
        val knownTypesMap = new Array[ObjectType](objectTypesCount)
        val isInterfaceTypeMap = new Array[Boolean](objectTypesCount)
        val superclassTypeMap = new Array[ObjectType](objectTypesCount)
        val isKnownToBeFinalMap = new Array[Boolean](objectTypesCount)
        val superinterfaceTypesMap = new Array[UIDSet[ObjectType]](objectTypesCount)
        val subclassTypesMap = new Array[UIDSet[ObjectType]](objectTypesCount)
        val subinterfaceTypesMap = new Array[UIDSet[ObjectType]](objectTypesCount)

        val ObjectId = ObjectType.Object.id

        // Collects those classes which declare to implement a specific interface, but which
        // is actually a regular class.
        var classesWithBrokenInterfaceInheritance: Map[ObjectType, UIDSet[ObjectType]] = {
            Map.empty.withDefaultValue(UIDSet.empty)
        }

        /*
         * Extends the class hierarchy.
         */
        def process(
            objectType:             ObjectType,
            isInterfaceType:        Boolean,
            isFinal:                Boolean,
            theSuperclassType:      Option[ObjectType],
            theSuperinterfaceTypes: UIDSet[ObjectType]
        ): Unit = {

            if (isInterfaceType && isFinal) {
                val message = s"the class file ${objectType.toJava} defines a final interface "+
                    "which violates the JVM specification and is therefore ignored"
                OPALLogger.error("project configuration - class hierarchy", message)

                return ;
            }

            def addToSet(data: Array[UIDSet[ObjectType]], index: Int, t: ObjectType): Unit = {
                val objectTypes = data(index)
                data(index) = {
                    if (objectTypes eq null)
                        new UIDSet1(t)
                    else
                        objectTypes + t
                }
            }

            def ensureHasSet(data: Array[UIDSet[ObjectType]], index: Int): Unit = {
                if (data(index) eq null) {
                    data(index) = UIDSet.empty
                }
            }

            //
            // Update the class hierarchy from the point of view of the newly added type
            //
            val objectTypeId = objectType.id
            knownTypesMap(objectTypeId) = objectType
            isInterfaceTypeMap(objectTypeId) = isInterfaceType
            isKnownToBeFinalMap(objectTypeId) = isFinal
            superclassTypeMap(objectTypeId) = theSuperclassType.orNull
            superinterfaceTypesMap(objectTypeId) = theSuperinterfaceTypes
            ensureHasSet(subclassTypesMap, objectTypeId)
            ensureHasSet(subinterfaceTypesMap, objectTypeId)

            //
            // Update the class hierarchy from the point of view of the new type's super types
            // For each super(class|interface)type make sure that it is "known"
            //
            theSuperclassType foreach { superclassType =>
                val superclassTypeId = superclassType.id
                knownTypesMap(superclassTypeId) = superclassType

                if (isInterfaceType) {
                    // an interface always has `java.lang.Object` as its super class
                    addToSet(subinterfaceTypesMap, ObjectId /*java.lang.Object*/ , objectType)
                } else {
                    addToSet(subclassTypesMap, superclassTypeId, objectType)
                    ensureHasSet(subinterfaceTypesMap, superclassTypeId)
                }
            }
            theSuperinterfaceTypes foreach { aSuperinterfaceType =>
                val aSuperinterfaceTypeId = aSuperinterfaceType.id

                if (knownTypesMap(aSuperinterfaceTypeId) eq null) {
                    knownTypesMap(aSuperinterfaceTypeId) = aSuperinterfaceType
                    isInterfaceTypeMap(aSuperinterfaceTypeId) = true
                } else if (!isInterfaceTypeMap(aSuperinterfaceTypeId)) {
                    val message = s"the class file ${objectType.toJava} defines a "+
                        s"super interface ${knownTypesMap(aSuperinterfaceTypeId).toJava} "+
                        "which is actually a regular class file"
                    classesWithBrokenInterfaceInheritance +=
                        ((
                            objectType,
                            classesWithBrokenInterfaceInheritance(objectType) + aSuperinterfaceType
                        ))
                    OPALLogger.error("project configuration - class hierarchy", message)
                }
                if (isInterfaceType) {
                    addToSet(subinterfaceTypesMap, aSuperinterfaceTypeId, objectType)
                    ensureHasSet(subclassTypesMap, aSuperinterfaceTypeId)
                } else {
                    addToSet(subclassTypesMap, aSuperinterfaceTypeId, objectType)
                    // assert(subclassTypesMap(aSuperinterfaceTypeId).contains(objectType))
                    ensureHasSet(subinterfaceTypesMap, aSuperinterfaceTypeId)
                }
            }
        }

        // Analyzes the given class files and extends the current class hierarchy.
        val processedClassType: Array[Boolean] = new Array[Boolean](objectTypesCount)
        classFiles foreach { classFile =>
            if (!classFile.isModuleDeclaration) {
                // We always keep the FIRST class file which defines a type this is inline
                // with the behavior of the class Project which prioritizes a project class file
                // over library class files.
                val classType = classFile.thisType
                if (!processedClassType(classType.id)) {
                    processedClassType(classType.id) = true
                    process(
                        classType,
                        classFile.isInterfaceDeclaration,
                        classFile.isFinal,
                        classFile.superclassType,
                        UIDSet.empty ++ classFile.interfaceTypes
                    )
                }
            }
        }

        val processedTypeDeclaration: Array[Boolean] = new Array[Boolean](objectTypesCount)
        var duplicateTypeDeclarations: Set[String] = Set.empty
        typeDeclarations foreach { typeDeclaration =>
            val objectType = typeDeclaration.objectType
            val oid = objectType.id
            if (processedTypeDeclaration(oid)) {
                duplicateTypeDeclarations += objectType.toJava
            } else {
                processedTypeDeclaration(oid) = true
                // We generally don't want to use pre-configured type hierarchy information;
                // but we want to extend the information if it is obviously incomplete...
                if (!processedClassType(oid) ||
                    // processed - but complete?
                    ((oid != ObjectType.ObjectId) && superclassTypeMap(oid) == null)) {
                    processedClassType(objectType.id) = true
                    process(
                        objectType,
                        typeDeclaration.isInterfaceType,
                        isFinal = false,
                        typeDeclaration.theSuperclassType,
                        typeDeclaration.theSuperinterfaceTypes
                    )
                } else {
                    OPALLogger.warn(
                        "project configuration",
                        s"the type declaration for ${objectType.toJava} is ignored; "+
                            "class is already defined in the code base "+
                            s"with superclassType ${superclassTypeMap(oid).toJava}"+
                            "or defined multiple times in the configured type hierarchy"
                    )
                }
            }
        }
        if (duplicateTypeDeclarations.nonEmpty) {
            OPALLogger.info(
                "project configuration",
                duplicateTypeDeclarations.mkString(
                    "ignored duplicate type declarations for: {", ", ", "}"
                )
            )
        }

        // _____________________________________________________________________________________
        //
        // WHEN WE REACH THIS POINT WE HAVE COLLECTED ALL BASE INFORMATION
        // LET'S ENSURE THAT WE DIDN'T MAKE ANY FAILURES
        // _____________________________________________________________________________________
        //

        assert(knownTypesMap.length == isInterfaceTypeMap.length)
        assert(knownTypesMap.length == isKnownToBeFinalMap.length)
        assert(knownTypesMap.length == superclassTypeMap.length)
        assert(knownTypesMap.length == superinterfaceTypesMap.length)
        assert(knownTypesMap.length == subclassTypesMap.length)
        assert(knownTypesMap.length == subinterfaceTypesMap.length)
        assert(
            knownTypesMap.indices forall { i =>
                (knownTypesMap(i) ne null) ||
                    ((subclassTypesMap(i) eq null) && (subinterfaceTypesMap(i) eq null))
            }
        )

        // _____________________________________________________________________________________
        //
        // LET'S DERIVE SOME FURTHER INFORMATION WHICH IS FREQUENTLY USED!
        // _____________________________________________________________________________________
        //

        val isKnownTypeMap: Array[Boolean] = knownTypesMap.map(_ != null)

        val rootTypesFuture = Future[UIDSet[ObjectType]] {
            knownTypesMap.foldLeft(UIDSet.empty[ObjectType]) { (rootTypes, objectType) =>
                if ((objectType ne null) && {
                    val oid = objectType.id
                    (superclassTypeMap(oid) eq null) &&
                        {
                            val superinterfaceTypes = superinterfaceTypesMap(oid)
                            (superinterfaceTypes eq null) || superinterfaceTypes.isEmpty
                        }
                }) {
                    rootTypes + objectType
                } else {
                    rootTypes
                }
            }
        }

        val subtypesFuture = Future[(UIDSet[ObjectType], Array[SubtypeInformation])] {
            val leafTypes = knownTypesMap.foldLeft(UIDSet.empty[ObjectType]) { (leafTypes, t) =>
                if ((t ne null) && {
                    val tid = t.id
                    subclassTypesMap(tid).isEmpty && subinterfaceTypesMap(tid).isEmpty
                }) {
                    leafTypes + t
                } else {
                    leafTypes
                }
            }

            // Let's compute for each type the set of all subtypes, by starting at the bottom!
            val subtypes = new Array[SubtypeInformation](knownTypesMap.length)
            var deferredTypes = UIDSet.empty[ObjectType] // we want to defer as much as possible
            val typesToProcess = mutable.Queue.empty[ObjectType]

            def scheduleSupertypes(objectType: ObjectType): Unit = {
                val oid = objectType.id
                val superclassType = superclassTypeMap(oid)
                if ((superclassType ne null) && (superclassType ne ObjectType.Object)) {
                    typesToProcess += superclassType
                }
                val superSuperinterfaceTypes = superinterfaceTypesMap(oid)
                if (superSuperinterfaceTypes ne null) {
                    typesToProcess ++= superSuperinterfaceTypes
                }
            }

            leafTypes foreach { leafType =>
                subtypes(leafType.id) = SubtypeInformation.None
                scheduleSupertypes(leafType)
            }

            var madeProgress = false
            while (typesToProcess.nonEmpty) {
                val t = typesToProcess.dequeue()
                val tid = t.id
                // it may be the case that some type was already processed
                if (subtypes(tid) == null) {
                    var allSubinterfaceTypes = UIDSet.empty[ObjectType]
                    var allSubclassTypes = UIDSet.empty[ObjectType]
                    var allSubtypes = UIDSet.empty[ObjectType]
                    val done =
                        subinterfaceTypesMap(tid).forall { subtype =>
                            subtypes(subtype.id) match {
                                case null =>
                                    false
                                case subSubtypes =>
                                    allSubinterfaceTypes ++= subSubtypes.interfaceTypes
                                    allSubclassTypes ++= subSubtypes.classTypes
                                    allSubinterfaceTypes += subtype
                                    allSubtypes ++= (subSubtypes.allTypes + subtype)
                                    true
                            }
                        } && subclassTypesMap(tid).forall { subtype =>
                            subtypes(subtype.id) match {
                                case null =>
                                    false
                                case subSubtypes =>
                                    // There will be no sub interface types!
                                    // (java.lang.Object is not considered)
                                    allSubclassTypes ++= subSubtypes.classTypes
                                    allSubclassTypes += subtype
                                    allSubtypes ++= (subSubtypes.allTypes + subtype)
                                    true
                            }
                        }

                    if (done) {
                        madeProgress = true
                        val subtypeInfo = SubtypeInformation.forSubtypesOfObject(
                            isKnownTypeMap,
                            isInterfaceTypeMap,
                            allSubclassTypes, allSubinterfaceTypes, allSubtypes
                        )
                        subtypes(t.id) = subtypeInfo
                        scheduleSupertypes(t)
                    } else {
                        deferredTypes += t
                    }
                } else {
                    madeProgress = true // this is philosophical...
                }

                // test if we have really finished processing all types
                if (typesToProcess.isEmpty && deferredTypes.nonEmpty) {
                    if (!madeProgress) {
                        // The following is NOT performance sensitive... we are lost anyway
                        // and we just want to provide some hints to the user...
                        // 1. Do we have a cycle in the extracted type information ?
                        {
                            val ns = knownTypesMap.length
                            val es: Int => IntIterator = (oid: Int) => {
                                if (knownTypesMap(oid) ne null) {
                                    val it =
                                        subinterfaceTypesMap(oid).map(_.id).iterator ++
                                            subclassTypesMap(oid).map(_.id).iterator
                                    new IntIterator {
                                        def hasNext: Boolean = it.hasNext
                                        def next(): Int = it.next()
                                    }
                                } else {
                                    IntIterator.empty
                                }
                            }
                            val cyclicTypeDependencies =
                                org.opalj.graphs.sccs(ns, es, filterSingletons = true)
                            if (cyclicTypeDependencies.nonEmpty) {
                                OPALLogger.error(
                                    "project configuration",
                                    cyclicTypeDependencies.map { scc =>
                                        scc.map { oid =>
                                            if (knownTypesMap(oid) ne null)
                                                knownTypesMap(oid).toJava
                                            else
                                                "N/A"
                                        }.mkString(", ")
                                    }.mkString("cyclic type hierarchy:\n\t", "\n\t", "\n")
                                )
                            }
                        }

                        // 2. Which type(s) cause the problem?
                        val allIssues =
                            for {
                                dt <- deferredTypes
                                subtype <- subinterfaceTypesMap(dt.id) ++ subclassTypesMap(dt.id)
                                if subtypes(subtype.id) != null
                                if !deferredTypes.contains(subtype)
                            } yield {
                                s"${dt.toJava} (waits)->(subtype) ${subtype.toJava}"
                            }
                        OPALLogger.error(
                            "project configuration",
                            allIssues.mkString(
                                "could not compute subtype information for:\n\t", "\n\t", "\n"
                            )
                        )

                    } else {
                        madeProgress = false
                        typesToProcess ++= deferredTypes
                        deferredTypes = UIDSet.empty[ObjectType]
                    }
                }
            }
            var allNoneObjectClassTypes = UIDSet.empty[ObjectType]
            var allInterfaceType = UIDSet.empty[ObjectType]
            var allNoneObjectTypes = UIDSet.empty[ObjectType]
            for {
                t <- knownTypesMap
                if t ne null
            } {
                val tid = t.id
                val theSubtypes = subtypes(tid)
                if (isInterfaceTypeMap(tid)) {
                    allInterfaceType ++= theSubtypes.interfaceTypes
                    allNoneObjectTypes ++= theSubtypes.allTypes
                } else if (t ne ObjectType.Object) {
                    allNoneObjectClassTypes ++= theSubtypes.classTypes
                    allNoneObjectClassTypes += t
                    allNoneObjectTypes ++= theSubtypes.allTypes
                }
            }
            subtypes(ObjectType.ObjectId) =
                SubtypeInformation.forObject(
                    allNoneObjectClassTypes, allInterfaceType, allNoneObjectTypes
                )

            (leafTypes, subtypes)
        }

        val supertypesFuture = Future[Array[SupertypeInformation]] {
            // Selects all interfaces which either have no superinterfaces or where we have no
            // information about all implemented superinterfaces.
            def rootInterfaceTypes: Iterator[ObjectType] = {
                superinterfaceTypesMap.iterator.zipWithIndex.filter { si =>
                    val (superinterfaceTypes, id) = si
                    isInterfaceTypeMap(id) &&
                        ((superinterfaceTypes eq null) || superinterfaceTypes.isEmpty)
                }.map { ts => knownTypesMap(ts._2) }
            }

            val supertypes = new Array[SupertypeInformation](knownTypesMap.length)
            supertypes(ObjectId) = SupertypeInformation.ForObject

            val typesToProcess = mutable.Queue.empty[ObjectType] ++ rootInterfaceTypes

            // IDEA: breadth-first traversal of the class hierarchy with some fallback if an
            // interface inherits from multiple interfaces (and maybe from the same (indirect)
            // superinterface.)

            // 1. process all interface types
            while (typesToProcess.nonEmpty) {
                val t = typesToProcess.dequeue()
                val tid = t.id
                val superinterfaceTypes = {
                    val superinterfaceTypes = superinterfaceTypesMap(tid)
                    if (superinterfaceTypes ne null)
                        superinterfaceTypes
                    else
                        UIDSet.empty[ObjectType]
                }

                // let's check if we already have complete information about all supertypes
                var allSuperSuperinterfaceTypes = UIDSet.empty[ObjectType]
                var allSupertypes = UIDSet.empty[ObjectType]
                if (superinterfaceTypes.forall { supertype =>
                    val supertypeId = supertype.id
                    supertypes(supertypeId) match {
                        case null =>
                            // It may happen that we we will never have complete information about a
                            // superinterface type, because we have an incomplete project OR
                            // that the class hierarchy is totally broken in the sense that
                            // the super interface types are actually class types.
                            // In that case, we just ignore it...
                            superinterfaceTypesMap(supertypeId) == null ||
                                classesWithBrokenInterfaceInheritance(t).containsId(supertypeId)
                        case supertypes =>
                            allSuperSuperinterfaceTypes ++= supertypes.interfaceTypes
                            allSupertypes ++= supertypes.allTypes
                            true

                    }
                }) {
                    supertypes(t.id) =
                        SupertypeInformation.forSubtypesOfObject(
                            isKnownTypeMap,
                            isInterfaceTypeMap,
                            ClassHierarchy.JustObject,
                            allSuperSuperinterfaceTypes ++ superinterfaceTypes,
                            allSupertypes ++ superinterfaceTypes
                        )
                    typesToProcess ++= subinterfaceTypesMap(t.id)
                } else {
                    typesToProcess += t
                }
            }

            // 2. process all class types
            val rootTypes = await(rootTypesFuture, Inf) // we may have to wait...
            typesToProcess ++= rootTypes.iterator.filterNot(t => isInterfaceTypeMap(t.id))
            while (typesToProcess.nonEmpty) {
                val t = typesToProcess.dequeue()
                val tid = t.id
                if (tid != ObjectId) {
                    val superinterfaceTypes = {
                        val superinterfaceTypes = superinterfaceTypesMap(tid)
                        if (superinterfaceTypes ne null)
                            superinterfaceTypes
                        else
                            UIDSet.empty[ObjectType]
                    }
                    val superclassType = superclassTypeMap(t.id)
                    val allSuperinterfaceTypes =
                        superinterfaceTypes.foldLeft(
                            if (superclassType ne null) {
                                // interfaces inherited via super class
                                supertypes(superclassType.id).interfaceTypes
                            } else {
                                UIDSet.empty[ObjectType]
                            }
                        ) { (allInterfaceTypes, nextSuperinterfacetype) =>
                                (supertypes(nextSuperinterfacetype.id) match {
                                    case null       => allInterfaceTypes
                                    case supertypes => allInterfaceTypes ++ supertypes.interfaceTypes
                                }) + nextSuperinterfacetype
                            }
                    supertypes(tid) =
                        SupertypeInformation.forSubtypesOfObject(
                            isKnownTypeMap,
                            isInterfaceTypeMap,
                            {
                                if (superclassType ne null)
                                    supertypes(superclassType.id).classTypes + superclassType
                                else
                                    ClassHierarchy.JustObject // we do our best....
                            },
                            allSuperinterfaceTypes,
                            {
                                if (superclassType ne null)
                                    supertypes(superclassType.id).allTypes + superclassType
                                else
                                    ClassHierarchy.JustObject // we do our best....
                            }
                        )
                }
                typesToProcess ++= subclassTypesMap(t.id)
            }

            supertypes
        }

        val isSupertypeInformationCompleteFuture = Future[Array[Boolean]] {
            val isSupertypeInformationCompleteMap = new Array[Boolean](knownTypesMap.length)
            java.util.Arrays.fill(isSupertypeInformationCompleteMap, true)

            val (_, subtypes) = await(subtypesFuture, Inf)
            // NOTE: The supertype information for each type that directly inherits from
            // java.lang.Object is still not necessarily complete as the type may implement an
            // unknown interface.
            for {
                rootType <- await(rootTypesFuture, Inf) // we may have to wait...
                if rootType ne ObjectType.Object
            } {
                isSupertypeInformationCompleteMap(rootType.id) = false
                subtypes(rootType.id).foreach(t => isSupertypeInformationCompleteMap(t.id) = false)
            }
            isSupertypeInformationCompleteMap
        }

        val rootTypes = await(rootTypesFuture, Inf)

        /* Validate the class hierarchy... ensure concurrent execution! */
        Future[Unit] {
            // Checks if the class hierarchy is self-consistent and fixes related issues
            // if possible. All issues and fixes are logged.
            val unexpectedRootTypes = rootTypes.iterator filter (_ ne ObjectType.Object)
            if (unexpectedRootTypes.hasNext) {
                OPALLogger.warn(
                    "project configuration - class hierarchy",
                    unexpectedRootTypes
                        .map { t =>
                            (if (isInterfaceTypeMap(t.id)) "interface " else "class ") + t.toJava
                        }
                        .toList.sorted
                        .take(10)
                        .mkString(
                            "supertype information incomplete: {",
                            ", ",
                            if (unexpectedRootTypes.size > 10) ", ...}" else "}"
                        )
                )
            }

            isKnownToBeFinalMap.iterator.zipWithIndex foreach { e =>
                val (isFinal, oid) = e
                if (isFinal) {
                    if (subclassTypesMap(oid).nonEmpty) {
                        OPALLogger.warn(
                            "project configuration - class hierarchy",
                            s"the final type ${knownTypesMap(oid).toJava} "+
                                "has subclasses: "+subclassTypesMap(oid)+
                                "; resetting the \"is final\" property."
                        )
                        isKnownToBeFinalMap(oid) = false
                    }

                    if (subinterfaceTypesMap(oid).nonEmpty) {
                        OPALLogger.warn(
                            "project configuration - class hierarchy",
                            s"the final type ${knownTypesMap(oid).toJava} "+
                                "has subinterfaces: "+subclassTypesMap(oid)+
                                "; resetting the \"is final\" property."
                        )
                        isKnownToBeFinalMap(oid) = false
                    }
                }
            }
        }

        val isSupertypeInformationCompleteMap = await(isSupertypeInformationCompleteFuture, Inf)

        val (leafTypes, subtypes) = await(subtypesFuture, Inf)

        val supertypes = await(supertypesFuture, Inf)

        new ClassHierarchy(
            // BAREBONE INFORMATION
            knownTypesMap,
            isKnownTypeMap,
            isInterfaceTypeMap,
            isKnownToBeFinalMap,
            superclassTypeMap,
            superinterfaceTypesMap,
            subclassTypesMap,
            subinterfaceTypesMap,
            // DERIVED INFORMATION
            rootTypes,
            leafTypes,
            isSupertypeInformationCompleteMap,
            supertypes,
            subtypes
        )
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy