blob: aab48b98dd5afa920cbc9416fb05d903ea61d98f [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.util.function.ConstantSupplier;
import java.io.Serializable;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
/**
* A {@link Traversal} can maintain global sideEffects.
* Unlike {@link Traverser} "sacks" which are local sideEffects, TraversalSideEffects are accessible by all {@link Traverser} instances within the {@link Traversal}.
*
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
public interface TraversalSideEffects extends Cloneable, Serializable, AutoCloseable {
/**
* Return true if the key is a registered side-effect.
*
* @param key the key to check for existence
* @return whether the key exists or not
*/
public default boolean exists(final String key) {
return this.keys().contains(key);
}
/**
* Get the sideEffect associated with the provided key.
* If the sideEffect contains an object for the key, return it.
* Else if the sideEffect has a registered {@code Supplier} for that key, generate the object, store the object in the sideEffects, and return it.
*
* @param key the key to get the value for
* @param <V> the type of the value to retrieve
* @return the value associated with key
* @throws IllegalArgumentException if the key does not reference an object or a registered supplier.
*/
public <V> V get(final String key) throws IllegalArgumentException;
/**
* Set the specified key to the specified value.
* This method should not be used in a distributed environment. Instead, use {@link TraversalSideEffects#add(String, Object)}.
* This method is only safe when there is only one representation of the side-effect and thus, not distributed across threads or machines.
*
* @param key the key they key of the side-effect
* @param value the value the new value for the side-effect
* @throws IllegalArgumentException if the key does not reference a registered side-effect.
*/
public void set(final String key, final Object value) throws IllegalArgumentException;
/**
* Remove both the value and registered {@link java.util.function.Supplier} associated with provided key.
*
* @param key the key of the value and registered supplier to remove
*/
public void remove(final String key);
/**
* The keys of the sideEffect which includes registered {@code Supplier} keys. In essence, that which is possible
* to {@link #get(String)}.
*
* @return the keys of the sideEffect
*/
public Set<String> keys();
/**
* Invalidate the side effect cache for traversal.
*/
public default void close() throws Exception {
// do nothing
}
/**
* Determines if there are any side-effects to be retrieved.
*/
public default boolean isEmpty() {
return keys().size() == 0;
}
/**
* Register a side-effect with the {@link TraversalSideEffects} providing a {@link Supplier} and a {@link BinaryOperator}.
* If a null value is provided for the supplier or reducer, then it no supplier or reducer is registered.
*
* @param key the key of the side-effect value
* @param initialValue the initial value supplier
* @param reducer the reducer to use for merging a distributed side-effect value into a single value
* @param <V> the type of the side-effect value
*/
public <V> void register(final String key, final Supplier<V> initialValue, final BinaryOperator<V> reducer);
/**
* Register a side-effect with the {@link TraversalSideEffects} providing a {@link Supplier} and a {@link BinaryOperator}.
* The registration will only overwrite a supplier or reducer if no supplier or reducer existed prior.
* If a null value is provided for the supplier or reducer, then it no supplier or reducer is registered.
*
* @param key the key of the side-effect value
* @param initialValue the initial value supplier
* @param reducer the reducer to use for merging a distributed side-effect value into a single value
* @param <V> the type of the side-effect value
*/
public <V> void registerIfAbsent(final String key, final Supplier<V> initialValue, final BinaryOperator<V> reducer);
/**
* Get the reducer associated with the side-effect key. If no reducer was registered, then {@link Operator#assign} is provided.
*
* @param key the key of the side-effect
* @param <V> the type of the side-effect value
* @return the registered reducer
* @throws IllegalArgumentException if no side-effect exists for the provided key
*/
public <V> BinaryOperator<V> getReducer(final String key) throws IllegalArgumentException;
/**
* Get the supplier associated with the side-effect key. If no supplier was registered, then {@link ConstantSupplier} is provided.
*
* @param key the key of the side-effect
* @param <V> the type of the side-effect value
* @return the registered supplier
* @throws IllegalArgumentException if no side-effect exists for the provided key
*/
public <V> Supplier<V> getSupplier(final String key) throws IllegalArgumentException;
/**
* Add a value to the global side-effect value.
* This should be used by steps to ensure that side-effects are merged properly in a distributed environment.
* {@link TraversalSideEffects#set(String, Object)} should only be used in single-threaded systems or by a master traversal in a distributed environment.
*
* @param key the key of the side-effect.
* @param value the partital value (to be merged) of the side-effect.
* @throws IllegalArgumentException if no side-effect exists for the provided key
*/
public void add(final String key, final Object value) throws IllegalArgumentException;
/**
* Set the initial value of each {@link Traverser} "sack" along with the operators for splitting and merging sacks.
* If no split operator is provided, then a direct memory copy is assumed (this is typically good for primitive types and strings).
* If no merge operator is provided, then traversers with sacks will not be merged.
*
* @param initialValue the initial value supplier of the traverser sack
* @param splitOperator the split operator for splitting traverser sacks
* @param mergeOperator the merge operator for merging traverser sacks
* @param <S> the sack type
*/
public <S> void setSack(final Supplier<S> initialValue, final UnaryOperator<S> splitOperator, final BinaryOperator<S> mergeOperator);
/**
* If sacks are enabled, get the initial value of the {@link Traverser} sack.
* If its not enabled, then <code>null</code> is returned.
*
* @param <S> the sack type
* @return the supplier of the initial value of the traverser sack
*/
public <S> Supplier<S> getSackInitialValue();
/**
* If sacks are enabled and a split operator has been specified, then get it (else get <code>null</code>).
* The split operator is used to split a sack when a bifurcation in a {@link Traverser} happens.
*
* @param <S> the sack type
* @return the operator for splitting a traverser sack
*/
public <S> UnaryOperator<S> getSackSplitter();
/**
* If sacks are enabled and a merge function has been specified, then get it (else get <code>null</code>).
* The merge function is used to merge two sacks when two {@link Traverser}s converge.
*
* @param <S> the sack type
* @return the operator for merging two traverser sacks
*/
public <S> BinaryOperator<S> getSackMerger();
////////////
public default <V> void forEach(final BiConsumer<String, V> biConsumer) {
this.keys().forEach(key -> biConsumer.accept(key, this.<V>get(key)));
}
/**
* Cloning is used to duplicate the sideEffects typically in distributed execution environments.
*
* @return The cloned sideEffects
*/
@SuppressWarnings("CloneDoesntDeclareCloneNotSupportedException")
public TraversalSideEffects clone();
/**
* Add the current {@link TraversalSideEffects} values, suppliers, and reducers to the provided {@link TraversalSideEffects}.
* The implementation should (under the hood), use {@link TraversalSideEffects#registerIfAbsent(String, Supplier, BinaryOperator)} so that
* if the argument {@link TraversalSideEffects} already has a registered supplier or binary operator, then don't overwrite it.
*
* @param sideEffects the sideEffects to add this traversal's sideEffect data to.
*/
public void mergeInto(final TraversalSideEffects sideEffects);
public static class Exceptions {
public static IllegalArgumentException sideEffectKeyCanNotBeEmpty() {
return new IllegalArgumentException("Side-effect key can not be the empty string");
}
public static IllegalArgumentException sideEffectKeyCanNotBeNull() {
return new IllegalArgumentException("Side-effect key can not be null");
}
public static IllegalArgumentException sideEffectValueCanNotBeNull() {
return new IllegalArgumentException("Side-effect value can not be null");
}
public static IllegalArgumentException sideEffectKeyDoesNotExist(final String key) {
return new IllegalArgumentException("The side-effect key does not exist in the side-effects: " + key);
}
}
}