blob: c4bc347256ca651f3912159e589b6d682a7d0e41 [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;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.javatuples.Pair;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* A Path denotes a particular walk through a {@link Graph} as defined by a {@link Traversal}.
* In abstraction, any Path implementation maintains two lists: a list of sets of labels and a list of objects.
* The list of labels are the labels of the steps traversed. The list of objects are the objects traversed.
*
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
public interface Path extends Cloneable, Iterable<Object> {
/**
* Get the number of step in the path.
*
* @return the size of the path
*/
public default int size() {
return this.objects().size();
}
/**
* Add a new step to the path with an object and any number of associated labels.
*
* @param object the new head of the path
* @param labels the labels at the head of the path
* @return the extended path
*/
public Path extend(final Object object, final Set<String> labels);
/**
* Add labels to the head of the path.
*
* @param labels the labels at the head of the path
* @return the path with added labels
*/
public Path extend(final Set<String> labels);
/**
* Remove labels from path.
*
* @param labels the labels to remove
* @return the path with removed labels
*/
public Path retract(final Set<String> labels);
/**
* Get the object associated with the particular label of the path.
* If the path as multiple labels of the type, then return a {@link List} of those objects.
*
* @param label the label of the path
* @param <A> the type of the object associated with the label
* @return the object associated with the label of the path
* @throws IllegalArgumentException if the path does not contain the label
*/
public default <A> A get(final String label) throws IllegalArgumentException {
final List<Object> objects = this.objects();
final List<Set<String>> labels = this.labels();
Object object = null;
for (int i = 0; i < labels.size(); i++) {
if (labels.get(i).contains(label)) {
if (null == object) {
object = objects.get(i);
} else if (object instanceof List) {
((List) object).add(objects.get(i));
} else {
final List list = new ArrayList(2);
list.add(object);
list.add(objects.get(i));
object = list;
}
}
}
if (null == object)
throw Path.Exceptions.stepWithProvidedLabelDoesNotExist(label);
return (A) object;
}
/**
* Pop the object(s) associated with the label of the path.
*
* @param pop first for least recent, last for most recent, and all for all in a list
* @param label the label of the path
* @param <A> the type of the object associated with the label
* @return the object associated with the label of the path
* @throws IllegalArgumentException if the path does not contain the label
*/
public default <A> A get(final Pop pop, final String label) throws IllegalArgumentException {
if (Pop.all == pop) {
if (this.hasLabel(label)) {
final Object object = this.get(label);
if (object instanceof List)
return (A) object;
else
return (A) Collections.singletonList(object);
} else {
return (A) Collections.emptyList();
}
} else {
final Object object = this.get(label);
if (object instanceof List) {
return Pop.last == pop ? ((List<A>) object).get(((List) object).size() - 1) : ((List<A>) object).get(0);
} else
return (A) object;
}
}
/**
* Get the object associated with the specified index into the path.
*
* @param index the index of the path
* @param <A> the type of the object associated with the index
* @return the object associated with the index of the path
*/
public default <A> A get(final int index) {
return (A) this.objects().get(index);
}
/**
* Return true if the path has the specified label, else return false.
*
* @param label the label to search for
* @return true if the label exists in the path
*/
public default boolean hasLabel(final String label) {
return this.labels().stream().filter(labels -> labels.contains(label)).findAny().isPresent();
}
/**
* An ordered list of the objects in the path.
*
* @return the objects of the path
*/
public List<Object> objects();
/**
* An ordered list of the labels associated with the path
* The set of labels for a particular step are ordered by the order in which {@link Path#extend(Object, Set)} was called.
*
* @return the labels of the path
*/
public List<Set<String>> labels();
@SuppressWarnings("CloneDoesntDeclareCloneNotSupportedException")
public Path clone();
/**
* Determines whether the path is a simple or not.
* A simple path has no cycles and thus, no repeated objects.
*
* @return Whether the path is simple or not
*/
public default boolean isSimple() {
final List<Object> objects = this.objects();
for (int i = 0; i < objects.size() - 1; i++) {
for (int j = i + 1; j < objects.size(); j++) {
if (objects.get(i).equals(objects.get(j)))
return false;
}
}
return true;
}
public default Iterator<Object> iterator() {
return this.objects().iterator();
}
public default void forEach(final BiConsumer<Object, Set<String>> consumer) {
final List<Object> objects = this.objects();
final List<Set<String>> labels = this.labels();
for (int i = 0; i < objects.size(); i++) {
consumer.accept(objects.get(i), labels.get(i));
}
}
public default Stream<Pair<Object, Set<String>>> stream() {
final List<Set<String>> labels = this.labels();
final List<Object> objects = this.objects();
return IntStream.range(0, this.size()).mapToObj(i -> Pair.with(objects.get(i), labels.get(i)));
}
public default boolean popEquals(final Pop pop, final Object other) {
if (!(other instanceof Path))
return false;
final Path otherPath = (Path) other;
return !this.labels().stream().
flatMap(Set::stream).
filter(label -> !otherPath.hasLabel(label) || !otherPath.get(pop, label).equals(this.get(pop, label))).
findAny().
isPresent();
}
public static class Exceptions {
public static IllegalArgumentException stepWithProvidedLabelDoesNotExist(final String label) {
return new IllegalArgumentException("The step with label " + label + " does not exist");
}
}
}