
org.opalj.fpcf.EOptionPSet.scala Maven / Gradle / Ivy
The newest version!
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package fpcf
import scala.collection.immutable.IntMap
import scala.collection.mutable
/**
* An semi-mutable set storing EPKs and interim properties.
*
* The set is semi-mutable in the sense that the concrete property values associated with
* a specific entity can be updated, but as soon as dependencies to other E/PKs are added or removed
* a new set is created; this behavior is required by the property store since the list of
* dependees w.r.t. the entity/property kind must not change
*
* The set never contains FinalEPs and can (should) therefore directly be used as the dependency
* set passed to [[InterimResult]]s.
*
* @author Michael Eichberg
*/
sealed trait EOptionPSet[E <: Entity, P <: Property] extends Iterable[EOptionP[E, P]] {
// The number of times a dependency was added or removed. That is, this number always
// increases and is used by the property store to detect if an EOptionPSet was actually
// updated w.r.t. the set of epk dependencies. However, if a dependency is updated and the
// new property is final and dependency is therefor deleted, we still do not consider that
// as an explicit epk dependency update!
private[fpcf] def epkUpdatesCount: Int
override def foreach[U](f: EOptionP[E, P] => U): Unit
override def isEmpty: Boolean
final override def hasDefiniteSize: Boolean = true
/** Filters the respective EOptionP values and returns a new EOptionPSet. */
override def filter(p: EOptionP[E, P] => Boolean): EOptionPSet[E, P] = {
// an implementation is required to shut up the compiler...
throw new UnknownError("this method must be overridden by subclasses")
}
/**
* Returns the last queried value or queries the property store and stores the value unless
* the value is final.
*
* The value is stored to ensure that a client gets a consistent view of the same EPK if it is
* queried multiple times during an analysis.
*
* If the queried eOptionP is final then it is not added to the list of dependees.
*/
def getOrQueryAndUpdate[NewE <: E, NewP <: P](
e: NewE,
pk: PropertyKey[NewP]
)(
implicit
ps: PropertyStore
): EOptionP[NewE, NewP]
/*
/** Removes all EOptionP values with the given entity from this set. */
def remove(e: Entity): Unit
/** Removes all EOptionP values with the given `PropertyKey` from this set. */
def remove(pk: SomePropertyKey): Unit
/** Removes the given EOptionP value from this set. */
def remove(eOptionP: SomeEOptionP): Unit
def clear(): Unit
*/
/**
* Updates this set's EOptionP that has the same entity and PropertyKey with the given one.
* '''Here, update means that the value is replaced, unless the new value is final. In
* that case the value is removed because it is no longer required as a dependency!'''
*/
def update(eps: SomeEPS): Unit
/**
* Updates all dependent values. Similar to the update method final values will be removed.
*/
def updateAll()(implicit ps: PropertyStore): Unit
/** Creates new successor instance which can be manipulated independently from this instance. */
override def clone(): EOptionPSet[E, P] = {
// an implementation is required to shut up the compiler...
throw new UnknownError("this method must be overridden by subclasses")
}
}
private[fpcf] class MultiEOptionPSet[E <: Entity, P <: Property](
private var data: Map[Int, mutable.Map[Entity, EOptionP[E, P]]] = IntMap.empty,
private[fpcf] var epkUpdatesCount: Int = 0
) extends EOptionPSet[E, P] {
override def foreach[U](f: EOptionP[E, P] => U): Unit = {
data.valuesIterator.foreach(_.valuesIterator.foreach(f))
}
override def forall(p: EOptionP[E, P] => Boolean) = {
data.valuesIterator.forall(_.valuesIterator.forall(p))
}
override def exists(p: EOptionP[E, P] => Boolean) = {
data.valuesIterator.exists(_.valuesIterator.exists(p))
}
override def isEmpty: Boolean = data.isEmpty // <= we always minimize the store
override def size: Int = { var size = 0; data.valuesIterator.foreach(size += _.size); size }
override def filter(p: EOptionP[E, P] => Boolean): EOptionPSet[E, P] = {
var newData: Map[Int, mutable.Map[Entity, EOptionP[E, P]]] = IntMap.empty
var filteredSomeValue = false
data.foreach { entry =>
val (pkId, eEOptionPs) = entry
val newEEOptionPs =
eEOptionPs.filter { entry =>
val (e, eOptionP) = entry
if (p(eOptionP)) {
true
} else {
filteredSomeValue = true
false
}
}
if (newEEOptionPs.nonEmpty) {
newData += ((pkId, newEEOptionPs))
}
}
if (filteredSomeValue) {
new MultiEOptionPSet(newData, epkUpdatesCount + 1)
} else {
new MultiEOptionPSet(data, epkUpdatesCount)
}
}
override def getOrQueryAndUpdate[NewE <: E, NewP <: P](
e: NewE,
pk: PropertyKey[NewP]
)(
implicit
ps: PropertyStore
): EOptionP[NewE, NewP] = {
val pkId = pk.id
data.get(pkId) match {
case Some(eEOptionPs) =>
eEOptionPs.get(e) match {
case Some(eOptionP) => eOptionP.asInstanceOf[EOptionP[NewE, NewP]]
case _ =>
val eOptionP: EOptionP[NewE, NewP] = ps(e, pk)
if (eOptionP.isRefinable) {
eEOptionPs += (eOptionP.e -> eOptionP)
epkUpdatesCount += 1
}
eOptionP
}
case _ =>
val eOptionP: EOptionP[NewE, NewP] = ps(e, pk)
if (eOptionP.isRefinable) {
data = data + (pkId -> mutable.Map(eOptionP.e -> eOptionP))
epkUpdatesCount += 1
}
eOptionP
}
}
/*
def remove(e: Entity): Unit = {
data = data.mapValues(_ -= e).filter(_._2.nonEmpty)
}
def remove(pk: SomePropertyKey): Unit = {
data = data - pk.id
}
def remove(eOptionP: SomeEOptionP): Unit = {
val pkId = eOptionP.pk.id
data.get(pkId) match {
case None => /*nothing to do*/
case Some(eEOptionPs) =>
eEOptionPs.remove(eOptionP)
data = data.updated(pkId, eEOptionPs - eOptionP.e)
}
}
def clear(): Unit = {
data = IntMap.empty
}
*/
override def update(eps: SomeEPS): Unit = {
val pkId = eps.pk.id
data.get(pkId) match {
case Some(eEOptionPs) =>
if (eps.isFinal) {
if (eEOptionPs.remove(eps.e).isEmpty) {
throw new IllegalStateException(s"no old entry found for $eps")
}
if (eEOptionPs.isEmpty) {
data = data - pkId
}
} else {
eEOptionPs.update(eps.e, eps.asInstanceOf[EPS[E, P]])
}
case None => throw new IllegalStateException(s"no old entry found for $eps")
}
}
override def updateAll()(implicit ps: PropertyStore): Unit = {
data.valuesIterator.foreach { eEOptionPs =>
eEOptionPs
.mapValuesInPlace((_, eOptionP) =>
if (eOptionP.isEPK)
ps(eOptionP.asEPK)
else
ps(eOptionP.toEPK))
.filterInPlace((_, eOptionP) =>
eOptionP.isRefinable)
}
data = data.filter(_._2.nonEmpty)
}
override def clone(): EOptionPSet[E, P] = new MultiEOptionPSet(data, epkUpdatesCount)
override def toString(): String = {
var s = "MultiEOptionPSet("
foreach(e => s += s"\n\t$e")
s+"\n)"
}
override def iterator: Iterator[EOptionP[E, P]] = data.valuesIterator.flatMap(vals => vals.valuesIterator)
}
object EOptionPSet {
def empty[E <: Entity, P <: Property]: EOptionPSet[E, P] = new MultiEOptionPSet(IntMap.empty)
// IMPROVE Provide specialized implementations for sets consisting only of e/p pairs belonging to the same PK
def apply[E <: Entity, P <: Property](eOptionP: EOptionP[E, P]): EOptionPSet[E, P] = {
if (eOptionP.isRefinable) {
new MultiEOptionPSet(IntMap(eOptionP.pk.id -> mutable.Map(eOptionP.e -> eOptionP)))
} else {
empty
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy