/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program 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. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.libraries.base.boot; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.libraries.base.util.ObjectUtilities; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; /** * 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 * @noinspection ComparableImplementedButEqualsNotOverridden */ 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. */ private 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, never null. * @noinspection AssignmentToCollectionOrArrayFieldFromParameter as this is a 100% private class and it is * guaranteed that no one is going to change the array list in question. */ public void setDependSubsystems( final ArrayList<String> 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. * * @return a string representation of this module. * @see Object#toString() */ public String toString() { final StringBuilder buffer = new StringBuilder( 100 ); 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. * * @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. * @see Comparable#compareTo(Object) */ 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; } } /** * A logger for debug-messages. */ private static final Log LOGGER = LogFactory.getLog( PackageSorter.class ); /** * 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<PackageState> modules ) { if ( modules == null ) { throw new NullPointerException(); } final HashMap<String, SortModule> moduleMap = new HashMap<String, SortModule>(); final ArrayList<PackageState> errorModules = new ArrayList<PackageState>(); final ArrayList<SortModule> weightModules = new ArrayList<SortModule>(); final int modulesCount = modules.size(); for ( int i = 0; i < modulesCount; i++ ) { final PackageState state = 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 = 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. final ModuleInfo[] optionalModules = module.getOptionalModules(); for ( int modPos = 0; modPos < optionalModules.length; modPos++ ) { final String moduleName = optionalModules[ 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) final ModuleInfo[] requiredModules = module.getRequiredModules(); for ( int modPos = 0; modPos < requiredModules.length; modPos++ ) { final String moduleName = requiredModules[ modPos ].getModuleClass(); final SortModule reqMod = (SortModule) moduleMap.get( moduleName ); if ( reqMod == null ) { LOGGER.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 ( ObjectUtilities.equal( subSystem, 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 ) { final ModuleInfo[] requiredModules = mod.getRequiredModules(); for ( int i = 0; i < requiredModules.length; i++ ) { if ( requiredModules[ i ].getModuleClass().equals( mi.getModuleClass() ) ) { return true; } } final ModuleInfo[] optionalModules = mod.getOptionalModules(); for ( int i = 0; i < optionalModules.length; i++ ) { if ( optionalModules[ 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<String> collectSubsystemModules ( final Module childMod, final HashMap<String, SortModule> moduleMap ) { final ArrayList<String> collector = new ArrayList<String>(); final ModuleInfo[] requiredModules = childMod.getRequiredModules(); for ( int i = 0; i < requiredModules.length; i++ ) { final SortModule dependentModule = moduleMap.get( requiredModules[ i ].getModuleClass() ); if ( dependentModule == null ) { LOGGER.warn ( "A dependent module was not found in the list of known modules." + requiredModules[ i ].getModuleClass() ); continue; } collector.add( dependentModule.getState().getModule().getSubSystem() ); } final ModuleInfo[] optionalModules = childMod.getOptionalModules(); for ( int i = 0; i < optionalModules.length; i++ ) { final Object o = moduleMap.get( optionalModules[ i ].getModuleClass() ); final SortModule dependentModule = (SortModule) o; if ( dependentModule == null ) { LOGGER.warn( "A dependent module was not found in the list of known modules." ); continue; } collector.add( dependentModule.getState().getModule().getSubSystem() ); } return collector; } }