/* ======================================================================== * JCommon : a free general purpose class library for the Java(tm) platform * ======================================================================== * * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. * * Project Info: http://www.jfree.org/jcommon/index.html * * This library 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 2.1 of the License, or * (at your option) any later version. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * [Java is a trademark or registered trademark of Sun Microsystems, Inc. * in the United States and other countries.] * * ------------------ * PackageSorter.java * ------------------ * (C)opyright 2003, 2004, by Thomas Morgner and Contributors. * * Original Author: Thomas Morgner; * Contributor(s): David Gilbert (for Object Refinery Limited); * * $Id: PackageSorter.java,v 1.3 2007/05/15 12:32:15 taqua Exp $ * * Changes * ------- * 02-Sep-2003 : Initial version * 07-Jun-2004 : Added JCommon header (DG); * */ package org.jfree.base.modules; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.jfree.util.Log; /** * Compares two modules for order. A module is considered less than an other * module if the module is a required module of the compared module. Modules * are considered equal if they have no relation. * <p> * When sorting, we match this modules position against all dependent * modules until all positions are stable. Circular references are evil * and are filtered during the module loading process in the package manager. * * @author Thomas Morgner */ public final class PackageSorter { /** * An Internal wrapper class which collects additional information * on the given module. Every module has a position, which is heigher * than the position of all dependent modules. * * @author Thomas Morgner */ private static class SortModule implements Comparable { /** stores the relative position of the module in the global list. */ private int position; /** The package state of the to be matched module. */ private final PackageState state; /** A list of all directly dependent subsystems. */ private ArrayList dependSubsystems; // direct dependencies, indirect ones are handled by the // dependent classes ... /** * Creates a new SortModule for the given package state. * * @param state the package state object, that should be wrapped up * by this class. */ public SortModule(final PackageState state) { this.position = -1; this.state = state; } /** * Returns the list of all dependent subsystems. The list gets defined * when the sorting is started. * * @return the list of all dependent subsystems. */ public ArrayList getDependSubsystems() { return this.dependSubsystems; } /** * Defines a list of dependent subsystems for this module. The list contains * the names of the dependent subsystems as strings. * * @param dependSubsystems a list of all dependent subsystems. */ public void setDependSubsystems(final ArrayList dependSubsystems) { this.dependSubsystems = dependSubsystems; } /** * Returns the current position of this module in the global list. * The position is computed by comparing all positions of all dependent * subsystem modules. * * @return the current module position. */ public int getPosition() { return this.position; } /** * Defines the position of this module in the global list of all * known modules. * * @param position the position. */ public void setPosition(final int position) { this.position = position; } /** * Returns the package state contained in this SortModule. * * @return the package state of this module. */ public PackageState getState() { return this.state; } /** * Returns a basic string representation of this SortModule. This * should be used for debugging purposes only. * @see java.lang.Object#toString() * * @return a string representation of this module. */ public String toString () { final StringBuffer buffer = new StringBuffer(); buffer.append("SortModule: "); buffer.append(this.position); buffer.append(" "); buffer.append(this.state.getModule().getName()); buffer.append(" "); buffer.append(this.state.getModule().getModuleClass()); return buffer.toString(); } /** * Compares this module against an other sort module. * * @see java.lang.Comparable#compareTo(java.lang.Object) * * @param o the other sort module instance. * @return -1 if the other's module position is less than * this modules position, +1 if this module is less than the * other module or 0 if both modules have an equal position in * the list. */ public int compareTo(final Object o) { final SortModule otherModule = (SortModule) o; if (this.position > otherModule.position) { return +1; } if (this.position < otherModule.position) { return -1; } return 0; } } /** * DefaultConstructor. */ private PackageSorter() { // nothing required. } /** * Sorts the given list of package states. The packages * are sorted by their dependencies in a way so that all * dependent packages are placed on lower positions than * the packages which declared the dependency. * * @param modules the list of modules. */ public static void sort (final List modules) { final HashMap moduleMap = new HashMap(); final ArrayList errorModules = new ArrayList(); final ArrayList weightModules = new ArrayList(); for (int i = 0; i < modules.size(); i++) { final PackageState state = (PackageState) modules.get(i); if (state.getState() == PackageState.STATE_ERROR) { errorModules.add (state); } else { final SortModule mod = new SortModule(state); weightModules.add (mod); moduleMap.put(state.getModule().getModuleClass(), mod); } } final SortModule[] weigths = (SortModule[]) weightModules.toArray(new SortModule[weightModules.size()]); for (int i = 0; i < weigths.length; i++) { final SortModule sortMod = weigths[i]; sortMod.setDependSubsystems (collectSubsystemModules(sortMod.getState().getModule(), moduleMap)); } // repeat the computation until all modules have a matching // position. This is not the best algorithm, but it works // and is relativly simple. It will need some optimizations // in the future, but as this is only executed once, we don't // have to care much about it. boolean doneWork = true; while (doneWork) { doneWork = false; for (int i = 0; i < weigths.length; i++) { final SortModule mod = weigths[i]; final int position = searchModulePosition(mod, moduleMap); if (position != mod.getPosition()) { mod.setPosition(position); doneWork = true; } } } Arrays.sort(weigths); modules.clear(); for (int i = 0; i < weigths.length; i++) { modules.add (weigths[i].getState()); } for (int i = 0; i < errorModules.size(); i++) { modules.add (errorModules.get(i)); } } /** * Computes the new module position. This position is computed * according to the dependent modules and subsystems. The returned * position will be higher than the highest dependent module position. * * @param smodule the sort module for that we compute the new positon. * @param moduleMap the map with all modules. * @return the new positon. */ private static int searchModulePosition (final SortModule smodule, final HashMap moduleMap) { final Module module = smodule.getState().getModule(); int position = 0; // check the required modules. Increase our level to at least // one point over the highest dependent module // ignore missing modules. ModuleInfo[] modInfo = module.getOptionalModules(); for (int modPos = 0; modPos < modInfo.length; modPos++) { final String moduleName = modInfo[modPos].getModuleClass(); final SortModule reqMod = (SortModule) moduleMap.get(moduleName); if (reqMod == null) { continue; } if (reqMod.getPosition() >= position) { position = reqMod.getPosition() + 1; } } // check the required modules. Increase our level to at least // one point over the highest dependent module // there are no missing modules here (or the package manager // is invalid) modInfo = module.getRequiredModules(); for (int modPos = 0; modPos < modInfo.length; modPos++) { final String moduleName = modInfo[modPos].getModuleClass(); final SortModule reqMod = (SortModule) moduleMap.get(moduleName); if (reqMod == null) { Log.warn ("Invalid state: Required dependency of '" + moduleName + "' had an error."); continue; } if (reqMod.getPosition() >= position) { position = reqMod.getPosition() + 1; } } // check the subsystem dependencies. This way we make sure // that subsystems are fully initialized before we try to use // them. final String subSystem = module.getSubSystem(); final Iterator it = moduleMap.values().iterator(); while (it.hasNext()) { final SortModule mod = (SortModule) it.next(); // it is evil to compute values on ourself... if (mod.getState().getModule() == module) { // same module ... continue; } final Module subSysMod = mod.getState().getModule(); // if the module we check is part of the same subsystem as // we are, then we dont do anything. Within the same subsystem // the dependencies are computed solely by the direct references. if (subSystem.equals(subSysMod.getSubSystem())) { // same subsystem ... ignore continue; } // does the module from the global list <mod> depend on the // subsystem we are part of? // // if yes, we have a relation and may need to adjust the level... if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem())) { // check whether the module is a base module of the given // subsystem. We will not adjust our position in that case, // as this would lead to an infinite loop if (isBaseModule(subSysMod, module) == false) { if (mod.getPosition() >= position) { position = mod.getPosition() + 1; } } } } return position; } /** * Checks, whether a module is a base module of an given module. * * @param mod the module which to check * @param mi the module info of the suspected base module. * @return true, if the given module info describes a base module of the * given module, false otherwise. */ private static boolean isBaseModule(final Module mod, final ModuleInfo mi) { ModuleInfo[] info = mod.getRequiredModules(); for (int i = 0; i < info.length; i++) { if (info[i].getModuleClass().equals(mi.getModuleClass())) { return true; } } info = mod.getOptionalModules(); for (int i = 0; i < info.length; i++) { if (info[i].getModuleClass().equals(mi.getModuleClass())) { return true; } } return false; } /** * Collects all directly dependent subsystems. * * @param childMod the module which to check * @param moduleMap the map of all other modules, keyed by module class. * @return the list of all dependent subsystems. */ private static ArrayList collectSubsystemModules (final Module childMod, final HashMap moduleMap) { final ArrayList collector = new ArrayList(); ModuleInfo[] info = childMod.getRequiredModules(); for (int i = 0; i < info.length; i++) { final SortModule dependentModule = (SortModule) moduleMap.get(info[i].getModuleClass()); if (dependentModule == null) { Log.warn (new Log.SimpleMessage ("A dependent module was not found in the list of known modules.", info[i].getModuleClass())); continue; } collector.add (dependentModule.getState().getModule().getSubSystem()); } info = childMod.getOptionalModules(); for (int i = 0; i < info.length; i++) { final Module dependentModule = (Module) moduleMap.get(info[i].getModuleClass()); if (dependentModule == null) { Log.warn ("A dependent module was not found in the list of known modules."); continue; } collector.add (dependentModule.getSubSystem()); } return collector; } }