package dk.brics.xact.analysis.xmlgraph;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
import dk.brics.string.util.MultiMap;
import dk.brics.xmlgraph.MultiContentNode;
import dk.brics.xmlgraph.NoContentNode;
import dk.brics.xmlgraph.Node;
import dk.brics.xmlgraph.NodeProcessor;
import dk.brics.xmlgraph.SingleContentNode;
import dk.brics.xmlgraph.XMLGraph;
/**
* Base class for performing backwards dataflow analysis on XML graphs.
* Subclasses should call {@link #doAnalysis()} when the analysis should be
* performed, which by convention should be at the end of its constructor.
* <p/>
* Only reachable nodes are analyzed – requesting the lattice point for an
* unreachable node will always return <tt>null</tt>.
*
* @param <T> lattice type. Requires a working {@link #equals(Object)}.
*/
public abstract class BackwardsXGAnalyzer<T> {
private XMLGraph xg;
private MultiMap<Integer,Integer> backedges = new MultiMap<Integer,Integer>();
private Map<Integer, T> map = new HashMap<Integer, T>();
private HashSet<Integer> inqueue = new HashSet<Integer>();
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>();
private Map<Integer, Integer> node2priority = new HashMap<Integer,Integer>();
private ArrayList<Integer> priority2node = new ArrayList<Integer>();
public BackwardsXGAnalyzer(XMLGraph xg) {
this.xg = xg;
}
protected void doAnalysis() {
final Set<Integer> initial = new HashSet<Integer>();
xg.processReachableNodes(new NodeProcessor<Object>() {
@Override
public Object process(MultiContentNode n) {
initialize(n);
for (int child : n.getContents()) {
backedges.add(child, n.getIndex());
}
return this;
}
@Override
public Object process(SingleContentNode n) {
initialize(n);
backedges.add(n.getContent(), n.getIndex());
return this;
}
@Override
public Object process(NoContentNode n) {
initialize(n);
return this;
}
private void initialize(Node node) {
T x = initial(node);
if (x == null) {
x = bottom();
} else {
initial.add(node.getIndex());
}
map.put(node.getIndex(), x);
}
});
for (int x : initial) {
for (int pred : backedges.getView(x)) {
enqueue(pred);
}
}
while (!queue.isEmpty()) {
int p = queue.remove();
inqueue.remove(p);
int index = priority2node.get(p);
Node node = xg.getNode(index);
T oldValue = map.get(index);
T newValue = transfer(node, oldValue);
if (!oldValue.equals(newValue)) {
map.put(index, newValue);
for (int pred : backedges.getView(index)) {
enqueue(pred);
}
}
}
}
private void enqueue(int index) {
Integer p = node2priority.get(index);
if (p == null) {
p = node2priority.size();
node2priority.put(index, p);
priority2node.add(index);
}
if (inqueue.add(p)) {
queue.add(p);
}
}
/**
* Returns the lattice data associated with the given node;
* or <tt>null</tt> if the node is unreachable.
* @param index index of a node
* @return a lattice point
*/
public T get(int index) {
return map.get(index);
}
/**
* Returns the lattice data associated with the given node;
* or <tt>null</tt> if the node is unreachable.
* @param node a node
* @return a lattice point
*/
public T get(Node node) {
return map.get(node.getIndex());
}
/**
* Transfer function for the specified node. The node
* should only read lattice data from its children and itself.
* @param node in the XML graph to analyze
* @param currentValue current lattice point for the node. Should not be modified.
* @return a new instance of a lattice element
*/
protected abstract T transfer(Node node, T currentValue);
/**
* Returns BOTTOM element.
*/
protected abstract T bottom();
/**
* Returns initial value for the specified node. If node
* has BOTTOM as initial value, return <tt>null</tt> instead.
*/
protected abstract T initial(Node node);
}