blob: 1a916ca32ae110e6f73dba9c95cfece0f20c0f49 [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 org.apache.tinkerpop.gremlin.structure.Element;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
public class ImmutablePath implements Path, ImmutablePathImpl, Serializable, Cloneable {
private ImmutablePathImpl previousPath = TailPath.instance();
private Object currentObject;
private Set<String> currentLabels = new LinkedHashSet<>();
protected ImmutablePath() {
}
public static Path make() {
return TailPath.instance();
}
@SuppressWarnings("CloneDoesntCallSuperClone,CloneDoesntDeclareCloneNotSupportedException")
@Override
public ImmutablePath clone() {
return this;
}
private ImmutablePath(final ImmutablePathImpl previousPath, final Object currentObject, final Set<String> currentLabels) {
this.previousPath = previousPath;
this.currentObject = currentObject;
this.currentLabels.addAll(currentLabels);
}
@Override
public int size() {
return this.previousPath.size() + 1;
}
@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) {
final Set<String> temp = new LinkedHashSet<>();
temp.addAll(this.currentLabels);
temp.addAll(labels);
return new ImmutablePath(this.previousPath, this.currentObject, temp);
}
@Override
public <A> A get(final int index) {
return (this.size() - 1) == index ? (A) this.currentObject : this.previousPath.get(index);
}
@Override
public <A> A getSingleHead(final String label) {
// Recursively search for the single value to avoid building throwaway collections, and to stop looking when we
// find it.
A single;
// See if we have a value.
if (this.currentLabels.contains(label)) {
single = (A) this.currentObject;
} else {
// Look for a previous value.
single = this.previousPath.getSingleHead(label);
}
return single;
}
@Override
public <A> A getSingleTail(final String label) {
// Recursively search for the single value to avoid building throwaway collections, and to stop looking when we
// find it.
A single = this.previousPath.getSingleTail(label);
if (null == single) {
// See if we have a value.
if (this.currentLabels.contains(label)) {
single = (A) this.currentObject;
}
}
return single;
}
@Override
public <A> A get(final Pop pop, final String label) {
if (Pop.all == pop) {
// Recursively build the list to avoid building objects/labels collections.
final List<A> list = this.previousPath.get(Pop.all, label);
// Add our object, if our step labels match.
if (this.currentLabels.contains(label))
list.add((A) currentObject);
return (A) list;
} else {
// Delegate to the non-throwing, optimized head/tail calculations.
final A single = Pop.first == pop ? this.getSingleTail(label) : this.getSingleHead(label);
// Throw if we didn't find the label.
if (null == single)
throw Path.Exceptions.stepWithProvidedLabelDoesNotExist(label);
return single;
}
}
@Override
public boolean hasLabel(final String label) {
return this.currentLabels.contains(label) || this.previousPath.hasLabel(label);
}
@Override
public List<Object> objects() {
final List<Object> objectPath = new ArrayList<>(); // TODO: optimize
objectPath.addAll(this.previousPath.objects());
objectPath.add(this.currentObject);
return Collections.unmodifiableList(objectPath);
}
@Override
public List<Set<String>> labels() {
final List<Set<String>> labelPath = new ArrayList<>(); // TODO: optimize
labelPath.addAll(this.previousPath.labels());
labelPath.add(this.currentLabels);
return Collections.unmodifiableList(labelPath);
}
@Override
public String toString() {
return this.objects().toString();
}
@Override
public int hashCode() {
return this.objects().hashCode();
}
@Override
public boolean equals(final Object other) {
if (!(other instanceof Path))
return false;
final Path otherPath = (Path) other;
if (otherPath.size() != this.size())
return false;
for (int i = this.size() - 1; i >= 0; i--) {
if (!this.get(i).equals(otherPath.get(i)))
return false;
if (!this.labels().get(i).equals(otherPath.labels().get(i)))
return false;
}
return true;
}
private static class TailPath implements Path, ImmutablePathImpl {
private static final TailPath INSTANCE = new TailPath();
private TailPath() {
}
@Override
public int size() {
return 0;
}
@Override
public Path extend(final Object object, final Set<String> labels) {
return new ImmutablePath(TailPath.instance(), object, labels);
}
@Override
public Path extend(final Set<String> labels) {
throw new UnsupportedOperationException("A head path can not have labels added to it");
}
@Override
public <A> A get(final String label) {
throw Path.Exceptions.stepWithProvidedLabelDoesNotExist(label);
}
@Override
public <A> A get(final int index) {
return (A) Collections.emptyList().get(index);
}
@Override
public <A> A get(final Pop pop, final String label) {
return pop == Pop.all ? (A) new ArrayList<>() : null;
}
@Override
public <A> A getSingleHead(final String label) {
// Provide a null to bounce the search back to ImmutablePath.getSingleHead.
return null;
}
@Override
public <A> A getSingleTail(final String label) {
// Provide a null to bounce the search back to ImmutablePath.getSingleTail.
return null;
}
@Override
public boolean hasLabel(final String label) {
return false;
}
@Override
public List<Object> objects() {
return Collections.emptyList();
}
@Override
public List<Set<String>> labels() {
return Collections.emptyList();
}
@Override
public boolean isSimple() {
return true;
}
@SuppressWarnings("CloneDoesntCallSuperClone,CloneDoesntDeclareCloneNotSupportedException")
@Override
public TailPath clone() {
return this;
}
public static TailPath instance() {
return INSTANCE;
}
@Override
public String toString() {
return Collections.emptyList().toString();
}
@Override
public int hashCode() {
return Collections.emptyList().hashCode();
}
@Override
public boolean equals(final Object other) {
return other instanceof Path && ((Path) other).size() == 0;
}
}
}