| /* |
| * 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.step.util; |
| |
| import org.apache.tinkerpop.gremlin.process.traversal.Path; |
| import org.apache.tinkerpop.gremlin.process.traversal.Pop; |
| |
| import java.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * @author Marko A. Rodriguez (http://markorodriguez.com) |
| */ |
| public class ImmutablePath implements Path, Serializable, Cloneable { |
| |
| private static final ImmutablePath TAIL_PATH = new ImmutablePath(null, null, null); |
| |
| private ImmutablePath previousPath; |
| private Object currentObject; |
| private Set<String> currentLabels; |
| |
| public static Path make() { |
| return TAIL_PATH; |
| } |
| |
| @SuppressWarnings("CloneDoesntCallSuperClone,CloneDoesntDeclareCloneNotSupportedException") |
| @Override |
| public ImmutablePath clone() { |
| return this; |
| } |
| |
| private ImmutablePath(final ImmutablePath previousPath, final Object currentObject, final Set<String> currentLabels) { |
| this.previousPath = previousPath; |
| this.currentObject = currentObject; |
| this.currentLabels = currentLabels; |
| } |
| |
| private final boolean isTail() { |
| return null == this.currentObject; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return this.isTail(); |
| } |
| |
| @Override |
| public int size() { |
| int counter = 0; |
| ImmutablePath currentPath = this; |
| while (true) { |
| if (currentPath.isTail()) return counter; |
| counter++; |
| currentPath = currentPath.previousPath; |
| } |
| } |
| |
| @Override |
| public <A> A head() { |
| return (A) this.currentObject; |
| } |
| |
| @Override |
| public Path extend(final Object object, final Set<String> labels) { |
| return new ImmutablePath(this, object, labels); |
| } |
| |
| @Override |
| public Path extend(final Set<String> labels) { |
| if (labels.isEmpty() || this.currentLabels.containsAll(labels)) |
| return this; |
| else { |
| final Set<String> newLabels = new LinkedHashSet<>(); |
| newLabels.addAll(this.currentLabels); |
| newLabels.addAll(labels); |
| return new ImmutablePath(this.previousPath, this.currentObject, newLabels); |
| } |
| } |
| |
| @Override |
| public Path retract(final Set<String> labels) { |
| if (labels.isEmpty()) |
| return this; |
| |
| // get all the immutable path sections |
| final List<ImmutablePath> immutablePaths = new ArrayList<>(); |
| ImmutablePath currentPath = this; |
| while (true) { |
| if (currentPath.isTail()) |
| break; |
| immutablePaths.add(0, currentPath); |
| currentPath = currentPath.previousPath; |
| } |
| // build a new immutable path using the respective path sections that are not to be retracted |
| Path newPath = TAIL_PATH; |
| for (final ImmutablePath immutablePath : immutablePaths) { |
| final Set<String> temp = new LinkedHashSet<>(immutablePath.currentLabels); |
| temp.removeAll(labels); |
| if (!temp.isEmpty()) |
| newPath = newPath.extend(immutablePath.currentObject, temp); |
| } |
| return newPath; |
| } |
| |
| @Override |
| public <A> A get(final int index) { |
| int counter = this.size(); |
| ImmutablePath currentPath = this; |
| while (true) { |
| if (index == --counter) |
| return (A) currentPath.currentObject; |
| currentPath = currentPath.previousPath; |
| } |
| } |
| |
| @Override |
| public <A> A get(final Pop pop, final String label) { |
| if (Pop.mixed == pop) { |
| return this.get(label); |
| } else if (Pop.all == pop) { |
| // Recursively build the list to avoid building objects/labels collections. |
| final List<Object> list = new ArrayList<>(); |
| ImmutablePath currentPath = this; |
| while (true) { |
| if (currentPath.isTail()) |
| break; |
| else if (currentPath.currentLabels.contains(label)) |
| list.add(0, currentPath.currentObject); |
| currentPath = currentPath.previousPath; |
| } |
| return (A) list; |
| } else if (Pop.last == pop) { |
| ImmutablePath currentPath = this; |
| while (true) { |
| if (currentPath.isTail()) |
| throw Path.Exceptions.stepWithProvidedLabelDoesNotExist(label); |
| else if (currentPath.currentLabels.contains(label)) |
| return (A) currentPath.currentObject; |
| else |
| currentPath = currentPath.previousPath; |
| } |
| } else { // Pop.first |
| A found = null; |
| ImmutablePath currentPath = this; |
| while (true) { |
| if (currentPath.isTail()) |
| break; |
| else if (currentPath.currentLabels.contains(label)) |
| found = (A) currentPath.currentObject; |
| currentPath = currentPath.previousPath; |
| } |
| if (null == found) |
| throw Path.Exceptions.stepWithProvidedLabelDoesNotExist(label); |
| return found; |
| } |
| } |
| |
| @Override |
| public boolean hasLabel(final String label) { |
| ImmutablePath currentPath = this; |
| while (true) { |
| if (currentPath.isTail()) |
| return false; |
| else if (currentPath.currentLabels.contains(label)) |
| return true; |
| else |
| currentPath = currentPath.previousPath; |
| } |
| } |
| |
| @Override |
| public List<Object> objects() { |
| final List<Object> objects = new ArrayList<>(); |
| ImmutablePath currentPath = this; |
| while (true) { |
| if (currentPath.isTail()) |
| break; |
| objects.add(0, currentPath.currentObject); |
| currentPath = currentPath.previousPath; |
| } |
| return Collections.unmodifiableList(objects); |
| } |
| |
| @Override |
| public List<Set<String>> labels() { |
| final List<Set<String>> labels = new ArrayList<>(); |
| ImmutablePath currentPath = this; |
| while (true) { |
| if (currentPath.isTail()) |
| break; |
| labels.add(0, currentPath.currentLabels); |
| currentPath = currentPath.previousPath; |
| } |
| return Collections.unmodifiableList(labels); |
| } |
| |
| @Override |
| public String toString() { |
| return this.objects().toString(); |
| } |
| |
| @Override |
| public int hashCode() { |
| // hashCode algorithm from AbstractList |
| int[] hashCodes = new int[this.size()]; |
| int index = hashCodes.length - 1; |
| ImmutablePath currentPath = this; |
| while (true) { |
| if (currentPath.isTail()) |
| break; |
| hashCodes[index] = currentPath.currentObject.hashCode(); |
| currentPath = currentPath.previousPath; |
| index--; |
| } |
| int hashCode = 1; |
| for (final int hash : hashCodes) { |
| hashCode = hashCode * 31 + hash; |
| } |
| return hashCode; |
| } |
| |
| @Override |
| public boolean equals(final Object other) { |
| if (!(other instanceof Path)) |
| return false; |
| final Path otherPath = (Path) other; |
| int size = this.size(); |
| if (otherPath.size() != size) |
| return false; |
| if (size > 0) { |
| ImmutablePath currentPath = this; |
| final List<Object> otherObjects = otherPath.objects(); |
| final List<Set<String>> otherLabels = otherPath.labels(); |
| for (int i = otherLabels.size() - 1; i >= 0; i--) { |
| if (currentPath.isTail()) |
| return true; |
| else if (!currentPath.currentObject.equals(otherObjects.get(i)) || |
| !currentPath.currentLabels.equals(otherLabels.get(i))) |
| return false; |
| else |
| currentPath = currentPath.previousPath; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean popEquals(final Pop pop, final Object other) { |
| if (!(other instanceof Path)) |
| return false; |
| final Path otherPath = (Path) other; |
| ImmutablePath currentPath = this; |
| while (true) { |
| if (currentPath.isTail()) |
| break; |
| for (final String label : currentPath.currentLabels) { |
| if (!otherPath.hasLabel(label) || !this.get(pop, label).equals(otherPath.get(pop, label))) |
| return false; |
| } |
| currentPath = currentPath.previousPath; |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean isSimple() { |
| final Set<Object> objects = new HashSet<>(); |
| ImmutablePath currentPath = this; |
| while (true) { |
| if (currentPath.isTail()) |
| return true; |
| else if (objects.contains(currentPath.currentObject)) |
| return false; |
| else { |
| objects.add(currentPath.currentObject); |
| currentPath = currentPath.previousPath; |
| } |
| } |
| } |
| } |