Permalink
Browse files

Pulling Zipper hole shifting and axes into separate traits.

This removes the need in implicit conversion for Zipper axes support.
  • Loading branch information...
1 parent e20bf45 commit f566b66cf252aa03efb64ba57f46f18339307201 @ncreep committed Dec 13, 2011
@@ -35,7 +35,6 @@ import scala.collection.generic.{CanBuildFrom, FilterMonadic}
import scala.collection.immutable.{SortedMap, IndexedSeq, SortedSet}
import scala.collection.mutable.Builder
import Zipper._
-import Zipper.ZipperPathOrdering
import CanBuildFromWithZipper._
/**
@@ -123,7 +122,9 @@ import CanBuildFromWithZipper._
*/
trait Zipper[+A <: Node] extends Group[A]
with IndexedSeqLike[A, Zipper[A]]
- with ZipperGroupOverrides[A] { self =>
+ with ZipperGroupOverrides[A]
+ with ZipperHoleShifting
+ with ZipperAxes { self =>
/**
* Returns the original group that was selected upon when the Zipper was created. A value of `None` indicates that
@@ -140,77 +141,6 @@ trait Zipper[+A <: Node] extends Group[A]
/** The zipper context or None if this is a broken zipper. */
private[antixml] val context: Option[Context]
- /** Shifts the focus of the zipper to another set of holes.
- *
- * The shifting is performed using a shifting function which is applied to each
- * path visible in the zipper and produces a new sequence of paths.
- * These paths are sorted lexicographically and duplicates are removed.
- *
- * A new zipper displaying the above paths is returned while internally maintaining data
- * about any previously contained paths.
- *
- * The values which are attached to the paths come from two sources:
- * - If the zipper previously contained the path, the data attached to it is used.
- * - If the path is new, the data is fetched directly from the parent of the zipper.
- *
- * In case a hole was previously multiplied (e.g. using flatMap) it is placed
- * as is in the resulting zipper.
- *
- * Note: shifting is not supported for parentless (broken) zippers.
- *
- * @param shiftFunc A function to be supplied with the parent of the zipper
- * and applied to the indexed contents of the zipper.
- * Assumed to produce valid paths with regard to the supplied parent. */
- private[antixml] def shiftHoles(shiftFunc: Group[Node] => ZipperPath => Seq[ZipperPath]): Zipper[Node] = context match {
- case Some(context @ Context(parent, lastUpdate, metas, additionalHoles, hiddenNodes)) => {
- implicit val lexicographic = ZipperPathOrdering
-
- val shift = shiftFunc(parent)
- val unsoretedPaths = for {
- m <- metas
- path <- shift(m._1) if path != ZipperPath.empty // ignoring empty paths
- } yield path
-
- // not allowing duplicates and empty paths and sorting lexicographically
- val newPaths = SortedSet(unsoretedPaths: _*)
- val holeInfo = new HoleMapper(context).holeInfo
-
- val b = newZipperContextBuilder[Node](Some(parent))
- val pathsInit: VectorCase[ElemsWithContextVisible[Node]] = util.Vector0
-
- val (unusedPaths, usedPaths) = // leaving paths that were never used before
- holeInfo.depthFirst.foldLeft((newPaths, pathsInit)) { case ((nPaths, used), hole) =>
- val (path, (nodesTimes, masterTime)) = hole
-
- val holes =
- if (nodesTimes.isEmpty) Seq((path, masterTime, util.Vector0))
- else nodesTimes.map { case (n, t) => (path, t, Seq(n)) } // this contains duplicates for multiplied locations
-
- val visible = (ElemsWithContextVisible.apply[Node] _).tupled
- val hidden = (ElemsWithContextHidden.apply _).tupled
-
- if (nPaths contains path) {
- (nPaths - path, used ++ holes.map(visible))
- } else {
- b ++= holes.map(hidden)
- (nPaths, used)
- }
- }
-
- val initTime = 0 // these paths were never modified
- val unusedElems = unusedPaths.toList map { p =>
- ElemsWithContextVisible[Node](p, initTime, PathFetcher.getNode(parent)(p))
- }
-
- // this can contain duplicate locations from the previously used paths
- val visibleElems = (unusedElems ++ usedPaths).sortBy(_.path)
- b ++= visibleElems
-
- b.result
- }
- case None => sys.error("Cannot shift root.")
- }
-
/**
* Returns a `Group` containing the same nodes as this Zipper, but without any Zipper context, and in particular,
* without any implict references to the zipper's parent.
@@ -224,7 +154,10 @@ trait Zipper[+A <: Node] extends Group[A]
}
/** Each hole is associated with a list of node/time pairs as well as a master update time */
- private type HoleInfo = ZipperHoleMap[(VectorCase[(Node,Time)],Time)]
+ private[antixml] type HoleInfo = ZipperHoleMap[(VectorCase[(Node,Time)],Time)]
+
+ /** Converts the given context object into a hole info instance. */
+ private[antixml] def toHoleInfo(context: Context) = new HoleMapper(context).holeInfo
/** A utility class to convert the contents of the zipper into a hole map. */
private[this] class HoleMapper(context: Context) {
@@ -278,7 +211,7 @@ trait Zipper[+A <: Node] extends Group[A]
/** Utility class to perform unselect. */
private[this] class Unselector(context: Context, mergeStrategy: ZipperMergeStrategy) {
- private val topLevelHoleInfo = new HoleMapper(context).holeInfo
+ private val topLevelHoleInfo = toHoleInfo(context)
/** Applies the node updates to the parent and returns the result. */
def unselect: Zipper[Node] =
@@ -391,12 +324,6 @@ object Zipper {
}
}
- /** Lexicographic ordering for path objects. */
- private object ZipperPathOrdering extends Ordering[ZipperPath] {
- override def compare(x: ZipperPath, y: ZipperPath) =
- Ordering.Iterable[Int].compare(x,y)
- }
-
/** Returns a builder that produces a zipper without a parent */
def newBuilder[A <: Node] = VectorCase.newBuilder[A].mapResult(brokenZipper(_))
@@ -2,7 +2,7 @@ package com.codecommit.antixml
import scala.annotation.tailrec
/**
- * Wraps [[com.codecommit.antixml.Zipper]] instances with some XPath like axes.
+ * Adds some XPath like axes to [[com.codecommit.antixml.Zipper]] instances.
*
* Note1: the axes are applied to each node in a zipper individually and the result
* is a new zipper with the nodes concatenated and sorted lexicographically by
@@ -11,10 +11,10 @@ import scala.annotation.tailrec
* Note2: the axes are calculated using holes in the zipper, hence for a modified
* zipper some nodes may be multiplied or elided.
*/
-class ZipperAxes(zipper: Zipper[Node]) {
+trait ZipperAxes { self: Zipper[Node] =>
/** Returns the direct parent of a node. */
def directParent = {
- zipper shiftHoles (g => (PathTransformer(g).shiftUp(_)).andThen(_.toList))
+ shiftHoles (g => (PathTransformer(g).shiftUp(_)).andThen(_.toList))
}
/** Returns the ancestors of a node. */
@@ -40,7 +40,7 @@ class ZipperAxes(zipper: Zipper[Node]) {
* @param appendSource True if the initial path should be part of the result.
*/
private def transFuncToShift(func: PathTransformer => ZipperPath => Option[ZipperPath], withSource: Boolean) = {
- zipper shiftHoles { g =>
+ shiftHoles { g =>
val pathToOpt = func(PathTransformer(g))
@tailrec
@@ -62,10 +62,4 @@ class ZipperAxes(zipper: Zipper[Node]) {
}
}
-}
-
-object ZipperAxes {
- /** Pimps a plain zipper to have axes selection methods.
- * TODO move to package object? */
- implicit def zipperToAxes(zipper: Zipper[Node]) = new ZipperAxes(zipper)
}
@@ -0,0 +1,92 @@
+package com.codecommit.antixml
+
+import Zipper._
+import ZipperHoleShifting._
+import util.VectorCase
+import scala.collection.immutable.SortedSet
+import CanBuildFromWithZipper._
+
+/** Responsible for zipper hole shifting support. */
+trait ZipperHoleShifting { self: Zipper[Node] =>
+
+ /** Shifts the focus of the zipper to another set of holes.
+ *
+ * The shifting is performed using a shifting function which is applied to each
+ * path visible in the zipper and produces a new sequence of paths.
+ * These paths are sorted lexicographically and duplicates are removed.
+ *
+ * A new zipper displaying the above paths is returned while internally maintaining data
+ * about any previously contained paths.
+ *
+ * The values which are attached to the paths come from two sources:
+ * - If the zipper previously contained the path, the data attached to it is used.
+ * - If the path is new, the data is fetched directly from the parent of the zipper.
+ *
+ * In case a hole was previously multiplied (e.g. using flatMap) it is placed
+ * as is in the resulting zipper.
+ *
+ * Note: shifting is not supported for parentless (broken) zippers.
+ *
+ * @param shiftFunc A function to be supplied with the parent of the zipper
+ * and applied to the indexed contents of the zipper.
+ * Assumed to produce valid paths with regard to the supplied parent.
+ */
+ private[antixml] def shiftHoles(shiftFunc: Group[Node] => ZipperPath => Seq[ZipperPath]): Zipper[Node] = context match {
+ case Some(context @ Context(parent, lastUpdate, metas, additionalHoles, hiddenNodes)) => {
+ implicit val lexicographic = ZipperPathOrdering
+
+ val shift = shiftFunc(parent)
+ val unsoretedPaths = for {
+ m <- metas
+ path <- shift(m._1) if path != ZipperPath.empty // ignoring empty paths
+ } yield path
+
+ // not allowing duplicates and empty paths and sorting lexicographically
+ val newPaths = SortedSet(unsoretedPaths: _*)
+ val holeInfo = toHoleInfo(context)
+
+ val b = newZipperContextBuilder[Node](Some(parent))
+ val pathsInit: VectorCase[ElemsWithContextVisible[Node]] = util.Vector0
+
+ val (unusedPaths, usedPaths) = // leaving paths that were never used before
+ holeInfo.depthFirst.foldLeft((newPaths, pathsInit)) {
+ case ((nPaths, used), hole) =>
+ val (path, (nodesTimes, masterTime)) = hole
+
+ val holes =
+ if (nodesTimes.isEmpty) Seq((path, masterTime, util.Vector0))
+ else nodesTimes.map { case (n, t) => (path, t, Seq(n)) } // this contains duplicates for multiplied locations
+
+ val visible = (ElemsWithContextVisible.apply[Node] _).tupled
+ val hidden = (ElemsWithContextHidden.apply _).tupled
+
+ if (nPaths contains path) {
+ (nPaths - path, used ++ holes.map(visible))
+ } else {
+ b ++= holes.map(hidden)
+ (nPaths, used)
+ }
+ }
+
+ val initTime = 0 // these paths were never modified
+ val unusedElems = unusedPaths.toList map { p =>
+ ElemsWithContextVisible[Node](p, initTime, PathFetcher.getNode(parent)(p))
+ }
+
+ // this can contain duplicate locations from the previously used paths
+ val visibleElems = (unusedElems ++ usedPaths).sortBy(_.path)
+ b ++= visibleElems
+
+ b.result
+ }
+ case None => sys.error("Cannot shift root.")
+ }
+}
+
+object ZipperHoleShifting {
+ /** Lexicographic ordering for path objects. */
+ private object ZipperPathOrdering extends Ordering[ZipperPath] {
+ override def compare(x: ZipperPath, y: ZipperPath) =
+ Ordering.Iterable[Int].compare(x,y)
+ }
+}
@@ -2,7 +2,6 @@ package com.codecommit.antixml
import org.specs2.mutable._
import XML._
-import ZipperAxes._
import scala.collection.immutable.SortedSet
class ZipperAxesSpecs extends SpecificationWithJUnit {

0 comments on commit f566b66

Please sign in to comment.