package edu.uw.zookeeper.data;
import java.util.Iterator;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
public abstract class ZNodePath extends ZNodeLabelVector {
public static RootZNodePath root() {
return RootZNodePath.getInstance();
}
/**
* @param path to be validated
* @return path
* @throws IllegalArgumentException
*/
public static String validate(String path) {
return validate(path, 0, path.length());
}
@SuppressWarnings("unchecked")
public static <T extends CharSequence> T validate(T path, int start, int end) {
if (start < end) {
if (path.charAt(start) != SLASH) {
throw new IllegalArgumentException(String.format("Missing slash at index %d for %s", start, path));
}
if ((start + 1) < end) {
int j = start+1;
while (j<end && (path.charAt(j) != SLASH)) {
j++;
}
if (j < end) {
RelativeZNodePath.validate(path, start+1, end);
} else {
ZNodeLabel.validate(path, start+1, end);
}
}
return ((start > 0) || (end < path.length() - 1)) ? (T) path.subSequence(start, end) : path;
} else {
throw new IllegalArgumentException(String.format("Empty index range [%d,%d) for %s", start, end, path));
}
}
public static ZNodePath validated(String path) {
return fromString(validate(path));
}
public static ZNodePath validated(String path, int start, int end) {
return fromString(validate(path));
}
/**
* Validates, replaces self and parent labels, and trims slashes.
*
* @param path to be canonicalized
* @return canonicalized path
*/
public static String canonicalize(final String path) {
final int length = path.length();
int lastSlash = -1;
StringBuilder builder = null;
for (int i=0; i<length; ++i) {
char c = path.charAt(i);
if ((c == SLASH) || (i == length - 1)) {
if (i > 0) {
// neighboring slash?
if ((c == SLASH) && (lastSlash == i-1)) {
if (builder == null) {
builder = new StringBuilder(length)
.append(path, 0, lastSlash+1);
}
} else {
ZNodeLabel label = ZNodeLabel.validated(path, lastSlash+1, (c == SLASH) ? i : i+1);
if (ZNodeLabel.self().equals(label)) {
if (builder == null) {
builder = new StringBuilder(length)
.append(path, 0, lastSlash+1);
}
} else if (ZNodeLabel.parent().equals(label)) {
if (builder == null) {
builder = new StringBuilder(length)
.append(path, 0, lastSlash+1);
}
int buildLength = builder.length();
assert (builder.charAt(buildLength -1) == SLASH);
int parentSlash = -1;
for (int j = buildLength-2; j >= 0; j--) {
if (builder.charAt(j) == SLASH) {
parentSlash = j;
break;
}
}
if (parentSlash < 0) {
throw new IllegalArgumentException(String.format("missing parent at index %d of %s", i, path));
}
builder.delete(parentSlash + 1, buildLength);
} else {
if (builder != null) {
builder.append(path, lastSlash+1, i+1);
}
}
}
}
if (c == SLASH) {
lastSlash = i;
}
}
}
String canonicalized;
// trailing slash?
if (builder == null) {
canonicalized = ((length > 1) && (path.charAt(length - 1) == SLASH)) ? path.substring(0, length - 1) : path;
} else {
int buildLength = builder.length();
if ((buildLength > 1) && (builder.charAt(buildLength - 1) == SLASH)) {
builder.deleteCharAt(buildLength - 1);
}
canonicalized = builder.toString();
}
if (canonicalized.isEmpty()) {
throw new IllegalArgumentException(String.format("empty canonicalized path for %s", path));
}
if (canonicalized.charAt(0) != SLASH) {
throw new IllegalArgumentException(String.format("not absolute path for %s", path));
}
return canonicalized;
}
public static ZNodePath canonicalized(String path) {
return fromString(canonicalize(path));
}
@Serializes(from=String.class, to=ZNodePath.class)
public static ZNodePath fromString(String path) {
assert (path.charAt(0) == SLASH);
if (path.length() == 1) {
return RootZNodePath.getInstance();
} else {
return AbsoluteZNodePath.fromString(path);
}
}
protected ZNodePath(String label) {
super(label);
}
@Override
public final boolean isAbsolute() {
return true;
}
public RelativeZNodePath relative(ZNodePath other) {
// calculate our common prefix
ImmutableList.Builder<String> parts = ImmutableList.builder();
Iterator<ZNodeLabel> myLabels = iterator();
Iterator<ZNodeLabel> otherLabels = other.iterator();
while (myLabels.hasNext() && otherLabels.hasNext()) {
ZNodeLabel next = myLabels.next();
if (next.equals(otherLabels.next())) {
parts.add(next.toString());
} else {
break;
}
}
final ZNodePath prefix = ZNodePath.root().join(RelativeZNodePath.fromString(ZNodePath.join(parts.build().iterator())));
// calculate my difference to the prefix
parts = ImmutableList.builder();
final ZNodeName mySuffix = suffix(prefix);
if (mySuffix instanceof EmptyZNodeLabel) {
parts.add(ZNodeLabel.self().toString());
} else {
Iterator<ZNodeLabel> itr = (mySuffix instanceof ZNodeLabel) ?
Iterators.singletonIterator((ZNodeLabel) mySuffix) :
((RelativeZNodePath) mySuffix).iterator();
while(itr.hasNext()) {
parts.add(ZNodeLabel.parent().toString());
itr.next();
}
}
// add the other suffix
parts.add(other.suffix(prefix).toString());
return RelativeZNodePath.fromString(ZNodePath.join(parts.build().iterator()));
}
@Override
public abstract ZNodePath join(ZNodeName other);
public abstract boolean isRoot();
public abstract AbstractZNodeLabel label();
}