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

scalax.collection.constrained.Constraint.scala Maven / Gradle / Ivy

The newest version!
package scalax.collection.constrained

import scala.annotation.unchecked.{uncheckedVariance => uV}
import scala.language.higherKinds
import scala.collection.Set

import scalax.collection.GraphPredef._

/** Base trait for ordinary `Constraint` companion objects.
  */
trait ConstraintCompanion[+CC[N, E[X] <: EdgeLikeIn[X], G <: Graph[N, E]] <: Constraint[N, E, G]] {
  thisCompanion =>

  /** Instantiates a user constraint. */
  def apply[N, E[X] <: EdgeLikeIn[X], G <: Graph[N, E]](self: G): CC[N, E, G]

  /** Creates a new constraint companion of the type `ConstraintCompanionBinaryOp`
    * the `apply` of which returns `ConstraintBinaryOp` with the `And` operator.
    */
  def &&(that: ConstraintCompanion[Constraint]) = new ConstraintCompanionBinaryOp(And, this, that)

  /** Creates a new constraint companion of the type `ConstraintCompanionBinaryOp`
    * the `apply` of which returns `ConstraintBinaryOp` with the `Or` operator.
    */
  def ||(that: ConstraintCompanion[Constraint]) = new ConstraintCompanionBinaryOp(Or, this, that)

  protected[constrained] class PrefixedConstraintCompanion(prefix: Option[String]) extends ConstraintCompanion[CC] {
    override val stringPrefix: Option[String]                                   = prefix
    def apply[N, E[X] <: EdgeLikeIn[X], G <: Graph[N, E]](self: G): CC[N, E, G] = thisCompanion[N, E, G](self)
  }

  /** The `stringPrefix` of constrained `Graph`s using `this` constraint will be replaced
    *  by this string unless `None`. */
  def stringPrefix: Option[String] = None

  /** Sets `stringPrefix` of constrained `Graph`s using `this` combined constraint companion
    *  to `graphStringPrefix`. */
  def withStringPrefix(stringPrefix: String): PrefixedConstraintCompanion = {
    val printable = stringPrefix.trim filter (_.isLetterOrDigit)
    new PrefixedConstraintCompanion(if (printable.length > 0) Some(printable) else None)
  }
}

/** Enumerates the possible return statuses (also: follow-up activity) of a pre-check:
  * `Abort` instructs the caller to cancel the operation because the pre-check failed;
  * `PostCheck` means that the post-check (commit) still must be called;
  * `Complete` means that the operation can safely be completed without invoking the post-check.
  */
object PreCheckFollowUp extends Enumeration {
  type PreCheckFollowUp = Value
  val Abort, PostCheck, Complete = Value

  /** The minimum of `a` and `b` treating `Abort` < `PostCheck` < `Complete`. */
  final def min(a: PreCheckFollowUp, b: PreCheckFollowUp): PreCheckFollowUp = if (a.id < b.id) a else b
}
import PreCheckFollowUp._

sealed trait ConstraintViolation

/** The return type of any pre-check. `followUp` contains the return status (follow-up
  *  activity). `Constraint` implementations are encouraged to extend this class to contain
  *  further data calculated in course of the pre-check and reusable in the following post-check.
  */
class PreCheckResult(val followUp: PreCheckFollowUp) extends ConstraintViolation {

  final def apply(): PreCheckFollowUp = followUp

  /** Whether `this.followUp` equals to `Abort`. */
  final def abort: Boolean = followUp == Abort

  /** Whether `this.followUp` equals to `PostCheck`. */
  final def postCheck: Boolean = followUp == PostCheck

  /** Whether `this.followUp` equals to `Complete`. */
  final def complete: Boolean = followUp == Complete

  /** Returns a tuple of `this` and `this.followUp`. */
  final def tupled: (PreCheckResult, PreCheckFollowUp) = this match { case PreCheckResult(r, f) => (r, f) }
}

trait PreCheckResultCompanion {
  def apply(followUp: PreCheckFollowUp): PreCheckResult

  /** If `ok` is `true` returns a new `PreCheckResult` with `followUp` equaling to `PostCheck`
    *  otherwise to `Abort`. */
  def postCheck(ok: Boolean): PreCheckResult = apply(if (ok) PostCheck else Abort)

  /** If `ok` is `true` returns a new `PreCheckResult` with `followUp` equaling to `Complete`
    *  otherwise to `Abort`. */
  def complete(ok: Boolean): PreCheckResult = apply(if (ok) Complete else Abort)
}

object PreCheckResult extends PreCheckResultCompanion {
  def apply(followUp: PreCheckFollowUp) = new PreCheckResult(followUp)
  def unapply(preCheck: PreCheckResult): Option[(PreCheckResult, PreCheckFollowUp)] =
    if (preCheck eq null) None else Some(preCheck, preCheck.followUp)
}

case class PostCheckFailure(cause: Any) extends ConstraintViolation

/** This template contains all methods that constrained graphs call
  * to decide whether operations altering a mutable graph or operations
  * yielding a new graph from an immutable or mutable graph are valid.
  *
  * Constraint methods are called on graph creation and node/edge addition and subtraction
  * at two points of time, respectively: prior to the operation and after the operation has
  * taken place but still may be rolled back. Thus,
  * constraint method names are prefixed by `pre` or `post`. Pre-ckecks return `Abort`,
  * `PostCheck` or `Complete` while post-checks return Boolean stating whether the operation
  * should be committed or rolled back. Pre-checks can inspect the operands only. In contrast,
  * post-checks additionally allow to inspect the would-be graph after the operation has taken place
  * but has not yet been committed.
  *
  * For performance reasons, implementations should prefer implementing pre-check methods.
  * If it's necessary to check not only the operands but the whole would-be graph,
  * the appropriate post-check methods should be overridden.
  *
  * @define SELFGRAPH Use `self` to access the associated graph.
  * @define PREPOST This pre-check may be omitted by letting it always return `postCheck`
  *         and overriding the corresponding post-check `commit*` method.
  * @define SELFCOMMIT For immutable graphs, `self` maintains the state before the
  *         addition but for mutable graphs, it is already mutated to the required state.
  * @define PRECHECKRET The results of the pre-check containing the follow-up activity
  *         and possibly any intermediate computation result to be used during the
  *         post-check. To add computation results `PreCheckResult` must be extended.
  * @author Peter Empen
  */
trait ConstraintMethods[N, E[X] <: EdgeLikeIn[X], +G <: Graph[N, E]] {

  /** When extending `Constraint`, `self` will denote the attached constrained graph.
    * The factory methods of the companion object `scalax.collection.constrained.Graph`
    * initialize `self` to the correct graph instance.
    * When extending `Constrained`, `self` will denote `this` graph.
    */
  val self: G

  /** This pre-check is called on constructing a graph through its companion object.
    * It must return whether the graph is allowed to be populated with `nodes` and `edges`.
    * The default implementation calls `preAdd` for each node and edge.
    *
    * Note that nodes and edges coming from node/edge input streams are not checked.
    * So when utilizing streams the post check `postAdd` must be served.
    *
    *  @param nodes the outer nodes the graph is to be populated with; nodes
    *         being edge ends may but need not be contained in `nodes`.
    *  @param edges the outer edges the graph is to be populated with.
    *  @return $PRECHECKRET
    */
  def preCreate(nodes: collection.Traversable[N], edges: collection.Traversable[E[N]]): PreCheckResult =
    PreCheckResult.postCheck(
      (nodes forall ((n: N) => !preAdd(n).abort)) &&
        (edges forall ((e: E[N]) => !preAdd(e).abort)))

  /** This pre-check must return `Abort` if the addition is to be canceled, `PostCheck` if `postAdd`
    * is to be called to decide or `Complete` if the outer `node` is allowed to be added.
    * If `postAdd` has been implemented, this method may always return `PostCheck`.
    * $PREPOST
    * $SELFGRAPH
    *
    * @param node to be added
    * @return $PRECHECKRET
    */
  def preAdd(node: N): PreCheckResult

  /** This pre-check must return `Abort` if the addition is to be canceled, `PostCheck` if `postAdd`
    * is to be called to decide or `Complete` if the outer `edge` is allowed to be added.
    * If `postAdd` has been implemented, this method may always return `PostCheck`.
    * $PREPOST
    * $SELFGRAPH
    *
    * @param edge to be added.
    * @return $PRECHECKRET
    */
  def preAdd(edge: E[N]): PreCheckResult

  /** This pre-check must return `Abort` if the addition of the outer nodes and/or edges in `elems`
    * is to be canceled, `PostCheck` if `postAdd` is to be called to decide or
    * `Complete` if the the outer nodes and/or edges are allowed to be added.
    * If `postAdd` has been implemented, this method may always return `PostCheck`.
    * The default implementation calls `preAdd(node)`/`preAdd(edge)` element-wise.
    * As for most cases this won't be satisfactory a domain-specific implementation should
    * be provided.
    * $SELFGRAPH
    *
    * @param elems nodes and/or edges to be added possibly containing duplicates.
    * @return $PRECHECKRET
    */
  def preAdd(elems: InParam[N, E]*): PreCheckResult =
    PreCheckResult.postCheck(elems forall {
      _ match {
        case node: OuterNode[N]    => !preAdd(node.value).abort
        case edge: OuterEdge[N, E] => !preAdd(edge.edge).abort
      }
    })

  /** This pre-check must return `Abort` if the subtraction of `node` is to be canceled,
    * `PostCheck` if `postSubtract` is to be called to decide or
    * `Complete` if the the `node` is allowed to be subtracted.
    * $PREPOST
    * $SELFGRAPH
    *
    * @param node the inner to be subtracted.
    * @param forced `true` for standard (ripple by `-`), `false` for gentle (by `-?`) removal.
    * @return $PRECHECKRET
    */
  def preSubtract(node: self.NodeT, forced: Boolean): PreCheckResult

  /** This pre-check must return `Abort` if the subtraction of `edge` is to be canceled,
    * `PostCheck` if `postSubtract` is to be called to decide or
    * `Complete` if the the `edge` is allowed to be subtracted.
    * $PREPOST
    * $SELFGRAPH
    *
    * @param edge the inner edge to be subtracted.
    * @param simple `true` for standard (edge-only by `-`),
    *               `false` for ripple (by `-!`) removal.
    * @return $PRECHECKRET
    */
  def preSubtract(edge: self.EdgeT, simple: Boolean): PreCheckResult

  /** This pre-check must return `Abort` if the subtraction of `nodes` and/or `edges`
    * is to be canceled, `PostCheck` if `postSubtract` is to be called to decide or
    * `Complete` if `nodes` and/or `edges` are allowed to be subtracted.
    * It is typically triggered by the `--` operation.
    * The default implementation element-wise calls `preSubtract(node, simple)` or
    * `preSubtract(edge, simple)`, respectively.
    * As for most cases this won't be satisfactory a domain-specific implementation
    * should be provided.
    * $SELFGRAPH
    *
    * @param nodes the inner nodes to be subtracted not necessarily including
    *              the ends of edges to be subtracted. Call allNodes to get the
    *              complete set of nodes to be subtracted.
    * @param edges the inner edges to be subtracted.
    * @param simple `true` for standard (edge-only by `-`),
    *               `false` for ripple (by `-!`) removal.
    * @return $PRECHECKRET
    */
  def preSubtract(nodes: => Set[self.NodeT], edges: => Set[self.EdgeT], simple: Boolean): PreCheckResult =
    PreCheckResult.postCheck(
      (nodes forall (n => !preSubtract(n, simple).abort)) &&
        (edges forall (e => !preSubtract(e, simple).abort)))

  /** This post-check must return whether `newGraph` should be committed or the add
    * operation is to be rolled back.
    * $SELFGRAPH
    * $SELFCOMMIT
    *
    * @param newGraph the after-addition would-be graph waiting for commit.
    * @param passedNodes the normalized nodes passed to the add operation.
    * @param passedEdges the normalized edges passed to the add operation.
    * @param preCheck the result of `preAdd`.
    * @return `None` to accept `newGraph` or `Some` reason for constraint violation resp. rejection
    */
  def postAdd(newGraph: G @uV,
              passedNodes: Traversable[N],
              passedEdges: Traversable[E[N]],
              preCheck: PreCheckResult): Either[PostCheckFailure, G] = Right(newGraph)

  /** This post-check must return whether `newGraph` should be committed or the subtraction
    * is to be rolled back.
    * $SELFGRAPH
    * $SELFCOMMIT
    *
    * @param newGraph the after-subtraction would-be graph waiting for commit.
    * @param passedNodes the normalized nodes passed to the subtraction operation.
    * @param passedEdges the normalized edges passed to the subtraction operation.
    * @param preCheck the result of `preSubtract`.
    * @return `None` to accept `newGraph` or `Some` reason for constraint violation resp. rejection
    */
  def postSubtract(newGraph: G @uV,
                   passedNodes: Traversable[N],
                   passedEdges: Traversable[E[N]],
                   preCheck: PreCheckResult): Either[PostCheckFailure, G] = Right(newGraph)

  /** Consolidates all outer nodes of the arguments by adding the edge ends
    *  of `passedEdges` to `passedNodes`. */
  protected def allNodes(passedNodes: Traversable[N], passedEdges: Traversable[E[N]]): Set[N] = {
    val nodes = collection.mutable.Set[N]() ++ passedNodes
    passedEdges foreach (nodes ++= _)
    nodes
  }

  protected def nodesToAdd(passedNodes: Traversable[N], passedEdges: Traversable[E[N]]): Set[N] =
    allNodes(passedNodes, passedEdges).filter(self.find(_).isEmpty)
}

/** Template to be mixed in by any constrained graph class.
  *
  * The user of the dynamically constrained class [[scalax.collection.constrained.Graph]]
  * or its mutable counterpart need not to be concerned about this trait because
  * it has been mixed in there. She only needs to pass the companion object for her `Constraint`
  * implementation.
  *
  * Implementors of statically constrained graph classes have to mix in this trait
  * in their constrained graph implementations.
  *
  * @see ConstraintMethods
  * @author Peter Empen
  */
trait Constrained[N, E[X] <: EdgeLikeIn[X], +G <: Graph[N, E]] extends ConstraintMethods[N, E, G]

/** Template to be implemented and passed to a dynamically constrained graph class
  * by the user. Note that mutable state will be lost on any operation yielding a
  * new graph. Thus it is essential to either design classes inheriting from `Constraint`
  * in a pure immutable manner or taking internally care of whether the state has been lost.
  *
  * @param self denotes the attached constrained graph.
  * @see ConstraintMethods
  * @author Peter Empen
  */
abstract class Constraint[N, E[X] <: EdgeLikeIn[X], G <: Graph[N, E]](override val self: G)
    extends ConstraintMethods[N, E, G] {

  /** Creates a new constraint of the type `ConstraintBinaryOp` with pre- and post-check methods
    * each of which returning `true` if both `this`' ''and'' `that`'s corresponding
    * pre- and post-checks return `true`.
    */
  def &&(that: Constraint[N, E, G]) = new ConstraintBinaryOp[N, E, G](self, And, this, that)

  /** Creates a new constraint of the type `ConstraintBinaryOp` with pre- and post-check methods
    * each of which returning `true` if either `this`' ''or'' `other`'s corresponding
    * pre- and post-checks returns `true`.
    */
  def ||(that: Constraint[N, E, G]) = new ConstraintBinaryOp[N, E, G](self, Or, this, that)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy