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

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

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

/**
 * An entity associated with the current extension of a property or `None` if no (preliminary)
 * property is already computed.
 *
 * @author Michael Eichberg
 */
sealed trait EOptionP[+E <: Entity, +P <: Property] {

    /**
     * The entity. E.g., a class, method or field. In general, it is recommended
     * to use entities that stand for specific elements in the code, but which
     * are not the concrete source code entities themselves. This greatly facilitates
     * associating properties with entities where the respective code is not available.
     * For example, by using an object which acts as a representative for a concrete method
     * it is possible to associate (predetermined) properties with (selected) library methods
     * even if those methods are not part of the analysis.
     *
     * @note Entities have to implement `equals`/`hashCode` methods which are very efficient,
     *       because entity based comparisons happen very frequently!
     */
    val e: E

    /**
     * The property key of the optionally available property.
     */
    def pk: PropertyKey[P]

    def isEPK: Boolean
    def asEPK: EPK[E, P]

    /** This EOptionP as a pair of an entity and a property key. */
    def toEPK: EPK[E, P]

    /**
     * @return  `true` if the entity is associated with a (preliminary) property which represents
     *          an upper bound. Always `true` in case of a `FinalP`.
     */
    def hasUBP: Boolean
    final def hasNoUBP: Boolean = !hasUBP

    /**
     * @return  `true` if the entity is associated with a (preliminary) property which represents
     *          a lower bound. Always `true` in case of a `FinalP`.
     */
    def hasLBP: Boolean
    final def hasNoLBP: Boolean = !hasLBP

    def isEPS: Boolean
    /**
     * This EOptionP as an EPS object; defined iff at least a preliminary property exists.
     */
    def asEPS: EPS[E, P]

    def toEPS: Option[EPS[E, P]]

    /**
     * Returns `true` if and only if we have a property and the property was stored in the
     * store using `(Multi)Result`.
     */
    def isFinal: Boolean
    final def isRefinable: Boolean = !isFinal
    def asFinal: FinalEP[E, P]

    def asInterim: InterimEP[E, P]

    /**
     * Combines the test if we have a final property and – if we have one – if it is equal (by
     * means of an equality check) to the given one.
     */
    def is(p: AnyRef): Boolean = /*this.hasProperty && */ this.isFinal && this.ub == p

    /**
     * Converts an `InterimEP` to a `FinalEP`; fails otherwise.
     */
    private[fpcf] def toFinalEP: FinalEP[E, P]

    private[fpcf] def toFinalEUBP: FinalEP[E, P]

    private[fpcf] def toFinalELBP: FinalEP[E, P]

    /**
     * Returns the upper bound of the property if it is available – [[hasUBP]] has to be
     * `true` – otherwise an `UnsupportedOperationException` is thrown.
     *
     * The upper bound always models the best/most precise result w.r.t. the underlying lattice.
     * Here, "best" means that the set of potentially reachable states/instructions that the
     * analyzed program can ever assume is potentially smaller when compared to a worse property.
     *
     * The upper bound models the sound and precise result under the assumption that the
     * properties of all explicitly and implicitly relevant entities is as last queried or
     * implicitly assumed. I.e., unless a dependee is updated the upper bound represents
     * the correct and most precise result.
     *
     * The lower bound models the worst case property that a specific entity can have under the
     * assumption that all other relevant properties will get their worst properties. This
     * can – but does not have to be – the underlying lattice's bottom value.
     * The lower bound is generally helpful for client analyses to determine final
     * results quicker. For example, imagine the following code:
     * {{{
     * def f(a : AnyRef) : Unit = a match {
     *   case a : List[_] => if (a.exists( _ == null)) throw  new IllegalArgumentException
     *   case _ => throw new UnknownError
     * }
     * def m(){
     *   try {
     *     f(List(1,2,3))
     *   } catch {
     *     case nfe:  NumberFormatException => ...
     *   }
     * }
     * }}}
     * In that case – assuming we do not perform context sensitive analyses –
     * if the lower bound for `f` for the set of thrown exceptions is determined
     * to be `Set(IllegalArgumentException,UnkownError)`, then the catch of the
     * `NumberFormatException` can be ruled out and a final result for `m` can be
     * computed.
     *
     * @note If the property is final, lb (and ub) will return the final property `p`.
     */
    @throws[UnsupportedOperationException]("if no property is available")
    def ub: P

    /**
     * Returns the lower bound of the property if it is available,
     * otherwise an `UnsupportedOperationException` is thrown. For details regarding the
     * precise semantics see the discussion for [[ub]].
     *
     * @note If the property is final, lb (and ub) will return the final property `p`.
     */
    @throws[UnsupportedOperationException]("if no property is available")
    def lb: P

    /**
     * Returns `true` if this `EOptionP` is updated when compared with the given `oldEOptionP`,
     * provided that this `EOptionP` is guaranteed to be at most as old as `oldEOptionP`.
     * That is, this EPS is considered to be newer if the properties are different.
     *
     * @note The caller has to ensure that this EOptionP and and the given EOptionP are
     *       comparable. That is, they define properties of the same kind associated with
     *       the same entity and same bounds.
     */
    private[fpcf] def isUpdatedComparedTo(oldEOptionP: EOptionP[Entity, Property]): Boolean

    @throws[IllegalArgumentException]("if the given eps is not a valid update")
    private[fpcf] def checkIsValidPropertiesUpdate(
        eps:          SomeEPS,
        newDependees: Iterable[SomeEOptionP]
    ): Unit

}

/**
 * Factory and extractor for [[EOptionP]] objects.
 *
 * @author Michael Eichberg
 */
object EOptionP {

    def unapply[E <: Entity, P <: Property](eOptP: EOptionP[E, P]): Some[(E, PropertyKey[P])] = {
        Some((eOptP.e, eOptP.pk))
    }
}

/**
 * A pairing of an [[Entity]] and an associated [[Property]] along with its state.
 *
 * @note Entities and properties are compared using `equals`.
 *
 * @author Michael Eichberg
 */
sealed trait EPS[+E <: Entity, +P <: Property] extends EOptionP[E, P] {

    final override def isEPK: Boolean = false
    def asEPK: EPK[E, P] = throw new ClassCastException();

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

    /**
     * Creates a [[FinalP]] object using the current ub if the ub is available. If the ub
     * is not available an exception is thrown.
     *
     * @note No check is done whether the property is actually final.
     */
    override def toFinalEUBP: FinalEP[E, P] = FinalEP(e, ub)

    /**
     * Creates a [[FinalP]] object using the current lb if the lb is available. If the lb
     * is not available an exception is thrown.
     *
     * @note No check is done whether the property is actually final.
     */
    override def toFinalELBP: FinalEP[E, P] = FinalEP(e, lb)

    final override def isEPS: Boolean = true
    final override def asEPS: EPS[E, P] = this

    final override def toEPS: Option[EPS[E, P]] = Some(this)
}

/**
 * Provides a factory for [[EPS]] objects.
 *
 * @author Michael Eichberg
 */
object EPS {

    def apply[E <: Entity, P <: Property](e: E, lb: P, ub: P): EPS[E, P] = {
        if (lb == ub) {
            if (lb == null /* && | || ub == null*/ ) {
                throw new IllegalArgumentException(s"lb and ub are null")
            } else {
                FinalEP(e, ub)
            }
        } else if (lb == null)
            InterimEUBP(e, ub)
        else if (ub == null)
            InterimELBP(e, lb)
        else
            InterimELUBP(e, lb, ub)
    }

    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[E] = Some(eps.e)
}

/**
 * Factory and extractor for [[EPS]] objects.
 *
 * @author Michael Eichberg
 */
object SomeEPS {

    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[(E, PropertyKey[P])] = {
        Some((eps.e, eps.pk))
    }
}

object LBPS {

    /**
     * Returns the tuple `(LowerBound, Boolean)`.
     *
     * @note Using LBPS to extract a property for which no lower bound was computed
     *       will (deliberately) result in an exception!
     */
    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[(P, Boolean)] = {
        Some((eps.lb, eps.isFinal))
    }

}

object UBPS {

    /**
     * Returns the tuple `(UpperBound, Boolean)`.
     *
     * @note Using UBPS to extract a property for which no upper bound was computed
     *       will (deliberately) result in an exception!
     */
    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[(P, Boolean)] = {
        Some((eps.ub, eps.isFinal))
    }
}

object LUBPS {

    /**
     * Returns the triple `(LowerBound, UpperBound, Boolean)`.
     *
     * @note Using LUBPS to extract a property for which no lower or upper bound was computed
     *       will (deliberately) result in an exception!
     */
    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[(P, P, Boolean)] = {
        Some((eps.lb, eps.ub, eps.isFinal))
    }

}

/**
 * Provides an extractor for [[EPS]] objects.
 *
 * @author Michael Eichberg
 */
object ELUBPS {

    /**
     * Returns the quadruple `(Entity, LowerBound, UpperBound, Boolean)`.
     *
     * @note Using ELUBPS to extract a property for which no lower or upper bound was computed
     *       will (deliberately) result in an exception!
     */
    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[(E, P, P, Boolean)] = {
        Some((eps.e, eps.lb, eps.ub, eps.isFinal))
    }
}

object ELUBP {

    /**
     * Returns the triple `(Entity, lowerBound : Property, upperBound : Property)`.
     *
     * @note Using ELUBP to extract a property for which no lower or upper bound was computed
     *       will (deliberately) result in an exception!
     */
    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[(E, P, P)] = {
        Some((eps.e, eps.lb, eps.ub))
    }
}

object LUBP {

    /**
     * Returns the pair `(lowerBound : Property, upperBound : Property)`.
     *
     * @note Using LUBP to extract a property for which no lower or upper bound was computed
     *       will (deliberately) result in an exception!
     */
    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[(P, P)] = {
        Some((eps.lb, eps.ub))
    }
}

object EUBPS {

    /**
     * Returns the triple `(Entity, upperBound : Property, isFinal : Boolean)`.
     */
    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[(E, P, Boolean)] = {
        Some((eps.e, eps.ub, eps.isFinal))
    }

}

object EUBP {

    /**
     * Returns the pair `(Entity, upperBound : Property)`.
     */
    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[(E, P)] = {
        Some((eps.e, eps.ub))
    }

}

object UBP {

    /**
     * Returns the `lowerBound : Property`.
     */
    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[P] = Some(eps.ub)

}

object ELBPS {

    /**
     * Returns the triple `(Entity, lowerBound : Property, isFinal : Boolean)`.
     */
    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[(E, P, Boolean)] = {
        Some((eps.e, eps.lb, eps.isFinal))
    }

}

object ELBP {

    /**
     * Returns the pair `(Entity, lowerBound : Property)`.
     */
    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[(E, P)] = {
        Some((eps.e, eps.lb))
    }

}

object LBP {

    /**
     * Returns the `lowerBound : Property`.
     */
    def unapply[E <: Entity, P <: Property](eps: EPS[E, P]): Some[P] = Some(eps.lb)

}

/**
 * Encapsulate the final property `P` for the entity `E`.
 *
 * For a detailed discussion of the semantics of `lb` and `ub` see [[EOptionP.ub]].
 */
final class FinalEP[+E <: Entity, +P <: Property](val e: E, val p: P) extends EPS[E, P] {

    override lazy val pk: PropertyKey[P] = p.key.asInstanceOf[PropertyKey[P]]

    override def isFinal: Boolean = true
    override def asFinal: FinalEP[E, P] = this

    override private[fpcf] def toFinalEP: FinalEP[E, P] = this

    override def hasLBP: Boolean = true
    override def lb: P = p
    override def toFinalELBP: FinalEP[E, P] = this

    override def hasUBP: Boolean = true
    override def ub: P = p
    override def toFinalEUBP: FinalEP[E, P] = this

    override def asInterim: InterimEP[E, P] = throw new ClassCastException();

    override private[fpcf] def isUpdatedComparedTo(
        oldEOptionP: EOptionP[Entity, Property]
    ): Boolean = {
        oldEOptionP.isRefinable
    }

    private[fpcf] def checkIsValidPropertiesUpdate(
        eps:          SomeEPS,
        newDependees: Iterable[SomeEOptionP]
    ): Unit = {
        throw new IllegalArgumentException("already final")
    }

    override def equals(other: Any): Boolean = {
        other match {
            case that: FinalEP[_, _] => (this eq that) || (that.e == this.e && this.p == that.p)
            case _                   => false
        }
    }

    override lazy val hashCode: Int = e.hashCode() * 727 + p.hashCode()

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

}

object FinalEP {

    def apply[E <: Entity, P <: Property](e: E, p: P): FinalEP[E, P] = new FinalEP(e, p)

    def unapply[E <: Entity, P <: Property](eps: FinalEP[E, P]): Some[(E, P)] = {
        Some((eps.e, eps.p))
    }

}

/**
 * Factory and extractor for [[FinalEP]] objects.
 *
 * @author Michael Eichberg
 */
object SomeFinalEP {

    def unapply[E <: Entity, P <: Property](finalEP: FinalEP[E, P]): Some[(E, PropertyKey[P])] = {
        Some((finalEP.e, finalEP.pk))
    }
}

object FinalP {

    def unapply[E <: Entity, P >: Null <: Property](eps: FinalEP[E, P]): Some[P] = Some(eps.p)

}

object FinalE {

    def unapply[E <: Entity, P <: Property](eps: FinalEP[E, P]): Some[E] = Some(eps.e)

}

sealed trait InterimEP[+E <: Entity, +P <: Property] extends EPS[E, P] {

    override def isFinal: Boolean = false
    override def asFinal: FinalEP[E, P] = throw new ClassCastException();

    override def asInterim: InterimEP[E, P] = this

    private[fpcf] def checkIsValidLBPropertyUpdate(eps: SomeEPS): Unit = {
        val newLBAsOP = eps.lb.asOrderedProperty
        val lbAsOP = lb.asInstanceOf[newLBAsOP.Self]
        newLBAsOP.checkIsEqualOrBetterThan(e, lbAsOP)
    }

    private[fpcf] def checkIsValidUBPropertyUpdate(eps: SomeEPS): Unit = {
        val ubAsOP = ub.asOrderedProperty
        val newUBAsOP = eps.ub.asInstanceOf[ubAsOP.Self]
        ubAsOP.checkIsEqualOrBetterThan(e, newUBAsOP)
    }

    override private[fpcf] def checkIsValidPropertiesUpdate(
        eps:          SomeEPS,
        newDependees: Iterable[SomeEOptionP]
    ): Unit = {
        try {
            if (eps.isRefinable && (hasLBP != eps.hasLBP || hasUBP != eps.hasUBP)) {
                throw new IllegalArgumentException(s"inconsistent property bounds: $this vs. $eps")
            }

            if (hasLBP && eps.lb.isOrderedProperty) {
                checkIsValidLBPropertyUpdate(eps)
            }
            if (hasUBP && eps.ub.isOrderedProperty) {
                checkIsValidUBPropertyUpdate(eps)
            }
        } catch {
            case t: Throwable =>
                val m = s"$e: illegal update oldLB: $lb vs. newLB=$eps.lb "+
                    newDependees.mkString("newDependees={", ", ", "}; cause=") + t.getMessage
                throw new IllegalArgumentException(m, t)
        }
    }
}

object InterimE {

    /**
     * Extracts the entity of an interim property.
     *
     * @note When we just have an InterimP object, we don't know which properties (ub, lb or both)
     *       are available.
     */
    def unapply[E <: Entity, P <: Property](interimP: InterimEP[E, P]): Some[E] = Some(interimP.e)

}

/**
 * Encapsulates the intermediate lower- and upper bound related to the computation of the respective
 * property kind for the entity `E`.
 *
 * For a detailed discussion of the semantics of `lb` and `ub` see [[EOptionP.ub]].
 */
final class InterimELUBP[+E <: Entity, +P <: Property](
        val e:  E,
        val lb: P,
        val ub: P
) extends InterimEP[E, P] {

    assert(lb != null)
    assert(ub != null)

    if (PropertyStore.Debug && lb /*or ub*/ .isOrderedProperty) {
        val ubAsOP = ub.asOrderedProperty
        ubAsOP.checkIsEqualOrBetterThan(e, lb.asInstanceOf[ubAsOP.Self])
    }

    override lazy val pk: PropertyKey[P] = ub /* or lb */ .key.asInstanceOf[PropertyKey[P]]

    override private[fpcf] def toFinalEP: FinalEP[E, P] = FinalEP(e, ub)

    override def hasLBP: Boolean = true
    override def hasUBP: Boolean = true

    override private[fpcf] def isUpdatedComparedTo(
        oldEOptionP: EOptionP[Entity, Property]
    ): Boolean = {
        oldEOptionP.isEPK || oldEOptionP.lb != this.lb || oldEOptionP.ub != this.ub
    }

    override def equals(other: Any): Boolean = {
        other match {
            case that: InterimELUBP[_, _] =>
                (this eq that) || (e == that.e && lb == that.lb && ub == that.ub)
            case _ =>
                false
        }
    }

    override lazy val hashCode: Int = ((e.hashCode() * 31 + lb.hashCode()) * 31) + ub.hashCode()

    override def toString: String = {
        s"InterimELUBP($e@${System.identityHashCode(e).toHexString},lb=$lb,ub=$ub)"
    }
}

object InterimELUBP {

    def apply[E <: Entity, P <: Property](e: E, lb: P, ub: P): InterimELUBP[E, P] = {
        assert(lb ne ub)
        new InterimELUBP(e, lb, ub)
    }

    def unapply[E <: Entity, P >: Null <: Property](eps: InterimELUBP[E, P]): Some[(E, P, P)] = {
        Some((eps.e, eps.lb, eps.ub))
    }
}

object InterimLUBP {

    def unapply[E <: Entity, P >: Null <: Property](eps: InterimELUBP[E, P]): Some[(P, P)] = {
        Some((eps.lb, eps.ub))
    }
}

final class InterimEUBP[+E <: Entity, +P <: Property](
        val e:  E,
        val ub: P
) extends InterimEP[E, P] {

    assert(ub != null)

    override lazy val pk: PropertyKey[P] = ub.key.asInstanceOf[PropertyKey[P]]

    override private[fpcf] def toFinalEP: FinalEP[E, P] = FinalEP(e, ub)

    override def hasLBP: Boolean = false
    override def hasUBP: Boolean = true

    override def lb: Nothing = throw new UnsupportedOperationException();

    override private[fpcf] def isUpdatedComparedTo(
        oldEOptionP: EOptionP[Entity, Property]
    ): Boolean = {
        oldEOptionP.isEPK || oldEOptionP.ub != this.ub
    }

    override def equals(other: Any): Boolean = {
        other match {
            case that: InterimEUBP[_, _] =>
                (this eq that) || (this.e == that.e && this.ub == that.ub)
            case _ =>
                false
        }
    }

    override lazy val hashCode: Int = e.hashCode() * 31 + ub.hashCode()

    override def toString: String = {
        s"InterimEUBP($e@${System.identityHashCode(e).toHexString},ub=$ub)"
    }
}

/**
 * Factory and extractor for `InterimEUBP` objects. The extractor also matches `InterimELUBP`
 * objects, but will throw an exception for `InterimELBP` objects. If you want to match
 * final and interim objects at the same time use the `E(LB|UB)PS` extractors.
 */
object InterimEUBP {

    def apply[E <: Entity, P <: Property](e: E, ub: P): InterimEUBP[E, P] = {
        new InterimEUBP(e, ub)
    }

    def unapply[E <: Entity, P >: Null <: Property](eps: InterimEP[E, P]): Some[(E, P)] = {
        Some((eps.e, eps.ub))
    }
}

/**
 * Factory and extractor for `InterimEUBP` objects. The extractor also matches `InterimELUBP`
 * objects, but will throw an exception for `InterimELBP` objects. If you want to match
 * final and interim objects at the same time use the `E(LB|UB)PS` extractors.
 */
object InterimEUB {

    def unapply[E <: Entity, P <: Property](eps: InterimEP[E, P]): Some[E] = {
        if (!eps.hasUBP)
            throw new IllegalArgumentException(s"$eps does not define an upper bound property");

        Some(eps.e)
    }
}

/**
 * Defines an extractor that matches EPKs and Interim properties where the latter only defines an
 * upper bound, but does not define a lower bound.
 *
 * For example, an analysis which declares that it can only handle lower bounds, but can't process
 * EPSs which only define an upper bound, may still see EPS which define only the upper bound
 * but can process them in the same way as an EPK; that is, it can basically ignore the
 * upper bound information. The scheduler ensures that the analysis scenario is a valid one
 * and no cyclic dependent computations may arise.
 * {{{
 * def continuation(eps : EPS) : ... = eps match {
 *   case FinalEP(...) => // Matches only final properties.
 *   case LBP(...)     => // Matches final  and interim properties which define a lower bound.
 *   case NoLBP(...)   => // Matches EPKs and EPS which just define an upper bound.
 * }
 * }}}
 */
object NoLBP {

    def unapply[E <: Entity, P >: Null <: Property](eps: EOptionP[E, P]): Option[EOptionP[E, P]] = {
        if (!eps.hasLBP)
            Some(eps)
        else
            None
    }
}

/**
 * Defines an extractor that matches EPKs and Interim properties where the latter only defines a
 * lower bound, but does not define an upper bound.
 */
object NoUBP {

    def unapply[E <: Entity, P >: Null <: Property](eps: EOptionP[E, P]): Option[EOptionP[E, P]] = {
        if (!eps.hasUBP)
            Some(eps)
        else
            None
    }
}

object InterimUBP {

    def unapply[E <: Entity, P <: Property](eps: InterimEP[E, P]): Some[P] = Some(eps.ub)

}

final class InterimELBP[+E <: Entity, +P <: Property](
        val e:  E,
        val lb: P
) extends InterimEP[E, P] {

    assert(lb != null)

    override lazy val pk: PropertyKey[P] = lb.key.asInstanceOf[PropertyKey[P]]

    override private[fpcf] def toFinalEP: FinalEP[E, P] = FinalEP(e, lb)

    override def hasLBP: Boolean = true
    override def hasUBP: Boolean = false

    override def ub: Nothing = throw new UnsupportedOperationException();

    override private[fpcf] def isUpdatedComparedTo(
        oldEOptionP: EOptionP[Entity, Property]
    ): Boolean = {
        oldEOptionP.isEPK || oldEOptionP.lb != this.lb
    }

    override def equals(other: Any): Boolean = {
        other match {
            case that: InterimELBP[_, _] =>
                (this eq that) || (this.lb == that.lb && this.e == that.e)
            case _ =>
                false
        }
    }

    override lazy val hashCode: Int = e.hashCode() * 31 + lb.hashCode()

    override def toString: String = {
        s"InterimELBP($e@${System.identityHashCode(e).toHexString},lb=$lb)"
    }
}

/**
 * Factory and extractor for `InterimLBP` objects. The extractor also matches `InterimLUBP`
 * objects, but will throw an exception for `InterimUBP` objects. If you want to match
 * final and interim objects at the same time use the `E(LB|UB)PS` extractors.
 */
object InterimELBP {

    def apply[E <: Entity, P <: Property](e: E, ub: P): InterimELBP[E, P] = {
        new InterimELBP(e, ub)
    }

    def unapply[E <: Entity, P >: Null <: Property](eps: InterimEP[E, P]): Some[(E, P)] = {
        Some((eps.e, eps.ub))
    }
}

object InterimLBP {

    def unapply[E <: Entity, P >: Null <: Property](eps: InterimEP[E, P]): Some[P] = Some(eps.ub)

}

/**
 * A simple pair consisting of an [[Entity]] and a [[PropertyKey]].
 *
 * @author Michael Eichberg
 */
final class EPK[+E <: Entity, +P <: Property](
        val e:  E,
        val pk: PropertyKey[P]
) extends EOptionP[E, P] {

    override def hasLBP: Boolean = false
    override def lb: Nothing = throw new UnsupportedOperationException();
    override private[fpcf] def toFinalELBP = throw new UnsupportedOperationException(toString);

    override def hasUBP: Boolean = false
    override def ub: Nothing = throw new UnsupportedOperationException();
    override private[fpcf] def toFinalEUBP = throw new UnsupportedOperationException(toString);

    override def isFinal: Boolean = false
    override def asFinal: FinalEP[E, P] = throw new ClassCastException();

    override private[fpcf] def toFinalEP: FinalEP[E, P] = {
        throw new UnsupportedOperationException(toString)
    };

    override def isEPS: Boolean = false
    override def asEPS: EPS[E, P] = {
        throw new ClassCastException()
    };

    override def isEPK: Boolean = true
    override def asEPK: EPK[E, P] = this

    override def toEPK: this.type = this

    override def toEPS: Option[EPS[E, P]] = None

    override def asInterim: InterimEP[E, P] = throw new ClassCastException();

    override private[fpcf] def isUpdatedComparedTo(
        oldEOptionP: EOptionP[Entity, Property]
    ): Boolean = {
        false
    }

    override private[fpcf] def checkIsValidPropertiesUpdate(
        eps:          SomeEPS,
        newDependees: Iterable[SomeEOptionP]
    ): Unit = {}

    override def equals(other: Any): Boolean = {
        other match {
            case that: EPK[_, _] => (this eq that) || (this.pk == that.pk && that.e == this.e)
            case _               => false
        }
    }

    override lazy val hashCode: Int = e.hashCode() * 31 + pk.id

    override def toString: String = {
        val pkId = pk.id
        val pkName = PropertyKey.name(pkId)
        s"EPK($e@${System.identityHashCode(e).toHexString},$pkName#$pkId)"
    }
}

/**
 * Factory and extractor for [[EPK]] objects.
 *
 * @author Michael Eichberg
 */
object EPK {

    def apply[E <: Entity, P <: Property](e: E, pk: PropertyKey[P]): EPK[e.type, P] = new EPK(e, pk)

    def apply[E <: Entity, P <: Property](e: E, p: P): EPK[E, P] = {
        new EPK(e, p.key.asInstanceOf[PropertyKey[P]])
    }

    def unapply[E <: Entity, P >: Null <: Property](epk: EPK[E, P]): Some[(E, PropertyKey[P])] = {
        Some((epk.e, epk.pk))
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy