All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tinkerpop.gremlin.scala.GremlinScalaPipeline.scala Maven / Gradle / Ivy

The newest version!
package com.tinkerpop.gremlin.scala

import com.tinkerpop.blueprints._
import java.util.{ Map ⇒ JMap, HashMap ⇒ JHashMap, Collection ⇒ JCollection, List ⇒ JList }
import java.lang.{ Boolean ⇒ JBoolean, Iterable ⇒ JIterable }
import com.tinkerpop.gremlin.Tokens
import com.tinkerpop.pipes._
import com.tinkerpop.pipes.branch.LoopPipe.LoopBundle
import com.tinkerpop.pipes.util.structures.{ Tree, Table, Row, Pair ⇒ TPair }
import com.tinkerpop.pipes.transform.TransformPipe.Order
import com.tinkerpop.pipes.util.structures.{ Pair ⇒ TinkerPair }
import com.tinkerpop.pipes.filter._
import com.tinkerpop.pipes.transform._
import com.tinkerpop.pipes.branch._
import com.tinkerpop.pipes.util._
import com.tinkerpop.pipes.sideeffect._
import com.tinkerpop.gremlin.scala.pipes.PropertyMapPipe
import com.tinkerpop.pipes.util.structures.AsMap
import scala.language.dynamics
import scala.collection.convert.wrapAsJava
import scala.collection.JavaConversions._
import scala.collection.mutable
import scala.reflect.{ classTag, ClassTag }

class GremlinScalaPipeline[S, E] extends Pipeline[S, E] with Dynamic {
  def id: GremlinScalaPipeline[S, Object] = addPipe(new IdPipe)

  def label: GremlinScalaPipeline[S, String] = addPipe(new LabelPipe)

  def out: GremlinScalaPipeline[S, Vertex] = out()
  def out(labels: String*): GremlinScalaPipeline[S, Vertex] = out(branchFactor = Int.MaxValue, labels: _*)
  def out(branchFactor: Int, labels: String*): GremlinScalaPipeline[S, Vertex] =
    addVertexPipe(branchFactor, Direction.OUT, labels: _*)

  def outE: GremlinScalaPipeline[S, Edge] = outE()
  def outE(labels: String*): GremlinScalaPipeline[S, Edge] = outE(branchFactor = Int.MaxValue, labels: _*)
  def outE(branchFactor: Int, labels: String*): GremlinScalaPipeline[S, Edge] =
    addVertexPipe(branchFactor, Direction.OUT, labels: _*)

  def outV: GremlinScalaPipeline[S, Vertex] = addPipe(new OutVertexPipe)

  def in: GremlinScalaPipeline[S, Vertex] = in()
  def in(labels: String*): GremlinScalaPipeline[S, Vertex] = in(branchFactor = Int.MaxValue, labels: _*)
  def in(branchFactor: Int, labels: String*): GremlinScalaPipeline[S, Vertex] =
    addVertexPipe(branchFactor, Direction.IN, labels: _*)

  def inE: GremlinScalaPipeline[S, Edge] = inE()
  def inE(labels: String*): GremlinScalaPipeline[S, Edge] = inE(branchFactor = Int.MaxValue, labels: _*)
  def inE(branchFactor: Int, labels: String*): GremlinScalaPipeline[S, Edge] =
    addVertexPipe(branchFactor, Direction.IN, labels: _*)

  def inV: GremlinScalaPipeline[S, Vertex] = addPipe(new InVertexPipe)

  def addVertexPipe[A <: Element: ClassTag](branchFactor: Int, direction: Direction, labels: String*): GremlinScalaPipeline[S, A] = {
    val clazz = classTag[A].runtimeClass.asInstanceOf[Class[A]]
    addPipe(new VertexQueryPipe(clazz, direction, null, null, branchFactor, 0, Integer.MAX_VALUE, labels: _*))
  }

  def V(graph: Graph): GremlinScalaPipeline[Vertex, Vertex] =
    manualStart(graph.getVertices)

  def E(graph: Graph): GremlinScalaPipeline[Edge, Edge] =
    manualStart(graph.getEdges)

  /** Check if the element has a property with provided key */
  def has(key: String): GremlinScalaPipeline[S, E] =
    has(key, Tokens.T.neq, null)

  /** Check if the element has a property with provided key/value */
  def has(key: String, value: Any): GremlinScalaPipeline[S, E] =
    has(key, Tokens.T.eq, value)

  /** Check if the element does not have a property with provided key. */
  def hasNot(key: String): GremlinScalaPipeline[S, E] =
    has(key, Tokens.T.eq, null)

  /** Check if the element does not have a property with provided key/value */
  def hasNot(key: String, value: Any): GremlinScalaPipeline[S, E] =
    has(key, Tokens.T.neq, value)

  /** Check if the element has a property with provided key/value that matches the given comparison token */
  def has(key: String, comparison: Tokens.T, value: Any): GremlinScalaPipeline[S, E] =
    has(key, Tokens.mapPredicate(comparison), value)

  /** Check if the element has a property with provided key/value that matches the given predicate */
  def has(key: String, predicate: Predicate, value: Any): GremlinScalaPipeline[S, E] = {
    val pipeline = key match {
      case Tokens.ID    ⇒ addPipe(new IdFilterPipe(predicate, value))
      case Tokens.LABEL ⇒ addPipe(new LabelFilterPipe(predicate, value))
      case _            ⇒ addPipe[Nothing](new PropertyFilterPipe(key, predicate, value))
    }
    pipeline.asInstanceOf[GremlinScalaPipeline[S, E]]
  }

  /** checks if a given property is in an interval (startValue: inclusive, endValue: exclusive)  */
  def interval(key: String, startValue: Comparable[_], endValue: Comparable[_]): GremlinScalaPipeline[S, Element] =
    addPipe(new IntervalFilterPipe[Element](key, startValue, endValue))

  def both(labels: String*): GremlinScalaPipeline[S, Vertex] = both(Int.MaxValue, labels: _*)
  def both(branchFactor: Int, labels: String*): GremlinScalaPipeline[S, Vertex] =
    addPipe(new VertexQueryPipe(classOf[Vertex], Direction.BOTH, null, null, branchFactor, 0, Integer.MAX_VALUE, labels: _*))

  def bothE(labels: String*): GremlinScalaPipeline[S, Edge] = bothE(Int.MaxValue, labels: _*)
  def bothE(branchFactor: Int, labels: String*): GremlinScalaPipeline[S, Edge] =
    addPipe(new VertexQueryPipe(classOf[Edge], Direction.BOTH, null, null, branchFactor, 0, Integer.MAX_VALUE, labels: _*))

  def bothV: GremlinScalaPipeline[S, Vertex] = addPipe(new BothVerticesPipe)

  def propertyMap(keys: String*): GremlinScalaPipeline[S, Map[String, Any]] = addPipe(new PropertyMapPipe(keys: _*))
  def propertyMap: GremlinScalaPipeline[S, Map[String, Any]] = propertyMap()

  def property[F](key: String): GremlinScalaPipeline[S, F] = addPipe(new PropertyPipe(key, false))

  /** Gets the objects on each step on the path through the pipeline as lists. Example:
   *  graph.v(1).out.path
   *  ==>[v[1], v[2]]
   *  ==>[v[1], v[4]]
   *  ==>[v[1], v[3]]
   */
  def path: GremlinScalaPipeline[S, Seq[_]] = addPipe(new PathPipe[Any]).map(_.toSeq)

  /** Gets the objects on each named step on the path through the pipeline as rows. */
  def select: GremlinScalaPipeline[S, Row[_]] =
    addPipe(new SelectPipe(null, FluentUtility.getAsPipes(this)))

  /** Gets the objects for given named steps on the path through the pipeline as rows. */
  def select(steps: String*): GremlinScalaPipeline[S, Row[_]] =
    addPipe(new SelectPipe(steps, FluentUtility.getAsPipes(this)))

  ////////////////////
  /// BRANCH PIPES ///
  ////////////////////
  /** Copies incoming object to internal pipes. */
  def copySplit(pipes: Pipe[E, _]*): GremlinScalaPipeline[S, _] = addPipe(new CopySplitPipe(pipes))

  def exhaustMerge: GremlinScalaPipeline[S, _] =
    addPipe(new ExhaustMergePipe(FluentUtility.getPreviousPipe(this).asInstanceOf[MetaPipe].getPipes))

  /** Used in combination with a copySplit, merging the parallel traversals in a round-robin fashion. */
  def fairMerge: GremlinScalaPipeline[S, _] =
    addPipe(new FairMergePipe(FluentUtility.getPreviousPipe(this).asInstanceOf[MetaPipe].getPipes))

  def ifThenElse(ifFunction: E ⇒ Boolean, thenFunction: E ⇒ _, elseFunction: E ⇒ _): GremlinScalaPipeline[S, _] =
    addPipe(new IfThenElsePipe(
      new ScalaPipeFunction(ifFunction),
      new ScalaPipeFunction(thenFunction),
      new ScalaPipeFunction(elseFunction)
    ))

  /** Add a LoopPipe to the end of the Pipeline.
   *  Looping is useful for repeating a section of a pipeline.
   *  The provided whileFunction determines when to drop out of the loop.
   *  The whileFunction is provided a LoopBundle object which contains the object in loop along with other useful metadata.
   *
   *  @param namedStep     the name of the step to loop back to
   *  @param whileFunction whether or not to continue looping on the current object
   */
  def loop(namedStep: String, whileFunction: LoopBundle[E] ⇒ Boolean): GremlinScalaPipeline[S, E] =
    addPipe(new LoopPipe(new Pipeline(FluentUtility.removePreviousPipes(this, namedStep)), whileFunction))

  /** Add a LoopPipe to the end of the Pipeline.
   *  Looping is useful for repeating a section of a pipeline.
   *  The provided whileFunction determines when to drop out of the loop.
   *  The provided emitFunction can be used to emit objects that are still going through a loop.
   *  The whileFunction and emitFunctions are provided a LoopBundle object which contains the object in loop along with other useful metadata.
   *
   *  @param namedStep     the number of steps to loop back to
   *  @param whileFun whether or not to continue looping on the current object
   *  @param emit whether or not to emit the current object (irrespective of looping)
   */
  def loop(namedStep: String,
           whileFun: LoopBundle[E] ⇒ Boolean,
           emit: LoopBundle[E] ⇒ Boolean): GremlinScalaPipeline[S, E] =
    addPipe(new LoopPipe(new Pipeline(FluentUtility.removePreviousPipes(this, namedStep)), whileFun, emit))

  ////////////////////
  /// FILTER PIPES ///
  ////////////////////
  /** Takes a collection of pipes and emits incoming objects that are true for all of the pipes. */
  def and(pipes: Pipe[E, _]*): GremlinScalaPipeline[S, E] = addPipe(new AndFilterPipe[E](pipes: _*))

  def back(namedStep: String): GremlinScalaPipeline[S, Any] =
    addPipe(new BackFilterPipe(new Pipeline(FluentUtility.removePreviousPipes(this, namedStep))))

  def dedup: GremlinScalaPipeline[S, E] = addPipe(new DuplicateFilterPipe[E])

  /** only emits the object if the object generated by its function hasn't been seen before. */
  def dedup(dedupFunction: E ⇒ _): GremlinScalaPipeline[S, E] = addPipe(new DuplicateFilterPipe[E](dedupFunction))

  /** emits everything except what is in the supplied collection. */
  def except(iterable: Iterable[E]): GremlinScalaPipeline[S, E] = addPipe(new ExceptFilterPipe[E](iterable))

  /** emits everything except what is in the results of a named step.
   *
   *  not currently supported because ExceptFilterPipe uses ElementHelper.areEqual to compare two elements, which compares if the classes are equal.
   *  I'll open a pull request to fix that in blueprints shortly...
   */
  def except(namedSteps: String*): GremlinScalaPipeline[S, E] = throw new NotImplementedError("not currently supported")

  /** retains everything that is in the supplied collection. */
  def retain(iterable: Iterable[E]): GremlinScalaPipeline[S, E] = addPipe(new RetainFilterPipe[E](iterable))

  /** retains everything that is in the results of a named step.
   *
   *  not currently supported because RetainFilterPipe uses ElementHelper.areEqual to compare two elements, which compares if the classes are equal.
   *  I'll open a pull request to fix that in blueprints shortly...
   */
  def retain(namedSteps: String*): GremlinScalaPipeline[S, E] = throw new NotImplementedError("not currently supported")

  def filter(f: E ⇒ Boolean): GremlinScalaPipeline[S, E] = addPipe(new FilterFunctionPipe[E](f))
  def filterNot(f: E ⇒ Boolean): GremlinScalaPipeline[S, E] = addPipe(new FilterFunctionPipe[E]({ e: E ⇒ !f(e) }))

  def or(pipes: Pipe[E, _]*): GremlinScalaPipeline[S, E] = addPipe(new OrFilterPipe[E](pipes: _*))

  def random(bias: Double): GremlinScalaPipeline[S, E] = addPipe(new RandomFilterPipe[E](bias))

  /** only emit a given range of elements */
  def range(low: Int, high: Int): GremlinScalaPipeline[S, E] = addPipe(new RangeFilterPipe[E](low, high))

  /** simplifies the path by removing cycles */
  def simplePath: GremlinScalaPipeline[S, E] = addPipe(new CyclicPathFilterPipe[E])

  /** Adds input into buffer greedily - it will exhaust all the items that come to it from previous steps before emitting the next element.
   *  Note that this is a side effect step: the input will just flow through to the next step, but you can use `cap` to get the buffer into the pipeline.
   *  @see example in SideEffectTest
   */
  def aggregate(buffer: mutable.Buffer[E]): GremlinScalaPipeline[S, E] = addPipe(new AggregatePipe[E](buffer))

  /** Like aggregate, but applies `fun` to each element prior to adding it to the Buffer */
  def aggregate[F](buffer: mutable.Buffer[F])(fun: E ⇒ F): GremlinScalaPipeline[S, E] =
    addPipe(new AggregatePipe[E](buffer, fun))

  /** Emits input, but adds input to collection. This is a lazy step, i.e. it adds it to the buffer as the elements are being traversed.  */
  def store(buffer: mutable.Buffer[E]): GremlinScalaPipeline[S, E] = addPipe(new StorePipe[E](buffer))

  /** Like store , but applies `fun` to each element prior to adding it to the Buffer */
  def store[F](buffer: mutable.Buffer[F], fun: E ⇒ F): GremlinScalaPipeline[S, E] =
    addPipe(new StorePipe[E](buffer, fun))

  /** Behaves similar to `back` except that it does not filter. It will go down a particular path and back up to where it left off.
   *  As such, its useful for yielding a side-effect down a particular branch.
   */
  def optional(namedStep: String): GremlinScalaPipeline[S, _] =
    addPipe(new OptionalPipe(new Pipeline(FluentUtility.removePreviousPipes(this, namedStep))))

  /** Groups input by given keyFunction greedily - it will exhaust all the items that come to it from previous steps before emitting the next element.
   *  Note that this is a side effect step: the input will just flow through to the next step, but you can use `cap` to get the buffer into the pipeline.
   *  @see example in SideEffectTest
   */
  def groupBy[K, V](map: JMap[K, JList[Any]] = new JHashMap)(keyFunction: E ⇒ K, valueFunction: E ⇒ V): GremlinScalaPipeline[S, E] =
    addPipe(
      new GroupByPipe(
        map,
        keyFunction,
        new ScalaPipeFunction(valueFunction).asInstanceOf[ScalaPipeFunction[E, Any]])
    )

  /** counts each traversed object and stores it in a map */
  def groupCount: GremlinScalaPipeline[S, E] = addPipe(new GroupCountPipe)

  /** Emits input, but calls a side effect closure on each input. */
  def sideEffect[F](sideEffectFunction: E ⇒ F): GremlinScalaPipeline[S, E] =
    addPipe(new SideEffectFunctionPipe(FluentUtility.prepareFunction(asMap, sideEffectFunction))).asInstanceOf[GremlinScalaPipeline[S, E]]

  /** Emit input, but stores the tree formed by the traversal as a map. */
  def tree: GremlinScalaPipeline[S, E] = addPipe(new TreePipe)

  ///////////////////////
  /// TRANSFORM PIPES ///
  ///////////////////////

  /** Emit the incoming vertex, but have other vertex provide an incoming and outgoing edge to incoming vertex. */
  def linkBoth(label: String, other: Vertex): GremlinScalaPipeline[S, Vertex] = 
    addPipe(new LinkPipe(Direction.BOTH, label, other))

  /** Emit the incoming vertex, but have other vertex provide an incoming and outgoing edge to incoming vertex. */
  def linkOut(label: String, other: Vertex): GremlinScalaPipeline[S, Vertex] = 
    addPipe(new LinkPipe(Direction.OUT, label, other))

  /** Emit the incoming vertex, but have other vertex provide an incoming and outgoing edge to incoming vertex. */
  def linkIn(label: String, other: Vertex): GremlinScalaPipeline[S, Vertex] = 
    addPipe(new LinkPipe(Direction.IN, label, other))

  /** Remembers a particular mapping from input to output. Long or expensive expressions with no side effects can use this step
   *  to remember a mapping, which helps reduce load when previously processed objects are passed into it.
   *  For situations where memoization may consume large amounts of RAM, consider using an embedded key-value store like JDBM or
   *  some other persistent Map implementation.
   */
  def memoize(namedStep: String): GremlinScalaPipeline[S, E] =
    addPipe(new MemoizePipe(new Pipeline(FluentUtility.removePreviousPipes(this, namedStep))))

  /** Add a ShufflePipe to the end of the Pipeline.
   *  All the objects previous to this step are aggregated in a greedy fashion, their order randomized and emitted
   *  as a List.
   */
  def shuffle: GremlinScalaPipeline[S, List[E]] = addPipe(new ShufflePipe)

  /** All the objects previous to this step are aggregated in a greedy fashion and emitted as a List.
   *  Normally they would be traversed over lazily.
   *  Gather/Scatter is good for breadth-first traversals where the gather closure filters out unwanted elements at the current radius.
   *  @see https://github.com/tinkerpop/gremlin/wiki/Depth-First-vs.-Breadth-First
   *
   *  Note: Gremlin-Groovy comes with an overloaded gather pipe that takes a function to
   *  transform the last step. You can achieve the same by just appending a map step.
   */
  def gather: GremlinScalaPipeline[S, List[E]] = addPipe(new GatherPipe[E]) map (_.toList)

  /** This will unroll any iterator/iterable/map that is provided to it.
   *  Gather/Scatter is good for breadth-first traversals where the gather closure filters out unwanted elements at the current radius.
   *  @see https://github.com/tinkerpop/gremlin/wiki/Depth-First-vs.-Breadth-First
   *
   *  Note: only for one level - it will not unroll an iterator within an iterator.
   */
  def scatter: GremlinScalaPipeline[S, _] = {
    import com.tinkerpop.gremlin.scala.pipes.ScatterPipe
    addPipe(new ScatterPipe)
  }

  /** emits the side-effect of the previous pipe (e.g. groupBy) - and not the values that flow through it.
   *  If you use it, this normally is the last step. @see examples in SideEffectTest
   */
  def cap: GremlinScalaPipeline[S, _] = {
    val sideEffectPipe = FluentUtility.removePreviousPipes(this, 1).get(0).asInstanceOf[SideEffectPipe[S, _]]
    addPipe(new SideEffectCapPipe(sideEffectPipe))
  }

  /** map objects over a given function
   *  aliases: transform (standard gremlin) and ∘ (category theory)
   */
  def map[F](function: E ⇒ F): GremlinScalaPipeline[S, F] = {
    val pipeFunction = new ScalaPipeFunction(function)
    addPipe(new TransformFunctionPipe(pipeFunction))
  }
  def ∘[F](function: E ⇒ F): GremlinScalaPipeline[S, F] = map(function)
  def transform[F](function: E ⇒ F): GremlinScalaPipeline[S, F] = map(function)

  def order: GremlinScalaPipeline[S, E] = addPipe(new OrderPipe)
  def order(by: Order = Order.INCR): GremlinScalaPipeline[S, E] = addPipe(new OrderPipe(by))

  def order(compare: (E, E) ⇒ Int): GremlinScalaPipeline[S, E] = {
    val compareFun: PipeFunction[TPair[E, E], Integer] =
      new ScalaPipeFunction({ pair: TPair[E, E] ⇒ compare(pair.getA, pair.getB) })
    addPipe(new OrderPipe(compareFun))
  }

  ////////////////////////
  /// utility methods ///
  ///////////////////////

  def as(name: String): GremlinScalaPipeline[S, E] = {
    val pipeline = addPipe(new AsPipe(name, FluentUtility.removePreviousPipes(this, 1).get(0)))
    asMap.refresh()
    pipeline.asInstanceOf[GremlinScalaPipeline[S, E]]
  }

  def start(startObject: S): GremlinScalaPipeline[S, S] = {
    addPipe(new StartPipe[S](startObject))
    FluentUtility.setStarts(this, startObject)
    this.asInstanceOf[GremlinScalaPipeline[S, S]]
  }

  /** run through pipeline and get results as List */
  def toList[_](): List[E] = iterableAsScalaIterable(this).toList

  /** run through pipeline and get results as Set */
  def toSet[_](): Set[E] = iterableAsScalaIterable(this).toSet

  /** run through pipeline and get results as Stream */
  def toStream(): Stream[E] = iterableAsScalaIterable(this).toStream

  /** Completely drain the pipeline of its objects - useful when a sideEffect of the pipeline is desired */
  override def iterate() = PipeHelper.iterate(this)

  def addPipe[T](pipe: Pipe[_ <: Any, T]): GremlinScalaPipeline[S, T] = {
    super.addPipe(pipe)
    this.asInstanceOf[GremlinScalaPipeline[S, T]]
  }

  private def manualStart[T](start: JIterable[T]): GremlinScalaPipeline[T, T] = {
    val pipe = addPipe(new StartPipe[S](start))
    FluentUtility.setStarts(this, start)
    pipe.asInstanceOf[GremlinScalaPipeline[T, T]]
  }

  implicit def boolean2BooleanFn(fn: E ⇒ Boolean)(e: E): JBoolean = fn(e)
  def selectDynamic[F](field: String): GremlinScalaPipeline[S, F] = property(field)

  protected def asMap = new AsMap(this)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy