/*******************************************************************************
* Copyright (c) 2006, 2015 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.internal.texteditor.rulers;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
/**
* A directed acyclic graph. See http://en.wikipedia.org/wiki/Directed_acyclic_graph
* @param <E> type of the vertices
*
* @since 3.3
*/
public final class DAG<E> {
/**
* Multimap, supports <code>null</code> key, but not <code>null</code> values.
* @param <K> key type
* @param <V> values type
*/
private static final class MultiMap<K, V> {
private final Map<K, Set<V>> fMap= new LinkedHashMap<>();
/**
* Adds <code>val</code> to the values mapped to by <code>key</code>. If
* <code>val</code> is <code>null</code>, <code>key</code> is added to the key set of
* the multimap.
*
* @param key the key
* @param val the value
*/
public void put(K key, V val) {
Set<V> values= fMap.get(key);
if (values == null) {
values= new LinkedHashSet<>();
fMap.put(key, values);
}
if (val != null)
values.add(val);
}
/**
* Returns all mappings for the given key, an empty set if there are no mappings.
*
* @param key the key
* @return the mappings for <code>key</code>
*/
public Set<V> get(K key) {
Set<V> values= fMap.get(key);
return values == null ? Collections.emptySet() : values;
}
public Set<K> keySet() {
return fMap.keySet();
}
/**
* Removes all mappings for <code>key</code> and removes <code>key</code> from the key
* set.
*
* @param key the key to remove
* @return the removed mappings
*/
public Set<V> removeAll(K key) {
Set<V> values= fMap.remove(key);
return values == null ? Collections.emptySet() : values;
}
/**
* Removes a mapping from the multimap, but does not remove the <code>key</code> from the
* key set.
*
* @param key the key
* @param val the value
*/
public void remove(K key, V val) {
Set<V> values= fMap.get(key);
if (values != null)
values.remove(val);
}
@Override
public String toString() {
return fMap.toString();
}
}
private final MultiMap<E, E> fOut= new MultiMap<>();
private final MultiMap<E, E> fIn= new MultiMap<>();
/**
* Adds a directed edge from <code>origin</code> to <code>target</code>. The vertices are not
* required to exist prior to this call - if they are not currently contained by the graph, they are
* automatically added.
*
* @param origin the origin vertex of the dependency
* @param target the target vertex of the dependency
* @return <code>true</code> if the edge was added, <code>false</code> if the
* edge was not added because it would have violated the acyclic nature of the
* receiver.
*/
public boolean addEdge(E origin, E target) {
Assert.isLegal(origin != null);
Assert.isLegal(target != null);
if (hasPath(target, origin))
return false;
fOut.put(origin, target);
fOut.put(target, null);
fIn.put(target, origin);
fIn.put(origin, null);
return true;
}
/**
* Adds a vertex to the graph. If the vertex does not exist prior to this call, it is added with
* no incoming or outgoing edges. Nothing happens if the vertex already exists.
*
* @param vertex the new vertex
*/
public void addVertex(E vertex) {
Assert.isLegal(vertex != null);
fOut.put(vertex, null);
fIn.put(vertex, null);
}
/**
* Removes a vertex and all its edges from the graph.
*
* @param vertex the vertex to remove
*/
public void removeVertex(E vertex) {
Set<E> targets= fOut.removeAll(vertex);
for (Iterator<E> it= targets.iterator(); it.hasNext();)
fIn.remove(it.next(), vertex);
Set<E> origins= fIn.removeAll(vertex);
for (Iterator<E> it= origins.iterator(); it.hasNext();)
fOut.remove(it.next(), vertex);
}
/**
* Returns the sources of the receiver. A source is a vertex with no incoming edges. The
* returned set's iterator traverses the nodes in the order they were added to the graph.
*
* @return the sources of the receiver
*/
public Set<E> getSources() {
return computeZeroEdgeVertices(fIn);
}
/**
* Returns the sinks of the receiver. A sink is a vertex with no outgoing edges. The returned
* set's iterator traverses the nodes in the order they were added to the graph.
*
* @return the sinks of the receiver
*/
public Set<E> getSinks() {
return computeZeroEdgeVertices(fOut);
}
private static <T> Set<T> computeZeroEdgeVertices(MultiMap<T, T> map) {
Set<T> candidates= map.keySet();
Set<T> roots= new LinkedHashSet<>(candidates.size());
for (Iterator<T> it= candidates.iterator(); it.hasNext();) {
T candidate= it.next();
if (map.get(candidate).isEmpty())
roots.add(candidate);
}
return roots;
}
/**
* Returns the direct children of a vertex. The returned {@link Set} is unmodifiable.
*
* @param vertex the parent vertex
* @return the direct children of <code>vertex</code>
*/
public Set<E> getChildren(E vertex) {
return Collections.unmodifiableSet(fOut.get(vertex));
}
private boolean hasPath(E start, E end) {
// break condition
if (start == end)
return true;
Set<E> children= fOut.get(start);
for (Iterator<E> it= children.iterator(); it.hasNext();)
// recursion
if (hasPath(it.next(), end))
return true;
return false;
}
@Override
public String toString() {
return "Out: " + fOut.toString() + " In: " + fIn.toString(); //$NON-NLS-1$ //$NON-NLS-2$
}
}