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 forwards 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 ForwardsXGAnalyzer<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 ForwardsXGAnalyzer(XMLGraph xg) {
this.xg = xg;
}
protected void doAnalysis() {
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) {
map.put(node.getIndex(), bottom());
}
});
for (int x : initialNodes()) {
map.put(x, initial(xg.getNode(x)));
enqueue(x);
}
while (!queue.isEmpty()) {
int p = queue.remove();
inqueue.remove(p);
int index = priority2node.get(p);
Node node = xg.getNode(index);
T src = map.get(index);
transfer(src, node);
}
}
public XMLGraph getXmlGraph() {
return xg;
}
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());
}
/**
* Changes the lattice point for the specified node. The underlying
* worklist is updated if the new value is different from the old value.
* <p/>
* Note that this method does not perform any least upper bound
* computation – you often want to merge with the existing value
* before calling {@code put}.
* @param index
* @param newValue
*/
protected void put(int index, T newValue) {
T old = map.get(index);
if (!old.equals(newValue)) {
map.put(index, newValue);
enqueue(index);
}
}
/**
* Transfer data from the specified node to its children.
* Update node data using {@link #put(int, Object)}.
* @param src lattice point for the specified node, provided for convenience
* @param node the node to transfer from
*/
protected abstract void transfer(T src, Node node);
/**
* Returns BOTTOM element.
* @return a lattice point
*/
protected abstract T bottom();
/**
* Returns initial value for the specified node.
* @param node one of the nodes whose index was returned by {@link #initialNodes()}
* @return a lattice point
*/
protected abstract T initial(Node node);
/**
* Returns the initial nodes. This will typically be the root nodes in the XML graph.
* All initial nodes must be reachable from the graph's root nodes.
* @return read-only set
*/
protected abstract Set<Integer> initialNodes();
}