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

org.opalj.fpcf.seq.PKESequentialPropertyStore.scala Maven / Gradle / Ivy

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

import scala.language.existentials

import scala.collection.mutable
import scala.collection.mutable.AnyRefMap
import scala.collection.mutable.ArrayBuffer

import com.typesafe.config.Config

import org.opalj.control.foreachWithIndex
import org.opalj.log.LogContext
import org.opalj.log.OPALLogger.{debug => trace}
import org.opalj.log.OPALLogger.info
import org.opalj.fpcf.PropertyKind.SupportedPropertyKinds

/**
 * A reasonably optimized, complete, but non-concurrent implementation of the property store.
 * Primarily intended to be used for evaluation, debugging and prototyping purposes.
 *
 * @author Michael Eichberg
 */
final class PKESequentialPropertyStore protected (
        val ctx:                Map[Class[_], AnyRef],
        val tasksManager:       TasksManager,
        val MaxEvaluationDepth: Int
)(
        implicit
        val logContext: LogContext
) extends SeqPropertyStore { store =>

    info("property store", s"using $tasksManager for managing tasks")
    info("property store", s"the MaxEvaluationDepth is $MaxEvaluationDepth")

    import PKESequentialPropertyStore.EntityDependers

    // --------------------------------------------------------------------------------------------
    //
    // STATISTICS
    //
    // --------------------------------------------------------------------------------------------

    private[this] var scheduledTasksCounter: Int = 0
    override def scheduledTasksCount: Int = scheduledTasksCounter

    private[this] var scheduledOnUpdateComputationsCounter: Int = 0
    override def scheduledOnUpdateComputationsCount: Int = scheduledOnUpdateComputationsCounter

    private[this] var fallbacksUsedForComputedPropertiesCounter: Int = 0
    override def fallbacksUsedForComputedPropertiesCount: Int = {
        fallbacksUsedForComputedPropertiesCounter
    }
    override private[fpcf] def incrementFallbacksUsedForComputedPropertiesCounter(): Unit = {
        fallbacksUsedForComputedPropertiesCounter += 1
    }

    private[this] var quiescenceCounter = 0
    override def quiescenceCount: Int = quiescenceCounter

    // --------------------------------------------------------------------------------------------
    //
    // CORE DATA STRUCTURES
    //
    // --------------------------------------------------------------------------------------------

    private[this] var evaluationDepth: Int = 0

    // If the map's value is an epk a lazy analysis was started if it exists.
    private[this] val ps: Array[mutable.AnyRefMap[Entity, SomeEOptionP]] = {
        Array.fill(PropertyKind.SupportedPropertyKinds) { mutable.AnyRefMap.empty }
    }

    // type EntityDependers = AnyRefMap[SomeEPK, OnUpdateContinuation]
    private[this] val dependers: Array[AnyRefMap[Entity, EntityDependers]] = {
        Array.fill(SupportedPropertyKinds) { new AnyRefMap() }
    }

    private[this] val dependees: Array[AnyRefMap[Entity, Iterable[SomeEOptionP]]] = {
        Array.fill(SupportedPropertyKinds) { new AnyRefMap() }
    }

    private[seq] def dependeesCount(eOptionP: SomeEOptionP): Int = {
        dependees(eOptionP.pk.id).get(eOptionP.e) match {
            case Some(dependees) => dependees.size
            case _               => 0
        }
    }

    private[seq] def liveDependeesCount(eOptionP: SomeEOptionP): Int = {
        dependees(eOptionP.pk.id).get(eOptionP.e) match {
            case Some(dependees) => dependees.count(dependee => store(dependee.toEPK).isRefinable)
            case _               => 0
        }
    }

    private[seq] def dependees(eOptionP: SomeEOptionP): Iterable[SomeEOptionP] = {
        dependees(eOptionP.pk.id).getOrElse(eOptionP.e, Nil)
    }

    private[seq] def dependersCount(eOptionP: SomeEOptionP): Int = {
        dependers(eOptionP.pk.id).get(eOptionP.e) match {
            case Some(dependers) => dependers.size
            case _               => 0
        }
    }

    private[seq] def dependers(eOptionP: SomeEOptionP): Iterable[SomeEPK] = {
        dependers(eOptionP.pk.id).get(eOptionP.e) match {
            case Some(dependers) => dependers.keys
            case _               => Nil
        }
    }

    private[seq] def hasDependers(eOptionP: SomeEOptionP): Boolean = {
        dependers(eOptionP.pk.id).get(eOptionP.e) match {
            case Some(dependers) => dependers.nonEmpty
            case _               => false
        }
    }

    // The registered triggered computations along with the set of entities for which the analysis was triggered
    private[this] val triggeredComputations: Array[mutable.AnyRefMap[SomePropertyComputation, mutable.HashSet[Entity]]] = {
        Array.fill(PropertyKind.SupportedPropertyKinds) { mutable.AnyRefMap.empty }
    }

    override def toString(printProperties: Boolean): String = {
        if (printProperties) {
            val properties = for {
                (epks, pkId) <- ps.iterator.zipWithIndex
                (e, eOptionP) <- epks.iterator
            } yield {
                val propertyKindName = PropertyKey.name(pkId)
                s"$e -> $propertyKindName[$pkId] = $eOptionP"
            }
            properties.mkString("PropertyStore(\n\t\t", "\n\t\t", "\n)")
        } else {
            s"PropertyStore(#properties=${ps.iterator.map(_.size).sum})"
        }
    }

    override def isKnown(e: Entity): Boolean = ps.exists(_.contains(e))

    override def hasProperty(e: Entity, pk: PropertyKind): Boolean = {
        require(e ne null)
        val eOptionPOption = ps(pk.id).get(e)
        eOptionPOption.isDefined && {
            val eOptionP = eOptionPOption.get
            eOptionP.hasUBP || eOptionP.hasLBP
        }
    }

    override def properties[E <: Entity](e: E): Iterator[EPS[E, Property]] = {
        require(e ne null)
        for {
            epks <- ps.iterator
            eOptionPOption = epks.get(e)
            if eOptionPOption.isDefined
            eOptionP = eOptionPOption.get
            if eOptionP.isEPS
        } yield {
            eOptionP.asEPS.asInstanceOf[EPS[E, Property]]
        }
    }

    override def entities(propertyFilter: SomeEPS => Boolean): Iterator[Entity] = {
        // We have no further EPKs when we are quiescent!
        for {
            epks <- ps.iterator
            (e, eOptionP) <- epks.iterator
            eps <- eOptionP.toEPS
            if propertyFilter(eps)
        } yield {
            e
        }
    }

    override def entities[P <: Property](lb: P, ub: P): Iterator[Entity] = {
        require(lb ne null)
        require(ub ne null)
        assert(lb.key == ub.key)
        for { ELUBP(e, `lb`, `ub`) <- ps(lb.id).valuesIterator } yield { e }
    }

    override def entitiesWithLB[P <: Property](lb: P): Iterator[Entity] = {
        require(lb ne null)
        for { ELBP(e, `lb`) <- ps(lb.id).valuesIterator } yield { e }
    }

    override def entitiesWithUB[P <: Property](ub: P): Iterator[Entity] = {
        require(ub ne null)
        for { EUBP(e, `ub`) <- ps(ub.id).valuesIterator } yield { e }
    }

    override def entities[P <: Property](pk: PropertyKey[P]): Iterator[EPS[Entity, P]] = {
        ps(pk.id).valuesIterator.collect { case eps: SomeEPS => eps.asInstanceOf[EPS[Entity, P]] }
    }

    override def get[E <: Entity, P <: Property](
        e:  E,
        pk: PropertyKey[P]
    ): Option[EOptionP[E, P]] = {
        ps(pk.id).get(e).asInstanceOf[Option[EOptionP[E, P]]]
    }

    override def get[E <: Entity, P <: Property](epk: EPK[E, P]): Option[EOptionP[E, P]] = {
        get(epk.e, epk.pk)
    }

    override protected[this] def doApply[E <: Entity, P <: Property](
        epk:  EPK[E, P],
        e:    E,
        pkId: Int
    ): EOptionP[E, P] = {
        val epss = ps(pkId)
        epss.get(e) match {
            case None =>
                // the entity is unknown ...
                lazyComputations(pkId) match {
                    case null =>
                        if (propertyKindsComputedInThisPhase(pkId)) {
                            val transformerSpecification = transformersByTargetPK(pkId)
                            if (transformerSpecification != null) {
                                // ... we have a transformer that can produce a property
                                // of the required kind; let's check if we can invoke it now or
                                // have to invoke it later.
                                val (sourcePK, transform) = transformerSpecification
                                val sourceEPK = EPK(e, sourcePK)
                                // We have to "apply" to ensure that all necessary lazy analyses
                                // get triggered
                                val sourceEOptionP = apply(sourceEPK)
                                if (sourceEOptionP.isFinal) {
                                    val FinalP(sourceP) = sourceEOptionP
                                    val finalEP = transform(e, sourceP).asInstanceOf[FinalEP[E, P]]
                                    update(finalEP, Nil)
                                    return finalEP;
                                } else {
                                    // Add this transformer as a depender to the transformer's
                                    // source; this works, because notifications about intermediate
                                    // values are suppressed.
                                    // This will happen only once, because afterwards an EPK
                                    // will be stored in the properties data structure and
                                    // then returned.
                                    val c: OnUpdateContinuation = (eps) => {
                                        val FinalP(p) = eps
                                        Result(transform(e, p))
                                    }
                                    dependers(sourcePK.id)
                                        .getOrElseUpdate(e, AnyRefMap.empty)
                                        .put(epk, c)
                                    dependees(pkId).put(e, List(sourceEPK))
                                }
                            }
                            epss.put(e, epk)
                            epk
                        } else {
                            val finalEP = computeFallback(e, pkId)
                            update(finalEP, Nil)
                            finalEP
                        }

                    case lc: PropertyComputation[E] @unchecked =>

                        // associate e with EPK to ensure that we do not schedule
                        // multiple (lazy) computations and that we do not run in cycles
                        // => the entity is now known
                        epss.put(e, epk)
                        if (evaluationDepth < MaxEvaluationDepth) {
                            evaluationDepth += 1
                            handleResult(lc(e))
                            evaluationDepth -= 1
                            // we now have a new result (at least an EPK)
                            epss(e).asInstanceOf[EOptionP[E, P]]
                        } else {
                            scheduleLazyComputationForEntity(e)(lc)
                            // return the "current" result
                            epk
                        }
                }

            case Some(eOptionP: EOptionP[E, P] @unchecked) =>
                eOptionP
        }
    }

    override def force[E <: Entity, P <: Property](e: E, pk: PropertyKey[P]): Unit = {
        if (debug) {
            val pkId = pk.id
            if (lazyComputations(pkId) == null && transformersByTargetPK(pkId) == null) {
                throw new IllegalArgumentException(s"force for a non-lazily computed property: $pk")
            }
        }
        apply[E, P](EPK(e, pk))
    }

    override protected[this] def doRegisterTriggeredComputation[E <: Entity, P <: Property](
        pk: PropertyKey[P],
        pc: PropertyComputation[E]
    ): Unit = {
        val triggeredEntities = mutable.HashSet.empty[Entity]
        triggeredComputations(pk.id).addOne(pc, triggeredEntities)
    }

    private[this] def triggerComputations(e: Entity, pkId: Int): Unit = {
        val triggeredComputations = this.triggeredComputations(pkId)
        if (triggeredComputations != null) {
            triggeredComputations foreach { pcEntities =>
                val (pc, triggeredEntities) = pcEntities
                if (!triggeredEntities.contains(e)) {
                    triggeredEntities += e
                    scheduleEagerComputationForEntity(e)(pc.asInstanceOf[PropertyComputation[Entity]])
                }
            }
        }
    }

    private[this] def scheduleLazyComputationForEntity[E <: Entity](
        e: E
    )(
        pc: PropertyComputation[E]
    ): Unit = handleExceptions {
        scheduledTasksCounter += 1
        tasksManager.push(new PropertyComputationTask(this, e, pc))
    }

    override def doScheduleEagerComputationForEntity[E <: Entity](
        e: E
    )(
        pc: PropertyComputation[E]
    ): Unit = handleExceptions {
        scheduledTasksCounter += 1
        tasksManager.push(new PropertyComputationTask(this, e, pc))
    }

    private[this] def removeDependerFromDependees(dependerEPK: SomeEPK): Unit = {
        val dependerPKId = dependerEPK.pk.id
        val e = dependerEPK.e
        for {
            epkDependees <- dependees(dependerPKId).remove(e)
            EOptionP(oldDependeeE, oldDependeePK) <- epkDependees // <= the old ones
            oldDependeePKId = oldDependeePK.id
            dependeeDependers <- dependers(oldDependeePKId).get(oldDependeeE)
        } {
            dependeeDependers -= dependerEPK
            if (dependeeDependers.isEmpty) {
                dependers(oldDependeePKId).remove(oldDependeeE)
            }
        }
    }

    /**
     * Updates the entity and triggers dependers.
     */
    private[this] def update(
        eps: SomeEPS,
        // RECALL, IF THE EPS IS THE RESULT OF A PARTIAL RESULT UPDATE COMPUTATION, THEN
        // NEW DEPENDEES WILL ALWAYS BE EMPTY!
        newDependees: Iterable[SomeEOptionP]
    ): Unit = {
        val pkId = eps.pk.id
        val e = eps.e
        val notificationRequired = ps(pkId).put(e, eps) match {
            case None =>
                // The entity was unknown; i.e., there can't be any dependees - no one queried
                // the property.
                triggerComputations(e, pkId)
                if (newDependees.nonEmpty) {
                    dependees(pkId).put(e, newDependees)
                }
                // registration with the new dependees is done when processing InterimResult

                // let's check if we have dependers!
                true

            case Some(oldEOptionP) =>
                // The entity is already known and therefore we may have (old) dependees
                // and/or also dependers.
                if (oldEOptionP.isEPK) {
                    triggerComputations(e, pkId)
                }
                if (debug) oldEOptionP.checkIsValidPropertiesUpdate(eps, newDependees)
                if (newDependees.isEmpty)
                    dependees(pkId).remove(e)
                else
                    dependees(pkId).put(e, newDependees)
                eps.isUpdatedComparedTo(oldEOptionP)
        }
        if (notificationRequired) {
            val isFinal = eps.isFinal
            val theDependers = dependers(pkId).get(e)
            theDependers.foreach { dependersOfEPK =>
                val currentDependers = dependersOfEPK.keys
                dependersOfEPK foreach { dependerEKPc =>
                    val (dependerEPK, c) = dependerEKPc
                    if (isFinal || !suppressInterimUpdates(dependerEPK.pk.id)(pkId)) {
                        val t: QualifiedTask =
                            if (isFinal) {
                                new OnFinalUpdateComputationTask(this, eps.asFinal, c)
                            } else {
                                new OnUpdateComputationTask(this, eps.toEPK, c)
                            }
                        tasksManager.push(t, dependerEPK, eps, newDependees, currentDependers)
                        scheduledOnUpdateComputationsCounter += 1
                        removeDependerFromDependees(dependerEPK)
                    } else if (traceSuppressedNotifications) {
                        trace("analysis progress", s"suppressed notification: $eps -> $dependerEPK")
                    }
                }
            }
        }
    }

    override def doSet(e: Entity, p: Property): Unit = handleExceptions {
        val key = p.key
        val pkId = key.id

        val oldPV = ps(pkId).put(e, new FinalEP(e, p))
        if (oldPV.isDefined) {
            throw new IllegalStateException(s"$e had already the property $oldPV")
        }
    }

    override def doPreInitialize[E <: Entity, P <: Property](
        e:  E,
        pk: PropertyKey[P]
    )(
        pc: EOptionP[E, P] => InterimEP[E, P]
    ): Unit = {
        val pkId = pk.id
        val propertiesOfKind = ps(pkId)
        val newEPS =
            propertiesOfKind.get(e) match {
                case None         => pc(EPK(e, pk))
                case Some(oldEPS) => pc(oldEPS.asInstanceOf[EOptionP[E, P]])
            }
        propertiesOfKind.put(e, newEPS)
    }

    private[this] def handlePartialResult(
        e:  Entity,
        pk: SomePropertyKey,
        u:  UpdateComputation[_ <: Entity, _ <: Property]
    ): Unit = {
        type E = e.type
        type P = Property
        val eOptionP = apply[E, P](e: E, pk: PropertyKey[P])
        val newEPSOption = u.asInstanceOf[EOptionP[E, P] => Option[EPS[E, P]]](eOptionP)
        newEPSOption foreach { newEPS => update(newEPS, Nil /*<= w.r.t. the "newEPS"!*/ ) }
    }

    @inline private[this] def handlePartialResults(prs: Iterable[SomePartialResult]): Unit = {
        // It is ok if prs is empty!
        prs foreach { pr => handlePartialResult(pr.e, pr.pk, pr.u) }
    }

    /* Returns `true` if no dependee was updated in the meantime. */
    private[this] def processDependeesOfInterimPartialResult(
        partialResults:     Iterable[SomePartialResult],
        processedDependees: Iterable[SomeEOptionP],
        c:                  OnUpdateContinuation
    ): (Iterable[SomeEOptionP], OnUpdateContinuation) = {
        var nextPartialResults = partialResults
        var nextProcessedDependees = processedDependees
        var nextC = c

        var continue = false
        do {
            continue = false

            handlePartialResults(nextPartialResults) // this may have triggered some computations...

            nextProcessedDependees exists { processedDependee =>
                val processedDependeeE = processedDependee.e
                val processedDependeePK = processedDependee.pk
                val processedDependeePKId = processedDependeePK.id
                val currentDependee = ps(processedDependeePKId)(processedDependeeE)
                if (currentDependee.isUpdatedComparedTo(processedDependee)) {

                    def handleOtherResult(result: PropertyComputationResult): Unit = {
                        // this shouldn't happen... too often
                        scheduledOnUpdateComputationsCounter += 1
                        val t = HandleResultTask(this, result)
                        tasksManager.push(t)
                    }
                    // There were updates...
                    val nextR = nextC(currentDependee.asEPS)
                    nextProcessedDependees = null
                    nextC = null
                    nextR match {

                        case InterimPartialResult(newPartialResults, newProcessedDependees, newC) =>
                            nextPartialResults = newPartialResults
                            nextProcessedDependees = newProcessedDependees
                            nextC = newC
                            continue = true

                        case Results(results) =>
                            results.foreach {
                                case InterimPartialResult(newPartialResults, newProcessedDependees, newC) if nextC == null =>
                                    nextPartialResults = newPartialResults
                                    nextProcessedDependees = newProcessedDependees
                                    nextC = newC
                                    continue = true

                                /*case InterimResult(newEPS @ SomeEPS(`e`, `pk`), newDependees, newC, _) =>
                                        nextEPS = newEPS
                                        nextC = newC
                                        nextDependees = newDependees
                                        continue = true*/

                                case r: Result =>
                                    update(r.finalEP, Nil)

                                case PartialResult(e, pk, u) =>
                                    handlePartialResult(e, pk, u)

                                case result =>
                                    handleOtherResult(result)
                            }

                        case result =>
                            handleOtherResult(result)
                    }
                    true
                } else {
                    false
                }
            }

        } while (continue)

        (nextProcessedDependees, nextC)
    }

    private[this] def processDependeesOfInterimResult(
        initialEPS:       SomeEPS,
        initialDependees: Iterable[SomeEOptionP],
        initialC:         OnUpdateContinuation
    ): (SomeEPS, Iterable[SomeEOptionP], OnUpdateContinuation) = {
        // The idea is to stack/aggregate all changes in dependees.
        val e = initialEPS.e
        val pk = initialEPS.pk

        var nextEPS = initialEPS
        var nextDependees = initialDependees
        var nextC = initialC

        var continue = false
        do {
            continue = false
            nextDependees exists /* <= used for early termination purposes */ { nextDependee =>
                val nextDependeeE = nextDependee.e
                val nextDependeePK = nextDependee.pk
                val nextDependeePKId = nextDependeePK.id
                val currentDependee = ps(nextDependeePKId)(nextDependeeE)
                if (currentDependee.isUpdatedComparedTo(nextDependee)) {
                    nextC(currentDependee.asEPS) match {
                        case InterimResult(newEPS @ SomeEPS(`e`, `pk`), newDependees, newC) =>
                            nextEPS = newEPS
                            nextC = newC
                            nextDependees = newDependees
                            continue = true

                        case Result(finalEP @ SomeFinalEP(`e`, `pk`)) =>
                            nextEPS = finalEP
                            nextDependees = Nil
                            nextC = null
                        // continue remains "false"

                        case r =>
                            // Actually this shouldn't happen, though it is not a problem!
                            scheduledOnUpdateComputationsCounter += 1
                            tasksManager.push(HandleResultTask(store, r))
                            // The last comparable result still needs to be stored,
                            // but obviously, no further relevant computations need to be
                            // carried out.
                            nextDependees = Nil
                            nextC = null
                    }
                    true // <= abort processing current dependees
                } else {
                    false
                }
            }
        } while (continue)

        (nextEPS, nextDependees, nextC)
    }

    override def handleResult(r: PropertyComputationResult): Unit = handleExceptions {

        // if (debug) {
        //     trace("analysis progress", s"handling result: $r")
        // }

        r.id match {

            case NoResult.id =>
            // A computation reported no result; i.e., it is not possible to
            // compute a/some property/properties for a given entity.

            case IncrementalResult.id =>
                val IncrementalResult(ir, npcs /*: Iterator[(PropertyComputation[e],e)]*/ ) = r
                handleResult(ir)
                npcs foreach { npc => val (pc, e) = npc; scheduleEagerComputationForEntity(e)(pc) }

            case Results.id =>
                r.asResults.foreach(r => handleResult(r))

            case MultiResult.id =>
                val MultiResult(results) = r
                results.iterator.foreach { finalEP => update(finalEP, newDependees = Nil) }

            //
            // Methods which actually store results...
            //

            case Result.id =>
                update(r.asResult.finalEP, Nil)

            case PartialResult.id =>
                val PartialResult(e, pk, u) = r
                handlePartialResult(e, pk, u)

            case InterimPartialResult.id =>
                val InterimPartialResult(prs, processedDependees, c) = r
                // 1. let's check if a new dependee is already updated...
                val (newDependees, newC) =
                    processDependeesOfInterimPartialResult(prs, processedDependees, c)

                // 2. register depender/dependees relation
                if (newC ne null) {
                    val sourceE = new Object() // an arbitrary, but unique object
                    // The most current value of every dependee was taken into account
                    // register with the (!) dependees.
                    val dependerAK = EPK(sourceE, AnalysisKey)
                    newDependees foreach { dependee =>
                        val dependeeDependers =
                            dependers(dependee.pk.id).getOrElseUpdate(dependee.e, AnyRefMap.empty)
                        dependeeDependers += ((dependerAK, newC))
                    }
                    dependees(AnalysisKeyId).put(sourceE, newDependees)
                } else {
                    // There was an update and we already scheduled the computation... hence,
                    // we have no live dependees any more.
                    assert(newDependees == null || newDependees.isEmpty)
                }

            case InterimResult.id =>
                val ir = r.asInterimResult
                val eps = ir.eps
                val dependees = ir.dependees
                val c = ir.c
                // 1. let's check if a dependee is already updated...
                //    If so, we directly schedule a task again to compute the property.
                val (newEPS, newDependees, newC) =
                    processDependeesOfInterimResult(eps, dependees, c)

                assert(newEPS.e == eps.e)
                assert(newEPS.pk == eps.pk)

                // 2. update the value and trigger dependers/clear old dependees;
                update(newEPS, newDependees)
                if (newDependees.nonEmpty) {
                    val dependerEPK = newEPS.toEPK
                    newDependees foreach { dependee =>
                        val dependeeDependers =
                            dependers(dependee.pk.id).getOrElseUpdate(dependee.e, AnyRefMap.empty)
                        dependeeDependers += ((dependerEPK, newC))
                    }
                }
        }
    }

    override def isIdle: Boolean = tasksManager.isEmpty

    protected[this] def processTasks(): Unit = {
        while (!tasksManager.isEmpty) {
            tasksManager.pollAndExecute()
            if (doTerminate) throw new InterruptedException()
        }
    }

    override def execute(f: => Unit): Unit = handleExceptions {
        f
    }

    override def waitOnPhaseCompletion(): Unit = handleExceptions {
        require(subPhaseId == 0, "unpaired waitOnPhaseCompletion call")

        if (triggeredComputations.exists(_.nonEmpty)) {
            // Let's trigger triggered computations for those entities, which have values!
            foreachWithIndex(ps) { (epss, pkId) =>
                epss foreach { eps =>
                    val (e, eOptionP) = eps
                    if (eOptionP.isEPS) {
                        triggerComputations(e, pkId)
                    }
                }
            }
        }

        val maxPKIndex = PropertyKey.maxId
        var continueComputation: Boolean = false
        do {
            continueComputation = false

            processTasks()

            quiescenceCounter += 1
            if (debug) {
                trace("analysis progress", s"reached quiescence $quiescenceCounter")
            }

            // We have reached quiescence....

            // 1. Let's search for all EPKs (not EPS) and use the fall back for them.
            //    (Recall that we return fallback properties eagerly if no analysis is
            //     scheduled or will be scheduled, However, it is still possible that we will
            //     not have computed a property for a specific entity, if the underlying
            //     analysis doesn't compute one; in that case we need to put in fallback
            //     values.)
            var pkId = 0
            while (pkId <= maxPKIndex) {
                if (propertyKindsComputedInThisPhase(pkId)) {
                    val epkIterator =
                        ps(pkId)
                            .valuesIterator
                            .filter { eOptionP =>
                                eOptionP.isEPK &&
                                    // There is no suppression; i.e., we have no dependees
                                    dependees(pkId).get(eOptionP.e).isEmpty
                            }
                    continueComputation |= epkIterator.hasNext
                    epkIterator.foreach { eOptionP =>
                        val e = eOptionP.e
                        val r = computeFallback(e, pkId)
                        update(r, Nil)
                    }
                }
                pkId += 1
            }

            // 2. Let's search for entities with interim properties where some dependers
            //    were not yet notified about intermediate updates. In this case, the
            //    current results of the dependers cannot be finalized; instead, we need
            //    to finalize (the cyclic dependent) dependees first and notify the
            //    dependers.
            //    Recall, that collaboratively computed properties are not allowed to be
            //    part of a cyclic computation if we also have suppressed notifications.
            if (!continueComputation && hasSuppressedNotifications) {
                // Collect all InterimEPs to find cycles.
                val interimEPs = ArrayBuffer.empty[SomeEOptionP]
                var pkId = 0
                while (pkId <= maxPKIndex) {
                    if (propertyKindsComputedInThisPhase(pkId)) {
                        ps(pkId).valuesIterator foreach { eps =>
                            if (eps.isRefinable) interimEPs += eps
                        }
                    }
                    pkId += 1
                }

                val successors = (interimEP: SomeEOptionP) => {
                    dependees(interimEP.pk.id).getOrElse(interimEP.e, Nil)
                }
                val cSCCs = graphs.closedSCCs(interimEPs, successors)
                continueComputation = cSCCs.nonEmpty
                for (cSCC <- cSCCs) {
                    // Clear all dependees of all members of a cycle to avoid inner cycle
                    // notifications!
                    for (interimEP <- cSCC) { removeDependerFromDependees(interimEP.toEPK) }
                    // 2. set all values
                    for (interimEP <- cSCC) { update(interimEP.toFinalEP, Nil) }
                }
            }

            // 3. Let's finalize remaining interim EPS; e.g., those related to
            //    collaboratively computed properties or "just all" if we don't have suppressed
            //    notifications. Recall that we may have cycles if we have no suppressed
            //    notifications, because in the latter case, we may have dependencies.
            //    We used no fallbacks, but we may still have collaboratively computed properties
            //    (e.g. CallGraph) which are not yet final; let's finalize them in the specified
            //    order (i.e., let's finalize the subphase)!
            while (!continueComputation && subPhaseId < subPhaseFinalizationOrder.length) {
                val pksToFinalize = subPhaseFinalizationOrder(subPhaseId)
                if (debug) {
                    trace(
                        "analysis progress",
                        pksToFinalize.map(PropertyKey.name).mkString("finalization of: ", ",", "")
                    )
                }
                // The following will also kill dependers related to anonymous computations using
                // the generic property key: "AnalysisKey"; i.e., those without explicit properties!
                pksToFinalize foreach { pk =>
                    val dependeesIt = dependees(pk.id).keysIterator
                    continueComputation |= dependeesIt.nonEmpty
                    dependeesIt foreach { e =>
                        removeDependerFromDependees(EPK(e, PropertyKey.key(pk.id)))
                    }
                }
                pksToFinalize foreach { pk =>
                    val interimEPSs = ps(pk.id).valuesIterator.filter(_.isRefinable)
                    interimEPSs foreach { interimEP =>
                        val finalEP = interimEP.toFinalEP
                        update(finalEP, Nil)
                    }
                }
                // Clear "dangling" maps in the depender/dependee data structures:
                pksToFinalize foreach { pk =>
                    dependees(pk.id) == null // <= we are really done
                    dependers(pk.id) == null // <= we are really done
                }
                subPhaseId += 1
            }
            if (debug && continueComputation && !tasksManager.isEmpty) {
                trace(
                    "analysis progress",
                    s"finalization of sub phase $subPhaseId of "+
                        s"${subPhaseFinalizationOrder.length} led to ${tasksManager.size} updates "
                )
            }
        } while (continueComputation)

        if (exception != null) throw exception;
    }

    def shutdown(): Unit = {}
}

/**
 * Factory for creating `PKESequentialPropertyStore`s.
 *
 * The task manager that will be used to instantiate the project will be extracted from the
 * `PropertyStoreContext` if the context contains a `Config` object. The fallback is the
 * `ManyDirectDependersLastTasksManager`.
 *
 * @author Michael Eichberg
 */
object PKESequentialPropertyStore extends PropertyStoreFactory[PKESequentialPropertyStore] {

    final type EntityDependers = AnyRefMap[SomeEPK, OnUpdateContinuation]

    final val TasksManagerKey = "org.opalj.fpcf.seq.PKESequentialPropertyStore.TasksManager"
    final val MaxEvaluationDepthKey = "org.opalj.fpcf.seq.PKESequentialPropertyStore.MaxEvaluationDepth"

    final val Strategies = List(
        "ManyDirectDependenciesLast",
        "ManyDirectDependersLast",
        "ManyDependeesOfDirectDependersLast",
        "ManyDependeesAndDependersOfDirectDependersLast",
        "ManyDirectDependenciesFirst",
        "ManyDirectDependersFirst",
        "ManyDependeesOfDirectDependersFirst",
        "ManyDependeesAndDependersOfDirectDependersFirst",
        "FIFO",
        "LIFO" /*,
        "ForwardAllDependeesLast",
        "ForwardAllDependeesFirst",
        "BackwardAllDependeesLast",
        "BackwardAllDependeesFirst"*/
    )

    def apply(
        context: PropertyStoreContext[_ <: AnyRef]*
    )(
        implicit
        logContext: LogContext
    ): PKESequentialPropertyStore = {
        val contextMap: Map[Class[_], AnyRef] = context.map(_.asTuple).toMap
        val config =
            contextMap.get(classOf[Config]) match {
                case Some(config: Config) => config
                case _                    => org.opalj.BaseConfig
            }
        val taskManagerId = config.getString(TasksManagerKey)
        val maxEvaluationDepth = config.getInt(MaxEvaluationDepthKey)
        apply(taskManagerId, maxEvaluationDepth)(contextMap)
    }

    def apply(
        taskManagerId:      String,
        maxEvaluationDepth: Int
    )(
        context: Map[Class[_], AnyRef] = Map.empty
    )(
        implicit
        logContext: LogContext
    ): PKESequentialPropertyStore = {
        val tasksManager: TasksManager = taskManagerId match {

            case "FIFO"                       => new FIFOTasksManager
            case "LIFO"                       => new LIFOTasksManager

            case "ManyDirectDependenciesLast" => new ManyDirectDependenciesLastTasksManager
            case "ManyDirectDependersLast"    => new ManyDirectDependersLastTasksManager
            case "ManyDependeesOfDirectDependersLast" =>
                new ManyDependeesOfDirectDependersLastTasksManager
            case "ManyDependeesAndDependersOfDirectDependersLast" =>
                new ManyDependeesAndDependersOfDirectDependersLastTasksManager

            case "ManyDirectDependenciesFirst" => new ManyDirectDependenciesFirstTasksManager
            case "ManyDirectDependersFirst"    => new ManyDirectDependersFirstTasksManager
            case "ManyDependeesOfDirectDependersFirst" =>
                new ManyDependeesOfDirectDependersFirstTasksManager
            case "ManyDependeesAndDependersOfDirectDependersFirst" =>
                new ManyDependeesAndDependersOfDirectDependersFirstTasksManager

            case "ForwardAllDependeesLast" | "ForwardAllDependeesFirst" |
                "BackwardAllDependeesLast" | "BackwardAllDependeesFirst" =>
                val forward = taskManagerId.startsWith("Forward")
                val manyDependeesLast = taskManagerId.endsWith("Last")
                new AllDependeesTasksManager(forward, manyDependeesLast)

            case _ => throw new IllegalArgumentException(s"unknown task manager $taskManagerId")
        }

        val ps = new PKESequentialPropertyStore(context, tasksManager, maxEvaluationDepth)
        tasksManager match {
            case propertyStoreDependentTaskManager: PropertyStoreDependentTasksManager =>
                propertyStoreDependentTaskManager.setSeqPropertyStore(ps)
                ps
            case _ =>
                ps
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy