blob: af47f295247afd864790d8da68c71c5e49c6c835 [file] [log] [blame]
/**
*
* 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.aries.blueprint.container;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.aries.blueprint.di.CircularDependencyException;
import org.apache.aries.blueprint.di.Recipe;
import org.apache.aries.blueprint.di.RefRecipe;
import org.osgi.service.blueprint.container.NoSuchComponentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DependencyGraph {
private static final Logger LOGGER = LoggerFactory.getLogger(DependencyGraph.class);
private BlueprintRepository repository;
public DependencyGraph(BlueprintRepository repository) {
this.repository = repository;
}
public LinkedHashMap<String, Recipe> getSortedRecipes(Collection<String> names) {
// construct the graph
Map<String, Node> nodes = new LinkedHashMap<String, Node>();
for (String name : names) {
Object object = repository.getObject(name);
if (object == null) {
throw new NoSuchComponentException(name);
}
if (object instanceof Recipe) {
Recipe recipe = (Recipe) object;
if (!recipe.getName().equals(name)) {
throw new RuntimeException("Recipe '" + name + "' returned from the repository has name '" + name + "'");
}
createNode(name, recipe, nodes);
}
}
// find all initial leaf nodes (and islands)
List<Node> sortedNodes = new ArrayList<Node>(nodes.size());
LinkedList<Node> leafNodes = new LinkedList<Node>();
for (Node n : nodes.values()) {
if (n.referenceCount == 0) {
// if the node is totally isolated (no in or out refs),
// move it directly to the finished list, so they are first
if (n.references.size() == 0) {
sortedNodes.add(n);
} else {
leafNodes.add(n);
}
}
}
// pluck the leaves until there are no leaves remaining
while (!leafNodes.isEmpty()) {
Node node = leafNodes.removeFirst();
sortedNodes.add(node);
for (Node ref : node.references) {
ref.referenceCount--;
if (ref.referenceCount == 0) {
leafNodes.add(ref);
}
}
}
// There are no more leaves so if there are there still
// unprocessed nodes in the graph, we have one or more curcuits
if (sortedNodes.size() != nodes.size()) {
findCircuit(nodes.values().iterator().next(), new ArrayList<Recipe>(nodes.size()));
// find circuit should never fail, if it does there is a programming error
throw new RuntimeException("Internal Error: expected a CircularDependencyException");
}
// return the recipes
LinkedHashMap<String, Recipe> sortedRecipes = new LinkedHashMap<String, Recipe>();
for (Node node : sortedNodes) {
sortedRecipes.put(node.name, node.recipe);
}
return sortedRecipes;
}
private void findCircuit(Node node, ArrayList<Recipe> stack) {
if (stack.contains(node.recipe)) {
ArrayList<Recipe> circularity = new ArrayList<Recipe>(stack.subList(stack.indexOf(node.recipe), stack.size()));
// remove anonymous nodes from circularity list
for (Iterator<Recipe> iterator = circularity.iterator(); iterator.hasNext();) {
Recipe recipe = iterator.next();
if (recipe != node.recipe && recipe.getName() == null) {
iterator.remove();
}
}
// add ending node to list so a full circuit is shown
circularity.add(node.recipe);
throw new CircularDependencyException(circularity);
}
stack.add(node.recipe);
for (Node reference : node.references) {
findCircuit(reference, stack);
}
}
private Node createNode(String name, Recipe recipe, Map<String, Node> nodes) {
// if node already exists, verify that the exact same recipe instnace is used for both
if (nodes.containsKey(name)) {
Node node = nodes.get(name);
if (node.recipe != recipe) {
throw new RuntimeException("The name '" + name +"' is assigned to multiple recipies");
}
return node;
}
// create the node
Node node = new Node();
node.name = name;
node.recipe = recipe;
nodes.put(name, node);
// link in the references
LinkedList<Recipe> constructorRecipes = new LinkedList<Recipe>(recipe.getConstructorDependencies());
while (!constructorRecipes.isEmpty()) {
Recipe nestedRecipe = constructorRecipes.removeFirst();
if (nestedRecipe instanceof RefRecipe) {
nestedRecipe = nestedRecipe.getDependencies().get(0);
String nestedName = nestedRecipe.getName();
Node nestedNode = createNode(nestedName, nestedRecipe, nodes);
node.referenceCount++;
nestedNode.references.add(node);
} else {
constructorRecipes.addAll(nestedRecipe.getDependencies());
}
}
return node;
}
private class Node {
String name;
Recipe recipe;
final List<Node> references = new ArrayList<Node>();
int referenceCount;
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("Node[").append(name);
if (references.size() > 0) {
buf.append(" <- ");
Iterator<Node> iter = references.iterator();
while(iter.hasNext()) {
buf.append(iter.next().name);
if (iter.hasNext()) {
buf.append(", ");
}
}
}
buf.append("]");
return buf.toString();
}
}
}