Permalink
Browse files

Scaladoc additions and corrections

  • Loading branch information...
1 parent d869287 commit 6641a3bac2d9d9449b19c6873c092a085fb3dece @josharnold52 josharnold52 committed Oct 11, 2011
@@ -32,14 +32,38 @@ import scala.collection.GenTraversableOnce
import scala.collection.mutable.Builder
import scala.collection.generic.CanBuildFrom
-/** A factory for [[com.codecommit.antixml.Zipper]] instances.
- * This trait is similar to [[scala.collection.mutable.CanBuildFrom]], however its builders accept instances
- * of `ElemsWithContext[Elem]` rather than `Elem` instances. In addition, its `apply`
- * methods accept an optional reference to the zipper's parent.
+/** A factory for [[com.codecommit.antixml.Zipper]] instances.
+ *
+ * WARNING: This is a "low-level" trait that was primarily designed for internal
+ * use of the antixml package. It is tied to the `Zipper` implementation and
+ * could change significantly in a future release.
+ *
+ * This trait is similar to
+ * [[http://www.scala-lang.org/api/current/scala/collection/generic/CanBuildFrom.html CanBuildFrom]],
+ * except that it allows a zipper context to be specified in addition to the usual sequence of items.
+ * See the [[com.codecommit.antixml.Zipper]] trait for the definition of "zipper context".
+ *
+ * The [[http://www.scala-lang.org/api/current/scala/collection/mutable/Builder.html Builder]] produced
+ * by this class accepts objects of type
+ * [[com.codecommit.antixml.CanBuildFromWithZipper.ElemsWithContext]]. These objects
+ * contain the following information:
+ * - A sequence of items (nodes) to be added to the zipper.
+ * - A path specifying the hole associated with the above items.
+ * - An update counter, which indicates the relative order in which the zipper's items were last
+ * updated. This information is used by some [[com.codecommit.antixml.ZipperMergeStrategy]]
+ * implementations when resolving conflicts.
+ *
+ * Note that an `ElemsWithContext` may contain an empty sequence, in which case its path (hole)
+ * is added to zipper context without being associated to any items. Also note that it is legal for
+ * the same path (hole) to be added to the Builder multiple times. The resulting Zipper
+ * will associate ''all'' of the corresponding items to that hole.
+ *
+ * The parent of the zipper context is specified to the `apply` method of this trait.
*
* @tparam From The type of collection that is producing the zipper.
* @tparam Elem The type of nodes to be contained in the result (if any).
* @tparam To the type of collection being produced.
+ * @see [[com.codecommit.antixml.Zipper]]
*/
trait CanBuildFromWithZipper[-From, -Elem, To] {
import CanBuildFromWithZipper.ElemsWithContext
@@ -70,18 +94,17 @@ trait CanProduceZipper[-From, A <: Node, To] { this: CanBuildFrom[From, A, _ >:
object CanBuildFromWithZipper {
/**
- * Decorates a sequence of zipper elements with a zipper context and an update time. This is the
- * basic unit of information used to construct zippers.
+ * Decorates a sequence of zipper elements with a path and an update time. This is the
+ * basic unit of information used to construct zippers. See [[com.codecommit.antixml.CanBuildFromWithZipper]]
+ * for more information.
*
* @tparam Elem the type of node that will be contained in the zipper.
- * @param path Identifies a location in the zipper's parent. The path order is from top to bottom
- * (the first item specifies the index of a top-level node within the parent). When building a zipper,
- * it is legal for multiple ElemsWithContexts to specify the same path; In such cases, all of the
- * corresponding Elems will be added to the zipper and they will all be associated with that path.
+ * @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 updateTime the update time associated with these elements. One context is considered to have
* been updated later than another if its updateTime is greater.
- * @param elements the actual elements to be added to the zipper. Note that this sequence may be
- * empty. This would happen, for example, if `flatMap` operation removed all items for a given path.
+ * @param elements the actual elements to be added to the zipper.
+ * @see [[com.codecommit.antixml.CanBuildFromWithZipper]]
*/
case class ElemsWithContext[+Elem](path: Seq[Int], updateTime: Int, elements: GenTraversableOnce[Elem])
@@ -36,7 +36,7 @@ import scala.collection.immutable.{Vector, VectorBuilder}
import CanBuildFromWithZipper.ElemsWithContext
trait Selectable[+A <: Node] {
- import PathCreator.{allChildren, directChildren, PathFunction, PathVal}
+ import PathCreator.{allChildren, directChildren, fromNodes, allMaximalChildren, PathFunction, PathVal}
/**
* Performs a shallow-select on the XML tree according to the specified selector
@@ -151,15 +151,15 @@ trait Selectable[+A <: Node] {
* Like `\\`, this performs a depth-first search through the tree. However, any time
* the selector matches a node, it's children are skipped over rather than being searched.
* Thus, the result is guaranteed to never contain both a node and one of its descendants.
+ * In terms of the [com.codecommit.antixml.Zipper] trait, the result is guaranteed to be
+ * conflict-free.
*
* Just as with shallow selection, the very outermost level of the group is not
* considered in the selection.
*
* @usecase def \\!(selector: Selector[Node]): Zipper[Node]
*/
def \\![B, That](selector: Selector[B])(implicit cbfwz: CanBuildFromWithZipper[Group[_ <: Node], B, That]): That = {
- import Zipper._
- import PathCreator._
fromPathFunc(allMaximalChildren(selector), cbfwz)
}
@@ -178,11 +178,19 @@ trait Selectable[+A <: Node] {
* @usecase def select(selector: Selector[Node]): Zipper[Node]
*/
def select[B, That](selector: Selector[B])(implicit cbfwz: CanBuildFromWithZipper[Group[_ <: Node], B, That]): That = {
- import Zipper._
- import PathCreator._
fromPathFunc(fromNodes(selector),cbfwz)
}
+ /**
+ * Utility method to apply a path function to the group and bundle up the results in a Zipper.
+ *
+ * NOTE: If this method, or something like it, were to be made public then the issue of duplicates
+ * would need to be addressed. As it stands, Zipper always handles duplicates by concatenating the
+ * corresponding nodes during `unselect`. That's the right thing to do when duplicates arise from
+ * flatMapping another Zipper, but it may not be the expected behavior from the viewpoint of selection.
+ *
+ * In the current implementation, this method is never called with duplicates, so the question never arises.
+ */
private def fromPathFunc[B,That](pf: PathFunction[B], cbfwz: CanBuildFromWithZipper[Group[A], B, That]): That = {
val grp = toGroup
val bld = cbfwz(Some(toZipper), grp)
@@ -38,32 +38,87 @@ import scala.collection.mutable.Builder
import Zipper._
import CanBuildFromWithZipper.ElemsWithContext
-/** A zipper which allows deep selection.
+/**
+ * Provides an `unselect` operation which copies this Group's nodes back to the XML tree from which
+ * it was derived.See the [[http://anti-xml.org/zippers.html Anti-XML Overview]] for a
+ * high-level description of this functionality.
*
- * ==Unselection Algorithm==
+ * The `Zipper` trait augments a [[com.codecommit.antixml.Group]] with additional immutable state used
+ * to support the `unselect` method. This state is known as the "zipper context" and is defined by:
+ * - A reference to another `Group`, known as the ''parent'' of the Zipper.
+ * - A set of (possibly deep) locations within the parent, known
+ * as the ''holes'' of the Zipper.
+ * - A mapping from the top-level indices of the Zipper to its holes, known as
+ * the Zipper's ''replacement map''
*
- * Let G be a group, and Z be a zipper with G as its parent. For each node, N, in G (top-level or otherwise), we make
- * the following definitions:
+ * Loosely speaking, the `unselect` method produces an updated version of the
+ * parent by replacing its holes with the nodes from the Zipper, as determined by the replacement map.
+ * A formal definition of `unselect` can be found below.
*
- * - N ''lies in'' Z if N was matched by the original selection operation that led to the creation the zipper.
- * - N ''lies above'' Z if a descendant of N lies in Z
- * - For any N that lies in Z, the ''direct updates'' for N are the sequence of nodes that replaced N via update operations on the zipper.
- * - For any N that lies above Z, the ''indirect update'' for N is just N with its children flatMapped with the pullback function (defined below).
- * - The ''pullback'' of N consists of a sequence of nodes as defined below:
- * - If N does not lie in Z, then the pullback of N is the singleton sequence consisting of the indirect update for N
- * - If N lies in Z, but does not lie above Z, then the pullback of N is its direct updates.
- * - If N lies in Z ''and'' lies above Z, then the pullback of N is the result of merging its direct updates and its indirect update,
- * according to the [[com.codecommit.antixml.ZipperMergeStrategy]] passed as an implicit parameter to `unselect`.
+ * Certain "modify" operations on a `Zipper` will propogate the zipper context to the result.
+ * The new Zipper's `unselect` method can then be viewed as applying
+ * these modifications back to the parent tree. Currently, the following methods
+ * support this propagation of the zipper context:
+ * - `updated`, `map`, `flatMap`, `filter`, `collect`, `slice`, `drop`, `take`, `splitAt`, and
+ * `unselect` (the latter viewed as a modification of the parent `Zipper`).
+ *
+ * These operations all provide a natural identification of indices in the new Zipper with
+ * the indices they were derived from in the original. This identification is used to lift
+ * the replacement map to the new Zipper. The parent and holes of the new Zipper are always the same
+ * as those of the original.
+ *
+ * Of course, propogation is only possible if the result can legally be a `Zipper`. Replacing a `Node`
+ * with a `String`, for example, will result in an undecorated `IndexedSeq` because the result violates
+ * Zipper's type bounds.
+ *
+ * ==== Node Multiplication and Elision ====
+ *
+ * A Zipper's replacement map need neither be injective nor surjective.
+ * Injectivity can fail due to the action of `flatMap`, which replaces a node with a sequence of nodes,
+ * all of which are associated with the original node's hole. In such cases, `unselect` will replace the hole with the
+ * entire sequence of nodes mapping to it. Surjectivity can fail due to any operation that "removes" items
+ * from the `Zipper`. If a hole is not associated with any Zipper nodes, then `unselect` will remove that position
+ * from the resulting tree.
+ *
+ * ==== Conflicting Holes ====
*
- * (Strictly speaking, these definitions should be made on the location of N in G rather than on N itself, but we abuse the
- * terminology for the sake of brevity)
+ * For a given Zipper, a hole, H, is said to be ''conflicted'' if the Zipper contains another hole,
+ * H,,c,, , contained in the subtree at H. In this case, the Zipper is said to be
+ * ''conflicted at'' H. A Zipper that does not contain conflicted holes is said to be ''conflict free''.
+ * Conflicted holes arise when a selection operator yields both a node and one or more of its descendants.
+ * They are of concern because there is no canonical way to specify the behavior of `unselect` at a
+ * conflicted hole. Instead, a [[com.codecommit.antixml.ZipperMergeStrategy]], implicitly provided
+ * to `unselect`, is used to resolve the conflict.
*
- * With the above definitions, the result of `unselect` can be defined simply as G flatMapped with the pullBack function.
+ * A default ZipperMergeStrategy has been provided that should suffice for the most common use cases involving
+ * conflicted holes. In particular, if modifications to a conflicted element are limited to its top-level properties
+ * (`name`, `attributes`, etc.), then the default strategy will apply those changes while preserving any modifications
+ * made to those descendant nodes also present in the Zipper. However, if the `children` property of a conflicted element
+ * is directly modified, then the default strategy's behavior is formally unspecified. Currently it uses a hueristic
+ * algortihm to resolve conflicts, but its details may change in a future release.
+ *
+ * Of the [[com.codecommit.antixml.Selectable]] operators, only `\\` is capable of producing conflicts.
+ * The `select`, `\`, and `\\!` operators always produce conflict-free Zippers.
+ *
+ * ====Unselection Algorithm====
*
- * The last line in the definition of "pullback" deserves special mention because it defines the only condition under which
- * the [[com.codecommit.antixml.ZipperMergeStrategy]] is invoked. Given zipper Z and parent G, if there exists an N in G such that N lies both in and above Z,
- * then Z is said to be ''conflicted at N''. A zipper without such an N then Z is said to be ''conflict free'' and will never invoke
- * a ZipperMergeStrategy during unselection.
+ * Let G be a group, and Z be a zipper with G as its parent. For each location, L, in G (top-level or otherwise), we make
+ * the following definitions:
+ *
+ * - G(L) is the node in G at location L.
+ * - `children`(L) is the sequence of locations that are immediately below L in G.
+ * - L is a ''hole'' if it is in Z's "holes" set.
+ * - L is ''above a hole'' if some descendant of L is a hole.
+ * - If L is a hole, the ''direct updates'' for L is the sequence of nodes given by the inverse of Z's replacement map,
+ * in the order defined by Z.
+ * - For any L, The ''indirect update'' for L is just G(L) with its children replaced by `children`(L)`.flatMap(pullback)`.
+ * - For any L, `pullback`(L) is the sequence of nodes given by the following recursive definition:
+ * - If L is not a hole, then `pullback`(L) is the singleton sequence consisting of the indirect update for L
+ * - If L is a hole, but is not above a hole, then `pullback`(L) is the direct updates for L.
+ * - Otherwise, L is conflicted and `pullback`(L) is the result of merging its direct updates and its
+ * indirect update according to the [[com.codecommit.antixml.ZipperMergeStrategy]] provided to `unselect`.
+ *
+ * Let T be the sequence of top-level locations in G. Then `Z.unselect` is defined as `T.flatMap(pullback)`.
*
*/
trait Zipper[+A <: Node] extends Group[A] with IndexedSeqLike[A, Zipper[A]] { self =>
@@ -31,22 +31,21 @@ package com.codecommit.antixml
import scala.collection.immutable.IndexedSeq
/**
- * Describes the parameters of a merge operation.
- *
- * The purpose of a merge
+ * Describes the parameters of a merge operation. See the [[com.codecommit.antixml.Zipper]] trait
+ * for formal definitions of these parameters.
*
- * Operations such as `flatMap` make it possible for a zipper's node to be replaced by multiple nodes or to be removed altogether. For this
- * reason, direct updates are represented as a ''sequence'' of nodes. In contrast, the indirect update always consists of a single node.
- * Any multiplicative operations that occurred further down the tree will will already have been accounted for in that node's children.
- *
- * @param original the original Node that was selected when the zipper was produced.
- * @param directUpdate the direct replacements of the node and their corresponding update times. These are the nodes
- * that explicitly replaced `original` via updates to its position in the zipper.
- * @param lastDirectUpdate the largest update time of any direct update to the node. If `directUpdates` is empty, this
- * will be the time that the node was removed.
- * @param indirectUpdate the "indirect" replacement and associated update time. The indirect replacement is just the
- * original node with its children replaced by a recursive application of the unselection algorithm, as defined in the
- * [[com.codecommit.antixml.Zipper]] unselection algorithm.
+ * Note that a merge operation always occurs at a particular conflicted hole (location) within the parent XML tree.
+ * All of the ZipperMergeContext attributes are considered to be "located" at that hole.
+ *
+ * @param original &nbsp;the original Node that was present at the conflicted hole.
+ * @param directUpdate &nbsp;the direct updates for the hole and their corresponding update times. These are the nodes
+ * that explicitly replaced `original` via modifications made to the Zipper.
+ * @param lastDirectUpdate &nbsp;the largest update time of any direct update to the hole. If `directUpdates` is empty, this
+ * will indicate the time that the node was removed.
+ * @param indirectUpdate &nbsp;the indirect update for the hole and its associated update time. This node's decendants contains
+ * the results of all the updates made to the descendant holes causing the conflict. It's top-level attributes are the
+ * same as those of `original`
+ * @see [[com.codecommit.antixml.Zipper]], [[com.codecommit.antixml.ZipperMergeStrategy]]
*/
case class ZipperMergeContext(original: Node, directUpdate: IndexedSeq[(Node,Int)],
lastDirectUpdate: Int, indirectUpdate: (Node,Int))
Oops, something went wrong.

0 comments on commit 6641a3b

Please sign in to comment.