Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<Object> cycle;

public CyclicOrderException(String message, List<Object> cycle) {
super(buildDetailedMessage(message, cycle));
this.cycle = cycle;
}

private static String buildDetailedMessage(String message, List<Object> 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<Object> getCycle() {
return this.cycle;
}
}
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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.</p>
*
* <p>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.</p>
*
* <p>This annotation is particularly useful in scenarios where the initialization order of
* components affects application behavior, and topological sorting is required to resolve
* dependencies.</p>
*
* @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 {};
}
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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.</p>
*
* <p>For example, if class A must be initialized before class B, you can annotate class A
* with {@code @DependsOnBefore(B.class)}.</p>
*
* <p>This annotation is primarily used in dependency management scenarios where
* topological sorting is required to determine the correct order of execution.</p>
*
* @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 {};
}
153 changes: 153 additions & 0 deletions spring-core/src/main/java/org/springframework/core/OrderGraph.java
Original file line number Diff line number Diff line change
@@ -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<Object, Set<Object>> adjacencyList = new HashMap<>();
private final Map<Object, Integer> inDegree = new HashMap<>();
private final Set<Object> 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<Object> 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<Object> topologicalSort() {
Map<Object, Integer> tempInDegree = new HashMap<>(this.inDegree);
Queue<Object> queue = new LinkedList<>();
List<Object> 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<Object> 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<Object> detectCycle() {
Set<Object> visited = new HashSet<>();
Set<Object> recursionStack = new HashSet<>();
Map<Object, Object> parent = new HashMap<>();

for (Object node : this.allNodes) {
if (!visited.contains(node)) {
List<Object> cycle = dfsDetectCycle(node, visited, recursionStack, parent);
if (!cycle.isEmpty()) {
return cycle;
}
}
}
return Collections.emptyList();
}

private List<Object> dfsDetectCycle(Object node, Set<Object> visited,
Set<Object> recursionStack, Map<Object, Object> 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<Object> 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<Object> buildCyclePath(Object cycleStart, Object current, Map<Object, Object> parent) {
List<Object> 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;
}
}
Loading