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

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