/********************************************************************** * Copyright (c) 2005-2009 ant4eclipse project team. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nils Hartmann, Daniel Kasmeroglu, Gerd Wuetherich **********************************************************************/ package org.ant4eclipse.lib.core.dependencygraph; import org.ant4eclipse.lib.core.Assure; import org.ant4eclipse.lib.core.CoreExceptionCode; import org.ant4eclipse.lib.core.exception.Ant4EclipseException; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; /** * <p> * A {@link DependencyGraph} can be used to serialize tree structures. The tree will be transformed in a linear * structure (List). * </p> * * <p> * A referenced node will always appear <b>prior to</b> the referencing node. Example: * * <pre> * A * | * ------------- * | | | * B C D * | * E * </pre> * * will be transformed to E,B,D,C,A or B,E,D,C,A. * </p> * * <p> * The order of the nodes depends on the order of the tree definition via {@link DependencyGraph#addVertex(Object)} and * {@link DependencyGraph#addEdge(Object, Object)}. * </p> * * @author Gerd Wütherich (gerd@gerd-wuetherich.de) * @author Daniel Kasmeroglu (daniel.kasmeroglu@kasisoft.net) * * @param <T> * the type of the vertices */ public final class DependencyGraph<T> { /** vertices */ private List<T> _vertices; /** edges */ private List<Edge<T>> _edges; /** renderer */ private VertexRenderer<T> _renderer; /** * <p> * Creates a new instance of type DependencyGraph. * </p> */ public DependencyGraph() { this._vertices = new LinkedList<T>(); this._edges = new LinkedList<Edge<T>>(); } /** * <p> * Creates a new instance of type {@link DependencyGraph}. * </p> * * @param renderer * the provided renderer is used to create a custom string representation of a vertex for further usage in an * exception message. */ public DependencyGraph(VertexRenderer<T> renderer) { this(); Assure.notNull("renderer", renderer); this._renderer = renderer; } /** * <p> * Adds a vertex to the {@link DependencyGraph}. * </p> * * @param vertex * the vertex that will be added. */ public void addVertex(T vertex) { Assure.notNull("vertex", vertex); if (!this._vertices.contains(vertex)) { this._vertices.add(vertex); } } /** * <p> * Returns <code>true</code>, if the given vertex has already been added to the {@link DependencyGraph}. * </p> * * @param vertex * the vertex * @return <code>true</code>, if the given vertex has already been added to the {@link DependencyGraph}, otherwise * <code>false</code>. */ public boolean containsVertex(T vertex) { Assure.notNull("vertex", vertex); return this._vertices.contains(vertex); } /** * <p> * Adds an edge to the {@link DependencyGraph}. * </p> * * @param parent * the parent node * @param child * the child node */ public void addEdge(T parent, T child) { Assure.notNull("parent", parent); Assure.notNull("child", child); addVertex(parent); addVertex(child); this._edges.add(new Edge<T>(parent, child)); } /** * <p> * Computers the order of all the nodes. * </p> * * @return the ordered list of all the nodes.. */ public List<T> calculateOrder() { // setup a matrix that contains a true value iff there is a the first index donates a parent of the second value boolean[][] matrix = new boolean[this._vertices.size()][this._vertices.size()]; // fill the diagonale for (boolean[] element : matrix) { Arrays.fill(element, false); } // set each value to true iff there is a relationship form first to second... for (int i = 0; i < this._edges.size(); i++) { Edge<T> edge = this._edges.get(i); int fromidx = this._vertices.indexOf(edge.getParent()); int toidx = this._vertices.indexOf(edge.getChild()); if ((fromidx == -1) || (toidx == -1)) { // one of the edge's vertices has not been // added to this graph (f.e. if it's a dependency // on the outside of a specific context) continue; } matrix[fromidx][toidx] = true; } List<T> list = new LinkedList<T>(); // iterates across the matrix as long as we didn't found // a cycle and there are still edges to be processed while (reduce(list, matrix)) { // nothing to do... } return list; } /** * <p> * Reduces the internal matrix. Reduction means that the supplied matrix will be modified while vertices that doesn't * refer to any other one can be added to the list since they no longer depend on another vertex. Reduction is an * iterative process, so it's necessary to run this function until the matrix doesn't contain an edge. * </p> * * @param result * The list that will receive the vertices. * @param matrix * The current state of the matrix representing the graph. * * @return true <=> There are still edges within the matrix so a succeeding iteration is required. */ private boolean reduce(List<T> result, boolean[][] matrix) { int zeros = 0; int[] count = countEdges(matrix); List<T> removable = new LinkedList<T>(); // add currently independent vertices to the list // of removable candidates for (int i = 0; i < count.length; i++) { if (count[i] == 0) { T vertex = this._vertices.get(i); if (!result.contains(vertex)) { removable.add(vertex); } zeros++; } } if (removable.isEmpty()) { if (zeros < matrix.length) { // get the first cycle and create an apropriate textual representation StringBuffer buffer = new StringBuffer(); for (int i = 0; i < count.length; i++) { if (count[i] > 0) { cycleString(buffer, new HashSet<T>(), i, matrix); break; } } throw new Ant4EclipseException(CoreExceptionCode.CYCLIC_DEPENDENCIES_EXCEPTION, buffer.toString()); } } else { // we need to clear the removable vertices, so they won't // be processed within the next iteration for (int i = 0; i < matrix.length; i++) { for (int j = 0; j < matrix.length; j++) { if (matrix[i][j]) { if (removable.contains(this._vertices.get(j))) { matrix[i][j] = false; } } } } result.addAll(removable); } return zeros < matrix.length; } /** * <p> * Creates a String which is used to create a textual representation of a cycle. * </p> * * @param buffer * The buffer used to collect the data. * @param processed * A list of added vertices which is required for the abortion criteria. * @param idx * The index of the vertex which is part of the cycle. * @param matrix * The current state of the matrix. */ private void cycleString(StringBuffer buffer, Set<T> processed, int idx, boolean[][] matrix) { T vertex = this._vertices.get(idx); if (this._renderer == null) { buffer.append(String.valueOf(vertex)); } else { buffer.append(this._renderer.renderVertex(vertex)); } if (processed.contains(vertex)) { return; } buffer.append(" -> "); processed.add(vertex); for (int i = 0; i < matrix.length; i++) { if (matrix[idx][i]) { cycleString(buffer, processed, i, matrix); break; } } } /** * <p> * Returns a list with the counted number of edges. * </p> * * @param matrix * The matrix which provides graph representation. * * @return A list with the length of the matrix where each element holds the number of edges of the related row. */ private int[] countEdges(boolean[][] matrix) { int[] result = new int[matrix.length]; // count the number of edges for each row for (int i = 0; i < matrix.length; i++) { result[i] = sum(matrix[i]); } return result; } /** * <p> * Returns the number of 'true'-values in the given array. * </p> * * @param row * the array of boolean * @return the number of 'true'-values in the given array. */ private int sum(boolean[] row) { int result = 0; for (boolean element : row) { if (element) { result++; } } return result; } } /* ENDCLASS */