blob: e63a1f9becd8544208efe1d042a688e7b8b81bdb [file] [log] [blame]
package org.unicode.cldr.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class MergeLists<T> {
Collection<Collection<T>> source = new ArrayList<Collection<T>>();
Set<T> orderedWorkingSet;
public MergeLists() {
this(new LinkedHashSet<T>());
}
public MergeLists(Set<T> orderedWorkingSet) {
this.orderedWorkingSet = orderedWorkingSet;
}
public MergeLists<T> add(Collection<T> orderedItems) {
if (orderedItems.size() == 0) { // skip empties
return this;
}
final LinkedHashSet<T> linkedHashSet = new LinkedHashSet<T>(orderedItems);
if (linkedHashSet.size() != orderedItems.size()) {
throw new IllegalArgumentException("Multiple items in ordering!");
}
source.add(linkedHashSet);
return this;
}
@SuppressWarnings("unchecked")
public MergeLists<T> add(T... stuff) {
return add(Arrays.asList(stuff));
}
public MergeLists<T> addAll(Collection<Collection<T>> collectionsOfOrderedItems) {
for (Collection<T> orderedItems : collectionsOfOrderedItems) {
add(orderedItems);
}
return this;
}
public List<T> merge() {
List<T> result = new ArrayList<T>();
for (Collection<T> sublist : source) {
orderedWorkingSet.addAll(sublist);
}
// now that we have things ordered, we take the first one that is only at the front of a list
// this is slower, but puts things into as much of the order specified as possible
// could be optimized further, but we don't care that much
Set<T> first = new LinkedHashSet<T>();
while (orderedWorkingSet.size() != 0) {
getFirsts(first);
if (first.size() == 0) {
Map<T, Collection<T>> reasons = new LinkedHashMap<T, Collection<T>>();
getFirsts(first, reasons);
throw new IllegalArgumentException(
"Inconsistent requested ordering: cannot merge if we have [...A...B...] and [...B...A...]: "
+ reasons);
}
// now get first item that is in first
T best = extractFirstOk(orderedWorkingSet, first); // removes from working set
// remaining items now contains no non-first items
removeFromSource(best);
result.add(best);
}
return result;
}
public static <T> boolean hasConsistentOrder(Collection<T> a, Collection<T> b) {
LinkedHashSet<T> remainder = new LinkedHashSet<T>(a);
remainder.retainAll(b);
if (remainder.size() == 0) {
return true;
}
// remainder is now in a's order, and contains only the items that are in both
Iterator<T> bi = b.iterator();
T current = bi.next();
for (T item : remainder) {
if (item.equals(current)) {
if (!bi.hasNext()) {
return true;
}
current = bi.next();
}
}
return !bi.hasNext(); // if we have any left over, we failed
}
public static <T> Collection<T> hasConsistentOrderWithEachOf(Collection<T> a, Collection<Collection<T>> bs) {
for (Collection<T> b : bs) {
if (!hasConsistentOrder(a, b)) {
return b;
}
}
return null;
}
// could be optimized since we know the item will only occur at the head of a list
private void removeFromSource(T item) {
for (Iterator<Collection<T>> iterator = source.iterator(); iterator.hasNext();) {
Collection<T> sublist = iterator.next();
sublist.remove(item);
if (sublist.size() == 0) {
iterator.remove();
}
}
}
/**
* Get the first item that is also in the ok set.
*/
private T extractFirstOk(Collection<T> remainingItems, Set<T> ok) {
for (Iterator<T> it = remainingItems.iterator(); it.hasNext();) {
T item = it.next();
if (ok.contains(item)) {
it.remove();
return item;
}
}
throw new IllegalArgumentException("Internal Error");
}
public void getFirsts(Set<T> result) {
getFirsts(result, null);
}
/**
* Get first of each sets. Guaranteed non-empty
*/
public void getFirsts(Set<T> result, Map<T, Collection<T>> reasons) {
result.clear();
result.addAll(orderedWorkingSet);
for (Collection<T> sublist : source) {
// get all the first items
final Iterator<T> iterator = sublist.iterator();
iterator.next(); // skip first
while (iterator.hasNext()) {
final T nextItem = iterator.next();
boolean changed = result.remove(nextItem);
if (changed && reasons != null) {
reasons.put(nextItem, sublist);
}
}
}
}
}