/** * * 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(); } } }