blob: 1d097389e3a315b4c89fe721471e178d2eac6d5d [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.waveprotocol.wave.client.editor;
import org.waveprotocol.wave.model.document.ReadableDocument;
import org.waveprotocol.wave.model.document.util.Point;
/**
* Class representing a range that satisfies two constraints:
* 1) It lies between node boundaries (not in text nodes)
* 2) The two logical Points it represents share the same container
*
* This restricted scenario arises quite frequently, and this class
* statically enforces the constraints.
*
* One of the most important aspects that distinguishes it from just
* a pair of points is that, for the start of the range, we store
* the node BEFORE, not the usual after. Therefore, the "node before"
* and "node after" are not in the range, they bound it. This is
* particularly useful when we wish to define a range that will
* remain valid despite any changes to the nodes it contains.
*
* Numerous constructors and helper methods are provided. Some,
* as usual, expect a document implementation for the range's
* node type.
*
* @author danilatos@google.com (Daniel Danilatos)
* @param <N> Node type
*/
public class RestrictedRange<N> {
/** Point after the range. */
private final Point.El<N> after;
/** node before the range */
private final N nodeBefore;
private RestrictedRange(Point.El<N> after, N nodeBefore) {
this.after = after;
this.nodeBefore = nodeBefore;
}
/**
* Construct a restricted range bounded by the given parent,
* node before and node after.
*/
public static <N> RestrictedRange<N> boundedBy(N parent, N before, N after) {
return new RestrictedRange<N>(Point.inElement(parent, after), before);
}
/**
* Construct a collapsed range at the given point. Due to the properties
* of this object, if nodes get inserted at that point, the range will
* expand but remain valid.
*/
public static <N, E extends N, T extends N> RestrictedRange<N> collapsedAt(
ReadableDocument<N, E, T> doc, Point.El<N> collapsedAt) {
return new RestrictedRange<N>(collapsedAt, Point.nodeBefore(doc, collapsedAt));
}
/**
* Construct a collapsed range encompassing the given nodes. If the node(s) are
* removed, the range will be collapsed but remain valid, if the node(s) have
* siblings added, the range will expand.
*/
public static <N, E extends N, T extends N> RestrictedRange<N> around(
ReadableDocument<N, E, T> doc, N firstBoundedNode, N lastBoundedNode) {
return new RestrictedRange<N>(
Point.after(doc, lastBoundedNode),
doc.getPreviousSibling(firstBoundedNode));
}
/**
* Construct a range bounded by the node before, and the point after.
*/
public static <N> RestrictedRange<N> between(N nodeBefore, Point.El<N> after) {
return new RestrictedRange<N>(after, nodeBefore);
}
/**
* Construct a range bounded by the two given element points. The document
* parameter is required to find the node before.
*/
public static <N, E extends N, T extends N> RestrictedRange<N> between(
ReadableDocument<N, E, T> doc, Point.El<N> before, Point.El<N> after) {
assert before.getContainer().equals(after.getContainer());
N nodeBefore = before.getNodeAfter() == null
? doc.getLastChild(before.getContainer())
: doc.getPreviousSibling(before.getNodeAfter());
return new RestrictedRange<N>(after, nodeBefore);
}
/**
* Constructs a restricted range from two arbitrary points. If the points
* are in text nodes, they are rounded outwards to node boundaries.
*/
public static <N, E extends N, T extends N> RestrictedRange<N> roundedBetween(
ReadableDocument<N, E, T> doc, Point<N> before, Point<N> after) {
Point.El<N> beforeSafe, afterSafe;
beforeSafe = before.isInTextNode()
? Point.before(doc, before.getContainer())
: before.asElementPoint();
afterSafe = after.isInTextNode()
? Point.after(doc, after.getContainer())
: after.asElementPoint();
return between(doc, beforeSafe, afterSafe);
}
/**
* @return The first node at the start of the range. (If the range is collapsed, this would
* basically be nodeAfter).
*/
public <E extends N, T extends N> N getStartNode(ReadableDocument<N, E, T> doc) {
N firstNode = nodeBefore == null
? doc.getFirstChild(after.getContainer())
: doc.getNextSibling(nodeBefore);
return firstNode;
}
/**
* @return true if the given node is inside the range.
* NOTE(danilatos): Only the top level is searched (no descending into child nodes)
*/
public <E extends N, T extends N> boolean contains(ReadableDocument<N, E, T> doc, N node) {
N fromIncl = getStartNode(doc);
N toExcl = getNodeAfter();
return isBetween(doc, node, fromIncl, toExcl);
}
/**
* @return A point representing the start of the range
*/
public <E extends N, T extends N> Point.El<N> getPointBefore(ReadableDocument<N, E, T> doc){
return Point.inElement(after.getContainer(), getStartNode(doc));
}
/**
* @return A point representing the end of the range
*/
public Point.El<N> getPointAfter() {
return after;
}
/** @return the "node before" */
public N getNodeBefore() {
return nodeBefore;
}
/** @return the "node after" */
public N getNodeAfter() {
return after.getNodeAfter();
}
/** @return the element immediately encompassing the range */
public N getContainer() {
return after.getContainer();
}
/**
* Checks if a node is between fromInc1 and toExec1.
* @param <N> node type
* @param <E> element type
* @param <T> text type
* @param doc Needed to walk the nodes
* @param node The node to check
* @param fromIncl Is the node parameter at or after this node?
* @param toExcl Is the node parameter before and not including this node?
* @return true if the node parameter is in the specified range
*/
private static <N, E extends N, T extends N> boolean isBetween(
ReadableDocument<N, E, T> doc, N node, N fromIncl, N toExcl) {
for (N n = fromIncl; n != null && n != toExcl; n = doc.getNextSibling(n)) {
if (n == node) {
return true;
}
}
return false;
}
@Override
public String toString() {
return after.getContainer() + " ->( " + nodeBefore + " - " + after.getNodeAfter() + " )";
}
}