
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