
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