From 7dbe27af69f01c0d609f0e8d02420bb8957da6ae Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Fri, 22 Aug 2025 21:52:24 +0900 Subject: [PATCH 1/2] Add support for relative ordering Signed-off-by: yongjunhong --- .../core/CyclicOrderException.java | 49 ++++++ .../springframework/core/DependsOnAfter.java | 49 ++++++ .../springframework/core/DependsOnBefore.java | 48 ++++++ .../org/springframework/core/OrderGraph.java | 153 ++++++++++++++++++ .../core/RelativeOrderProcessor.java | 120 ++++++++++++++ .../core/TopologicalOrderSolver.java | 80 +++++++++ .../AnnotationAwareOrderComparator.java | 12 +- .../AnnotationAwareOrderComparatorTests.java | 59 +++++++ 8 files changed, 566 insertions(+), 4 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/core/CyclicOrderException.java create mode 100644 spring-core/src/main/java/org/springframework/core/DependsOnAfter.java create mode 100644 spring-core/src/main/java/org/springframework/core/DependsOnBefore.java create mode 100644 spring-core/src/main/java/org/springframework/core/OrderGraph.java create mode 100644 spring-core/src/main/java/org/springframework/core/RelativeOrderProcessor.java create mode 100644 spring-core/src/main/java/org/springframework/core/TopologicalOrderSolver.java diff --git a/spring-core/src/main/java/org/springframework/core/CyclicOrderException.java b/spring-core/src/main/java/org/springframework/core/CyclicOrderException.java new file mode 100644 index 000000000000..6dd4727f28f9 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/CyclicOrderException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.core; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Exception thrown when a cyclic ordering dependency is detected. + * + * @author Yongjun Hong + */ +@SuppressWarnings("serial") +public class CyclicOrderException extends RuntimeException { + + private final List cycle; + + public CyclicOrderException(String message, List cycle) { + super(buildDetailedMessage(message, cycle)); + this.cycle = cycle; + } + + private static String buildDetailedMessage(String message, List cycle) { + String cycleDescription = cycle.stream() + .map(obj -> (obj instanceof Class) ? ((Class) obj).getSimpleName() : obj.getClass().getSimpleName()) + .collect(Collectors.joining(" -> ")); + + return message + ". Detected cycle: " + cycleDescription + " -> " + + cycle.get(0).getClass().getSimpleName(); + } + + public List getCycle() { + return this.cycle; + } +} diff --git a/spring-core/src/main/java/org/springframework/core/DependsOnAfter.java b/spring-core/src/main/java/org/springframework/core/DependsOnAfter.java new file mode 100644 index 000000000000..307df8db2a05 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/DependsOnAfter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.core; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that the annotated component should be ordered after the specified target classes. + * + *

This annotation is an extension of the {@code @AutoConfigureAfter} pattern from Spring Boot, + * adapted for use in the core Spring framework. It allows developers to specify that a component + * must be initialized or processed after certain other components.

+ * + *

For example, if class A depends on class B being initialized first, class A can be annotated + * with {@code @DependsOnAfter(B.class)} to enforce this order.

+ * + *

This annotation is particularly useful in scenarios where the initialization order of + * components affects application behavior, and topological sorting is required to resolve + * dependencies.

+ * + * @author Yongjun Hong + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DependsOnAfter { + /** + * The target classes after which this component should be ordered. + */ + Class[] value() default {}; +} diff --git a/spring-core/src/main/java/org/springframework/core/DependsOnBefore.java b/spring-core/src/main/java/org/springframework/core/DependsOnBefore.java new file mode 100644 index 000000000000..291266eeddba --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/DependsOnBefore.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.core; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that the annotated component should be ordered before the specified target classes. + * + *

This annotation extends the {@code @AutoConfigureBefore} pattern from Spring Boot + * to the core Spring framework, allowing for fine-grained control over the initialization + * or processing order of components.

+ * + *

For example, if class A must be initialized before class B, you can annotate class A + * with {@code @DependsOnBefore(B.class)}.

+ * + *

This annotation is primarily used in dependency management scenarios where + * topological sorting is required to determine the correct order of execution.

+ * + * @author Yongjun Hong + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DependsOnBefore { + /** + * The target classes before which this component should be ordered. + */ + Class[] value() default {}; +} diff --git a/spring-core/src/main/java/org/springframework/core/OrderGraph.java b/spring-core/src/main/java/org/springframework/core/OrderGraph.java new file mode 100644 index 000000000000..c695b3d8f085 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/OrderGraph.java @@ -0,0 +1,153 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.core; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; + +/** + * A directed graph to represent relative ordering relationships. + * + * @author Yongjun Hong + */ +public class OrderGraph { + + private final Map> adjacencyList = new HashMap<>(); + private final Map inDegree = new HashMap<>(); + private final Set allNodes = new HashSet<>(); + + public void addNode(Object node) { + this.allNodes.add(node); + this.adjacencyList.putIfAbsent(node, new HashSet<>()); + this.inDegree.putIfAbsent(node, 0); + } + + /** + * Adds an edge indicating that 'from' must be ordered before 'to'. + */ + public void addEdge(Object from, Object to) { + addNode(from); + addNode(to); + + Set neighbors = this.adjacencyList.get(from); + if (neighbors != null && neighbors.add(to)) { + this.inDegree.put(to, this.inDegree.getOrDefault(to, 0) + 1); + } + } + + /** + * Performs a topological sort using Kahn's algorithm. + */ + public List topologicalSort() { + Map tempInDegree = new HashMap<>(this.inDegree); + Queue queue = new LinkedList<>(); + List result = new ArrayList<>(); + + for (Object node : this.allNodes) { + if (tempInDegree.getOrDefault(node, 0) == 0) { + queue.offer(node); + } + } + + while (!queue.isEmpty()) { + Object current = queue.poll(); + result.add(current); + + for (Object neighbor : this.adjacencyList.getOrDefault(current, Collections.emptySet())) { + tempInDegree.put(neighbor, tempInDegree.getOrDefault(neighbor, 0) - 1); + if (tempInDegree.get(neighbor) == 0) { + queue.offer(neighbor); + } + } + } + + if (result.size() != this.allNodes.size()) { + List cycle = detectCycle(); + throw new CyclicOrderException("Circular ordering dependency detected", cycle); + } + + return result; + } + + /** + * Detects a cycle in the graph using Depth-First Search (DFS). + */ + public List detectCycle() { + Set visited = new HashSet<>(); + Set recursionStack = new HashSet<>(); + Map parent = new HashMap<>(); + + for (Object node : this.allNodes) { + if (!visited.contains(node)) { + List cycle = dfsDetectCycle(node, visited, recursionStack, parent); + if (!cycle.isEmpty()) { + return cycle; + } + } + } + return Collections.emptyList(); + } + + private List dfsDetectCycle(Object node, Set visited, + Set recursionStack, Map parent) { + visited.add(node); + recursionStack.add(node); + + for (Object neighbor : this.adjacencyList.getOrDefault(node, Collections.emptySet())) { + if (neighbor != null) { + parent.put(neighbor, node); + } + + if (!visited.contains(neighbor)) { + List cycle = dfsDetectCycle(neighbor, visited, recursionStack, parent); + if (!cycle.isEmpty()) { + return cycle; + } + } + else if (recursionStack.contains(neighbor)) { + // Cycle detected - build the cycle path for the exception message + return buildCyclePath(neighbor, node, parent); + } + } + + recursionStack.remove(node); + return Collections.emptyList(); + } + + private List buildCyclePath(Object cycleStart, Object current, Map parent) { + List cycle = new ArrayList<>(); + Object node = current; + + while (node != null && !node.equals(cycleStart)) { + cycle.add(node); + node = parent.get(node); + } + if (node != null) { + cycle.add(node); + } + + Collections.reverse(cycle); + return cycle; + } +} diff --git a/spring-core/src/main/java/org/springframework/core/RelativeOrderProcessor.java b/spring-core/src/main/java/org/springframework/core/RelativeOrderProcessor.java new file mode 100644 index 000000000000..1a4b1e8986c4 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/RelativeOrderProcessor.java @@ -0,0 +1,120 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.core; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.AnnotationUtils; + +/** + * A processor that sorts a list of components based on @Order, @DependsOnBefore, and @DependsOnAfter annotations. + * This design separates the preparation phase (graph building and topological sort) + * from the comparison phase, adhering to the standard Comparator contract. + * + * @author Yongjun Hong + */ +public final class RelativeOrderProcessor { + + /** + * hared default instance of {@code RelativeOrderProcessor}. + */ + public static final RelativeOrderProcessor INSTANCE = new RelativeOrderProcessor(); + + private static final TopologicalOrderSolver TOPOLOGICAL_ORDER_SOLVER = new TopologicalOrderSolver(); + + private RelativeOrderProcessor() { + // Private constructor to prevent instantiation + } + + /** + * Sorts the given list of objects in place. + * @param list the list to be sorted. + */ + public static void sort(List list) { + if (list.size() <= 1) { + return; + } + + list.sort(getComparatorFor(list)); + } + + /** + * Creates a comparator tailored to the specific components in the input list. + * It pre-calculates the topological order for components with relative ordering needs. + * @param items the collection of items to create a comparator for. + * @return a fully configured Comparator. + */ + private static Comparator getComparatorFor(Collection items) { + List components = new ArrayList<>(items); + + Map relativeOrderMap = new HashMap<>(); + if (!components.isEmpty()) { + List sortedRelative = TOPOLOGICAL_ORDER_SOLVER.resolveOrder(components); + for (int i = 0; i < sortedRelative.size(); i++) { + relativeOrderMap.put(sortedRelative.get(i), i); + } + } + + return new ConfiguredComparator(relativeOrderMap); + } + + private static boolean hasRelativeOrderAnnotations(Object obj) { + if (obj == null) { + return false; + } + + Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); + return AnnotationUtils.findAnnotation(clazz, DependsOnBefore.class) != null || + AnnotationUtils.findAnnotation(clazz, DependsOnAfter.class) != null; + } + + /** + * The actual comparator implementation. It uses a pre-computed order map for relative + * components and falls back to AnnotationAwareOrderComparator for everything else. + */ + private static class ConfiguredComparator implements Comparator { + private final Map relativeOrderMap; + private final Comparator fallbackComparator = AnnotationAwareOrderComparator.INSTANCE; + + public ConfiguredComparator(Map relativeOrderMap) { + this.relativeOrderMap = relativeOrderMap; + } + + @Override + public int compare(Object o1, Object o2) { + boolean o1isRelative = hasRelativeOrderAnnotations(o1); + boolean o2isRelative = hasRelativeOrderAnnotations(o2); + + // Case 1: Both components have relative order. Compare their topological index. + if (o1isRelative || o2isRelative) { + int order1 = this.relativeOrderMap.getOrDefault(o1, 0); + int order2 = this.relativeOrderMap.getOrDefault(o2, 0); + return Integer.compare(order1, order2); + } + + // Case 2: One is relative, the other is absolute, or both are absolute. + // Use the fallback comparator (@Order, @Priority) for tie-breaking. + return this.fallbackComparator.compare(o1, o2); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/core/TopologicalOrderSolver.java b/spring-core/src/main/java/org/springframework/core/TopologicalOrderSolver.java new file mode 100644 index 000000000000..79e4471b9fa8 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/TopologicalOrderSolver.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.core; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.core.annotation.AnnotationUtils; + +/** + * Resolves relative ordering relationships using a topological sort. + * + * @author Yongjun Hong + */ +public class TopologicalOrderSolver { + + /** + * Resolves the relative order of the given components and returns a sorted list. + */ + public List resolveOrder(List components) { + if (components.size() <= 1) { + return new ArrayList<>(components); + } + OrderGraph graph = buildOrderGraph(components); + return graph.topologicalSort(); + } + + /** + * Builds the ordering relationship graph from the given components. + */ + private OrderGraph buildOrderGraph(List components) { + OrderGraph graph = new OrderGraph(); + + for (Object component : components) { + graph.addNode(component); + } + + for (Object component : components) { + addOrderConstraints(graph, component); + } + return graph; + } + + private void addOrderConstraints(OrderGraph graph, Object component) { + if (component == null) { + return; + } + Class componentClass = (component instanceof Class) ? (Class) component : component.getClass(); + + // Process @DependsOnBefore + DependsOnBefore dependsOnBefore = AnnotationUtils.findAnnotation(componentClass, DependsOnBefore.class); + if (dependsOnBefore != null) { + for (Class beforeClass : dependsOnBefore.value()) { + graph.addEdge(component, beforeClass); + } + } + + // Process @DependsOnAfter + DependsOnAfter dependsOnAfter = AnnotationUtils.findAnnotation(componentClass, DependsOnAfter.class); + if (dependsOnAfter != null) { + for (Class afterClass : dependsOnAfter.value()) { + graph.addEdge(afterClass, component); + } + } + } +} diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java index 4e7491abe6a2..bc5e43adc581 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java @@ -24,6 +24,7 @@ import org.springframework.core.DecoratingProxy; import org.springframework.core.OrderComparator; +import org.springframework.core.RelativeOrderProcessor; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; /** @@ -40,6 +41,7 @@ * @author Juergen Hoeller * @author Oliver Gierke * @author Stephane Nicoll + * @author Yongjun Hong * @since 2.0.1 * @see org.springframework.core.Ordered * @see org.springframework.core.annotation.Order @@ -105,9 +107,10 @@ public class AnnotationAwareOrderComparator extends OrderComparator { * @see java.util.List#sort(java.util.Comparator) */ public static void sort(List list) { - if (list.size() > 1) { - list.sort(INSTANCE); + if (list.size() <= 1) { + return; } + RelativeOrderProcessor.sort(list); } /** @@ -118,9 +121,10 @@ public static void sort(List list) { * @see java.util.Arrays#sort(Object[], java.util.Comparator) */ public static void sort(Object[] array) { - if (array.length > 1) { - Arrays.sort(array, INSTANCE); + if (array.length <= 1) { + return; } + RelativeOrderProcessor.sort(Arrays.asList(array)); } /** diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAwareOrderComparatorTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAwareOrderComparatorTests.java index 72c2c33273f1..f179db221ab9 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAwareOrderComparatorTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAwareOrderComparatorTests.java @@ -22,11 +22,17 @@ import jakarta.annotation.Priority; import org.junit.jupiter.api.Test; +import org.springframework.core.CyclicOrderException; +import org.springframework.core.DependsOnAfter; +import org.springframework.core.DependsOnBefore; + import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * @author Juergen Hoeller * @author Oliver Gierke + * @author Yongjun Hong */ class AnnotationAwareOrderComparatorTests { @@ -100,6 +106,44 @@ void sortWithNulls() { assertThat(list).containsExactly(A.class, B.class, null, null); } + @Test + void sortWithDependsOnBefore() { + List list = new ArrayList<>(); + list.add(A.class); + list.add(D.class); + AnnotationAwareOrderComparator.sort(list); + assertThat(list).containsExactly(D.class, A.class); + } + + @Test + void sortWithDependsOnAfter() { + List list = new ArrayList<>(); + list.add(B.class); + list.add(E.class); + AnnotationAwareOrderComparator.sort(list); + assertThat(list).containsExactly(B.class, E.class); + } + + @Test + void sortWithDependsOnBeforeAndAfter() { + List list = new ArrayList<>(); + list.add(A.class); + list.add(B.class); + list.add(D.class); + list.add(E.class); + AnnotationAwareOrderComparator.sort(list); + assertThat(list).containsExactly(D.class, A.class, B.class, E.class); + } + + @Test + void sortWithCircularDependsOn() { + List list = new ArrayList<>(); + list.add(F.class); + list.add(G.class); + assertThatThrownBy(() -> AnnotationAwareOrderComparator.sort(list)) + .isInstanceOf(CyclicOrderException.class); + } + @Order(1) private static class A { } @@ -119,4 +163,19 @@ private static class A2 { private static class B2 { } + @DependsOnBefore(A.class) + private static class D { + } + + @DependsOnAfter(B.class) + private static class E { + } + + @DependsOnBefore(G.class) + private static class F { + } + + @DependsOnBefore(F.class) + private static class G { + } } From 7bff887a98cc214ce37a848b602b5bb2f3bcf134 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Fri, 22 Aug 2025 23:10:45 +0900 Subject: [PATCH 2/2] Rename variable Signed-off-by: yongjunhong --- .../core/RelativeOrderProcessor.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/RelativeOrderProcessor.java b/spring-core/src/main/java/org/springframework/core/RelativeOrderProcessor.java index 1a4b1e8986c4..affd0620fce8 100644 --- a/spring-core/src/main/java/org/springframework/core/RelativeOrderProcessor.java +++ b/spring-core/src/main/java/org/springframework/core/RelativeOrderProcessor.java @@ -67,15 +67,15 @@ public static void sort(List list) { private static Comparator getComparatorFor(Collection items) { List components = new ArrayList<>(items); - Map relativeOrderMap = new HashMap<>(); + Map orderMap = new HashMap<>(); if (!components.isEmpty()) { List sortedRelative = TOPOLOGICAL_ORDER_SOLVER.resolveOrder(components); for (int i = 0; i < sortedRelative.size(); i++) { - relativeOrderMap.put(sortedRelative.get(i), i); + orderMap.put(sortedRelative.get(i), i); } } - return new ConfiguredComparator(relativeOrderMap); + return new ConfiguredComparator(orderMap); } private static boolean hasRelativeOrderAnnotations(Object obj) { @@ -93,11 +93,11 @@ private static boolean hasRelativeOrderAnnotations(Object obj) { * components and falls back to AnnotationAwareOrderComparator for everything else. */ private static class ConfiguredComparator implements Comparator { - private final Map relativeOrderMap; + private final Map orderMap; private final Comparator fallbackComparator = AnnotationAwareOrderComparator.INSTANCE; - public ConfiguredComparator(Map relativeOrderMap) { - this.relativeOrderMap = relativeOrderMap; + public ConfiguredComparator(Map orderMap) { + this.orderMap = orderMap; } @Override @@ -105,10 +105,10 @@ public int compare(Object o1, Object o2) { boolean o1isRelative = hasRelativeOrderAnnotations(o1); boolean o2isRelative = hasRelativeOrderAnnotations(o2); - // Case 1: Both components have relative order. Compare their topological index. + // Case 1: Either components have relative order. Compare their topological index. if (o1isRelative || o2isRelative) { - int order1 = this.relativeOrderMap.getOrDefault(o1, 0); - int order2 = this.relativeOrderMap.getOrDefault(o2, 0); + int order1 = this.orderMap.getOrDefault(o1, 0); + int order2 = this.orderMap.getOrDefault(o2, 0); return Integer.compare(order1, order2); }