/*!
* 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import org.pentaho.reporting.libraries.css.model.StyleKey;
import org.pentaho.reporting.libraries.base.util.DebugLog;
/**
* 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);
}
}