/*!
* 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.css.resolver.values;
import org.pentaho.reporting.libraries.base.util.DebugLog;
import org.pentaho.reporting.libraries.css.model.StyleKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
/**
* Compares two resolve handlers for order. A handler declares its dependencies and therefore requires that all
* dependent styles have been resolved before trying to compute these properties.
* <p/>
* When sorting, we match this modules position against all dependent modules until all positions are stable. Circular
* references are evil and must be filtered before passing the classes to this sorter.
*
* @author Thomas Morgner
*/
public final class ResolveHandlerSorter {
/**
* 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 StyleKey key;
/**
* A list of all directly dependent subsystems.
*/
private StyleKey[] dependentKeys;
private HashSet dependentKeySet;
private ResolveHandlerModule module;
/**
* Creates a new SortModule for the given package state.
*
* @param module the package state object, that should be wrapped up by this class.
*/
private SortModule( final ResolveHandlerModule module ) {
this.position = -1;
this.key = module.getKey();
this.module = module;
this.dependentKeySet = new HashSet();
addKeys( module.getDependentKeys() );
}
public boolean isDependency( final StyleKey key ) {
return dependentKeySet.contains( key );
}
public ResolveHandlerModule getModule() {
return module;
}
/**
* Returns the list of all dependent subsystems. The list gets defined when the sorting is started.
*
* @return the list of all dependent subsystems.
*/
public StyleKey[] getDependentKeys() {
if ( dependentKeys == null ) {
dependentKeys = (StyleKey[]) dependentKeySet.toArray
( new StyleKey[ dependentKeySet.size() ] );
}
return dependentKeys;
}
public void addKeys( final StyleKey[] keys ) {
boolean haveAdded = false;
for ( int i = 0; i < keys.length; i++ ) {
final StyleKey styleKey = keys[ i ];
if ( dependentKeySet.add( styleKey ) ) {
haveAdded = true;
}
}
if ( haveAdded ) {
dependentKeys = null;
}
}
/**
* 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;
this.module.setWeight( position );
}
/**
* Returns the key contained in this SortModule.
*
* @return the key of this module.
*/
public StyleKey getKey() {
return this.key;
}
/**
* 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 StringBuffer buffer = new StringBuffer();
buffer.append( "SortModule: " );
buffer.append( this.position );
buffer.append( ' ' );
buffer.append( this.getKey().getName() );
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;
}
}
/**
* DefaultConstructor.
*/
private ResolveHandlerSorter() {
// 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 ResolveHandlerModule[] sort( final ResolveHandlerModule[] modules ) {
// maps keys to sortmodules
final HashMap moduleMap = new HashMap();
for ( int i = 0; i < modules.length; i++ ) {
moduleMap.put( modules[ i ].getKey(), new SortModule( modules[ i ] ) );
}
final SortModule[] sortModules = (SortModule[])
moduleMap.values().toArray( new SortModule[ moduleMap.size() ] );
final ArrayList parents = new ArrayList();
for ( int i = 0; i < sortModules.length; i++ ) {
final SortModule sortMod = sortModules[ i ];
computeSubsystemModules( sortMod, parents, moduleMap );
parents.clear();
}
// 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 < sortModules.length; i++ ) {
final SortModule mod = sortModules[ i ];
final int position = searchModulePosition( mod, moduleMap );
if ( position != mod.getPosition() ) {
mod.setPosition( position );
doneWork = true;
}
}
}
Arrays.sort( sortModules );
final ResolveHandlerModule[] retvals = new ResolveHandlerModule[ sortModules.length ];
for ( int i = 0; i < sortModules.length; i++ ) {
retvals[ i ] = sortModules[ i ].getModule();
}
return retvals;
}
/**
* 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 ) {
int position = 0;
// check the required modules. Increase our level to at least
// one point over the highest dependent module
// ignore missing modules.
final StyleKey[] dependentKeys = smodule.getDependentKeys();
for ( int modPos = 0; modPos < dependentKeys.length; modPos++ ) {
final SortModule reqMod = (SortModule) moduleMap.get( dependentKeys[ modPos ] );
if ( reqMod == null ) {
continue;
}
if ( reqMod.getPosition() >= position ) {
position = reqMod.getPosition() + 1;
}
}
return position;
}
/**
* Collects all directly dependent subsystems.
*/
private static void computeSubsystemModules
( final SortModule module,
final ArrayList parents,
final HashMap moduleMap ) {
if ( parents.contains( module ) ) {
throw new IllegalStateException( "Loop detected:" + module + " (" + parents + ')' );
}
final StyleKey[] keys = module.getDependentKeys();
parents.add( module );
for ( int i = 0; i < keys.length; i++ ) {
final StyleKey key = keys[ i ];
if ( key.equals( module.getKey() ) ) {
throw new IllegalStateException( "module referencing itself as dependency" );
}
final SortModule child = (SortModule) moduleMap.get( key );
if ( child == null ) {
DebugLog.log( "Documented dependency but have no module for that one: " + key );
} else {
computeSubsystemModules( child, parents, moduleMap );
module.addKeys( child.getDependentKeys() );
}
}
parents.remove( module );
}
}