We now have ProjectedTraveser which is a Traverser with List<Object> of projections. OrderGlobalStep uses this and thus, can order for everything within the local star graph. Added MultiComparator which is like ChainedComparator but doesn't contain traversal projections -- just comparators.
Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/5045f67f Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/5045f67f Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/5045f67f Branch: refs/heads/TINKERPOP-1602 Commit: 5045f67f469e163d1363f953672a3f38b4ff2a3f Parents: 501e97a Author: Marko A. Rodriguez <okramma...@gmail.com> Authored: Wed Jan 18 09:38:21 2017 -0700 Committer: Marko A. Rodriguez <okramma...@gmail.com> Committed: Wed Jan 18 09:38:21 2017 -0700 ---------------------------------------------------------------------- .../traversal/step/map/OrderGlobalStep.java | 39 ++- .../traversal/traverser/OrderedTraverser.java | 235 ------------------- .../traversal/traverser/ProjectedTraverser.java | 199 ++++++++++++++++ .../gremlin/structure/io/gryo/GryoVersion.java | 8 +- .../gremlin/util/function/MultiComparator.java | 60 +++++ 5 files changed, 295 insertions(+), 246 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/5045f67f/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderGlobalStep.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderGlobalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderGlobalStep.java index ac5df90..9c071f1 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderGlobalStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/OrderGlobalStep.java @@ -27,10 +27,12 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.ByModulating; import org.apache.tinkerpop.gremlin.process.traversal.step.ComparatorHolder; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent; import org.apache.tinkerpop.gremlin.process.traversal.step.util.CollectingBarrierStep; -import org.apache.tinkerpop.gremlin.process.traversal.traverser.OrderedTraverser; +import org.apache.tinkerpop.gremlin.process.traversal.traverser.ProjectedTraverser; import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.TraverserSet; +import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil; import org.apache.tinkerpop.gremlin.structure.util.StringFactory; +import org.apache.tinkerpop.gremlin.util.function.MultiComparator; import org.javatuples.Pair; import java.io.Serializable; @@ -49,6 +51,7 @@ import java.util.stream.Collectors; public final class OrderGlobalStep<S, C extends Comparable> extends CollectingBarrierStep<S> implements ComparatorHolder<S, C>, TraversalParent, ByModulating { private List<Pair<Traversal.Admin<S, C>, Comparator<C>>> comparators = new ArrayList<>(); + private MultiComparator<C> multiComparator = null; private long limit = Long.MAX_VALUE; private boolean isShuffle = false; @@ -61,14 +64,16 @@ public final class OrderGlobalStep<S, C extends Comparable> extends CollectingBa if (this.isShuffle) traverserSet.shuffle(); else - traverserSet.sort(Comparator.naturalOrder()); + traverserSet.sort((Comparator) this.multiComparator); } @Override public void processAllStarts() { + if (null == this.multiComparator) + this.multiComparator = this.createMultiComparator(); if (this.starts.hasNext()) { while (this.starts.hasNext()) { - this.traverserSet.add(new OrderedTraverser<S>(this.starts.next(), (List) this.comparators)); + this.traverserSet.add(this.createOrderedTraverser(this.starts.next())); } this.barrierConsumer(this.traverserSet); } @@ -81,7 +86,7 @@ public final class OrderGlobalStep<S, C extends Comparable> extends CollectingBa } else if (this.starts.hasNext()) { this.processAllStarts(); } - return ((OrderedTraverser) this.traverserSet.remove()).getInternal(); + return ((ProjectedTraverser) this.traverserSet.remove()).getInternal(); } public void setLimit(final long limit) { @@ -155,7 +160,25 @@ public final class OrderGlobalStep<S, C extends Comparable> extends CollectingBa @Override public MemoryComputeKey<TraverserSet<S>> getMemoryComputeKey() { - return MemoryComputeKey.of(this.getId(), new OrderBiOperator<>(this.limit, this.isShuffle), false, true); + if (null == this.multiComparator) + this.multiComparator = this.createMultiComparator(); + return MemoryComputeKey.of(this.getId(), new OrderBiOperator<>(this.limit, this.isShuffle, this.multiComparator), false, true); + } + + private ProjectedTraverser<S> createOrderedTraverser(final Traverser.Admin<S> traverser) { + final List<Object> projections = new ArrayList<>(this.comparators.size()); + for (final Pair<Traversal.Admin<S, C>, Comparator<C>> pair : this.comparators) { + projections.add(TraversalUtil.apply(traverser, pair.getValue0())); + } + return new ProjectedTraverser<S>(traverser, projections); + } + + private MultiComparator<C> createMultiComparator() { + final List<Comparator<C>> list = new ArrayList<>(this.comparators.size()); + for (final Pair<Traversal.Admin<S, C>, Comparator<C>> pair : this.comparators) { + list.add(pair.getValue1()); + } + return new MultiComparator<>(list); } //////////////// @@ -164,14 +187,16 @@ public final class OrderGlobalStep<S, C extends Comparable> extends CollectingBa private long limit; private boolean isShuffle; + private MultiComparator comparator; private OrderBiOperator() { // for serializers that need a no-arg constructor } - public OrderBiOperator(final long limit, final boolean isShuffle) { + public OrderBiOperator(final long limit, final boolean isShuffle, final MultiComparator multiComparator) { this.limit = limit; this.isShuffle = isShuffle; + this.comparator = multiComparator; } @Override @@ -181,7 +206,7 @@ public final class OrderGlobalStep<S, C extends Comparable> extends CollectingBa if (this.isShuffle) setA.shuffle(); else - setA.sort(Comparator.naturalOrder()); + setA.sort(this.comparator); long counter = 0L; final Iterator<Traverser.Admin<S>> traversers = setA.iterator(); while (traversers.hasNext()) { http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/5045f67f/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/OrderedTraverser.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/OrderedTraverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/OrderedTraverser.java deleted file mode 100644 index 4dddaa3..0000000 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/OrderedTraverser.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * 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.traverser; - -import org.apache.tinkerpop.gremlin.process.traversal.Order; -import org.apache.tinkerpop.gremlin.process.traversal.Path; -import org.apache.tinkerpop.gremlin.process.traversal.Step; -import org.apache.tinkerpop.gremlin.process.traversal.Traversal; -import org.apache.tinkerpop.gremlin.process.traversal.TraversalSideEffects; -import org.apache.tinkerpop.gremlin.process.traversal.Traverser; -import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil; -import org.apache.tinkerpop.gremlin.structure.util.Attachable; -import org.javatuples.Pair; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Set; -import java.util.function.Function; - -/** - * @author Marko A. Rodriguez (http://markorodriguez.com) - */ -public final class OrderedTraverser<T> implements Traverser.Admin<T> { - - private Traverser.Admin<T> internal; - private int order; - private final List<Pair<Object, Comparator<?>>> orderChecks = new ArrayList<>(); - - private OrderedTraverser() { - // for serialization - } - - public OrderedTraverser(final Traverser.Admin<T> internal, final List<Pair<Traversal.Admin<T, ?>, Comparator>> checks) { - this(internal, 0); - for (final Pair<Traversal.Admin<T, ?>, Comparator> pairs : checks) { - this.orderChecks.add(Pair.with(TraversalUtil.apply(this.internal, pairs.getValue0()), pairs.getValue1())); - } - } - - public OrderedTraverser(final Traverser.Admin<T> internal, final int order) { - this.internal = internal instanceof OrderedTraverser ? ((OrderedTraverser) internal).internal : internal; - this.order = order; - } - - public Traverser.Admin<T> getInternal() { - return this.internal; - } - - public int order() { - return this.order; - } - - @Override - public void merge(final Admin<?> other) { - this.internal.merge(other); - } - - @Override - public <R> Admin<R> split(R r, Step<T, R> step) { - return new OrderedTraverser<>(this.internal.split(r, step), this.order); - } - - @Override - public Admin<T> split() { - return new OrderedTraverser<>(this.internal.split(), this.order); - } - - @Override - public void addLabels(final Set<String> labels) { - this.internal.addLabels(labels); - } - - @Override - public void keepLabels(final Set<String> labels) { - this.internal.keepLabels(labels); - } - - @Override - public void dropLabels(final Set<String> labels) { - this.internal.dropLabels(labels); - } - - @Override - public void dropPath() { - this.internal.dropPath(); - } - - @Override - public void set(final T t) { - this.internal.set(t); - } - - @Override - public void incrLoops(final String stepLabel) { - this.internal.incrLoops(stepLabel); - } - - @Override - public void resetLoops() { - this.internal.resetLoops(); - } - - @Override - public String getStepId() { - return this.internal.getStepId(); - } - - @Override - public void setStepId(final String stepId) { - this.internal.setStepId(stepId); - } - - @Override - public void setBulk(final long count) { - this.internal.setBulk(count); - } - - @Override - public Admin<T> detach() { - this.internal = this.internal.detach(); - return this; - } - - @Override - public T attach(final Function<Attachable<T>, T> method) { - return this.internal.attach(method); - } - - @Override - public void setSideEffects(final TraversalSideEffects sideEffects) { - this.internal.setSideEffects(sideEffects); - } - - @Override - public TraversalSideEffects getSideEffects() { - return this.internal.getSideEffects(); - } - - @Override - public Set<String> getTags() { - return this.internal.getTags(); - } - - @Override - public T get() { - return this.internal.get(); - } - - @Override - public <S> S sack() { - return this.internal.sack(); - } - - @Override - public <S> void sack(final S object) { - this.internal.sack(object); - } - - @Override - public Path path() { - return this.internal.path(); - } - - @Override - public int loops() { - return this.internal.loops(); - } - - @Override - public long bulk() { - return this.internal.bulk(); - } - - @Override - public int hashCode() { - return this.internal.hashCode(); - } - - @Override - public boolean equals(final Object object) { - return object instanceof OrderedTraverser && ((OrderedTraverser) object).internal.equals(this.internal); - } - - @Override - public OrderedTraverser<T> clone() { - try { - final OrderedTraverser<T> clone = (OrderedTraverser<T>) super.clone(); - clone.internal = (Traverser.Admin<T>) this.internal.clone(); - return clone; - } catch (final CloneNotSupportedException e) { - throw new IllegalStateException(e.getMessage(), e); - } - } - - @Override - public int compareTo(final Traverser<T> o) { - if (!(o instanceof OrderedTraverser)) - return 0; - else { - if (this.orderChecks.isEmpty()) { - return Order.incr.compare(this.get(), o.get()); - } else { - final OrderedTraverser<T> other = (OrderedTraverser<T>) o; - for (int i = 0; i < this.orderChecks.size(); i++) { - final Comparator comparator = this.orderChecks.get(i).getValue1(); - final Object thisObject = this.orderChecks.get(i).getValue0(); - final Object otherObject = other.orderChecks.get(i).getValue0(); - final int comparison = comparator.compare(thisObject, otherObject); - if (comparison != 0) - return comparison; - - } - return 0; - } - } - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/5045f67f/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/ProjectedTraverser.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/ProjectedTraverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/ProjectedTraverser.java new file mode 100644 index 0000000..67e723a --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/ProjectedTraverser.java @@ -0,0 +1,199 @@ +/* + * 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.traverser; + +import org.apache.tinkerpop.gremlin.process.traversal.Path; +import org.apache.tinkerpop.gremlin.process.traversal.Step; +import org.apache.tinkerpop.gremlin.process.traversal.TraversalSideEffects; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.structure.util.Attachable; + +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +/** + * @author Marko A. Rodriguez (http://markorodriguez.com) + */ +public final class ProjectedTraverser<T> implements Traverser.Admin<T> { + + private Traverser.Admin<T> internal; + private List<Object> projections; + + private ProjectedTraverser() { + // for serialization + } + + public ProjectedTraverser(final Traverser.Admin<T> internal, final List<Object> projections) { + this.internal = internal; + this.projections = projections; + } + + + public Traverser.Admin<T> getInternal() { + return this.internal; + } + + public List<Object> getProjections() { + return this.projections; + } + + @Override + public void merge(final Admin<?> other) { + this.internal.merge(other); + } + + @Override + public <R> Admin<R> split(R r, Step<T, R> step) { + return new ProjectedTraverser<>(this.internal.split(r, step), this.projections); + } + + @Override + public Admin<T> split() { + return new ProjectedTraverser<>(this.internal.split(), this.projections); + } + + @Override + public void addLabels(final Set<String> labels) { + this.internal.addLabels(labels); + } + + @Override + public void keepLabels(final Set<String> labels) { + this.internal.keepLabels(labels); + } + + @Override + public void dropLabels(final Set<String> labels) { + this.internal.dropLabels(labels); + } + + @Override + public void dropPath() { + this.internal.dropPath(); + } + + @Override + public void set(final T t) { + this.internal.set(t); + } + + @Override + public void incrLoops(final String stepLabel) { + this.internal.incrLoops(stepLabel); + } + + @Override + public void resetLoops() { + this.internal.resetLoops(); + } + + @Override + public String getStepId() { + return this.internal.getStepId(); + } + + @Override + public void setStepId(final String stepId) { + this.internal.setStepId(stepId); + } + + @Override + public void setBulk(final long count) { + this.internal.setBulk(count); + } + + @Override + public Admin<T> detach() { + this.internal = this.internal.detach(); + return this; + } + + @Override + public T attach(final Function<Attachable<T>, T> method) { + return this.internal.attach(method); + } + + @Override + public void setSideEffects(final TraversalSideEffects sideEffects) { + this.internal.setSideEffects(sideEffects); + } + + @Override + public TraversalSideEffects getSideEffects() { + return this.internal.getSideEffects(); + } + + @Override + public Set<String> getTags() { + return this.internal.getTags(); + } + + @Override + public T get() { + return this.internal.get(); + } + + @Override + public <S> S sack() { + return this.internal.sack(); + } + + @Override + public <S> void sack(final S object) { + this.internal.sack(object); + } + + @Override + public Path path() { + return this.internal.path(); + } + + @Override + public int loops() { + return this.internal.loops(); + } + + @Override + public long bulk() { + return this.internal.bulk(); + } + + @Override + public int hashCode() { + return this.internal.hashCode(); + } + + @Override + public boolean equals(final Object object) { + return object instanceof ProjectedTraverser && ((ProjectedTraverser) object).internal.equals(this.internal); + } + + @Override + public ProjectedTraverser<T> clone() { + try { + final ProjectedTraverser<T> clone = (ProjectedTraverser<T>) super.clone(); + clone.internal = (Traverser.Admin<T>) this.internal.clone(); + return clone; + } catch (final CloneNotSupportedException e) { + throw new IllegalStateException(e.getMessage(), e); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/5045f67f/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java index a04f2d9..f4e31fd 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java @@ -51,13 +51,11 @@ import org.apache.tinkerpop.gremlin.process.traversal.traverser.LP_O_OB_P_S_SE_S import org.apache.tinkerpop.gremlin.process.traversal.traverser.LP_O_OB_S_SE_SL_Traverser; import org.apache.tinkerpop.gremlin.process.traversal.traverser.O_OB_S_SE_SL_Traverser; import org.apache.tinkerpop.gremlin.process.traversal.traverser.O_Traverser; -import org.apache.tinkerpop.gremlin.process.traversal.traverser.OrderedTraverser; +import org.apache.tinkerpop.gremlin.process.traversal.traverser.ProjectedTraverser; import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.TraverserSet; -import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalMetrics; import org.apache.tinkerpop.gremlin.process.traversal.util.ImmutableMetrics; import org.apache.tinkerpop.gremlin.process.traversal.util.MutableMetrics; -import org.apache.tinkerpop.gremlin.process.traversal.util.OrP; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalExplanation; import org.apache.tinkerpop.gremlin.structure.Column; import org.apache.tinkerpop.gremlin.structure.Direction; @@ -81,6 +79,7 @@ import org.apache.tinkerpop.gremlin.structure.util.star.StarGraph; import org.apache.tinkerpop.gremlin.structure.util.star.StarGraphSerializer; import org.apache.tinkerpop.gremlin.util.function.HashSetSupplier; import org.apache.tinkerpop.gremlin.util.function.Lambda; +import org.apache.tinkerpop.gremlin.util.function.MultiComparator; import org.apache.tinkerpop.shaded.kryo.KryoSerializable; import org.apache.tinkerpop.shaded.kryo.serializers.JavaSerializer; import org.javatuples.Pair; @@ -246,7 +245,7 @@ public enum GryoVersion { add(GryoTypeReg.of(O_OB_S_SE_SL_Traverser.class, 89)); add(GryoTypeReg.of(LP_O_OB_S_SE_SL_Traverser.class, 90)); add(GryoTypeReg.of(LP_O_OB_P_S_SE_SL_Traverser.class, 91)); - add(GryoTypeReg.of(OrderedTraverser.class, 164)); // ***LAST ID*** + add(GryoTypeReg.of(ProjectedTraverser.class, 164)); add(GryoTypeReg.of(DefaultRemoteTraverser.class, 123, new GryoSerializers.DefaultRemoteTraverserSerializer())); add(GryoTypeReg.of(Bytecode.class, 122, new GryoSerializers.BytecodeSerializer())); @@ -261,6 +260,7 @@ public enum GryoVersion { add(GryoTypeReg.of(SackFunctions.Barrier.class, 135)); add(GryoTypeReg.of(TraversalOptionParent.Pick.class, 137)); add(GryoTypeReg.of(HashSetSupplier.class, 136, new UtilSerializers.HashSetSupplierSerializer())); + add(GryoTypeReg.of(MultiComparator.class, 165)); // ***LAST ID*** add(GryoTypeReg.of(TraverserSet.class, 58)); add(GryoTypeReg.of(Tree.class, 61)); http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/5045f67f/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/function/MultiComparator.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/function/MultiComparator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/function/MultiComparator.java new file mode 100644 index 0000000..427aa3d --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/function/MultiComparator.java @@ -0,0 +1,60 @@ +/* + * 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.util.function; + +import org.apache.tinkerpop.gremlin.process.traversal.Order; +import org.apache.tinkerpop.gremlin.process.traversal.traverser.ProjectedTraverser; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.List; + +/** + * @author Marko A. Rodriguez (http://markorodriguez.com) + */ +public final class MultiComparator<C> implements Comparator<C>, Serializable { + + private final List<Comparator> comparators; + + public MultiComparator(final List<Comparator<C>> comparators) { + this.comparators = (List) comparators; + } + + @Override + public int compare(final C objectA, final C objectB) { + if (this.comparators.isEmpty()) { + return Order.incr.compare(objectA, objectB); + } else { + for (int i = 0; i < this.comparators.size(); i++) { + final int comparison = this.comparators.get(i).compare(this.getObject(objectA, i), this.getObject(objectB, i)); + if (comparison != 0) + return comparison; + } + return 0; + } + } + + private final Object getObject(final C object, final int index) { + if (object instanceof ProjectedTraverser) + return ((ProjectedTraverser) object).getProjections().get(index); + else + return object; + } +}