cdc.io.data.paths.Path Maven / Gradle / Ivy
package cdc.io.data.paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import cdc.util.lang.Checks;
/**
* Simple XPath like class.
*
* Can only handle relative paths like: foo, foo/bar foo[@bar='fred'], foo/@bar, ...
*
* @author Damien Carbonne
*/
public final class Path {
private final String text;
private final Part[] parts;
public Path(String text) {
this.text = text == null ? "" : text;
this.parts = new Part[getNumParts(this.text)];
buildParts();
}
public static Path of(String text) {
return new Path(text);
}
private Path(Part... parts) {
Checks.isNotNullOrEmpty(parts, "parts");
this.parts = Arrays.copyOf(parts, parts.length);
this.text = buildName(this.parts);
}
private static Part[] normalize(Part... parts) {
final List tmp = new ArrayList<>();
Collections.addAll(tmp, parts);
normalize(tmp);
final Part[] result = new Part[tmp.size()];
return tmp.toArray(result);
}
private static void normalize(List parts) {
if (parts.isEmpty()) {
throw new IllegalArgumentException("Invalid empty parts");
}
int index = 1;
while (index < parts.size()) {
final Part part1 = parts.get(index - 1);
final Part part2 = parts.get(index);
switch (part2.getType()) {
case DOT:
// Remove it and don't move
parts.remove(index);
break;
case DOT_DOT:
switch (part1.getType()) {
case ATTRIBUTE:
case ELEMENT:
case SELECTOR:
// Remove part 1 and part 2 if previous is not ..
parts.remove(index);
parts.remove(index - 1);
index--;
if (index == 0) {
parts.add(0, Part.DOT);
index = 1;
}
break;
case DOT:
// Remove part1 (.)
parts.remove(index - 1);
break;
case DOT_DOT:
index++;
break;
default:
break;
}
break;
case ATTRIBUTE:
case ELEMENT:
case SELECTOR:
index++;
break;
default:
break;
}
}
if (parts.size() > 1 && parts.get(0).getType() == Part.Type.DOT) {
parts.remove(0);
}
}
private static int getNumParts(String code) {
int count = 0;
for (int index = 0; index < code.length(); index++) {
if (code.charAt(index) == '/') {
count++;
}
}
return count + 1;
}
private void buildParts() {
int prev = 0;
int index = 0;
while (prev >= 0) {
final int next = text.indexOf('/', prev);
final Part part;
if (next >= 0) {
part = new Part(text.substring(prev, next));
prev = next + 1;
} else {
part = new Part(text.substring(prev));
prev = -1;
}
parts[index] = part;
index++;
}
}
private static String buildName(Part... parts) {
final StringBuilder builder = new StringBuilder();
boolean first = true;
for (final Part part : parts) {
if (!first) {
builder.append('/');
}
builder.append(part.getText());
first = false;
}
return builder.toString();
}
public Path normalize() {
return new Path(normalize(this.parts));
}
public String getText() {
return text;
}
@Deprecated(forRemoval = true, since = "2023-07-14")
public String getName() {
return getText();
}
public Part[] getParts() {
return parts;
}
public Part getPart(int index) {
return parts[index];
}
public Part getLastPart() {
return parts[parts.length - 1];
}
public int getLength() {
return parts.length;
}
public Path getParent() {
final Part[] tmp = Arrays.copyOf(this.parts, this.parts.length + 1);
tmp[tmp.length - 1] = Part.DOT_DOT;
return new Path(normalize(tmp));
}
public Path getSubPath(int beginIndex,
int endIndex) {
if (beginIndex < 0 || beginIndex > getLength()) {
throw new IllegalArgumentException("Invalid beginIndex (" + this + ", " + beginIndex + ", " + endIndex + ")");
}
if (endIndex < beginIndex || endIndex > getLength()) {
throw new IllegalArgumentException("Invalid endIndex (" + this + ", " + beginIndex + ", " + endIndex + ")");
}
if (beginIndex == endIndex) {
return new Path(".");
}
final Part[] tmp = new Part[endIndex - beginIndex];
for (int index = beginIndex; index < endIndex; index++) {
tmp[index - beginIndex] = parts[index];
}
return new Path(normalize(tmp));
}
@Override
public int hashCode() {
return text.hashCode();
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Path)) {
return false;
}
final Path o = (Path) other;
return text.equals(o.text);
}
@Override
public String toString() {
return text;
}
}