org.modeshape.jcr.value.basic.AbstractPath Maven / Gradle / Ivy
/*
* ModeShape (http://www.modeshape.org)
*
* Licensed 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.modeshape.jcr.value.basic;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import org.modeshape.common.CommonI18n;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.common.text.TextEncoder;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.GraphI18n;
import org.modeshape.jcr.value.InvalidPathException;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.NamespaceRegistry;
import org.modeshape.jcr.value.Path;
/**
* An abstract foundation for different {@link Path} implementations. This class does not manage any of the {@link Path}'s state,
* but it does provide implementations for most of the methods based upon a few abstract methods. For example, any implementaton
* that requires the {@link org.modeshape.jcr.value.Path.Segment path's segments} are written to use the {@link #iterator()},
* since that is likely more efficient for the majority of implementations.
*/
@Immutable
public abstract class AbstractPath implements Path {
/**
* The initial serializable version. Version {@value}
*/
private static final long serialVersionUID = 1L;
public static final Path SELF_PATH = new BasicPath(Collections.singletonList(Path.SELF_SEGMENT), false);
protected static Iterator EMPTY_PATH_ITERATOR = new Iterator() {
@Override
public boolean hasNext() {
return false;
}
@Override
public Segment next() {
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
private transient int hc = 0;
protected boolean isNormalized( List segments ) {
boolean nonParentReference = false;
boolean first = isAbsolute(); // only care about first one when it's absolute
for (Segment segment : segments) {
if (segment.isSelfReference()) return false;
if (segment.isParentReference()) {
if (nonParentReference || first) return false;
} else {
nonParentReference = true;
}
first = false;
}
return true;
}
@Override
public boolean isIdentifier() {
return false;
}
@Override
public Path getCanonicalPath() {
if (!this.isAbsolute()) {
String msg = GraphI18n.pathIsNotAbsolute.text(this);
throw new InvalidPathException(msg);
}
if (this.isNormalized()) return this;
return this.getNormalizedPath();
}
@Override
public Path getCommonAncestor( Path that ) {
CheckArg.isNotNull(that, "that");
if (that.isRoot()) return that;
Path normalizedPath = this.getNormalizedPath();
int lastIndex = 0;
Iterator thisIter = normalizedPath.iterator();
Iterator thatIter = that.getNormalizedPath().iterator();
while (thisIter.hasNext() && thatIter.hasNext()) {
Segment thisSeg = thisIter.next();
Segment thatSeg = thatIter.next();
if (thisSeg.equals(thatSeg)) {
++lastIndex;
} else {
break;
}
}
if (lastIndex == 0) return RootPath.INSTANCE;
return normalizedPath.subpath(0, lastIndex);
}
@Override
public Path.Segment getLastSegment() {
return this.getSegmentsList().get(size() - 1);
}
@Override
public boolean endsWith( Name nameOfLastSegment ) {
Segment segment = getLastSegment();
return segment != null && segment.getName().equals(nameOfLastSegment) && !segment.hasIndex();
}
@Override
public boolean endsWith( Name nameOfLastSegment,
int snsIndex ) {
Segment segment = getLastSegment();
return segment != null && segment.getName().equals(nameOfLastSegment) && segment.getIndex() == snsIndex;
}
@Override
public Path getParent() {
return getAncestor(1);
}
@Override
public Segment getSegment( int index ) {
CheckArg.isNonNegative(index, "index");
return this.getSegmentsList().get(index);
}
@Override
public Segment[] getSegmentsArray() {
// By default, make a new array every time since arrays are mutable, and use the iterator
// since that is probably more efficient than creating a list ...
Segment[] result = new Path.Segment[size()];
int i = 0;
for (Segment segment : this) {
result[i] = segment;
++i;
}
return result;
}
@Override
public Path getNormalizedPath() {
if (this.isNormalized()) return this;
LinkedList newSegments = new LinkedList();
for (Segment segment : this) {
if (segment.isSelfReference()) continue;
if (segment.isParentReference()) {
if (newSegments.isEmpty()) {
if (this.isAbsolute()) {
throw new InvalidPathException(CommonI18n.pathCannotBeNormalized.text(this));
}
} else if (!newSegments.getLast().isParentReference()) {
newSegments.removeLast();
continue;
}
}
newSegments.add(segment);
}
if (newSegments.isEmpty()) {
if (this.isAbsolute()) return RootPath.INSTANCE;
// Otherwise relative and it had contained nothing but self references ...
return SELF_PATH;
}
return new BasicPath(newSegments, this.isAbsolute());
}
@Override
public String getString() {
return doGetString(null, null, null);
}
@Override
public String getString( TextEncoder encoder ) {
return doGetString(null, encoder, null);
}
@Override
public String getString( NamespaceRegistry namespaceRegistry ) {
CheckArg.isNotNull(namespaceRegistry, "namespaceRegistry");
return doGetString(namespaceRegistry, null, null);
}
@Override
public String getString( NamespaceRegistry namespaceRegistry,
TextEncoder encoder ) {
CheckArg.isNotNull(namespaceRegistry, "namespaceRegistry");
return doGetString(namespaceRegistry, encoder, null);
}
@Override
public String getString( NamespaceRegistry namespaceRegistry,
TextEncoder encoder,
TextEncoder delimiterEncoder ) {
return doGetString(namespaceRegistry, encoder, delimiterEncoder);
}
/**
* Method that creates the string representation. This method works two different ways depending upon whether the namespace
* registry is provided.
*
* @param namespaceRegistry
* @param encoder
* @param delimiterEncoder
* @return this path as a string
*/
protected String doGetString( NamespaceRegistry namespaceRegistry,
TextEncoder encoder,
TextEncoder delimiterEncoder ) {
if (encoder == null) encoder = DEFAULT_ENCODER;
final String delimiter = delimiterEncoder != null ? delimiterEncoder.encode(DELIMITER_STR) : DELIMITER_STR;
// Since the segments are immutable, this code need not be synchronized because concurrent threads
// may just compute the same value (with no harm done)
StringBuilder sb = new StringBuilder();
if (this.isAbsolute()) sb.append(delimiter);
boolean first = true;
for (Segment segment : this) {
if (first) {
first = false;
} else {
sb.append(delimiter);
}
assert segment != null;
sb.append(segment.getString(namespaceRegistry, encoder, delimiterEncoder));
}
String result = sb.toString();
// Save the result to the internal string if this the default encoder is used.
// This is not synchronized, but it's okay
return result;
}
@Override
public boolean hasSameAncestor( Path that ) {
CheckArg.isNotNull(that, "that");
if (that.size() != this.size()) return false;
if (this.size() == 1) return true; // both nodes are just under the root
for (int i = this.size() - 2; i >= 0; --i) {
Path.Segment thisSegment = this.getSegment(i);
Path.Segment thatSegment = that.getSegment(i);
if (!thisSegment.equals(thatSegment)) return false;
}
return true;
}
@Override
public boolean isAncestorOf( Path descendant ) {
CheckArg.isNotNull(descendant, "descendant");
return descendant.isDescendantOf(this);
}
@Override
public boolean isAtOrBelow( Path other ) {
CheckArg.isNotNull(other, "other");
if (this == other) return true;
if (other.isRoot()) return true;
if (other.size() > this.size()) return false;
// Find this path's ancestor that is of the same size ...
Path thisPath = this.getAncestor(this.size() - other.size());
Path thatPath = other;
assert thisPath.size() == other.size();
// Walk up and compare; it is more likely that the lower path segments differ (plus it's usually more efficient) ...
while (!thisPath.isRoot() && thisPath.getLastSegment().equals(thatPath.getLastSegment())) {
thisPath = thisPath.getParent();
thatPath = thatPath.getParent();
}
return thisPath.isRoot();
}
@Override
public boolean isAtOrAbove( Path other ) {
CheckArg.isNotNull(other, "other");
return other.isAtOrBelow(this);
}
@Override
public boolean isDescendantOf( Path ancestor ) {
CheckArg.isNotNull(ancestor, "ancestor");
if (this == ancestor) return false;
if (ancestor.isRoot()) return true;
if (ancestor.size() >= this.size()) return false;
// Find this path's ancestor that is of the same size ...
Path thisPath = this.getAncestor(this.size() - ancestor.size());
Path thatPath = ancestor;
assert thisPath.size() == thatPath.size();
// Walk up and compare; it is more likely that the lower path segments differ (plus it's usually more efficient) ...
while (!thisPath.isRoot() && thisPath.getLastSegment().equals(thatPath.getLastSegment())) {
thisPath = thisPath.getParent();
thatPath = thatPath.getParent();
}
return thisPath.isRoot();
}
@Override
public boolean isSameAs( Path other ) {
return other != null && this.compareTo(other) == 0;
}
@Override
public Iterator iterator() {
return getSegmentsList().iterator();
}
@Override
public Iterator pathsFromRoot() {
LinkedList paths = new LinkedList();
Path path = this;
while (path != null) {
paths.addFirst(path);
if (path.isRoot()) break;
path = path.getParent();
}
return paths.iterator();
}
@Override
public Path relativeToRoot() {
return new BasicPath(getSegmentsList(), false);
}
@Override
public Path relativeTo( Path startingPath ) {
CheckArg.isNotNull(startingPath, "to");
if (!this.isAbsolute()) {
String msg = GraphI18n.pathIsNotAbsolute.text(this);
throw new InvalidPathException(msg);
}
if (startingPath.isRoot()) {
// We just want a relative path containing the same segments ...
return relativeToRoot();
}
if (!startingPath.isAbsolute()) {
String msg = GraphI18n.pathIsNotAbsolute.text(startingPath);
throw new InvalidPathException(msg);
}
// Count the number of segments up to the common ancestor (relative path is what remains) ...
int lengthOfCommonAncestor = 0;
Iterator thisIter = this.getNormalizedPath().iterator();
Iterator toIter = startingPath.getNormalizedPath().iterator();
while (thisIter.hasNext() && toIter.hasNext()) {
Segment thisSeg = thisIter.next();
Segment toSeg = toIter.next();
if (thisSeg.equals(toSeg)) {
++lengthOfCommonAncestor;
} else {
break;
}
}
// Create the relative path, starting with parent references to the common ancestor ...
int numberOfParentReferences = startingPath.size() - lengthOfCommonAncestor;
List relativeSegments = new ArrayList();
for (int i = 0; i != numberOfParentReferences; ++i) {
relativeSegments.add(Path.PARENT_SEGMENT);
}
// Add the segments of this path from the common ancestor ...
for (int i = lengthOfCommonAncestor; i < this.size(); ++i) {
relativeSegments.add(getSegment(i));
}
if (relativeSegments.isEmpty()) {
relativeSegments.add(Path.SELF_SEGMENT);
}
return new BasicPath(relativeSegments, false);
}
@Override
public Path resolve( Path relativePath ) {
CheckArg.isNotNull(relativePath, "relative path");
if (!this.isAbsolute()) {
String msg = GraphI18n.pathIsNotAbsolute.text(this);
throw new InvalidPathException(msg);
}
if (relativePath.isAbsolute()) {
String msg = GraphI18n.pathIsNotRelative.text(relativePath);
throw new InvalidPathException(msg);
}
// If the relative path is the self or parent reference ...
relativePath = relativePath.getNormalizedPath();
if (relativePath.size() == 1) {
Segment onlySegment = relativePath.getSegment(0);
if (onlySegment.isSelfReference()) return this;
if (onlySegment.isParentReference()) return this.getParent();
}
List segments = new ArrayList(this.size() + relativePath.size());
for (Segment segment : this) {
segments.add(segment);
}
for (Segment segment : relativePath) {
segments.add(segment);
}
return new BasicPath(segments, true).getNormalizedPath();
}
@Override
public Path resolveAgainst( Path absolutePath ) {
CheckArg.isNotNull(absolutePath, "absolute path");
return absolutePath.resolve(this);
}
@Override
public Path subpath( int beginIndex ) {
return subpath(beginIndex, size());
}
@Override
public Path subpath( int beginIndex,
int endIndex ) {
CheckArg.isNonNegative(beginIndex, "beginIndex");
CheckArg.isNonNegative(endIndex, "endIndex");
int size = size();
if (beginIndex == 0) {
if (endIndex == 0) return RootPath.INSTANCE;
if (endIndex == size) return this;
}
if (beginIndex >= size) {
throw new IndexOutOfBoundsException(
GraphI18n.unableToCreateSubpathBeginIndexGreaterThanOrEqualToSize.text(beginIndex,
size));
}
if (beginIndex > endIndex) {
throw new IndexOutOfBoundsException(
GraphI18n.unableToCreateSubpathBeginIndexGreaterThanOrEqualToEndingIndex.text(beginIndex,
endIndex));
}
// This reuses the same list, so it's pretty efficient ...
return new BasicPath(createSegmentsSubList(beginIndex, endIndex), this.isAbsolute());
}
protected List createSegmentsSubList( int validBeginIndex,
int validEndIndex ) {
return this.getSegmentsList().subList(validBeginIndex, validEndIndex);
}
@Override
public int hashCode() {
if (hc == 0) {
int hashCode = 1;
for (Segment segment : this) {
hashCode = 31 * hashCode + segment.hashCode();
}
hc = hashCode;
}
return hc;
}
/**
* Method used by {@link AbstractPath#equals(Object)} implementation to quickly get an Iterator over the segments in the
* parent.
*
* @return the iterator over the segments; never null, but may not have any elements
*/
protected abstract Iterator getSegmentsOfParent();
@Override
public boolean equals( Object obj ) {
if (obj == this) return true;
if (obj instanceof Path) {
Path that = (Path)obj;
// First check whether the paths are roots ...
if (this.isRoot()) return that.isRoot();
else if (that.isRoot()) return false;
// Now check the hash code and size ...
if (this.hashCode() != that.hashCode()) return false;
if (this.size() != that.size()) return false;
// Check the last segments, since these will often differ anyway ...
if (!this.getLastSegment().equals(that.getLastSegment())) return false;
if (this.size() == 1) return true;
// Check the rest of the names ...
Iterator thisIter = that instanceof AbstractPath ? this.getSegmentsOfParent() : this.iterator();
Iterator thatIter = that instanceof AbstractPath ? ((AbstractPath)that).getSegmentsOfParent() : that.iterator();
while (thisIter.hasNext()) {
Segment thisSegment = thisIter.next();
Segment thatSegment = thatIter.next();
if (!thisSegment.equals(thatSegment)) return false;
}
return true;
}
return false;
}
@Override
public int compareTo( Path that ) {
if (this == that) return 0;
Iterator thisIter = getSegmentsList().iterator();
Iterator thatIter = that.iterator();
while (thisIter.hasNext() && thatIter.hasNext()) {
Segment thisSegment = thisIter.next();
Segment thatSegment = thatIter.next();
int diff = thisSegment.compareTo(thatSegment);
if (diff != 0) return diff;
}
if (thisIter.hasNext()) return 1;
if (thatIter.hasNext()) return -1;
return 0;
}
@Override
public String toString() {
return getString(Path.NO_OP_ENCODER);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy