/***
* Copyright (c) 2009 Caelum - www.caelum.com.br/opensource All rights reserved.
*
* 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.
*/
package br.com.caelum.vraptor.interceptor;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.newHashSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.enterprise.inject.Vetoed;
import br.com.caelum.vraptor.Intercepts;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
/**
* A set that orders interceptors topologically based on before and after from {@link Intercepts}
*
* @author Lucas Cavalcanti
* @author David Paniz
* @author Jose Donizetti
* @since 3.3.0
*
*/
@Vetoed
public class Graph<E> {
private final Multimap<E, E> graph = LinkedHashMultimap.create();
private List<E> orderedList;
private final Lock lock = new ReentrantLock();
public void addEdge(E from, E to) {
checkState(orderedList == null, "You shouldn't add more interceptors after ordering. Please notify vraptor developers.");
graph.put(from, to);
}
public void addEdges(E from, E... tos) {
if (tos.length == 0) {
addEdge(from, null);
} else {
for (E to : tos) {
addEdge(from, to);
}
}
}
public List<E> topologicalOrder() {
if (orderedList == null) {
lock.lock();
try {
if (orderedList == null) {
this.orderedList = orderTopologically();
}
} finally {
lock.unlock();
}
}
return this.orderedList;
}
private List<E> orderTopologically() {
List<E> list = new ArrayList<>();
while(!graph.keySet().isEmpty()) {
Set<E> roots = findRoots();
if (roots.isEmpty()) {
throw new IllegalStateException("There is a cycle on the interceptor sequence: \n" + cycle());
}
list.addAll(roots);
removeRoots(roots);
}
return list;
}
private void removeRoots(Set<E> roots) {
for (E root : roots) {
graph.removeAll(root);
}
}
private Set<E> findRoots() {
return difference(graph.keySet(), newHashSet(graph.values())).immutableCopy();
}
private String cycle() {
removeLeaves();
return findCycle().toString();
}
private List<E> findCycle() {
E node = firstElement(graph.keySet());
List<E> cycle = new ArrayList<>();
do {
cycle.add(node);
} while(!cycle.contains(node = firstElement(graph.get(node))));
return cycle.subList(cycle.indexOf(node), cycle.size());
}
private E firstElement(Iterable<E> elements) {
return elements.iterator().next();
}
private void removeLeaves() {
Set<E> leaves = findLeaves();
if (leaves.isEmpty()) {
return;
}
for (E key : newHashSet(graph.keySet())) {
for (E value : leaves) {
graph.remove(key, value);
}
}
removeLeaves();
}
private Set<E> findLeaves() {
return difference(newHashSet(graph.values()), graph.keySet());
}
}