blob: 7a6b9a81922ec06ac8b157e0693262d2205a11be [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.apache.tinkerpop.gremlin.process.traversal.step.util;
import org.apache.tinkerpop.gremlin.process.traversal.Path;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
public class ImmutablePath implements Path, Serializable, Cloneable {
private static final ImmutablePath TAIL_PATH = new ImmutablePath(null, null, null);
private ImmutablePath previousPath;
private Object currentObject;
private Set<String> currentLabels;
public static Path make() {
return TAIL_PATH;
}
@SuppressWarnings("CloneDoesntCallSuperClone,CloneDoesntDeclareCloneNotSupportedException")
@Override
public ImmutablePath clone() {
return this;
}
private ImmutablePath(final ImmutablePath previousPath, final Object currentObject, final Set<String> currentLabels) {
this.previousPath = previousPath;
this.currentObject = currentObject;
this.currentLabels = currentLabels;
}
private final boolean isTail() {
return null == this.currentObject;
}
@Override
public boolean isEmpty() {
return this.isTail();
}
@Override
public int size() {
int counter = 0;
ImmutablePath currentPath = this;
while (true) {
if (currentPath.isTail()) return counter;
counter++;
currentPath = currentPath.previousPath;
}
}
@Override
public <A> A head() {
return (A) this.currentObject;
}
@Override
public Path extend(final Object object, final Set<String> labels) {
return new ImmutablePath(this, object, labels);
}
@Override
public Path extend(final Set<String> labels) {
if (labels.isEmpty() || this.currentLabels.containsAll(labels))
return this;
else {
final Set<String> newLabels = new LinkedHashSet<>();
newLabels.addAll(this.currentLabels);
newLabels.addAll(labels);
return new ImmutablePath(this.previousPath, this.currentObject, newLabels);
}
}
@Override
public Path retract(final Set<String> labels) {
if (labels.isEmpty())
return this;
// get all the immutable path sections
final List<ImmutablePath> immutablePaths = new ArrayList<>();
ImmutablePath currentPath = this;
while (true) {
if (currentPath.isTail())
break;
immutablePaths.add(0, currentPath);
currentPath = currentPath.previousPath;
}
// build a new immutable path using the respective path sections that are not to be retracted
Path newPath = TAIL_PATH;
for (final ImmutablePath immutablePath : immutablePaths) {
final Set<String> temp = new LinkedHashSet<>(immutablePath.currentLabels);
temp.removeAll(labels);
if (!temp.isEmpty())
newPath = newPath.extend(immutablePath.currentObject, temp);
}
return newPath;
}
@Override
public <A> A get(final int index) {
int counter = this.size();
ImmutablePath currentPath = this;
while (true) {
if (index == --counter)
return (A) currentPath.currentObject;
currentPath = currentPath.previousPath;
}
}
@Override
public <A> A get(final Pop pop, final String label) {
if (Pop.mixed == pop) {
return this.get(label);
} else if (Pop.all == pop) {
// Recursively build the list to avoid building objects/labels collections.
final List<Object> list = new ArrayList<>();
ImmutablePath currentPath = this;
while (true) {
if (currentPath.isTail())
break;
else if (currentPath.currentLabels.contains(label))
list.add(0, currentPath.currentObject);
currentPath = currentPath.previousPath;
}
return (A) list;
} else if (Pop.last == pop) {
ImmutablePath currentPath = this;
while (true) {
if (currentPath.isTail())
throw Path.Exceptions.stepWithProvidedLabelDoesNotExist(label);
else if (currentPath.currentLabels.contains(label))
return (A) currentPath.currentObject;
else
currentPath = currentPath.previousPath;
}
} else { // Pop.first
A found = null;
ImmutablePath currentPath = this;
while (true) {
if (currentPath.isTail())
break;
else if (currentPath.currentLabels.contains(label))
found = (A) currentPath.currentObject;
currentPath = currentPath.previousPath;
}
if (null == found)
throw Path.Exceptions.stepWithProvidedLabelDoesNotExist(label);
return found;
}
}
@Override
public boolean hasLabel(final String label) {
ImmutablePath currentPath = this;
while (true) {
if (currentPath.isTail())
return false;
else if (currentPath.currentLabels.contains(label))
return true;
else
currentPath = currentPath.previousPath;
}
}
@Override
public List<Object> objects() {
final List<Object> objects = new ArrayList<>();
ImmutablePath currentPath = this;
while (true) {
if (currentPath.isTail())
break;
objects.add(0, currentPath.currentObject);
currentPath = currentPath.previousPath;
}
return Collections.unmodifiableList(objects);
}
@Override
public List<Set<String>> labels() {
final List<Set<String>> labels = new ArrayList<>();
ImmutablePath currentPath = this;
while (true) {
if (currentPath.isTail())
break;
labels.add(0, currentPath.currentLabels);
currentPath = currentPath.previousPath;
}
return Collections.unmodifiableList(labels);
}
@Override
public String toString() {
return this.objects().toString();
}
@Override
public int hashCode() {
// hashCode algorithm from AbstractList
int[] hashCodes = new int[this.size()];
int index = hashCodes.length - 1;
ImmutablePath currentPath = this;
while (true) {
if (currentPath.isTail())
break;
hashCodes[index] = currentPath.currentObject.hashCode();
currentPath = currentPath.previousPath;
index--;
}
int hashCode = 1;
for (final int hash : hashCodes) {
hashCode = hashCode * 31 + hash;
}
return hashCode;
}
@Override
public boolean equals(final Object other) {
if (!(other instanceof Path))
return false;
final Path otherPath = (Path) other;
int size = this.size();
if (otherPath.size() != size)
return false;
if (size > 0) {
ImmutablePath currentPath = this;
final List<Object> otherObjects = otherPath.objects();
final List<Set<String>> otherLabels = otherPath.labels();
for (int i = otherLabels.size() - 1; i >= 0; i--) {
if (currentPath.isTail())
return true;
else if (!currentPath.currentObject.equals(otherObjects.get(i)) ||
!currentPath.currentLabels.equals(otherLabels.get(i)))
return false;
else
currentPath = currentPath.previousPath;
}
}
return true;
}
@Override
public boolean popEquals(final Pop pop, final Object other) {
if (!(other instanceof Path))
return false;
final Path otherPath = (Path) other;
ImmutablePath currentPath = this;
while (true) {
if (currentPath.isTail())
break;
for (final String label : currentPath.currentLabels) {
if (!otherPath.hasLabel(label) || !this.get(pop, label).equals(otherPath.get(pop, label)))
return false;
}
currentPath = currentPath.previousPath;
}
return true;
}
@Override
public boolean isSimple() {
final Set<Object> objects = new HashSet<>();
ImmutablePath currentPath = this;
while (true) {
if (currentPath.isTail())
return true;
else if (objects.contains(currentPath.currentObject))
return false;
else {
objects.add(currentPath.currentObject);
currentPath = currentPath.previousPath;
}
}
}
}