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

org.opalj.fpcf.PropertyComputationResult.scala Maven / Gradle / Ivy

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

import org.opalj.collection.ForeachRefIterator

/**
 * Encapsulates the (intermediate) result of the computation of a property.
 *
 * @author Michael Eichberg
 */
sealed abstract class PropertyComputationResult {

    private[fpcf] def id: Int

    private[fpcf] def isInterimResult: Boolean = false
    private[fpcf] def asInterimResult: InterimResult[_ >: Null <: Property] = {
        throw new ClassCastException();
    }

    private[fpcf] def isInterimPartialResult: Boolean = false
    private[fpcf] def asInterimPartialResult: InterimPartialResult[_ >: Null <: Property] = {
        throw new ClassCastException();
    }

    private[fpcf] def asResult: Result = throw new ClassCastException();

    private[fpcf] def asResults: Results = throw new ClassCastException();

}

/**
 * Used if the analysis found no entities for which a property could be computed.
 *
 * @note A `NoResult` can only be used as the result of an initial computation. Hence, an
 *       `OnUpdateContinuation` must never return `NoResult`.
 */
object NoResult extends PropertyComputationResult {
    private[fpcf] final val id = 0
}

trait ProperPropertyComputationResult extends PropertyComputationResult

/**
 * Encapsulates the final result of the computation of a property. I.e., the analysis
 * determined that the computed property will not be updated in the future.
 *
 * A final result is only to be used if no further refinement is possible or may happen and
 * if the bounds are correct/sound abstractions.
 *
 * @note   The framework will invoke and deregister all dependent computations (observers).
 *         If – after having a result - another result w.r.t. the given entity and property is given
 *         to the property store the behavior is undefined and may/will result in immediate and/or
 *         deferred failures!
 */
sealed abstract class FinalPropertyComputationResult extends ProperPropertyComputationResult

/**
 * Encapsulates the '''final result''' of the computation of the property `p` for the given
 * entity `e`. See [[EOptionP#ub]] for a discussion related to properties.
 *
 * @see [[FinalPropertyComputationResult]] for further information.
 */
case class Result(finalEP: FinalEP[Entity, Property]) extends FinalPropertyComputationResult {

    def this(e: Entity, p: Property) = this(FinalEP(e, p))

    private[fpcf] final def id = Result.id

    override private[fpcf] def asResult: Result = this

    override def toString: String = {
        val e = finalEP.e
        val p = finalEP.p
        s"Result($e@${System.identityHashCode(e).toHexString},p=$p)"
    }
}
object Result {

    def apply(e: Entity, p: Property): Result = Result(FinalEP(e, p))

    private[fpcf] final val id = 1

}

/**
 * Encapsulates the '''final results''' of the computation of a set of properties. Hence, all
 * results have to be w.r.t. different e/pk pairs.
 *
 * The encapsulated results are not atomically set; they are set one after another.
 *
 * @see [[FinalPropertyComputationResult]] for further information.
 */
case class MultiResult(properties: ComputationResults) extends FinalPropertyComputationResult {

    private[fpcf] final def id = MultiResult.id

}
object MultiResult { private[fpcf] final val id = 2 }

/**
 * Encapsulates an intermediate result of the computation of a property.
 *
 * Intermediate results are to be used if further refinements are possible.
 * Hence, if a property of any of the dependees changes (outgoing dependencies),
 * the given continuation `c` is invoked.
 *
 * All current computations that depend on the property of the entity will be invoked.
 *
 * @param dependees The entity/property (kind) pairs the analysis depends on. Each
 *      `entity`/`property kind` pair must occur at most once in the list, the current
 *      entity/property kind (`ep`) must not occur; i.e., self-reference are forbidden!
 *      A dependee must have been queried using `PropertyStore.apply(...)`; directly
 *      returning a dependee without a prior querying of the property store can lead to
 *      unexpected results. A dependee must NEVER be less precise than the value returned by
 *      the query.
 *
 *      In general, the set of dependees is expected to shrink over time and the result should
 *      capture the effect of all properties. However, it is possible to first wait on specific
 *      properties of specific entities, if these properties ultimately determine the overall
 *      result. Hence, it is possible to partition the set of entity / properties and to query
 *      each group one after another.
 *
 *      An `InterimResult` returned by an `OnUpdateContinuation` must contain the EPS given
 *      to the continuation function or a newer EPS (i.e., an onUpdateContinuation is allowed
 *      to query the store again).
 *
 *      ''The given set of dependees must not be mutated; it is used internally and if the
 *      set is mutated, propagation of changes no longer works reliably.''
 *
 * @param c
 *      The function which is called if a property of any of the dependees is updated.
 *      `c` does not have to be thread safe unless the same instance of `c` is returned multiple
 *      times for different entities (`e`) which should be avoided and is generally not necessary.
 *      I.e., it is recommended to think about `c` as the function that completes the
 *      computation of the property `p` for the entity `e` identified by `ep`.
 *      In general, `c` can have (mutual) state that encapsulates
 *      (temporary) information required to compute the final property.
 *
 * @note All elements on which the result declares to be dependent on must have been queried
 *      before (using one of the `apply` functions of the property store.)
 */
final class InterimResult[P >: Null <: Property] private (
        val eps:       InterimEP[Entity, P],
        val dependees: Set[SomeEOptionP], //IMPROVE: require EOptionPSets?
        val c:         ProperOnUpdateContinuation
) extends ProperPropertyComputationResult { result =>

    def key: PropertyKey[P] = eps.pk

    if (PropertyStore.Debug) { // TODO move to generic handleResult method ...
        if (dependees.isEmpty) {
            throw new IllegalArgumentException(
                s"intermediate result without dependencies: $this"+
                    " (use PartialResults for collaboratively computed results)"
            )
        }

        if (dependees.exists(_.isFinal)) {
            val m = dependees.mkString("contains final dependee: ", ",", "")
            throw new IllegalArgumentException(m)
        }

        if (dependees.exists(eOptP => eOptP.e == eps.e && eOptP.pk == result.key)) {
            throw new IllegalArgumentException(
                s"intermediate result with an illegal self-dependency: "+this
            )
        }
    }

    private[fpcf] def id: Int = InterimResult.id

    private[fpcf] override def isInterimResult: Boolean = true
    private[fpcf] override def asInterimResult: InterimResult[P] = this

    override def hashCode: Int = eps.e.hashCode * 17 + dependees.hashCode

    override def equals(other: Any): Boolean = {
        other match {
            case that: InterimResult[_] if this.eps == that.eps =>
                val dependees = this.dependees
                dependees.size == that.dependees.size &&
                    dependees.forall(thisDependee => that.dependees.contains(thisDependee))

            case _ =>
                false
        }
    }

    override def toString: String = {
        s"InterimResult($eps,dependees=${dependees.mkString("[", ", ", "]")},c=$c)"
    }
}

object InterimResult {

    private[fpcf] final val id = 3

    def apply[P >: Null <: Property](
        eps:       InterimEP[Entity, P],
        dependees: Set[SomeEOptionP],
        c:         ProperOnUpdateContinuation
    ): InterimResult[P] = {
        new InterimResult[P](eps, dependees, c)
    }

    def apply[P >: Null <: Property](
        e:         Entity,
        lb:        P,
        ub:        P,
        dependees: Set[SomeEOptionP],
        c:         ProperOnUpdateContinuation
    ): InterimResult[P] = {
        require(lb != null && ub != null)
        new InterimResult[P](InterimELUBP(e, lb, ub), dependees, c)
    }

    def create[DependeeE <: Entity, DependeeP <: Property, P >: Null <: Property](
        e:         Entity,
        lb:        P,
        ub:        P,
        dependees: Set[SomeEOptionP],
        c:         QualifiedOnUpdateContinuation[DependeeE, DependeeP]
    ): InterimResult[P] = {
        require(lb != null && ub != null)
        new InterimResult[P](
            InterimELUBP(e, lb, ub),
            dependees,
            c.asInstanceOf[ProperOnUpdateContinuation]
        )
    }

    def unapply[P >: Null <: Property](
        r: InterimResult[P]
    ): Some[(SomeEPS, Iterable[SomeEOptionP], OnUpdateContinuation)] = {
        Some((r.eps, r.dependees, r.c))
    }

    def forLB[P >: Null <: Property](
        e:         Entity,
        lb:        P,
        dependees: Set[SomeEOptionP],
        c:         ProperOnUpdateContinuation
    ): InterimResult[P] = {
        new InterimResult[P](InterimELBP(e, lb), dependees, c)
    }

    def forUB[P >: Null <: Property](
        e:         Entity,
        ub:        P,
        dependees: Set[SomeEOptionP],
        c:         ProperOnUpdateContinuation
    ): InterimResult[P] = {
        new InterimResult[P](InterimEUBP(e, ub), dependees, c)
    }
}

/**
 * Encapsulates some result and also some computations that should be computed next.
 * In this case the property store DOES NOT guarantee that the result is processed
 * before the next computations are triggered. Hence, `nextComputations` can query the e/pk
 * related to the previous result, but should not expect to already see the value of the
 * given result(s).
 *
 * Incremental results are particularly useful to process tree structures such as the class
 * hierarchy.
 *
 * @note All computations must compute different e/pk pairs which are not yet computed/scheduled or
 *       for which lazy computations are scheduled.
 *
 * @note To ensure correctness it is absolutely essential that all entities - for which some
 *       property could eventually be computed - has a property before the
 *       property store reaches quiescence. Hence, it is generally not possible that a lazy
 *       computation returns `IncrementalResult` objects.
 */
case class IncrementalResult[E <: Entity](
        result:           ProperPropertyComputationResult,
        nextComputations: Iterator[(PropertyComputation[E], E)]
) extends ProperPropertyComputationResult {

    private[fpcf] final def id = IncrementalResult.id

}

object IncrementalResult { private[fpcf] final val id = 4 }

/**
 * Just a collection of multiple results. The results have to be disjoint w.r.t. the underlying
 * e/pk pairs for which it contains results.
 */
sealed abstract class Results extends ProperPropertyComputationResult {

    private[fpcf] final def id = Results.id

    private[fpcf] final override def asResults: Results = this

    def foreach(f: ProperPropertyComputationResult => Unit): Unit

}
object Results {

    private[fpcf] final val id = 5

    def unapply(r: Results): Some[Results] = Some(r)

    def apply(results: ProperPropertyComputationResult*): Results = new Results {
        def foreach(f: ProperPropertyComputationResult => Unit): Unit = results.foreach(f)
    }

    def apply(results: IterableOnce[ProperPropertyComputationResult]): Results = new Results {
        def foreach(f: ProperPropertyComputationResult => Unit): Unit = results.iterator.foreach(f)
    }

    def apply(
        result:  ProperPropertyComputationResult,
        results: IterableOnce[ProperPropertyComputationResult]
    ): Results = new Results {
        def foreach(f: ProperPropertyComputationResult => Unit): Unit = {
            f(result)
            results.iterator.foreach(f)
        }
    }

    def apply(
        results: IterableOnce[ProperPropertyComputationResult],
        result:  ProperPropertyComputationResult
    ): Results = new Results {
        def foreach(f: ProperPropertyComputationResult => Unit): Unit = {
            results.iterator.foreach(f)
            f(result)
        }
    }

    def apply(
        resultOption: Option[ProperPropertyComputationResult],
        results:      IterableOnce[ProperPropertyComputationResult]
    ): PropertyComputationResult = {
        val it = results.iterator
        if (resultOption.isEmpty && it.isEmpty)
            NoResult
        else
            new Results {
                def foreach(f: ProperPropertyComputationResult => Unit): Unit = {
                    resultOption.foreach(r => f(r))
                    it.foreach(f)
                }
            }
    }

    def apply(results: ForeachRefIterator[ProperPropertyComputationResult]): Results = new Results {
        def foreach(f: ProperPropertyComputationResult => Unit): Unit = results.foreach(f)
    }
}

/**
 * `PartialResult`s are used for properties of entities which are computed collaboratively/in
 * a piecewise fashion.
 *
 * For example, let's assume that we have an entity `Project` which has the property to store
 * the types which are instantiated and which is updated whenever an analysis of a method
 * detects the instantiation of a type. In this case, the analysis of the method could return
 * a [[Results]] object which contains the `(Intermediate)Result` for the analysis of the method as
 * such and a `PartialResult` which will update the information about the overall set of
 * instantiated types.
 *
 * @param e The entity for which we have a partial result.
 * @param pk The kind of the property for which we have a partial result.
 * @param u The function which is given the current property (if any) and which computes the
 *          new property. `u` has to return `None` if the update does not change the property
 *          and `Some(NewProperty)` otherwise.
 * @tparam P The type of the property.
 */
case class PartialResult[E >: Null <: Entity, P >: Null <: Property](
        e:  E,
        pk: PropertyKey[P],
        u:  UpdateComputation[E, P]
) extends ProperPropertyComputationResult {

    final def epk: EPK[E, P] = EPK(e, pk)

    private[fpcf] final def id = PartialResult.id

}
object PartialResult { private[fpcf] final val id = 6 }

/**
 * `InterimPartialResult`s are used for properties of entities which are computed
 * collaboratively where the individual contribution to the final result depends on the
 * given dependees. For example an analysis which analyzes a method to determine the set
 * of all instantiated types will use an `InterimPartialResult` to commit those results.
 */
case class InterimPartialResult[SE >: Null <: Property](
        us:        Iterable[SomePartialResult], // can be empty!
        dependees: Set[SomeEOptionP], //IMPROVE: require EOptionPSets?
        c:         OnUpdateContinuation
) extends ProperPropertyComputationResult {

    assert(dependees.nonEmpty)

    override private[fpcf] def isInterimPartialResult: Boolean = true
    override private[fpcf] def asInterimPartialResult: InterimPartialResult[SE] = this

    private[fpcf] final def id = InterimPartialResult.id

}
object InterimPartialResult {

    private[fpcf] final val id = 8

    /**
     * Creates a new `InterimPartialResult` for the case where we just want to (re)register
     * a depending computation.
     */
    def apply[SE >: Null <: Property](
        dependees: Set[SomeEOptionP],
        c:         OnUpdateContinuation
    ): InterimPartialResult[SE] = {
        new InterimPartialResult[SE](Nil, dependees, c)
    }

    /**
     * Creates a new `InterimPartialResult`s
     *
     * @param uE The entity for which we have a partial result.
     * @param uPK The kind of the property for which we have a partial result.
     * @param u The function which is given the current property (if any) and which computes the
     *          new property. `u` has to return `None` if the update does not change the property
     *          and `Some(NewProperty)` otherwise.
     */
    def apply[SE >: Null <: Property, UE >: Null <: Entity, UP >: Null <: Property](
        uE:        UE,
        uPK:       PropertyKey[UP],
        u:         UpdateComputation[UE, UP],
        dependees: Set[SomeEOptionP],
        c:         OnUpdateContinuation
    ): InterimPartialResult[SE] = {
        val pruc = PartialResult(uE, uPK, u)
        new InterimPartialResult[SE](List(pruc), dependees, c)
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy