/* * Copyright Technophobia Ltd 2012 * * This file is part of Substeps. * * Substeps is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Substeps is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Substeps. If not, see <http://www.gnu.org/licenses/>. */ package com.technophobia.substeps.runner; import java.util.Collection; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.technophobia.substeps.model.exception.SubstepsConfigurationException; /** * * Responsible for determining the running order of initialisation classes. * * To do this this class builds a dependency tree of initialisation classes, as * it adds a new initialisation class to the tree it ensures it is not at the * same time a parent and child of another node. Such a condition is equivalent * to a class which must be executed both before and after another. * * Once this graph is fully built it is converted into a list by traversing the * graph only adding nodes whose parents have already been added, it keeps doing * this until all nodes have been added. * * @author rbarefield */ public class InitialisationClassSorter { private final InitialisationClassNode root = new InitialisationClassNode(null); protected static final Logger log = LoggerFactory.getLogger(InitialisationClassSorter.class); public void addOrderedInitialisationClasses(Class<?>... classes) { InitialisationClassNode parentNode = root; for (Class<?> initClass : classes) { InitialisationClassNode newNode = new InitialisationClassNode(initClass); InitialisationClassNode existingNode = root.findNodeWith(initClass); if (existingNode != null) { newNode = existingNode; if (existingNode.hasChild(parentNode)) { throw new SubstepsConfigurationException("The order is invalid as " + existingNode.getInitialisationClass().getName() + " must come before and after " + parentNode.getInitialisationClass().getName()); } parentNode.addChild(existingNode); } else { parentNode.addChild(newNode); } parentNode = newNode; } } public List<Class<?>> getOrderedList() { List<Class<?>> linerizeClasses = Lists.newArrayList(); List<InitialisationClassNode> allNodes = root.linearize(); for (InitialisationClassNode node : allNodes.subList(1, allNodes.size())) { linerizeClasses.add(node.getInitialisationClass()); } return linerizeClasses; } } class InitialisationClassNode { private final Set<InitialisationClassNode> parentNodes = Sets.newHashSet(); private final Set<InitialisationClassNode> childNodes = Sets.newHashSet(); private final Class<?> initialisationClass; public InitialisationClassNode(Class<?> initialisationClass) { this.initialisationClass = initialisationClass; } public boolean hasParent(InitialisationClassNode parent) { return getAllParents().contains(parent); } private Collection<InitialisationClassNode> getAllParents() { List<InitialisationClassNode> parents = Lists.newArrayList(this); int i = 0; while (i < parents.size()) { InitialisationClassNode node = parents.get(i); Set<InitialisationClassNode> parentsOfNodes = node.parentNodes; for (InitialisationClassNode parentOfNode : parentsOfNodes) { if (!parents.contains(parentOfNode)) { parents.add(parentOfNode); } } i++; } return parents; } public Class<?> getInitialisationClass() { return initialisationClass; } public void removeChild(InitialisationClassNode child) { this.childNodes.remove(child); child.parentNodes.remove(this); } public void addChild(InitialisationClassNode child) { this.childNodes.add(child); child.parentNodes.add(this); } public boolean hasChild(InitialisationClassNode child) { if (this.childNodes.contains(child)) { return true; } for (InitialisationClassNode myChildNode : childNodes) { if (myChildNode.hasChild(child)) { return true; } } return false; } public InitialisationClassNode findNodeWith(Class<?> initialisationClass) { if (this.initialisationClass != null && this.initialisationClass == initialisationClass) { return this; } else { for (InitialisationClassNode childNode : childNodes) { InitialisationClassNode fromChild = childNode.findNodeWith(initialisationClass); if (fromChild != null) { return fromChild; } } return null; } } private boolean hasParentNotIn(Collection<InitialisationClassNode> nodes) { for (InitialisationClassNode parent : parentNodes) { if (!nodes.contains(parent)) { return true; } } return false; } public boolean addChildren(List<InitialisationClassNode> nodes) { boolean complete = true; for (InitialisationClassNode child : childNodes) { if (!child.hasParentNotIn(nodes)) { if (!nodes.contains(child)) { nodes.add(child); } complete &= child.addChildren(nodes); } } return complete; } public List<InitialisationClassNode> linearize() { List<InitialisationClassNode> allNodes = Lists.newArrayList(); allNodes.add(this); int safetyCount = 0; while (!this.addChildren(allNodes)) { safetyCount++; if (safetyCount > 100) { String message = "Unable to resolve class initialisation order, please log this as a bug with substeps"; InitialisationClassSorter.log.error(message); throw new RuntimeException(message); } } return allNodes; } @Override public String toString() { return this.initialisationClass == null ? "root" : this.initialisationClass.toString(); } }