/*
* 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.openejb.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
/**
* @version $Rev$ $Date$
*/
public class References {
public interface Visitor<T> {
String getName(T t);
Set<String> getReferences(T t);
}
public static <T> List<T> sort(final List<T> objects, final Visitor<T> visitor) {
if (objects.size() <= 1) {
return objects;
}
final Map<String, Node> nodes = new LinkedHashMap<String, Node>();
// Create nodes
for (final T obj : objects) {
final String name = visitor.getName(obj);
final Node node = new Node(name, obj);
nodes.put(name, node);
}
// Link nodes
for (final Node node : nodes.values()) {
for (final String name : visitor.getReferences((T) node.object)) {
final Node ref = nodes.get(name);
if (ref == null) {
throw new IllegalArgumentException("No such object in list: " + name);
}
node.references.add(ref);
node.initialReferences.add(ref);
}
}
boolean circuitFounded = false;
for (final Node node : nodes.values()) {
final Set<Node> visitedNodes = new HashSet<>();
if (!normalizeNodeReferences(node, node, visitedNodes)) {
circuitFounded = true;
break;
}
node.references.addAll(visitedNodes);
}
//detect circus
if (circuitFounded) {
final Set<Circuit> circuits = new LinkedHashSet<Circuit>();
for (final Node node : nodes.values()) {
findCircuits(circuits, node, new Stack<Node>());
}
final ArrayList<Circuit> list = new ArrayList<Circuit>(circuits);
Collections.sort(list);
final List<List> all = new ArrayList<List>();
for (final Circuit circuit : list) {
all.add(unwrap(circuit.nodes));
}
throw new CircularReferencesException(all);
}
//Build Double Link Node List
final Node rootNode = new Node(null, null);
rootNode.previous = rootNode;
rootNode.next = nodes.values().iterator().next();
for (final Node node : nodes.values()) {
node.previous = rootNode.previous;
rootNode.previous.next = node;
node.next = rootNode;
rootNode.previous = node;
}
for (final Node node : nodes.values()) {
for (final Node reference : node.references) {
swap(node, reference, rootNode);
}
}
final List sortedList = new ArrayList(nodes.size());
Node currentNode = rootNode.next;
while (currentNode != rootNode) {
sortedList.add(currentNode.object);
currentNode = currentNode.next;
}
return sortedList;
}
private static boolean normalizeNodeReferences(final Node rootNode, final Node node, final Set<Node> referenceNodes) {
if (node.references.contains(rootNode)) {
return false;
}
for (final Node reference : node.references) {
if (!referenceNodes.add(reference)) {
//this reference node has been visited in the past
continue;
}
if (!normalizeNodeReferences(rootNode, reference, referenceNodes)) {
return false;
}
}
return true;
}
private static void swap(final Node shouldAfterNode, final Node shouldBeforeNode, final Node rootNode) {
Node currentNode = shouldBeforeNode;
while (currentNode.next != rootNode) {
if (currentNode.next == shouldAfterNode) {
return;
}
currentNode = currentNode.next;
}
//Remove the shouldAfterNode from list
shouldAfterNode.previous.next = shouldAfterNode.next;
shouldAfterNode.next.previous = shouldAfterNode.previous;
//Insert the node immediately after the shouldBeforeNode
shouldAfterNode.previous = shouldBeforeNode;
shouldAfterNode.next = shouldBeforeNode.next;
shouldBeforeNode.next = shouldAfterNode;
shouldAfterNode.next.previous = shouldAfterNode;
}
private static <T> List<T> unwrap(final List<Node> nodes) {
final ArrayList<T> referees = new ArrayList<T>(nodes.size());
for (final Node node : nodes) {
referees.add((T) node.object);
}
return referees;
}
private static void findCircuits(final Set<Circuit> circuits, final Node node, final Stack<Node> stack) {
if (stack.contains(node)) {
final int fromIndex = stack.indexOf(node);
final int toIndex = stack.size();
final ArrayList<Node> circularity = new ArrayList<Node>(stack.subList(fromIndex, toIndex));
// add ending node to list so a full circuit is shown
circularity.add(node);
final Circuit circuit = new Circuit(circularity);
circuits.add(circuit);
return;
}
stack.push(node);
for (final Node reference : node.initialReferences) {
findCircuits(circuits, reference, stack);
}
stack.pop();
}
private static class Node implements Comparable<Node> {
private final String name;
private final Object object;
private final List<Node> initialReferences = new ArrayList<Node>();
private final Set<Node> references = new HashSet<Node>();
private Node next;
private Node previous;
public Node(final String name, final Object object) {
this.name = name;
this.object = object;
}
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Node node = (Node) o;
return name.equals(node.name);
}
public int hashCode() {
return name.hashCode();
}
public int compareTo(final Node o) {
return this.name.compareTo(o.name);
}
public String toString() {
return name;
//return "Node("+ name +" : "+ Join.join(",", unwrap(initialReferences))+")";
}
}
private static class Circuit implements Comparable<Circuit> {
private final List<Node> nodes;
private final List<Node> atomic;
public Circuit(final List<Node> nodes) {
this.nodes = nodes;
atomic = new ArrayList<Node>(nodes);
atomic.remove(atomic.size() - 1);
Collections.sort(atomic);
}
public List<Node> getNodes() {
return nodes;
}
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Circuit circuit = (Circuit) o;
if (!atomic.equals(circuit.atomic)) {
return false;
}
return true;
}
public int hashCode() {
return atomic.hashCode();
}
public int compareTo(final Circuit o) {
int i = atomic.size() - o.atomic.size();
if (i != 0) {
return i;
}
final Iterator<Node> iterA = atomic.listIterator();
final Iterator<Node> iterB = o.atomic.listIterator();
while (iterA.hasNext() && iterB.hasNext()) {
final Node a = iterA.next();
final Node b = iterB.next();
i = a.compareTo(b);
if (i != 0) {
return i;
}
}
return 0;
}
public String toString() {
return "Circuit(" + Join.join(",", unwrap(nodes)) + ")";
}
}
}