/*******************************************************************************
* Copyright (c) 2006-2012
* Software Technology Group, Dresden University of Technology
* DevBoost GmbH, Berlin, Amtsgericht Charlottenburg, HRB 140026
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Software Technology Group - TU Dresden, Germany;
* DevBoost GmbH - Berlin, Germany
* - initial API and implementation
******************************************************************************/
package org.reuseware.sokan.index.indexer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.reuseware.sokan.index.SokanIndexPlugin;
import org.reuseware.sokan.index.util.CoreUtil;
/**
* The index sorter is responsible for sorting indexers according
* to dependencies between them.
*/
public class IndexerSorter {
/**
* Maps indexerID to iConfig.
*/
private Map<String, IndexerConfiguration> idMap;
/**
* Maps indexerID to list of in-going indexerIDs <br>
* all indexers that this indexer depends on.
*/
private HashMap<String, Set<String>> dependIn;
/**
* Maps indexerID to list of out-going indexerIDs <br>
* all indexer that depend on this indexer.
*/
private Map<String, Set<String>> dependOut;
/**
* Queue of indexerIDs sorted by dependInCount.
*/
private List<String> unsorted;
/**
* Queue of sorted indexerIDs to retrieve by method sort()
*/
// private Queue<IndexerConfiguration> sorted;
/**
* Linked list of linked list of sorted indexerIDs <br>
* every inner list represents one index creation phase after which a commit
* is needed.
*/
private List<List<IndexerConfiguration>> sortedNew;
/**
* Indexer dependency cycles.
*/
private List<TreeSet<String>> cycles;
private class IndexerComparator<T> implements Comparator<T> {
public int compare(T obj1, T obj2) {
if (obj1.equals(obj2)) {
return 0;
}
if (!(obj1 instanceof String) || !(obj2 instanceof String)) {
return 0;
}
String id1 = (String) obj1;
String id2 = (String) obj2;
int count1 = getOutCount(id1);
int count2 = getOutCount(id2);
if (count1 < count2) {
return count1 - count2;
}
if (count1 > count2) {
return count1 - count2;
}
// return 0;
if (id1.compareTo(id2) < 0) {
return -1;
}
if (id1.compareTo(id2) > 0) {
return 1;
}
return 0;
}
}
/**
* Splits the given list of indexer into chunks according to their dependencies.
* Indexers that are grouped into one list, are independent and can be executed together
* between two index commits.
*
* @param indexerConfigs the indexers to sort
* @return the indexrs sorted into sub-lists according to their dependencies
*/
public List<List<IndexerConfiguration>> sort(
List<IndexerConfiguration> indexerConfigs) {
List<List<IndexerConfiguration>> mySorted = new LinkedList<List<IndexerConfiguration>>();
if (indexerConfigs == null || indexerConfigs.size() == 0) {
return mySorted;
}
if (indexerConfigs.size() == 1) {
LinkedList<IndexerConfiguration> singleCommitPhase = new LinkedList<IndexerConfiguration>();
singleCommitPhase.add(indexerConfigs.get(0));
mySorted.add(singleCommitPhase);
return mySorted;
}
buildDataStructures(indexerConfigs);
removeCycles();
performSort();
return sortedNew;
}
private void buildDataStructures(List<IndexerConfiguration> indexerConfigs) {
// build idMap
idMap = new TreeMap<String, IndexerConfiguration>();
for (IndexerConfiguration iConfig : indexerConfigs) {
idMap.put(iConfig.getId(), iConfig);
}
// build sorted
// sorted = new LinkedList<IndexerConfiguration>();
sortedNew = new LinkedList<List<IndexerConfiguration>>();
dependIn = new HashMap<String, Set<String>>(indexerConfigs.size());
dependOut = new HashMap<String, Set<String>>(indexerConfigs.size());
List<String> parentIndexerList;
Set<String> parents;
Set<String> children;
for (Entry<String, IndexerConfiguration> idPair : idMap.entrySet()) {
// pre-calculations
parentIndexerList = new LinkedList<String>();
for (String dependencyID : idPair.getValue().getDependencies()) {
parentIndexerList.add(dependencyID);
}
for (String parent : new ArrayList<String>(parentIndexerList)) {
if (idMap.get(parent) == null) {
parentIndexerList.remove(parent);
}
}
if (parentIndexerList == null || parentIndexerList.size() == 0) {
continue;
}
parents = new TreeSet<String>();
parents.addAll(parentIndexerList);
// build dependOut
dependOut.put(idPair.getKey(), parents);
// build dependIn
for (String parentID : parents) {
children = dependIn.get(parentID);
if (children != null) {
children.add(idPair.getKey());
} else {
children = new TreeSet<String>();
children.add(idPair.getKey());
dependIn.put(parentID, children);
}
}
}
// build unsorted
unsorted = new LinkedList<String>();
unsorted.addAll(idMap.keySet());
}
private void removeCycles() {
this.cycles = new LinkedList<TreeSet<String>>();
// detect cycles
List<String> visited = new LinkedList<String>();
List<String> idCache;
Collections.sort(unsorted, new IndexerComparator<String>());
Collections.reverse(unsorted);
for (String id : unsorted) {
if (visited.contains(id)) {
continue;
}
idCache = new LinkedList<String>();
idCache.add(id);
visited.add(id);
split(idCache, visited, dependOut.get(id));
}
// cut cycles
for (TreeSet<String> cycle : cycles) {
cutOutGoingConnection(cycle.first(), new ArrayList<String>(cycle));
}
// refresh sorting
Collections.sort(unsorted, new IndexerComparator<String>());
}
private void performSort() {
if (idMap == null) {
return;
}
LinkedList<String> commitPhase = new LinkedList<String>();
String focusID;
int outCount;
Set<String> children;
Set<String> parents;
while (unsorted.size() > 0 || commitPhase.size() > 0) {
focusID = unsorted.size() == 0 ? null : unsorted.get(0);
outCount = getOutCount(focusID);
if (outCount == 0) {
commitPhase.add(focusID);
unsorted.remove(0);
continue;
}
// cut dependOut of children
for (String id : commitPhase) {
children = dependIn.get(id);
if (children != null) {
for (String childID : children) {
parents = dependOut.get(childID);
parents.remove(id);
}
}
}
if (unsorted.size() > 1) {
Collections.sort(unsorted, new IndexerComparator<String>());
}
// add commitPhase to sorted
sortedNew.add(adapt(commitPhase));
// create new commitPhase
commitPhase = new LinkedList<String>();
// check for cycle
if (unsorted.size() > 0 && getOutCount(unsorted.get(0)) > 0) {
SokanIndexPlugin.logError(
"Cycle in indexer dependencies detected - "
+ "check extensions for 'org.reuseware.sokan.index'", null);
break;
}
}
}
private void split(List<String> idCache, List<String> visited,
Set<String> parents) {
if (parents == null) {
return;
}
for (String parent : parents) {
// check for cycle
if (idCache.contains(parent)) {
idCache.add(parent);
handleCycle(idCache);
idCache.remove(idCache.size() - 1);
continue;
}
// split idCache
List<String> newIDCache = new LinkedList<String>();
newIDCache.addAll(idCache);
newIDCache.add(parent);
// add to visited and next split
visited.add(parent);
split(newIDCache, visited, dependOut.get(parent));
}
}
private void handleCycle(List<String> newCycle) {
if (newCycle == null || newCycle.size() == 0) {
return;
}
List<String> myCycle = new LinkedList<String>();
myCycle.addAll(newCycle);
String first = myCycle.get(0);
String last = myCycle.get(myCycle.size() - 1);
while (!first.equals(last)) {
myCycle.remove(0);
first = myCycle.get(0);
}
TreeSet<String> cycleSet = new TreeSet<String>();
cycleSet.addAll(myCycle);
for (TreeSet<String> oldCycle : cycles) {
if (CoreUtil.equal(oldCycle, cycleSet)) {
return;
}
}
cycles.add(cycleSet);
}
private int getOutCount(String id) {
if (id == null) {
return -1;
}
Set<String> parents = dependOut.get(id);
if (parents == null) {
return 0;
}
return parents.size();
}
private void cutOutGoingConnection(String indexerID,
List<String> allowedParents) {
Set<String> parents = dependOut.get(indexerID);
String parentID = null;
for (String parent : parents) {
if (allowedParents == null) {
parentID = parent;
break;
}
if (allowedParents.contains(parent)) {
parentID = parent;
break;
}
}
if (parentID == null) {
return;
}
parents.remove(parentID);
Set<String> children = dependIn.get(parentID);
children.remove(indexerID);
}
private LinkedList<IndexerConfiguration> adapt(LinkedList<String> idList) {
if (idList == null) {
return null;
}
LinkedList<IndexerConfiguration> configList = new LinkedList<IndexerConfiguration>();
for (String id : idList) {
configList.add(idMap.get(id));
}
return configList;
}
}