| /* |
| * 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.tinkergraph.structure; |
| |
| import org.apache.commons.configuration2.BaseConfiguration; |
| import org.apache.commons.configuration2.Configuration; |
| import org.apache.tinkerpop.gremlin.process.computer.GraphComputer; |
| import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies; |
| import org.apache.tinkerpop.gremlin.structure.Edge; |
| import org.apache.tinkerpop.gremlin.structure.Element; |
| import org.apache.tinkerpop.gremlin.structure.Graph; |
| import org.apache.tinkerpop.gremlin.structure.Transaction; |
| import org.apache.tinkerpop.gremlin.structure.Vertex; |
| import org.apache.tinkerpop.gremlin.structure.VertexProperty; |
| import org.apache.tinkerpop.gremlin.structure.io.Io; |
| import org.apache.tinkerpop.gremlin.structure.io.IoCore; |
| import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion; |
| import org.apache.tinkerpop.gremlin.structure.io.gryo.GryoVersion; |
| import org.apache.tinkerpop.gremlin.structure.util.ElementHelper; |
| import org.apache.tinkerpop.gremlin.structure.util.GraphFactory; |
| import org.apache.tinkerpop.gremlin.structure.util.StringFactory; |
| import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputer; |
| import org.apache.tinkerpop.gremlin.tinkergraph.process.computer.TinkerGraphComputerView; |
| import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphCountStrategy; |
| import org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.strategy.optimization.TinkerGraphStepStrategy; |
| import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; |
| |
| import java.io.File; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.stream.Stream; |
| |
| /** |
| * An in-memory (with optional persistence on calls to {@link #close()}), reference implementation of the property |
| * graph interfaces provided by TinkerPop. |
| * |
| * @author Marko A. Rodriguez (http://markorodriguez.com) |
| * @author Stephen Mallette (http://stephen.genoprime.com) |
| */ |
| @Graph.OptIn(Graph.OptIn.SUITE_STRUCTURE_STANDARD) |
| @Graph.OptIn(Graph.OptIn.SUITE_STRUCTURE_INTEGRATE) |
| @Graph.OptIn(Graph.OptIn.SUITE_PROCESS_STANDARD) |
| @Graph.OptIn(Graph.OptIn.SUITE_PROCESS_COMPUTER) |
| public final class TinkerGraph implements Graph { |
| |
| static { |
| TraversalStrategies.GlobalCache.registerStrategies(TinkerGraph.class, TraversalStrategies.GlobalCache.getStrategies(Graph.class).clone().addStrategies( |
| TinkerGraphStepStrategy.instance(), |
| TinkerGraphCountStrategy.instance())); |
| } |
| |
| private static final Configuration EMPTY_CONFIGURATION = new BaseConfiguration() {{ |
| this.setProperty(Graph.GRAPH, TinkerGraph.class.getName()); |
| }}; |
| |
| public static final String GREMLIN_TINKERGRAPH_VERTEX_ID_MANAGER = "gremlin.tinkergraph.vertexIdManager"; |
| public static final String GREMLIN_TINKERGRAPH_EDGE_ID_MANAGER = "gremlin.tinkergraph.edgeIdManager"; |
| public static final String GREMLIN_TINKERGRAPH_VERTEX_PROPERTY_ID_MANAGER = "gremlin.tinkergraph.vertexPropertyIdManager"; |
| public static final String GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY = "gremlin.tinkergraph.defaultVertexPropertyCardinality"; |
| public static final String GREMLIN_TINKERGRAPH_GRAPH_LOCATION = "gremlin.tinkergraph.graphLocation"; |
| public static final String GREMLIN_TINKERGRAPH_GRAPH_FORMAT = "gremlin.tinkergraph.graphFormat"; |
| public static final String GREMLIN_TINKERGRAPH_ALLOW_NULL_PROPERTY_VALUES = "gremlin.tinkergraph.allowNullPropertyValues"; |
| |
| private final TinkerGraphFeatures features = new TinkerGraphFeatures(); |
| |
| protected AtomicLong currentId = new AtomicLong(-1L); |
| protected Map<Object, Vertex> vertices = new ConcurrentHashMap<>(); |
| protected Map<Object, Edge> edges = new ConcurrentHashMap<>(); |
| |
| protected TinkerGraphVariables variables = null; |
| protected TinkerGraphComputerView graphComputerView = null; |
| protected TinkerIndex<TinkerVertex> vertexIndex = null; |
| protected TinkerIndex<TinkerEdge> edgeIndex = null; |
| |
| protected final IdManager<?> vertexIdManager; |
| protected final IdManager<?> edgeIdManager; |
| protected final IdManager<?> vertexPropertyIdManager; |
| protected final VertexProperty.Cardinality defaultVertexPropertyCardinality; |
| protected final boolean allowNullPropertyValues; |
| |
| private final Configuration configuration; |
| private final String graphLocation; |
| private final String graphFormat; |
| |
| /** |
| * An empty private constructor that initializes {@link TinkerGraph}. |
| */ |
| private TinkerGraph(final Configuration configuration) { |
| this.configuration = configuration; |
| vertexIdManager = selectIdManager(configuration, GREMLIN_TINKERGRAPH_VERTEX_ID_MANAGER, Vertex.class); |
| edgeIdManager = selectIdManager(configuration, GREMLIN_TINKERGRAPH_EDGE_ID_MANAGER, Edge.class); |
| vertexPropertyIdManager = selectIdManager(configuration, GREMLIN_TINKERGRAPH_VERTEX_PROPERTY_ID_MANAGER, VertexProperty.class); |
| defaultVertexPropertyCardinality = VertexProperty.Cardinality.valueOf( |
| configuration.getString(GREMLIN_TINKERGRAPH_DEFAULT_VERTEX_PROPERTY_CARDINALITY, VertexProperty.Cardinality.single.name())); |
| allowNullPropertyValues = configuration.getBoolean(GREMLIN_TINKERGRAPH_ALLOW_NULL_PROPERTY_VALUES, false); |
| |
| graphLocation = configuration.getString(GREMLIN_TINKERGRAPH_GRAPH_LOCATION, null); |
| graphFormat = configuration.getString(GREMLIN_TINKERGRAPH_GRAPH_FORMAT, null); |
| |
| if ((graphLocation != null && null == graphFormat) || (null == graphLocation && graphFormat != null)) |
| throw new IllegalStateException(String.format("The %s and %s must both be specified if either is present", |
| GREMLIN_TINKERGRAPH_GRAPH_LOCATION, GREMLIN_TINKERGRAPH_GRAPH_FORMAT)); |
| |
| if (graphLocation != null) loadGraph(); |
| } |
| |
| /** |
| * Open a new {@link TinkerGraph} instance. |
| * <p/> |
| * <b>Reference Implementation Help:</b> If a {@link Graph} implementation does not require a {@code Configuration} |
| * (or perhaps has a default configuration) it can choose to implement a zero argument |
| * {@code open()} method. This is an optional constructor method for TinkerGraph. It is not enforced by the Gremlin |
| * Test Suite. |
| */ |
| public static TinkerGraph open() { |
| return open(EMPTY_CONFIGURATION); |
| } |
| |
| /** |
| * Open a new {@code TinkerGraph} instance. |
| * <p/> |
| * <b>Reference Implementation Help:</b> This method is the one use by the {@link GraphFactory} to instantiate |
| * {@link Graph} instances. This method must be overridden for the Structure Test Suite to pass. Implementers have |
| * latitude in terms of how exceptions are handled within this method. Such exceptions will be considered |
| * implementation specific by the test suite as all test generate graph instances by way of |
| * {@link GraphFactory}. As such, the exceptions get generalized behind that facade and since |
| * {@link GraphFactory} is the preferred method to opening graphs it will be consistent at that level. |
| * |
| * @param configuration the configuration for the instance |
| * @return a newly opened {@link Graph} |
| */ |
| public static TinkerGraph open(final Configuration configuration) { |
| return new TinkerGraph(configuration); |
| } |
| |
| ////////////// STRUCTURE API METHODS ////////////////// |
| |
| @Override |
| public Vertex addVertex(final Object... keyValues) { |
| ElementHelper.legalPropertyKeyValueArray(keyValues); |
| Object idValue = vertexIdManager.convert(ElementHelper.getIdValue(keyValues).orElse(null)); |
| final String label = ElementHelper.getLabelValue(keyValues).orElse(Vertex.DEFAULT_LABEL); |
| |
| if (null != idValue) { |
| if (this.vertices.containsKey(idValue)) |
| throw Exceptions.vertexWithIdAlreadyExists(idValue); |
| } else { |
| idValue = vertexIdManager.getNextId(this); |
| } |
| |
| final Vertex vertex = new TinkerVertex(idValue, label, this); |
| this.vertices.put(vertex.id(), vertex); |
| |
| ElementHelper.attachProperties(vertex, VertexProperty.Cardinality.list, keyValues); |
| return vertex; |
| } |
| |
| @Override |
| public <C extends GraphComputer> C compute(final Class<C> graphComputerClass) { |
| if (!graphComputerClass.equals(TinkerGraphComputer.class)) |
| throw Graph.Exceptions.graphDoesNotSupportProvidedGraphComputer(graphComputerClass); |
| return (C) new TinkerGraphComputer(this); |
| } |
| |
| @Override |
| public GraphComputer compute() { |
| return new TinkerGraphComputer(this); |
| } |
| |
| @Override |
| public Variables variables() { |
| if (null == this.variables) |
| this.variables = new TinkerGraphVariables(); |
| return this.variables; |
| } |
| |
| @Override |
| public <I extends Io> I io(final Io.Builder<I> builder) { |
| if (builder.requiresVersion(GryoVersion.V1_0) || builder.requiresVersion(GraphSONVersion.V1_0)) |
| return (I) builder.graph(this).onMapper(mapper -> mapper.addRegistry(TinkerIoRegistryV1d0.instance())).create(); |
| else if (builder.requiresVersion(GraphSONVersion.V2_0)) // there is no gryo v2 |
| return (I) builder.graph(this).onMapper(mapper -> mapper.addRegistry(TinkerIoRegistryV2d0.instance())).create(); |
| else |
| return (I) builder.graph(this).onMapper(mapper -> mapper.addRegistry(TinkerIoRegistryV3d0.instance())).create(); |
| } |
| |
| @Override |
| public String toString() { |
| return StringFactory.graphString(this, "vertices:" + this.vertices.size() + " edges:" + this.edges.size()); |
| } |
| |
| public void clear() { |
| this.vertices.clear(); |
| this.edges.clear(); |
| this.variables = null; |
| this.currentId.set(-1L); |
| this.vertexIndex = null; |
| this.edgeIndex = null; |
| this.graphComputerView = null; |
| } |
| |
| /** |
| * This method only has an effect if the {@link #GREMLIN_TINKERGRAPH_GRAPH_LOCATION} is set, in which case the |
| * data in the graph is persisted to that location. This method may be called multiple times and does not release |
| * resources. |
| */ |
| @Override |
| public void close() { |
| if (graphLocation != null) saveGraph(); |
| } |
| |
| @Override |
| public Transaction tx() { |
| throw Exceptions.transactionsNotSupported(); |
| } |
| |
| @Override |
| public Configuration configuration() { |
| return configuration; |
| } |
| |
| @Override |
| public Iterator<Vertex> vertices(final Object... vertexIds) { |
| return createElementIterator(Vertex.class, vertices, vertexIdManager, vertexIds); |
| } |
| |
| @Override |
| public Iterator<Edge> edges(final Object... edgeIds) { |
| return createElementIterator(Edge.class, edges, edgeIdManager, edgeIds); |
| } |
| |
| private void loadGraph() { |
| final File f = new File(graphLocation); |
| if (f.exists() && f.isFile()) { |
| try { |
| if (graphFormat.equals("graphml")) { |
| io(IoCore.graphml()).readGraph(graphLocation); |
| } else if (graphFormat.equals("graphson")) { |
| io(IoCore.graphson()).readGraph(graphLocation); |
| } else if (graphFormat.equals("gryo")) { |
| io(IoCore.gryo()).readGraph(graphLocation); |
| } else { |
| io(IoCore.createIoBuilder(graphFormat)).readGraph(graphLocation); |
| } |
| } catch (Exception ex) { |
| throw new RuntimeException(String.format("Could not load graph at %s with %s", graphLocation, graphFormat), ex); |
| } |
| } |
| } |
| |
| private void saveGraph() { |
| final File f = new File(graphLocation); |
| if (f.exists()) { |
| f.delete(); |
| } else { |
| final File parent = f.getParentFile(); |
| |
| // the parent would be null in the case of an relative path if the graphLocation was simply: "f.gryo" |
| if (parent != null && !parent.exists()) { |
| parent.mkdirs(); |
| } |
| } |
| |
| try { |
| if (graphFormat.equals("graphml")) { |
| io(IoCore.graphml()).writeGraph(graphLocation); |
| } else if (graphFormat.equals("graphson")) { |
| io(IoCore.graphson()).writeGraph(graphLocation); |
| } else if (graphFormat.equals("gryo")) { |
| io(IoCore.gryo()).writeGraph(graphLocation); |
| } else { |
| io(IoCore.createIoBuilder(graphFormat)).writeGraph(graphLocation); |
| } |
| } catch (Exception ex) { |
| throw new RuntimeException(String.format("Could not save graph at %s with %s", graphLocation, graphFormat), ex); |
| } |
| } |
| |
| private <T extends Element> Iterator<T> createElementIterator(final Class<T> clazz, final Map<Object, T> elements, |
| final IdManager idManager, |
| final Object... ids) { |
| final Iterator<T> iterator; |
| if (0 == ids.length) { |
| iterator = new TinkerGraphIterator<T>(elements.values().iterator()); |
| } else { |
| final List<Object> idList = Arrays.asList(ids); |
| validateHomogenousIds(idList); |
| |
| // if the type is of Element - have to look each up because it might be an Attachable instance or |
| // other implementation. the assumption is that id conversion is not required for detached |
| // stuff - doesn't seem likely someone would detach a Titan vertex then try to expect that |
| // vertex to be findable in OrientDB |
| return clazz.isAssignableFrom(ids[0].getClass()) ? |
| new TinkerGraphIterator<T>(IteratorUtils.filter(IteratorUtils.map(idList, id -> elements.get(clazz.cast(id).id())).iterator(), Objects::nonNull)) |
| : new TinkerGraphIterator<T>(IteratorUtils.filter(IteratorUtils.map(idList, id -> elements.get(idManager.convert(id))).iterator(), Objects::nonNull)); |
| } |
| return TinkerHelper.inComputerMode(this) ? |
| (Iterator<T>) (clazz.equals(Vertex.class) ? |
| IteratorUtils.filter((Iterator<Vertex>) iterator, t -> this.graphComputerView.legalVertex(t)) : |
| IteratorUtils.filter((Iterator<Edge>) iterator, t -> this.graphComputerView.legalEdge(t.outVertex(), t))) : |
| iterator; |
| } |
| |
| /** |
| * Return TinkerGraph feature set. |
| * <p/> |
| * <b>Reference Implementation Help:</b> Implementers only need to implement features for which there are |
| * negative or instance configured features. By default, all {@link Graph.Features} return true. |
| */ |
| @Override |
| public Features features() { |
| return features; |
| } |
| |
| private void validateHomogenousIds(final List<Object> ids) { |
| final Iterator<Object> iterator = ids.iterator(); |
| Object id = iterator.next(); |
| if (id == null) |
| throw Graph.Exceptions.idArgsMustBeEitherIdOrElement(); |
| final Class firstClass = id.getClass(); |
| while (iterator.hasNext()) { |
| id = iterator.next(); |
| if (id == null || !id.getClass().equals(firstClass)) |
| throw Graph.Exceptions.idArgsMustBeEitherIdOrElement(); |
| } |
| } |
| |
| public class TinkerGraphFeatures implements Features { |
| |
| private final TinkerGraphGraphFeatures graphFeatures = new TinkerGraphGraphFeatures(); |
| private final TinkerGraphEdgeFeatures edgeFeatures = new TinkerGraphEdgeFeatures(); |
| private final TinkerGraphVertexFeatures vertexFeatures = new TinkerGraphVertexFeatures(); |
| |
| private TinkerGraphFeatures() { |
| } |
| |
| @Override |
| public GraphFeatures graph() { |
| return graphFeatures; |
| } |
| |
| @Override |
| public EdgeFeatures edge() { |
| return edgeFeatures; |
| } |
| |
| @Override |
| public VertexFeatures vertex() { |
| return vertexFeatures; |
| } |
| |
| @Override |
| public String toString() { |
| return StringFactory.featureString(this); |
| } |
| |
| } |
| |
| public class TinkerGraphVertexFeatures implements Features.VertexFeatures { |
| |
| private final TinkerGraphVertexPropertyFeatures vertexPropertyFeatures = new TinkerGraphVertexPropertyFeatures(); |
| |
| private TinkerGraphVertexFeatures() { |
| } |
| |
| @Override |
| public boolean supportsNullPropertyValues() { |
| return allowNullPropertyValues; |
| } |
| |
| @Override |
| public Features.VertexPropertyFeatures properties() { |
| return vertexPropertyFeatures; |
| } |
| |
| @Override |
| public boolean supportsCustomIds() { |
| return false; |
| } |
| |
| @Override |
| public boolean willAllowId(final Object id) { |
| return vertexIdManager.allow(id); |
| } |
| |
| @Override |
| public VertexProperty.Cardinality getCardinality(final String key) { |
| return defaultVertexPropertyCardinality; |
| } |
| } |
| |
| public class TinkerGraphEdgeFeatures implements Features.EdgeFeatures { |
| |
| private TinkerGraphEdgeFeatures() { |
| } |
| |
| @Override |
| public boolean supportsNullPropertyValues() { |
| return allowNullPropertyValues; |
| } |
| |
| @Override |
| public boolean supportsCustomIds() { |
| return false; |
| } |
| |
| @Override |
| public boolean willAllowId(final Object id) { |
| return edgeIdManager.allow(id); |
| } |
| } |
| |
| public class TinkerGraphGraphFeatures implements Features.GraphFeatures { |
| |
| private TinkerGraphGraphFeatures() { |
| } |
| |
| @Override |
| public boolean supportsConcurrentAccess() { |
| return false; |
| } |
| |
| @Override |
| public boolean supportsTransactions() { |
| return false; |
| } |
| |
| @Override |
| public boolean supportsThreadedTransactions() { |
| return false; |
| } |
| |
| } |
| |
| public class TinkerGraphVertexPropertyFeatures implements Features.VertexPropertyFeatures { |
| |
| private TinkerGraphVertexPropertyFeatures() { |
| } |
| |
| @Override |
| public boolean supportsNullPropertyValues() { |
| return allowNullPropertyValues; |
| } |
| |
| @Override |
| public boolean supportsCustomIds() { |
| return false; |
| } |
| |
| @Override |
| public boolean willAllowId(final Object id) { |
| return vertexIdManager.allow(id); |
| } |
| } |
| |
| ///////////// GRAPH SPECIFIC INDEXING METHODS /////////////// |
| |
| /** |
| * Create an index for said element class ({@link Vertex} or {@link Edge}) and said property key. |
| * Whenever an element has the specified key mutated, the index is updated. |
| * When the index is created, all existing elements are indexed to ensure that they are captured by the index. |
| * |
| * @param key the property key to index |
| * @param elementClass the element class to index |
| * @param <E> The type of the element class |
| */ |
| public <E extends Element> void createIndex(final String key, final Class<E> elementClass) { |
| if (Vertex.class.isAssignableFrom(elementClass)) { |
| if (null == this.vertexIndex) this.vertexIndex = new TinkerIndex<>(this, TinkerVertex.class); |
| this.vertexIndex.createKeyIndex(key); |
| } else if (Edge.class.isAssignableFrom(elementClass)) { |
| if (null == this.edgeIndex) this.edgeIndex = new TinkerIndex<>(this, TinkerEdge.class); |
| this.edgeIndex.createKeyIndex(key); |
| } else { |
| throw new IllegalArgumentException("Class is not indexable: " + elementClass); |
| } |
| } |
| |
| /** |
| * Drop the index for the specified element class ({@link Vertex} or {@link Edge}) and key. |
| * |
| * @param key the property key to stop indexing |
| * @param elementClass the element class of the index to drop |
| * @param <E> The type of the element class |
| */ |
| public <E extends Element> void dropIndex(final String key, final Class<E> elementClass) { |
| if (Vertex.class.isAssignableFrom(elementClass)) { |
| if (null != this.vertexIndex) this.vertexIndex.dropKeyIndex(key); |
| } else if (Edge.class.isAssignableFrom(elementClass)) { |
| if (null != this.edgeIndex) this.edgeIndex.dropKeyIndex(key); |
| } else { |
| throw new IllegalArgumentException("Class is not indexable: " + elementClass); |
| } |
| } |
| |
| /** |
| * Return all the keys currently being index for said element class ({@link Vertex} or {@link Edge}). |
| * |
| * @param elementClass the element class to get the indexed keys for |
| * @param <E> The type of the element class |
| * @return the set of keys currently being indexed |
| */ |
| public <E extends Element> Set<String> getIndexedKeys(final Class<E> elementClass) { |
| if (Vertex.class.isAssignableFrom(elementClass)) { |
| return null == this.vertexIndex ? Collections.emptySet() : this.vertexIndex.getIndexedKeys(); |
| } else if (Edge.class.isAssignableFrom(elementClass)) { |
| return null == this.edgeIndex ? Collections.emptySet() : this.edgeIndex.getIndexedKeys(); |
| } else { |
| throw new IllegalArgumentException("Class is not indexable: " + elementClass); |
| } |
| } |
| |
| /** |
| * Construct an {@link TinkerGraph.IdManager} from the TinkerGraph {@code Configuration}. |
| */ |
| private static IdManager<?> selectIdManager(final Configuration config, final String configKey, final Class<? extends Element> clazz) { |
| final String vertexIdManagerConfigValue = config.getString(configKey, DefaultIdManager.ANY.name()); |
| try { |
| return DefaultIdManager.valueOf(vertexIdManagerConfigValue); |
| } catch (IllegalArgumentException iae) { |
| try { |
| return (IdManager) Class.forName(vertexIdManagerConfigValue).newInstance(); |
| } catch (Exception ex) { |
| throw new IllegalStateException(String.format("Could not configure TinkerGraph %s id manager with %s", clazz.getSimpleName(), vertexIdManagerConfigValue)); |
| } |
| } |
| } |
| |
| /** |
| * TinkerGraph will use an implementation of this interface to generate identifiers when a user does not supply |
| * them and to handle identifier conversions when querying to provide better flexibility with respect to |
| * handling different data types that mean the same thing. For example, the |
| * {@link DefaultIdManager#LONG} implementation will allow {@code g.vertices(1l, 2l)} and |
| * {@code g.vertices(1, 2)} to both return values. |
| * |
| * @param <T> the id type |
| */ |
| public interface IdManager<T> { |
| /** |
| * Generate an identifier which should be unique to the {@link TinkerGraph} instance. |
| */ |
| T getNextId(final TinkerGraph graph); |
| |
| /** |
| * Convert an identifier to the type required by the manager. |
| */ |
| T convert(final Object id); |
| |
| /** |
| * Determine if an identifier is allowed by this manager given its type. |
| */ |
| boolean allow(final Object id); |
| } |
| |
| /** |
| * A default set of {@link IdManager} implementations for common identifier types. |
| */ |
| public enum DefaultIdManager implements IdManager { |
| /** |
| * Manages identifiers of type {@code Long}. Will convert any class that extends from {@link Number} to a |
| * {@link Long} and will also attempt to convert {@code String} values |
| */ |
| LONG { |
| @Override |
| public Long getNextId(final TinkerGraph graph) { |
| return Stream.generate(() -> (graph.currentId.incrementAndGet())).filter(id -> !graph.vertices.containsKey(id) && !graph.edges.containsKey(id)).findAny().get(); |
| } |
| |
| @Override |
| public Object convert(final Object id) { |
| if (null == id) |
| return null; |
| else if (id instanceof Long) |
| return id; |
| else if (id instanceof Number) |
| return ((Number) id).longValue(); |
| else if (id instanceof String) { |
| try { |
| return Long.parseLong((String) id); |
| } catch (NumberFormatException nfe) { |
| throw new IllegalArgumentException(createErrorMessage(Long.class, id)); |
| } |
| } |
| else |
| throw new IllegalArgumentException(createErrorMessage(Long.class, id)); |
| } |
| |
| @Override |
| public boolean allow(final Object id) { |
| return id instanceof Number || id instanceof String; |
| } |
| }, |
| |
| /** |
| * Manages identifiers of type {@code Integer}. Will convert any class that extends from {@link Number} to a |
| * {@link Integer} and will also attempt to convert {@code String} values |
| */ |
| INTEGER { |
| @Override |
| public Integer getNextId(final TinkerGraph graph) { |
| return Stream.generate(() -> (graph.currentId.incrementAndGet())).map(Long::intValue).filter(id -> !graph.vertices.containsKey(id) && !graph.edges.containsKey(id)).findAny().get(); |
| } |
| |
| @Override |
| public Object convert(final Object id) { |
| if (null == id) |
| return null; |
| else if (id instanceof Integer) |
| return id; |
| else if (id instanceof Number) |
| return ((Number) id).intValue(); |
| else if (id instanceof String) { |
| try { |
| return Integer.parseInt((String) id); |
| } catch (NumberFormatException nfe) { |
| throw new IllegalArgumentException(createErrorMessage(Integer.class, id)); |
| } |
| } |
| else |
| throw new IllegalArgumentException(createErrorMessage(Integer.class, id)); |
| } |
| |
| @Override |
| public boolean allow(final Object id) { |
| return id instanceof Number || id instanceof String; |
| } |
| }, |
| |
| /** |
| * Manages identifiers of type {@code UUID}. Will convert {@code String} values to |
| * {@code UUID}. |
| */ |
| UUID { |
| @Override |
| public UUID getNextId(final TinkerGraph graph) { |
| return java.util.UUID.randomUUID(); |
| } |
| |
| @Override |
| public Object convert(final Object id) { |
| if (null == id) |
| return null; |
| else if (id instanceof java.util.UUID) |
| return id; |
| else if (id instanceof String) { |
| try { |
| return java.util.UUID.fromString((String) id); |
| } catch (IllegalArgumentException iae) { |
| throw new IllegalArgumentException(createErrorMessage(java.util.UUID.class, id)); |
| } |
| } else |
| throw new IllegalArgumentException(createErrorMessage(java.util.UUID.class, id)); |
| } |
| |
| @Override |
| public boolean allow(final Object id) { |
| return id instanceof UUID || id instanceof String; |
| } |
| }, |
| |
| /** |
| * Manages identifiers of any type. This represents the default way {@link TinkerGraph} has always worked. |
| * In other words, there is no identifier conversion so if the identifier of a vertex is a {@code Long}, then |
| * trying to request it with an {@code Integer} will have no effect. Also, like the original |
| * {@link TinkerGraph}, it will generate {@link Long} values for identifiers. |
| */ |
| ANY { |
| @Override |
| public Long getNextId(final TinkerGraph graph) { |
| return Stream.generate(() -> (graph.currentId.incrementAndGet())).filter(id -> !graph.vertices.containsKey(id) && !graph.edges.containsKey(id)).findAny().get(); |
| } |
| |
| @Override |
| public Object convert(final Object id) { |
| return id; |
| } |
| |
| @Override |
| public boolean allow(final Object id) { |
| return true; |
| } |
| }; |
| |
| private static String createErrorMessage(final Class<?> expectedType, final Object id) { |
| return String.format("Expected an id that is convertible to %s but received %s - [%s]", expectedType, id.getClass(), id); |
| } |
| } |
| } |