blob: 5dd4ff54aceb3d3a2531e4f6dc2860f7006b5597 [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.structure;
import org.apache.commons.configuration.Configuration;
import org.apache.tinkerpop.gremlin.process.computer.GraphComputer;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.structure.io.GraphReader;
import org.apache.tinkerpop.gremlin.structure.io.GraphWriter;
import org.apache.tinkerpop.gremlin.structure.io.Io;
import org.apache.tinkerpop.gremlin.structure.io.IoRegistry;
import org.apache.tinkerpop.gremlin.structure.util.FeatureDescriptor;
import org.apache.tinkerpop.gremlin.structure.util.GraphFactory;
import org.apache.tinkerpop.gremlin.structure.util.Host;
import org.javatuples.Pair;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* A {@link Graph} is a container object for a collection of {@link Vertex}, {@link Edge}, {@link VertexProperty},
* and {@link Property} objects.
*
* @author Marko A. Rodriguez (http://markorodriguez.com)
* @author Stephen Mallette (http://stephen.genoprime.com)
* @author Pieter Martin
*/
public interface Graph extends AutoCloseable, Host {
/**
* Configuration key used by {@link GraphFactory}} to determine which graph to instantiate.
*/
public static final String GRAPH = "gremlin.graph";
/**
* This should only be used by providers to create keys, labels, etc. in a namespace safe from users.
* Users are not allowed to generate property keys, step labels, etc. that are key'd "hidden".
*/
public static class Hidden {
/**
* The prefix to denote that a key is a hidden key.
*/
private static final String HIDDEN_PREFIX = "~";
private static final int HIDDEN_PREFIX_LENGTH = HIDDEN_PREFIX.length();
/**
* Turn the provided key into a hidden key. If the key is already a hidden key, return key.
*
* @param key The key to make a hidden key
* @return The hidden key
*/
public static String hide(final String key) {
return isHidden(key) ? key : HIDDEN_PREFIX.concat(key);
}
/**
* Turn the provided hidden key into an non-hidden key. If the key is not a hidden key, return key.
*
* @param key The hidden key
* @return The non-hidden representation of the key
*/
public static String unHide(final String key) {
return isHidden(key) ? key.substring(HIDDEN_PREFIX_LENGTH) : key;
}
/**
* Determines whether the provided key is a hidden key or not.
*
* @param key The key to check for hidden status
* @return Whether the provided key is a hidden key or not
*/
public static boolean isHidden(final String key) {
return key.startsWith(HIDDEN_PREFIX);
}
}
/**
* Add a {@link Vertex} to the graph given an optional series of key/value pairs. These key/values
* must be provided in an even number where the odd numbered arguments are {@link String} property keys and the
* even numbered arguments are the related property values.
*
* @param keyValues The key/value pairs to turn into vertex properties
* @return The newly created vertex
*/
public Vertex addVertex(final Object... keyValues);
/**
* Add a {@link Vertex} to the graph with provided vertex label.
*
* @param label the label of the vertex
* @return The newly created labeled vertex
*/
public default Vertex addVertex(final String label) {
return this.addVertex(T.label, label);
}
/**
* Declare the {@link GraphComputer} to use for OLAP operations on the graph.
* If the graph does not support graph computer then an {@code UnsupportedOperationException} is thrown.
*
* @param graphComputerClass The graph computer class to use.
* @return A graph computer for processing this graph
* @throws IllegalArgumentException if the provided {@link GraphComputer} class is not supported.
*/
public <C extends GraphComputer> C compute(final Class<C> graphComputerClass) throws IllegalArgumentException;
/**
* Generate a {@link GraphComputer} using the default engine of the underlying graph system.
* This is a shorthand method for the more involved method that uses {@link Graph#compute(Class)}.
*
* @return A default graph computer
* @throws IllegalArgumentException if there is no default graph computer
*/
public GraphComputer compute() throws IllegalArgumentException;
/**
* Generate a {@link TraversalSource} using the specified {@code TraversalSource} class.
* The reusable {@link TraversalSource} provides methods for spawning {@link Traversal} instances.
*
* @param traversalSourceClass The traversal source class
* @param <C> The traversal source class
*/
public default <C extends TraversalSource> C traversal(final Class<C> traversalSourceClass) {
try {
return traversalSourceClass.getConstructor(Graph.class).newInstance(this);
} catch (final Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
/**
* Generate a reusable {@link GraphTraversalSource} instance.
* The {@link GraphTraversalSource} provides methods for creating {@link GraphTraversal} instances.
*
* @return A graph traversal source
*/
public default GraphTraversalSource traversal() {
return new GraphTraversalSource(this);
}
/**
* Get the {@link Vertex} objects in this graph with the provided vertex ids or {@link Vertex} objects themselves.
* If no ids are provided, get all vertices. Note that a vertex identifier does not need to correspond to the
* actual id used in the graph. It needs to be a bit more flexible than that in that given the
* {@link Graph.Features} around id support, multiple arguments might be applicable here.
* <p/>
* If the graph return {@code true} for {@link Features.VertexFeatures#supportsNumericIds()} then it should support
* filters as with:
* <ul>
* <li>g.vertices(v)</li>
* <li>g.vertices(v.id())</li>
* <li>g.vertices(1)</li>
* <li>g.vertices(1L)</li>
* <li>g.vertices(1.0d)</li>
* <li>g.vertices(1.0f)</li>
* <li>g.vertices("1")</li>
* </ul>
* <p/>
* If the graph return {@code true} for {@link Features.VertexFeatures#supportsCustomIds()} ()} then it should support
* filters as with:
* <ul>
* <li>g.vertices(v)</li>
* <li>g.vertices(v.id())</li>
* <li>g.vertices(v.id().toString())</li>
* </ul>
* <p/>
* If the graph return {@code true} for {@link Features.VertexFeatures#supportsAnyIds()} ()} then it should support
* filters as with:
* <ul>
* <li>g.vertices(v)</li>
* <li>g.vertices(v.id())</li>
* </ul>
* <p/>  
* If the graph return {@code true} for {@link Features.VertexFeatures#supportsStringIds()} ()} then it should support
* filters as with:
* <ul>
* <li>g.vertices(v)</li>
* <li>g.vertices(v.id().toString())</li>
* <li>g.vertices("id")</li>
* </ul>
* <p/>
* If the graph return {@code true} for {@link Features.EdgeFeatures#supportsStringIds()} ()} then it should support
* filters as with:
* <ul>
* <li>g.vertices(v)</li>
* <li>g.vertices(v.id().toString())</li>
* <li>g.vertices("id")</li>
* </ul>
*
* @param vertexIds the ids of the vertices to get
* @return an {@link Iterator} of vertices that match the provided vertex ids
*/
public Iterator<Vertex> vertices(final Object... vertexIds);
/**
* Get the {@link Edge} objects in this graph with the provided edge ids or {@link Edge} objects. If no ids are
* provided, get all edges. Note that an edge identifier does not need to correspond to the actual id used in the
* graph. It needs to be a bit more flexible than that in that given the {@link Graph.Features} around id support,
* multiple arguments might be applicable here.
* <p/>
* If the graph return {@code true} for {@link Features.EdgeFeatures#supportsNumericIds()} then it should support
* filters as with:
* <ul>
* <li>g.edges(e)</li>
* <li>g.edges(e.id())</li>
* <li>g.edges(1)</li>
* <li>g.edges(1L)</li>
* <li>g.edges(1.0d)</li>
* <li>g.edges(1.0f)</li>
* <li>g.edges("1")</li>
* </ul>
* <p/>
* If the graph return {@code true} for {@link Features.EdgeFeatures#supportsCustomIds()} ()} then it should support
* filters as with:
* <ul>
* <li>g.edges(e)</li>
* <li>g.edges(e.id())</li>
* <li>g.edges(e.id().toString())</li>
* </ul>
* <p/>
* If the graph return {@code true} for {@link Features.EdgeFeatures#supportsAnyIds()} ()} then it should support
* filters as with:
* <ul>
* <li>g.edges(e)</li>
* <li>g.edges(e.id())</li>
* </ul>
* <p/>
* If the graph return {@code true} for {@link Features.EdgeFeatures#supportsStringIds()} ()} then it should support
* filters as with:
* <ul>
* <li>g.edges(e)</li>
* <li>g.edges(e.id().toString())</li>
* <li>g.edges("id")</li>
* </ul>
*
* @param edgeIds the ids of the edges to get
* @return an {@link Iterator} of edges that match the provided edge ids
*/
public Iterator<Edge> edges(final Object... edgeIds);
/**
* Configure and control the transactions for those graphs that support this feature. Note that this method does
* not indicate the creation of a "transaction" object. A {@link Transaction} in the TinkerPop context is a
* transaction "factory" or "controller" that helps manage transactions owned by the underlying graph database.
*/
public Transaction tx();
/**
* Closing a {@code Graph} is equivalent to "shutdown" and implies that no further operations can be executed on
* the instance. Users should consult the documentation of the underlying graph database implementation for what
* this "shutdown" will mean in general and, if supported, how open transactions are handled. It will typically
* be the end user's responsibility to synchronize the thread that calls {@code close()} with other threads that
* are accessing open transactions. In other words, be sure that all work performed on the {@code Graph} instance
* is complete prior to calling this method.
* <p/>
* TinkerPop does not enforce any particular semantics with respect to "shutdown". It is up to the graph provider
* to decide what this method will do.
*/
@Override
void close() throws Exception;
/**
* Construct a particular {@link Io} implementation for reading and writing the {@code Graph} and other data.
* End-users will "select" the {@link Io} implementation that they want to use by supplying the
* {@link Io.Builder} that constructs it. In this way, {@code Graph} vendors can supply their {@link IoRegistry}
* to that builder thus allowing for custom serializers to be auto-configured into the {@link Io} instance.
* Registering custom serializers is particularly useful for those graphs that have complex types for
* {@link Element} identifiers.
* </p>
* For those graphs that do not need to register any custom serializers, the default implementation should suffice.
* If the default is overridden, take care to register the current graph via the
* {@link org.apache.tinkerpop.gremlin.structure.io.Io.Builder#graph(Graph)} method.
*
* @deprecated As of release 3.4.0, partially replaced by {@link GraphTraversalSource#io(String)}. Notice
* {@link GraphTraversalSource#io(String)} doesn't support read operation from {@code java.io.InputStream}
* or write operation to {@code java.io.OutputStream}. Thus for readers or writers which need this functionality
* are safe to use this deprecated method. There is no intention to remove this method unless all the
* functionality is replaced by the `io` step of {@link GraphTraversalSource}.
*/
@Deprecated
public default <I extends Io> I io(final Io.Builder<I> builder) {
return (I) builder.graph(this).create();
}
/**
* A collection of global {@link Variables} associated with the graph.
* Variables are used for storing metadata about the graph.
*
* @return The variables associated with this graph
*/
public Variables variables();
/**
* Get the {@code Configuration} associated with the construction of this graph. Whatever configuration was
* passed to {@link GraphFactory#open(Configuration)} is what should be returned by this method.
*
* @return the configuration used during graph construction.
*/
public Configuration configuration();
/**
* Graph variables are a set of key/value pairs associated with the graph. The keys are String and the values
* are Objects.
*/
public interface Variables {
/**
* Keys set for the available variables.
*/
public Set<String> keys();
/**
* Gets a variable.
*/
public <R> Optional<R> get(final String key);
/**
* Sets a variable.
*/
public void set(final String key, Object value);
/**
* Removes a variable.
*/
public void remove(final String key);
/**
* Gets the variables of the {@link Graph} as a {@code Map}.
*/
public default Map<String, Object> asMap() {
final Map<String, Object> map = keys().stream()
.map(key -> Pair.with(key, get(key).get()))
.collect(Collectors.toMap(Pair::getValue0, Pair::getValue1));
return Collections.unmodifiableMap(map);
}
public static class Exceptions {
private Exceptions() {
}
public static IllegalArgumentException variableKeyCanNotBeEmpty() {
return new IllegalArgumentException("Graph variable key can not be the empty string");
}
public static IllegalArgumentException variableKeyCanNotBeNull() {
return new IllegalArgumentException("Graph variable key can not be null");
}
public static IllegalArgumentException variableValueCanNotBeNull() {
return new IllegalArgumentException("Graph variable value can not be null");
}
public static UnsupportedOperationException dataTypeOfVariableValueNotSupported(final Object val) {
return dataTypeOfVariableValueNotSupported(val, null);
}
public static UnsupportedOperationException dataTypeOfVariableValueNotSupported(final Object val, final Exception rootCause) {
return new UnsupportedOperationException(String.format("Graph variable value [%s] is of type %s is not supported", val, val.getClass()), rootCause);
}
}
}
/**
* Gets the {@link Features} exposed by the underlying {@code Graph} implementation.
*/
public default Features features() {
return new Features() {
};
}
/**
* An interface that represents the capabilities of a {@code Graph} implementation. By default all methods
* of features return {@code true} and it is up to implementers to disable feature they don't support. Users
* should check features prior to using various functions of TinkerPop to help ensure code portability
* across implementations. For example, a common usage would be to check if a graph supports transactions prior
* to calling the commit method on {@link #tx()}.
* <p/>
* As an additional notice to Graph Providers, feature methods will be used by the test suite to determine which
* tests will be ignored and which will be executed, therefore proper setting of these features is essential to
* maximizing the amount of testing performed by the suite. Further note, that these methods may be called by the
* TinkerPop core code to determine what operations may be appropriately executed which will have impact on
* features utilized by users.
*/
public interface Features {
/**
* Gets the features related to "graph" operation.
*/
public default GraphFeatures graph() {
return new GraphFeatures() {
};
}
/**
* Gets the features related to "vertex" operation.
*/
public default VertexFeatures vertex() {
return new VertexFeatures() {
};
}
/**
* Gets the features related to "edge" operation.
*/
public default EdgeFeatures edge() {
return new EdgeFeatures() {
};
}
/**
* Features specific to a operations of a "graph".
*/
public interface GraphFeatures extends FeatureSet {
public static final String FEATURE_COMPUTER = "Computer";
public static final String FEATURE_TRANSACTIONS = "Transactions";
public static final String FEATURE_PERSISTENCE = "Persistence";
public static final String FEATURE_THREADED_TRANSACTIONS = "ThreadedTransactions";
public static final String FEATURE_CONCURRENT_ACCESS = "ConcurrentAccess";
public static final String FEATURE_IO_READ = "IoRead";
public static final String FEATURE_IO_WRITE = "IoWrite";
/**
* Determines if the {@code Graph} implementation supports {@link GraphComputer} based processing.
*/
@FeatureDescriptor(name = FEATURE_COMPUTER)
public default boolean supportsComputer() {
return true;
}
/**
* Determines if the {@code Graph} implementation supports persisting it's contents natively to disk.
* This feature does not refer to every graph's ability to write to disk via the Gremlin IO packages
* (.e.g. GraphML), unless the graph natively persists to disk via those options somehow. For example,
* TinkerGraph does not support this feature as it is a pure in-sideEffects graph.
*/
@FeatureDescriptor(name = FEATURE_PERSISTENCE)
public default boolean supportsPersistence() {
return true;
}
/**
* Determines if the {@code Graph} implementation supports more than one connection to the same instance
* at the same time. For example, Neo4j embedded does not support this feature because concurrent
* access to the same database files by multiple instances is not possible. However, Neo4j HA could
* support this feature as each new {@code Graph} instance coordinates with the Neo4j cluster allowing
* multiple instances to operate on the same database.
*/
@FeatureDescriptor(name = FEATURE_CONCURRENT_ACCESS)
public default boolean supportsConcurrentAccess() {
return true;
}
/**
* Determines if the {@code Graph} implementations supports transactions.
*/
@FeatureDescriptor(name = FEATURE_TRANSACTIONS)
public default boolean supportsTransactions() {
return true;
}
/**
* Determines if the {@code Graph} implementation supports threaded transactions which allow a transaction
* to be executed across multiple threads via {@link Transaction#createThreadedTx()}.
*/
@FeatureDescriptor(name = FEATURE_THREADED_TRANSACTIONS)
public default boolean supportsThreadedTransactions() {
return true;
}
/**
* Determines if the {@code Graph} implementations supports read operations as executed with the
* {@link GraphTraversalSource#io(String)} step. Graph implementations will generally support this by
* default as any graph that can support direct mutation through the Structure API will by default
* accept data from the standard TinkerPop {@link GraphReader} implementations. However, some graphs like
* {@code HadoopGraph} don't accept direct mutations but can still do reads from that {@code io()} step.
*/
@FeatureDescriptor(name = FEATURE_IO_READ)
public default boolean supportsIoRead() {
return true;
}
/**
* Determines if the {@code Graph} implementations supports write operations as executed with the
* {@link GraphTraversalSource#io(String)} step. Graph implementations will generally support this by
* default given the standard TinkerPop {@link GraphWriter} implementations. However, some graphs like
* {@code HadoopGraph} will use a different approach to handle writes.
*/
@FeatureDescriptor(name = FEATURE_IO_WRITE)
public default boolean supportsIoWrite() {
return true;
}
/**
* Gets the features related to "graph sideEffects" operation.
*/
public default VariableFeatures variables() {
return new VariableFeatures() {
};
}
}
/**
* Features that are related to {@link Vertex} operations.
*/
public interface VertexFeatures extends ElementFeatures {
public static final String FEATURE_ADD_VERTICES = "AddVertices";
public static final String FEATURE_MULTI_PROPERTIES = "MultiProperties";
public static final String FEATURE_DUPLICATE_MULTI_PROPERTIES = "DuplicateMultiProperties";
public static final String FEATURE_META_PROPERTIES = "MetaProperties";
public static final String FEATURE_REMOVE_VERTICES = "RemoveVertices";
public static final String FEATURE_UPSERT = "Upsert";
/**
* Gets the {@link VertexProperty.Cardinality} for a key. By default, this method will return
* {@link VertexProperty.Cardinality#list}. Implementations that employ a schema can consult it to
* determine the {@link VertexProperty.Cardinality}. Those that do no have a schema can return their
* default {@link VertexProperty.Cardinality} for every key.
* <p/>
* Note that this method is primarily used by TinkerPop for internal usage and may not be suitable to
* reliably determine the cardinality of a key. For some implementation it may offer little more than a
* hint on the actual cardinality. Generally speaking it is likely best to drop down to the API of the
* {@link Graph} implementation for any schema related queries.
*/
public default VertexProperty.Cardinality getCardinality(final String key) {
return VertexProperty.Cardinality.list;
}
/**
* Determines if a {@link Vertex} can be added to the {@code Graph}.
*/
@FeatureDescriptor(name = FEATURE_ADD_VERTICES)
public default boolean supportsAddVertices() {
return true;
}
/**
* Determines if a {@link Vertex} can be removed from the {@code Graph}.
*/
@FeatureDescriptor(name = FEATURE_REMOVE_VERTICES)
public default boolean supportsRemoveVertices() {
return true;
}
/**
* Determines if a {@link Vertex} can support multiple properties with the same key.
*/
@FeatureDescriptor(name = FEATURE_MULTI_PROPERTIES)
public default boolean supportsMultiProperties() {
return true;
}
/**
* Determines if a {@link Vertex} can support non-unique values on the same key. For this value to be
* {@code true}, then {@link #supportsMetaProperties()} must also return true. By default this method,
* just returns what {@link #supportsMultiProperties()} returns.
*/
@FeatureDescriptor(name = FEATURE_DUPLICATE_MULTI_PROPERTIES)
public default boolean supportsDuplicateMultiProperties() {
return supportsMultiProperties();
}
/**
* Determines if a {@link Vertex} can support properties on vertex properties. It is assumed that a
* graph will support all the same data types for meta-properties that are supported for regular
* properties.
*/
@FeatureDescriptor(name = FEATURE_META_PROPERTIES)
public default boolean supportsMetaProperties() {
return true;
}
/**
* Determines if the {@code Graph} implementation uses upsert functionality as opposed to insert
* functionality for {@link #addVertex(String)}. This feature gives graph providers some flexibility as
* to how graph mutations are treated. For graph providers, testing of this feature (as far as TinkerPop
* is concerned) only covers graphs that can support user supplied identifiers as there is no other way
* for TinkerPop to know what aspect of a vertex is unique to appropriately apply assertions. Graph
* providers, especially those who support schema features, may have other methods for uniquely identifying
* a vertex and should therefore resort to their own body of tests to validate this feature.
*/
@FeatureDescriptor(name = FEATURE_UPSERT)
public default boolean supportsUpsert() {
return false;
}
/**
* Gets features related to "properties" on a {@link Vertex}.
*/
public default VertexPropertyFeatures properties() {
return new VertexPropertyFeatures() {
};
}
}
/**
* Features that are related to {@link Edge} operations.
*/
public interface EdgeFeatures extends ElementFeatures {
public static final String FEATURE_ADD_EDGES = "AddEdges";
public static final String FEATURE_REMOVE_EDGES = "RemoveEdges";
public static final String FEATURE_UPSERT = "Upsert";
/**
* Determines if an {@link Edge} can be added to a {@code Vertex}.
*/
@FeatureDescriptor(name = FEATURE_ADD_EDGES)
public default boolean supportsAddEdges() {
return true;
}
/**
* Determines if an {@link Edge} can be removed from a {@code Vertex}.
*/
@FeatureDescriptor(name = FEATURE_REMOVE_EDGES)
public default boolean supportsRemoveEdges() {
return true;
}
/**
* Determines if the {@code Graph} implementation uses upsert functionality as opposed to insert
* functionality for {@link Vertex#addEdge(String, Vertex, Object...)}. This feature gives graph providers
* some flexibility as to how graph mutations are treated. For graph providers, testing of this feature
* (as far as TinkerPop is concerned) only covers graphs that can support user supplied identifiers as
* there is no other way for TinkerPop to know what aspect of a edge is unique to appropriately apply
* assertions. Graph providers, especially those who support schema features, may have other methods for
* uniquely identifying a edge and should therefore resort to their own body of tests to validate this
* feature.
*/
@FeatureDescriptor(name = FEATURE_UPSERT)
public default boolean supportsUpsert() {
return false;
}
/**
* Gets features related to "properties" on an {@link Edge}.
*/
public default EdgePropertyFeatures properties() {
return new EdgePropertyFeatures() {
};
}
}
/**
* Features that are related to {@link Element} objects. This is a base interface.
*/
public interface ElementFeatures extends FeatureSet {
public static final String FEATURE_USER_SUPPLIED_IDS = "UserSuppliedIds";
public static final String FEATURE_NUMERIC_IDS = "NumericIds";
public static final String FEATURE_STRING_IDS = "StringIds";
public static final String FEATURE_UUID_IDS = "UuidIds";
public static final String FEATURE_CUSTOM_IDS = "CustomIds";
public static final String FEATURE_ANY_IDS = "AnyIds";
public static final String FEATURE_ADD_PROPERTY = "AddProperty";
public static final String FEATURE_REMOVE_PROPERTY = "RemoveProperty";
/**
* Determines if an {@link Element} allows properties to be added. This feature is set independently from
* supporting "data types" and refers to support of calls to {@link Element#property(String, Object)}.
*/
@FeatureDescriptor(name = FEATURE_ADD_PROPERTY)
public default boolean supportsAddProperty() {
return true;
}
/**
* Determines if an {@link Element} allows properties to be removed.
*/
@FeatureDescriptor(name = FEATURE_REMOVE_PROPERTY)
public default boolean supportsRemoveProperty() {
return true;
}
/**
* Determines if an {@link Element} can have a user defined identifier. Implementations that do not support
* this feature will be expected to auto-generate unique identifiers. In other words, if the {@link Graph}
* allows {@code graph.addVertex(id,x)} to work and thus set the identifier of the newly added
* {@link Vertex} to the value of {@code x} then this feature should return true. In this case, {@code x}
* is assumed to be an identifier data type that the {@link Graph} will accept.
*/
@FeatureDescriptor(name = FEATURE_USER_SUPPLIED_IDS)
public default boolean supportsUserSuppliedIds() {
return true;
}
/**
* Determines if an {@link Element} has numeric identifiers as their internal representation. In other
* words, if the value returned from {@link Element#id()} is a numeric value then this method
* should be return {@code true}.
* <p/>
* Note that this feature is most generally used for determining the appropriate tests to execute in the
* Gremlin Test Suite.
*/
@FeatureDescriptor(name = FEATURE_NUMERIC_IDS)
public default boolean supportsNumericIds() {
return true;
}
/**
* Determines if an {@link Element} has string identifiers as their internal representation. In other
* words, if the value returned from {@link Element#id()} is a string value then this method
* should be return {@code true}.
* <p/>
* Note that this feature is most generally used for determining the appropriate tests to execute in the
* Gremlin Test Suite.
*/
@FeatureDescriptor(name = FEATURE_STRING_IDS)
public default boolean supportsStringIds() {
return true;
}
/**
* Determines if an {@link Element} has UUID identifiers as their internal representation. In other
* words, if the value returned from {@link Element#id()} is a {@link UUID} value then this method
* should be return {@code true}.
* <p/>
* Note that this feature is most generally used for determining the appropriate tests to execute in the
* Gremlin Test Suite.
*/
@FeatureDescriptor(name = FEATURE_UUID_IDS)
public default boolean supportsUuidIds() {
return true;
}
/**
* Determines if an {@link Element} has a specific custom object as their internal representation.
* In other words, if the value returned from {@link Element#id()} is a type defined by the graph
* implementations, such as OrientDB's {@code Rid}, then this method should be return {@code true}.
* <p/>
* Note that this feature is most generally used for determining the appropriate tests to execute in the
* Gremlin Test Suite.
*/
@FeatureDescriptor(name = FEATURE_CUSTOM_IDS)
public default boolean supportsCustomIds() {
return true;
}
/**
* Determines if an {@link Element} any Java object is a suitable identifier. TinkerGraph is a good
* example of a {@link Graph} that can support this feature, as it can use any {@link Object} as
* a value for the identifier.
* <p/>
* Note that this feature is most generally used for determining the appropriate tests to execute in the
* Gremlin Test Suite. This setting should only return {@code true} if {@link #supportsUserSuppliedIds()}
* is {@code true}.
*/
@FeatureDescriptor(name = FEATURE_ANY_IDS)
public default boolean supportsAnyIds() {
return true;
}
/**
* Determines if an identifier will be accepted by the {@link Graph}. This check is different than
* what identifier internally supports as defined in methods like {@link #supportsNumericIds()}. Those
* refer to internal representation of the identifier. A {@link Graph} may accept an identifier that
* is not of those types and internally transform it to a native representation.
* <p/>
* Note that this method only applies if {@link #supportsUserSuppliedIds()} is {@code true}. Those that
* return {@code false} for that method can immediately return false for this one as it allows no ids
* of any type (it generates them all).
* <p/>
* The default implementation will immediately return {@code false} if {@link #supportsUserSuppliedIds()}
* is {@code false}. If custom identifiers are supported then it will throw an exception. Those that
* return {@code true} for {@link #supportsCustomIds()} should override this method. If
* {@link #supportsAnyIds()} is {@code true} then the identifier will immediately be allowed. Finally,
* if any of the other types are supported, they will be typed checked against the class of the supplied
* identifier.
*/
public default boolean willAllowId(final Object id) {
if (!supportsUserSuppliedIds()) return false;
if (supportsCustomIds())
throw new UnsupportedOperationException("The default implementation is not capable of validating custom ids - please override");
return supportsAnyIds() || (supportsStringIds() && id instanceof String)
|| (supportsNumericIds() && id instanceof Number) || (supportsUuidIds() && id instanceof UUID);
}
}
/**
* Features that are related to {@link Vertex} {@link Property} objects.
*/
public interface VertexPropertyFeatures extends PropertyFeatures {
public static final String FEATURE_REMOVE_PROPERTY = "RemoveProperty";
public static final String FEATURE_USER_SUPPLIED_IDS = "UserSuppliedIds";
public static final String FEATURE_NUMERIC_IDS = "NumericIds";
public static final String FEATURE_STRING_IDS = "StringIds";
public static final String FEATURE_UUID_IDS = "UuidIds";
public static final String FEATURE_CUSTOM_IDS = "CustomIds";
public static final String FEATURE_ANY_IDS = "AnyIds";
/**
* Determines if a {@link VertexProperty} allows properties to be removed.
*/
@FeatureDescriptor(name = FEATURE_REMOVE_PROPERTY)
public default boolean supportsRemoveProperty() {
return true;
}
/**
* Determines if a {@link VertexProperty} allows an identifier to be assigned to it.
*/
@FeatureDescriptor(name = FEATURE_USER_SUPPLIED_IDS)
public default boolean supportsUserSuppliedIds() {
return true;
}
/**
* Determines if an {@link VertexProperty} has numeric identifiers as their internal representation.
*/
@FeatureDescriptor(name = FEATURE_NUMERIC_IDS)
public default boolean supportsNumericIds() {
return true;
}
/**
* Determines if an {@link VertexProperty} has string identifiers as their internal representation.
*/
@FeatureDescriptor(name = FEATURE_STRING_IDS)
public default boolean supportsStringIds() {
return true;
}
/**
* Determines if an {@link VertexProperty} has UUID identifiers as their internal representation.
*/
@FeatureDescriptor(name = FEATURE_UUID_IDS)
public default boolean supportsUuidIds() {
return true;
}
/**
* Determines if an {@link VertexProperty} has a specific custom object as their internal representation.
*/
@FeatureDescriptor(name = FEATURE_CUSTOM_IDS)
public default boolean supportsCustomIds() {
return true;
}
/**
* Determines if an {@link VertexProperty} any Java object is a suitable identifier. Note that this
* setting can only return true if {@link #supportsUserSuppliedIds()} is true.
*/
@FeatureDescriptor(name = FEATURE_ANY_IDS)
public default boolean supportsAnyIds() {
return true;
}
/**
* Determines if an identifier will be accepted by the {@link Graph}. This check is different than
* what identifier internally supports as defined in methods like {@link #supportsNumericIds()}. Those
* refer to internal representation of the identifier. A {@link Graph} may accept an identifier that
* is not of those types and internally transform it to a native representation.
* <p/>
* Note that this method only applies if {@link #supportsUserSuppliedIds()} is {@code true}. Those that
* return {@code false} for that method can immediately return false for this one as it allows no ids
* of any type (it generates them all).
* <p/>
* The default implementation will immediately return {@code false} if {@link #supportsUserSuppliedIds()}
* is {@code false}. If custom identifiers are supported then it will throw an exception. Those that
* return {@code true} for {@link #supportsCustomIds()} should override this method. If
* {@link #supportsAnyIds()} is {@code true} then the identifier will immediately be allowed. Finally,
* if any of the other types are supported, they will be typed checked against the class of the supplied
* identifier.
*/
public default boolean willAllowId(final Object id) {
if (!supportsUserSuppliedIds()) return false;
if (supportsCustomIds())
throw new UnsupportedOperationException("The default implementation is not capable of validating custom ids - please override");
return supportsAnyIds() || (supportsStringIds() && id instanceof String)
|| (supportsNumericIds() && id instanceof Number) || (supportsUuidIds() && id instanceof UUID);
}
}
/**
* Features that are related to {@link Edge} {@link Property} objects.
*/
public interface EdgePropertyFeatures extends PropertyFeatures {
}
/**
* A base interface for {@link Edge} or {@link Vertex} {@link Property} features.
*/
public interface PropertyFeatures extends DataTypeFeatures {
public static final String FEATURE_PROPERTIES = "Properties";
/**
* Determines if an {@link Element} allows for the processing of at least one data type defined by the
* features. In this case "processing" refers to at least "reading" the data type. If any of the
* features on {@link PropertyFeatures} is true then this value must be true.
*/
@FeatureDescriptor(name = FEATURE_PROPERTIES)
public default boolean supportsProperties() {
return supportsBooleanValues() || supportsByteValues() || supportsDoubleValues() || supportsFloatValues()
|| supportsIntegerValues() || supportsLongValues() || supportsMapValues()
|| supportsMixedListValues() || supportsSerializableValues()
|| supportsStringValues() || supportsUniformListValues() || supportsBooleanArrayValues()
|| supportsByteArrayValues() || supportsDoubleArrayValues() || supportsFloatArrayValues()
|| supportsIntegerArrayValues() || supportsLongArrayValues() || supportsStringArrayValues();
}
}
/**
* Features for {@link Graph.Variables}.
*/
public interface VariableFeatures extends DataTypeFeatures {
public static final String FEATURE_VARIABLES = "Variables";
/**
* If any of the features on {@link VariableFeatures} is {@code true} then this value must be {@code true}.
*/
@FeatureDescriptor(name = FEATURE_VARIABLES)
public default boolean supportsVariables() {
return supportsBooleanValues() || supportsByteValues() || supportsDoubleValues() || supportsFloatValues()
|| supportsIntegerValues() || supportsLongValues() || supportsMapValues()
|| supportsMixedListValues() || supportsSerializableValues()
|| supportsStringValues() || supportsUniformListValues() || supportsBooleanArrayValues()
|| supportsByteArrayValues() || supportsDoubleArrayValues() || supportsFloatArrayValues()
|| supportsIntegerArrayValues() || supportsLongArrayValues() || supportsStringArrayValues();
}
}
/**
* Base interface for features that relate to supporting different data types.
*/
public interface DataTypeFeatures extends FeatureSet {
public static final String FEATURE_BOOLEAN_VALUES = "BooleanValues";
public static final String FEATURE_BYTE_VALUES = "ByteValues";
public static final String FEATURE_DOUBLE_VALUES = "DoubleValues";
public static final String FEATURE_FLOAT_VALUES = "FloatValues";
public static final String FEATURE_INTEGER_VALUES = "IntegerValues";
public static final String FEATURE_LONG_VALUES = "LongValues";
public static final String FEATURE_MAP_VALUES = "MapValues";
public static final String FEATURE_MIXED_LIST_VALUES = "MixedListValues";
public static final String FEATURE_BOOLEAN_ARRAY_VALUES = "BooleanArrayValues";
public static final String FEATURE_BYTE_ARRAY_VALUES = "ByteArrayValues";
public static final String FEATURE_DOUBLE_ARRAY_VALUES = "DoubleArrayValues";
public static final String FEATURE_FLOAT_ARRAY_VALUES = "FloatArrayValues";
public static final String FEATURE_INTEGER_ARRAY_VALUES = "IntegerArrayValues";
public static final String FEATURE_LONG_ARRAY_VALUES = "LongArrayValues";
public static final String FEATURE_SERIALIZABLE_VALUES = "SerializableValues";
public static final String FEATURE_STRING_ARRAY_VALUES = "StringArrayValues";
public static final String FEATURE_STRING_VALUES = "StringValues";
public static final String FEATURE_UNIFORM_LIST_VALUES = "UniformListValues";
/**
* Supports setting of a boolean value.
*/
@FeatureDescriptor(name = FEATURE_BOOLEAN_VALUES)
public default boolean supportsBooleanValues() {
return true;
}
/**
* Supports setting of a byte value.
*/
@FeatureDescriptor(name = FEATURE_BYTE_VALUES)
public default boolean supportsByteValues() {
return true;
}
/**
* Supports setting of a double value.
*/
@FeatureDescriptor(name = FEATURE_DOUBLE_VALUES)
public default boolean supportsDoubleValues() {
return true;
}
/**
* Supports setting of a float value.
*/
@FeatureDescriptor(name = FEATURE_FLOAT_VALUES)
public default boolean supportsFloatValues() {
return true;
}
/**
* Supports setting of a integer value.
*/
@FeatureDescriptor(name = FEATURE_INTEGER_VALUES)
public default boolean supportsIntegerValues() {
return true;
}
/**
* Supports setting of a long value.
*/
@FeatureDescriptor(name = FEATURE_LONG_VALUES)
public default boolean supportsLongValues() {
return true;
}
/**
* Supports setting of a {@code Map} value. The assumption is that the {@code Map} can contain
* arbitrary serializable values that may or may not be defined as a feature itself.
*/
@FeatureDescriptor(name = FEATURE_MAP_VALUES)
public default boolean supportsMapValues() {
return true;
}
/**
* Supports setting of a {@code List} value. The assumption is that the {@code List} can contain
* arbitrary serializable values that may or may not be defined as a feature itself. As this
* {@code List} is "mixed" it does not need to contain objects of the same type.
*
* @see #supportsMixedListValues()
*/
@FeatureDescriptor(name = FEATURE_MIXED_LIST_VALUES)
public default boolean supportsMixedListValues() {
return true;
}
/**
* Supports setting of an array of boolean values.
*/
@FeatureDescriptor(name = FEATURE_BOOLEAN_ARRAY_VALUES)
public default boolean supportsBooleanArrayValues() {
return true;
}
/**
* Supports setting of an array of byte values.
*/
@FeatureDescriptor(name = FEATURE_BYTE_ARRAY_VALUES)
public default boolean supportsByteArrayValues() {
return true;
}
/**
* Supports setting of an array of double values.
*/
@FeatureDescriptor(name = FEATURE_DOUBLE_ARRAY_VALUES)
public default boolean supportsDoubleArrayValues() {
return true;
}
/**
* Supports setting of an array of float values.
*/
@FeatureDescriptor(name = FEATURE_FLOAT_ARRAY_VALUES)
public default boolean supportsFloatArrayValues() {
return true;
}
/**
* Supports setting of an array of integer values.
*/
@FeatureDescriptor(name = FEATURE_INTEGER_ARRAY_VALUES)
public default boolean supportsIntegerArrayValues() {
return true;
}
/**
* Supports setting of an array of string values.
*/
@FeatureDescriptor(name = FEATURE_STRING_ARRAY_VALUES)
public default boolean supportsStringArrayValues() {
return true;
}
/**
* Supports setting of an array of long values.
*/
@FeatureDescriptor(name = FEATURE_LONG_ARRAY_VALUES)
public default boolean supportsLongArrayValues() {
return true;
}
/**
* Supports setting of a Java serializable value.
*/
@FeatureDescriptor(name = FEATURE_SERIALIZABLE_VALUES)
public default boolean supportsSerializableValues() {
return true;
}
/**
* Supports setting of a string value.
*/
@FeatureDescriptor(name = FEATURE_STRING_VALUES)
public default boolean supportsStringValues() {
return true;
}
/**
* Supports setting of a {@code List} value. The assumption is that the {@code List} can contain
* arbitrary serializable values that may or may not be defined as a feature itself. As this
* {@code List} is "uniform" it must contain objects of the same type.
*
* @see #supportsMixedListValues()
*/
@FeatureDescriptor(name = FEATURE_UNIFORM_LIST_VALUES)
public default boolean supportsUniformListValues() {
return true;
}
}
/**
* A marker interface to identify any set of Features. There is no need to implement this interface.
*/
public interface FeatureSet {
}
/**
* Implementers should not override this method. Note that this method utilizes reflection to check for
* feature support.
*/
default boolean supports(final Class<? extends FeatureSet> featureClass, final String feature)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
final Object instance;
if (featureClass.equals(GraphFeatures.class))
instance = this.graph();
else if (featureClass.equals(VariableFeatures.class))
instance = this.graph().variables();
else if (featureClass.equals(VertexFeatures.class))
instance = this.vertex();
else if (featureClass.equals(VertexPropertyFeatures.class))
instance = this.vertex().properties();
else if (featureClass.equals(EdgeFeatures.class))
instance = this.edge();
else if (featureClass.equals(EdgePropertyFeatures.class))
instance = this.edge().properties();
else if (featureClass.equals(PropertyFeatures.class))
throw new IllegalArgumentException(String.format(
"Do not reference PropertyFeatures directly in tests, utilize a specific instance: %s, %s",
EdgePropertyFeatures.class, VertexPropertyFeatures.class));
else
throw new IllegalArgumentException(String.format(
"Expecting featureClass to be a valid Feature instance and not %s", featureClass));
return (Boolean) featureClass.getMethod("supports" + feature).invoke(instance);
}
}
/**
* Common exceptions to use with a graph.
*/
public static class Exceptions {
private static final boolean debug = Boolean.parseBoolean(java.lang.System.getenv().getOrDefault("gremlin.structure.debug", "false"));
public static UnsupportedOperationException variablesNotSupported() {
return new UnsupportedOperationException("Graph does not support graph variables");
}
public static UnsupportedOperationException transactionsNotSupported() {
return new UnsupportedOperationException("Graph does not support transactions");
}
public static UnsupportedOperationException graphComputerNotSupported() {
return new UnsupportedOperationException("Graph does not support graph computer");
}
public static IllegalArgumentException graphDoesNotSupportProvidedGraphComputer(final Class graphComputerClass) {
return new IllegalArgumentException("Graph does not support the provided graph computer: " + graphComputerClass.getSimpleName());
}
public static UnsupportedOperationException vertexAdditionsNotSupported() {
return new UnsupportedOperationException("Graph does not support adding vertices");
}
public static IllegalArgumentException vertexWithIdAlreadyExists(final Object id) {
return new IllegalArgumentException(String.format("Vertex with id already exists: %s", id));
}
public static IllegalArgumentException edgeWithIdAlreadyExists(final Object id) {
return new IllegalArgumentException(String.format("Edge with id already exists: %s", id));
}
public static IllegalArgumentException idArgsMustBeEitherIdOrElement() {
return new IllegalArgumentException("id arguments must be either ids or Elements");
}
public static IllegalArgumentException argumentCanNotBeNull(final String argument) {
return new IllegalArgumentException(String.format("The provided argument can not be null: %s", argument));
}
}
/**
* Defines the test suite that the implementer has decided to support and represents publicly as "passing".
* Marking the {@link Graph} instance with this class allows that particular test suite to run.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(OptIns.class)
@Inherited
public @interface OptIn {
public static String SUITE_STRUCTURE_STANDARD = "org.apache.tinkerpop.gremlin.structure.StructureStandardSuite";
public static String SUITE_STRUCTURE_INTEGRATE = "org.apache.tinkerpop.gremlin.structure.StructureIntegrateSuite";
public static String SUITE_PROCESS_COMPUTER = "org.apache.tinkerpop.gremlin.process.ProcessComputerSuite";
public static String SUITE_PROCESS_STANDARD = "org.apache.tinkerpop.gremlin.process.ProcessStandardSuite";
/**
* The test suite class to opt in to.
*/
public String value();
}
/**
* Holds a collection of {@link OptIn} enabling multiple {@link OptIn} to be applied to a
* single suite.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface OptIns {
OptIn[] value();
}
/**
* Defines a test in the suite that the implementer does not want to run.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(OptOuts.class)
@Inherited
public @interface OptOut {
/**
* The test class to opt out of. This may be set to a base class of a test as in the case of the Gremlin
* process class of tests from which Gremlin flavors extend. If the actual test class is an inner class
* of then use a "$" as a separator between the outer class and inner class.
*/
public String test();
/**
* The specific name of the test method to opt out of or asterisk to opt out of all methods in a
* {@link #test}.
*/
public String method();
/**
* The reason the implementation is opting out of this test.
*/
public String reason();
/**
* For parameterized tests specify the name of the test itself without its "square brackets".
*/
public String specific() default "";
/**
* The list of {@link GraphComputer} implementations by class name that a test should opt-out from using (i.e. other
* graph computers not in this list will execute the test). This setting should only be included when
* the test is one that uses the {@code TraversalEngine.COMPUTER} - it will otherwise be ignored. By
* default, an empty array is assigned and it is thus assumed that all computers are excluded when an
* {@code OptOut} annotation is used, therefore this value must be overridden to be more specific.
*/
public String[] computers() default {};
}
/**
* Holds a collection of {@link OptOut} enabling multiple {@link OptOut} to be applied to a
* single suite.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface OptOuts {
OptOut[] value();
}
}