/*
* (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Nuxeo - initial API and implementation
*
* $Id$
*/
package org.nuxeo.common.collections;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
// TODO handle dependencies cycles.
@SuppressWarnings({ "ClassWithoutToString" })
public class DependencyTree<K, T> implements Iterable<DependencyTree.Entry<K, T>> {
private final Map<K, Entry<K, T>> registry;
// the sorted list of resolved entries.
// given an element e from that list it is ensured that any element at the left
// of 'e' doesn't depends on it
private final List<Entry<K, T>> resolved;
private EventHandler<T> eventHandler;
public DependencyTree() {
registry = new HashMap<K, Entry<K, T>>();
resolved = new ArrayList<Entry<K, T>>();
}
public Iterator<Entry<K, T>> iterator() {
return registry.values().iterator();
}
public void add(K key, T object, K... requires) {
add(key, object, Arrays.asList(requires));
}
public void add(K key, T object, Collection<K> requires) {
Entry<K, T> entry = registry.get(key);
if (entry == null) {
entry = new Entry<K, T>(key, object);
registry.put(key, entry);
} else if (entry.object == null) {
entry.object = object;
} else {
// TODO object already exists
return;
}
updateDependencies(entry, requires);
notifyRegistered(entry);
// resolve it if no pending requirements
if (entry.isResolved()) {
resolve(entry);
}
}
public void add(K key, T object) {
add(key, object, (Collection<K>) null);
}
public void remove(K key) {
Entry<K, T> entry = registry.remove(key);
if (entry != null) {
unregister(entry);
}
}
public void unregister(Entry<K, T> entry) {
if (entry.isResolved()) {
unresolve(entry);
}
notifyUnregistered(entry);
}
public Entry<K, T> getEntry(K key) {
return registry.get(key);
}
public T get(K key) {
Entry<K, T> entry = registry.get(key);
return entry != null ? entry.object : null;
}
public void resolve(Entry<K, T> entry) {
resolved.add(entry);
// notify listener
notifyResolved(entry);
// resolve any dependent entry if they are waiting only for me
Set<Entry<K, T>> deps = entry.getDependsOnMe();
if (deps != null) {
for (Entry<K, T> dep : deps) {
dep.removeWaitingFor(entry);
if (dep.isResolved()) {
resolve(dep); // resolve the dependent entry
}
}
}
}
public void unresolve(Entry<K, T> entry) {
// unresolve any dependent object
Set<Entry<K, T>> deps = entry.getDependsOnMe();
if (deps != null) {
for (Entry<K, T> dep : deps) {
dep.addWaitingFor(entry);
if (!dep.isResolved()) {
unresolve(dep); // unresolve the dependent entry
}
}
}
resolved.remove(entry);
notifyUnresolved(entry);
}
public boolean isRegistered(K key) {
Entry<K, T> entry = registry.get(key);
return entry != null && entry.object != null;
}
public boolean isResolved(K key) {
Entry<K, T> entry = registry.get(key);
return entry != null && entry.isResolved();
}
public void setEventHandler(EventHandler<T> eventHandler) {
this.eventHandler = eventHandler;
}
public Collection<Entry<K, T>> getEntries() {
return registry.values();
}
public List<Entry<K, T>> getPendingEntries() {
List<Entry<K, T>> result = new ArrayList<Entry<K, T>>();
for (Map.Entry<K, Entry<K, T>> entry : registry.entrySet()) {
Entry<K, T> val = entry.getValue();
if (!val.isResolved()) {
result.add(val);
}
}
return result;
}
public List<T> getPendingObjects() {
List<T> list = new ArrayList<T>();
List<Entry<K, T>> entries = getPendingEntries();
for (Entry<K, T> entry : entries) {
list.add(entry.object);
}
return list;
}
public List<Entry<K, T>> getMissingRequirements() {
List<Entry<K, T>> result = new ArrayList<Entry<K, T>>();
for (Map.Entry<K, Entry<K, T>> entry : registry.entrySet()) {
Entry<K, T> val = entry.getValue();
if (!val.isRegistered()) {
result.add(val);
}
}
return result;
}
/**
* Entries are sorted so an entry never depends on entries on its right.
*/
public List<Entry<K, T>> getResolvedEntries() {
return resolved;
}
public List<T> getResolvedObjects() {
List<T> list = new ArrayList<T>();
List<Entry<K, T>> entries = resolved;
for (Entry<K, T> entry : entries) {
list.add(entry.object);
}
return list;
}
public void clear() {
for (Entry<K, T> entry : resolved) {
entry = registry.remove(entry.key);
if (entry != null) {
notifyUnresolved(entry);
notifyUnregistered(entry);
}
}
resolved.clear();
Iterator<Entry<K, T>> it = registry.values().iterator();
while (it.hasNext()) {
Entry<K, T> entry = it.next();
it.remove();
if (entry.isRegistered()) {
notifyUnregistered(entry);
}
}
}
protected void updateDependencies(Entry<K, T> entry, Collection<K> requires) {
if (requires != null) {
for (K req : requires) {
Entry<K, T> reqEntry = registry.get(req);
if (reqEntry != null) {
if (reqEntry.isResolved()) {
// requirement satisfied -> continue
reqEntry.addDependsOnMe(entry);
continue;
}
} else {
reqEntry = new Entry<K, T>(req, null);
registry.put(req, reqEntry);
}
// dependencies not satisfied
reqEntry.addDependsOnMe(entry);
entry.addWaitingFor(reqEntry);
}
}
}
private void notifyRegistered(Entry<K, T> entry) {
if (eventHandler != null) {
eventHandler.registered(entry.object);
}
}
private void notifyUnregistered(Entry<K, T> entry) {
if (eventHandler != null) {
eventHandler.unregistered(entry.object);
}
}
private void notifyResolved(Entry<K, T> entry) {
if (eventHandler != null) {
eventHandler.resolved(entry.object);
}
}
private void notifyUnresolved(Entry<K, T> entry) {
if (eventHandler != null) {
eventHandler.unresolved(entry.object);
}
}
public static class Entry<K, T> {
private final K key;
private T object;
private Set<Entry<K, T>> waitsFor;
private Set<Entry<K, T>> dependsOnMe;
public Entry(K key, T object) {
this.key = key;
this.object = object;
}
public final boolean isRegistered() {
return object != null;
}
public final boolean isResolved() {
return object != null && waitsFor == null;
}
public final void addWaitingFor(Entry<K, T> entry) {
if (waitsFor == null) {
waitsFor = new HashSet<Entry<K, T>>();
}
waitsFor.add(entry);
}
public final void removeWaitingFor(Entry<K, T> key) {
if (waitsFor != null) {
waitsFor.remove(key);
if (waitsFor.isEmpty()) {
waitsFor = null;
}
}
}
public final void addDependsOnMe(Entry<K, T> entry) {
if (dependsOnMe == null) {
dependsOnMe = new HashSet<Entry<K, T>>();
}
dependsOnMe.add(entry);
}
public Set<Entry<K, T>> getDependsOnMe() {
return dependsOnMe;
}
/**
* @return Returns the waitsFor.
*/
public Set<Entry<K, T>> getWaitsFor() {
return waitsFor;
}
public final T get() {
return object;
}
/**
* @return Returns the key.
*/
public K getKey() {
return key;
}
@Override
public String toString() {
return key.toString();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Entry)) {
return false;
}
return key.equals(((Entry) obj).key);
}
@Override
public int hashCode() {
return key.hashCode();
}
}
interface EventHandler<T> {
void registered(T object);
void unregistered(T object);
void resolved(T object);
void unresolved(T object);
}
}