Skip to content

Commit

Permalink
Adding Zipper shifting function.
Browse files Browse the repository at this point in the history
  • Loading branch information
ncreep committed Dec 9, 2011
1 parent 7fae426 commit 06dafa0
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,27 +103,25 @@ object CanBuildFromWithZipper {
* Both types are represented as concrete subclasses of this one.
*
* @tparam Elem the type of node that will be contained in the zipper.
* @param path A zipper path instance leading to the location of the hole.
* @param updateTime the update time associated with these elements. One context is considered to have
* been updated later than another if its updateTime is greater.
* @see [[com.codecommit.antixml.CanBuildFromWithZipper]]
*/
sealed abstract class ElemsWithContext[+Elem](updateTime: Int)
sealed abstract class ElemsWithContext[+Elem](path: ZipperPath, updateTime: Int)
/**
* A visible zipper element.
* @param path Identifies a location (known as a "hole") in the zipper's parent. The order of the
* path is from top to bottom (the first item specifies the index of a top-level node in the parent Group).
* @param elements the actual elements to be added to the zipper.
*/
case class ElemsWithContextVisible[+Elem](path: Seq[Int], updateTime: Int, elements: GenTraversableOnce[Elem]) extends ElemsWithContext[Elem](updateTime)
case class ElemsWithContextVisible[+Elem](path: ZipperPath, updateTime: Int, elements: GenTraversableOnce[Elem]) extends ElemsWithContext[Elem](path, updateTime)
/**
* A hidden zipper element.
* @tparam Elem Dummy parameterization to satisfy the signature of methods like `flatMap`.
* @param path A zipper path instance leading to the location of the hole.
* @param elements The elements to be mapped to the path contained in the context. The type
* of the elements is the most general as they are not accessible through the zipper's methods
* and hence do not participate in any sort of transformations.
*/
case class ElemsWithContextHidden(path: ZipperPath, updateTime: Int, elements: GenTraversableOnce[Node]) extends ElemsWithContext[Nothing](updateTime)
case class ElemsWithContextHidden(path: ZipperPath, updateTime: Int, elements: GenTraversableOnce[Node]) extends ElemsWithContext[Nothing](path, updateTime)

/** Implicitly lifts [[scala.collection.mutable.CanBuildFrom]] instances into instances of [[com.codecommit.antixml.CanBuildFromWithZipper]]. The resulting builders simply ignore
* the extra information in `ElemsWithContext` and produce their collections as usual.
Expand Down
65 changes: 58 additions & 7 deletions src/main/scala/com/codecommit/antixml/Zipper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ import scala.collection.generic.{CanBuildFrom, FilterMonadic}
import scala.collection.immutable.{SortedMap, IndexedSeq}
import scala.collection.mutable.Builder
import Zipper._
import Zipper.ZipperPathOrdering
import CanBuildFromWithZipper.ElemsWithContext
import com.codecommit.antixml.CanBuildFromWithZipper.ElemsWithContextVisible
import com.codecommit.antixml.CanBuildFromWithZipper.ElemsWithContextHidden
import scala.collection.immutable.SortedSet

/**
* Provides an `unselect` operation which copies this Group's nodes back to the XML tree from which
Expand Down Expand Up @@ -154,6 +156,44 @@ trait Zipper[+A <: Node] extends Group[A] with IndexedSeqLike[A, Zipper[A]] { se
}
case None => brokenZipper(nodes.updated(index,node))
}

/** TODO */
private[antixml] def shiftHoles(shiftFunc: PathTransformer => ZipperPath => Seq[ZipperPath]): Zipper[Node] = context match {
case Some(context @ Context(parent, lastUpdate, metas, additionalHoles, hiddenNodes)) => {
implicit val lexicographic = ZipperPathOrdering

val shift = shiftFunc(PathTransformer(parent))

// not allowing duplicates and sorting lexicographical
val newPaths = SortedSet(metas.flatMap(m => shift(m._1)): _*)
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, (nt, time)) = hole
val (nodes, _) = nt.unzip

if (nPaths contains path) {
(nPaths - path, used :+ ElemsWithContextVisible(path, time, nodes))
} else {
b += ElemsWithContextHidden(path, time, nodes)
(nPaths, used)
}
}

val initTime = 0 // these paths were never modified
val unusedElems = unusedPaths.toList map(p => ElemsWithContextVisible[Node](p, initTime, PathFetcher.getNode(parent)(p)))

val visibleElems = SortedSet(unusedElems ++ usedPaths: _*)(Ordering.by(_.path))
b ++= visibleElems

b.result
}
case None => sys.error("Cannot shift root.")
}

override def slice(from: Int, until: Int): Zipper[A] = context match {
case Some(Context(parent, lastUpdate, metas, additionalHoles, hiddenNodes)) => {
Expand Down Expand Up @@ -305,12 +345,11 @@ trait Zipper[+A <: Node] extends Group[A] with IndexedSeqLike[A, Zipper[A]] { se
new Unselector(ctx, zms).unselect
}

/** Utility class to perform unselect. */
private[this] class Unselector(context: Context, mergeStrategy: ZipperMergeStrategy) {

/** Each hole is associated with a list of node/time pairs as well as a master update time */
type HoleInfo = ZipperHoleMap[(VectorCase[(Node,Time)],Time)]

/** 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)]

/** A utility class to convert the contents of the zipper into a hole map. */
private[this] class HoleMapper(context: Context) {
private val initHoleInfoItem:(VectorCase[(Node,Time)],Time) = (util.Vector0,0)

type HoleMapGet[A] = A => (ZipperPath, Time, GenTraversableOnce[Node])
Expand All @@ -327,7 +366,7 @@ trait Zipper[+A <: Node] extends Group[A] with IndexedSeqLike[A, Zipper[A]] { se
}
}

private val topLevelHoleInfo: HoleInfo = {
val holeInfo: HoleInfo = {
val Context(_, _, metas, additionalHoles, hiddenNodes) = context

/* Getters for the different parts of the zipper. */
Expand Down Expand Up @@ -356,6 +395,12 @@ trait Zipper[+A <: Node] extends Group[A] with IndexedSeqLike[A, Zipper[A]] { se
addToHoleInfo(items, hi, get)
}
}
}

/** Utility class to perform unselect. */
private[this] class Unselector(context: Context, mergeStrategy: ZipperMergeStrategy) {

private val topLevelHoleInfo = new HoleMapper(context).holeInfo

/** Applies the node updates to the parent and returns the result. */
def unselect: Zipper[Node] =
Expand Down Expand Up @@ -468,6 +513,12 @@ 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(_))

Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/com/codecommit/antixml/ZipperHoleMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ private[antixml] final class ZipperHoleMap[+B] private (items: Map[Int, ZipperHo

/** Returns a traversable represented the tree's contents in pre-order (lexicographically by path).
* Note that this has not been optimized, as it is only currently used by toString and for testing.
*
* TODO using this for zipper shifting, consider optimizing
*/
def depthFirst: Traversable[(ZipperPath, B)] =
new Traversable[(ZipperPath, B)] {
Expand Down

0 comments on commit 06dafa0

Please sign in to comment.