package edu.neu.ccs.demeter.aplib; import java.util.*; /** A traversal graph TG(S,G,N,B) is constructed from an encapsulated strategy S, a class graph G, a name map N for S and G, and a constraint map B for S and G according to a modified version of Algorithm 1 in the paper "Traversals of Object Structures: Specification and Efficient Implementation" (the modifications allow G to be non-simple and N to be a relation instead of a function). TG is essentially a compilation (that is, a compact and efficient generator) of the set of paths through G that is selected by S. Conceptually, TG(S,G,N,B) consists of a graph G'' and a set Ts of start nodes in G'', where G'' is (a subset of) n copies of G, where n is the number of edges in S, with some extra edges to connect nodes in different copies. However, to keep the representation more compact, G'' is represented as two sets, NS and ES:
-l->
v, IM)
where u -l->
v is an edge
in G and IM is a set of pairs of indices (i,j) such that
ui -l->
vj is in G''.
-l->
v,
then ui -l->
vj
is in the traversal graph iff i == j or (i,j)
is in getIntercopyIndices(e). */
List getIntercopyIndices(EdgeI e) {
List ici = (List) icis.get(e);
if (ici == null) {
icis.put(e, ici = new ArrayList());
Iterator sgNodes = strategyGraph.getNodes().iterator();
while (sgNodes.hasNext()) {
Object a = sgNodes.next();
Collection I = strategyGraph.getIncomingEdges(a);
Collection O = strategyGraph.getOutgoingEdges(a);
List cp = crossProduct(I, O);
if (cp.isEmpty()) continue;
if (constraintMap.meetsConstraint(a, e, nameMap) ||
constraintMap.meetsConstraint(a, e.getTarget(), nameMap)) {
if (debug) System.err.println("Adding intercopy edges for "
+ edgeKey(e) + ": " + cp);
ici.addAll(cp);
}
}
// Compute the transitive closure of the intercopy indices.
// FIXME: this is probably incomplete, and/or slow...
List add = new ArrayList();
Iterator it1 = ici.iterator();
while (it1.hasNext()) {
IndexPair ip1 = (IndexPair) it1.next();
Iterator it2 = ici.iterator();
while (it2.hasNext()) {
IndexPair ip2 = (IndexPair) it2.next();
IndexPair ip3 = null;
if (ip1.getTarget() == ip2.getSource())
ip3 = new IndexPair(ip1.getSource(), ip2.getTarget());
else if (ip2.getTarget() == ip1.getTarget())
ip3 = new IndexPair(ip2.getSource(), ip1.getTarget());
if (ip3 != null && !add.contains(ip3) && !ici.contains(ip3))
add.add(ip3);
}
}
if (debug)
System.err.println("Adding transitive closure intercopy edges for "
+ edgeKey(e) + ": " + add);
ici.addAll(add);
}
return ici;
}
/** The cross product, as a List of IndexPair objects, of Collections
l1 and l2 of Integers. E.g.
crossProduct({1, 2}, {3, 4}) = {<1,3>, <1,4>, <2,3>, <2,4>}
*/
List crossProduct(Collection l1, Collection l2) {
if (debug) System.err.println(l1 + " x " + l2);
List prod = new ArrayList();
if (l1.isEmpty() || l2.isEmpty()) return prod;
Iterator i1 = l1.iterator();
while (i1.hasNext()) {
int i = ((Integer) i1.next()).intValue();
Iterator i2 = l2.iterator();
while (i2.hasNext()) {
int j = ((Integer) i2.next()).intValue();
prod.add(new IndexPair(i, j));
}
}
return prod;
}
/** Mark all nodes and edges in G'' reachable from s*. */
void markForward() throws TraversalException {
Iterator it = sourceNodeSets.values().iterator();
while (it.hasNext()) markReachableFrom((NodeSet) it.next(), true);
}
/** Mark all nodes and edges in G'' from which t* is reachable. */
void markBackward() throws TraversalException {
Iterator it = targetNodeSets.values().iterator();
while (it.hasNext()) markReachableFrom((NodeSet) it.next(), false);
}
/** Mark all nodes and edges in G reachable from the given nodes,
following S, N, and B. If forward is true, use forward links,
otherwise use backward links. */
void markReachableFrom(NodeSet nodes, boolean forward) {
if (debug)
System.err.println("markReachableFrom(" + nodes + ", " + forward + ")");
for (int i = 0; i < copies; i++)
if (nodes.hasIndex(i)) markReachable(nodes.getNode(), i, forward, null);
}
/** Mark all nodes and edges in G reachable from node vi following
S, N, and B. If forward is true, use forward links, otherwise
use backward links. The previous edge visited is prev (for
checking knowledge path contraints). */
void markReachable(Object v, int i, boolean forward, EdgeI prev) {
if (debug) System.err.println("markReachable(" + v + ", " + i + ", "
+ forward + ", " + edgeKey(prev) + ")");
Collection edges;
try {
edges = (forward ?
classGraph.getOutgoingEdges(v) :
classGraph.getIncomingEdges(v));
} catch (NoSuchClassGraphNodeException e) {
// v is not in G, e.g. terminal class.
// FIXME: disallow this?
return;
}
if (!constraintMap.meetsConstraint(i, v, nameMap)) return;
// Don't check marked(v,i,forward), because we might have been
// here before by a different path that caused some outgoing edges
// to be illegal (due to knowledge path restrictions), but might
// be legal now. This means we might visit a node multiple times,
// but never more than the number of incoming edges.
// We still need to mark the node, though, to make sure it gets
// added to the final TG.
mark(v, i, forward);
Iterator it = edges.iterator();
while (it.hasNext()) {
EdgeI edge = (EdgeI) it.next();
markReachableThroughEdge(edge, i, i, forward, prev);
Iterator ici = getIntercopyIndices(edge).iterator();
while (ici.hasNext()) {
IndexPair ip = (IndexPair) ici.next();
if (i == (forward ? ip.getSource() : ip.getTarget()))
markReachableThroughEdge(edge, ip.getSource(), ip.getTarget(),
forward, prev);
}
}
}
/** Mark all nodes and edges in G reachable through
edge = vi -> uj following S, N, and B.
If forward is true, use forward links, otherwise use backward links.
The previous edge visited is prev (for checking knowledge path
contraints). */
void markReachableThroughEdge(EdgeI edge, int i, int j,
boolean forward, EdgeI prev)
{
if (debug) System.err.println("markReachableThroughEdge(" + edgeKey(edge)
+ ", " + i + ", " + j + ", " + forward + ", "
+ edgeKey(prev) + ")");
if (prev != null)
if (forward
? edge.isAlternationEdge() && prev.isInheritanceEdge()
: edge.isInheritanceEdge() && prev.isAlternationEdge()) {
// Violates knowledge-path constraint.
return;
}
if (!marked(edge, i, j, forward)
&& constraintMap.meetsConstraint(i, edge, nameMap)) {
mark(edge, i, j, forward);
markReachable((forward ? edge.getTarget() : edge.getSource()),
(forward ? j : i), forward, edge);
}
}
/** A set of nodes vi in a traversal graph, where the nodes in the
set are all copies of the same class graph node v with different
indices i. */
public class NodeSet extends Traversal.NodeSet {
NodeSet(Object v) { super(v); indices = new BitSet(copies); }
BitSet indices;
/** A list of indices of the nodes in this set. */
public List getIndices() {
List list = new ArrayList();
for (int i = indices.nextSetBit(0); i >= 0; i = indices.nextSetBit(i+1))
list.add(new Integer(i));
return list;
}
/** Is vi in this set? */
public boolean hasIndex(int i) { return indices.get(i); }
/** Add index i to this set. */
void addIndex(int i) { indices.set(i); }
/** Is this set empty? */
boolean isEmpty() { return indices.isEmpty(); }
String indicesToString() { return indices.toString(); }
public boolean equals(Object o) {
return super.equals(o) && indices.equals(((NodeSet) o).indices);
}
/** The intersection of this node set and ns. */
NodeSet intersection(NodeSet ns) {
NodeSet ret = new NodeSet(node);
ret.indices = (BitSet) indices.clone();
ret.indices.and(ns.indices);
return ret;
}
/** The union of this node set and ns. */
NodeSet union(NodeSet ns) {
NodeSet ret = new NodeSet(node);
ret.indices = (BitSet) indices.clone();
ret.indices.or(ns.indices);
return ret;
}
/** A list of EdgeSet objects representing the edges in the
traversal graph going out of (if outgoing is true) or coming into
(if outgoing is false) the nodes in this set. Each EdgeSet will
be nonempty and have nonempty source and target NodeSets. */
List getIncidentEdgeSets(boolean outgoing) {
List edgeSets = new ArrayList();
if (isEmpty()) return edgeSets;
Iterator edges;
try {
edges = (outgoing
? classGraph.getOutgoingEdges(node)
: classGraph.getIncomingEdges(node)).iterator();
} catch (NoSuchClassGraphNodeException e) {
return edgeSets;
}
while (edges.hasNext()) {
EdgeI edge = (EdgeI) edges.next();
EdgeSet es = (EdgeSet) getEdgeSet(edge); // has all copies
if (es != null) {
EdgeSet edgeSet = new EdgeSet(edge);
for (int i = 0; i < copies; i++) {
if (hasIndex(i)) {
Iterator indices = es.getIncidentIndices(i, outgoing).iterator();
while (indices.hasNext()) {
int j = ((Integer) indices.next()).intValue();
if (outgoing)
edgeSet.addIndices(i, j);
else
edgeSet.addIndices(j, i);
}
}
}
if (!edgeSet.isEmpty())
// FIXME: check for nonempty endpoint NodeSets.
edgeSets.add(edgeSet);
}
}
return edgeSets;
}
}
/** A set of edges u^i -l->
v^j in a traversal graph, where the
edges in the set are all copies of the same class graph edge
u -l->
v with different endpoint indices i and j. */
public class EdgeSet extends Traversal.EdgeSet {
EdgeSet(EdgeI e) {
super(e);
indices = new BitSet(copies);
indexPairs = new LinkedHashSet();
}
BitSet indices;
Set indexPairs;
/** An unmodifiable Set of IndexPair objects representing the
endpoint indices of the intercopy copies in this set, i.e. the
set of all (i,j) such that i != j and u^i -l->
v^j is in the
set. */
public Set getIntercopyIndices() {
return Collections.unmodifiableSet(indexPairs);
}
/** Is u^i -l->
v^j in this set? */
public boolean hasIndices(int i, int j) {
return i == j ? indices.get(i) : indexPairs.contains(new IndexPair(i,j));
}
/** An unmodifiable list of target indices (Integer objects)
corresponding to source index i. */
public List getTargetIndices(int i) {
return Collections.unmodifiableList(getIncidentIndices(i, true));
}
/** An unmodifiable list of source indices (Integer objects)
corresponding to target index j. */
public List getSourceIndices(int j) {
return Collections.unmodifiableList(getIncidentIndices(j, false));
}
/** A list of indices (Integer objects) corresponding to the other
end of index i, which is the source if source is true. */
List getIncidentIndices(int i, boolean source) {
List list = new ArrayList();
if (isEmpty()) return list;
if (indices.get(i)) list.add(new Integer(i));
// FIXME: maybe keep two hashtables, one for each direction.
Iterator ips = indexPairs.iterator();
while (ips.hasNext()) {
IndexPair ip = (IndexPair) ips.next();
if ((source ? ip.getSource() : ip.getTarget()) == i) {
list.add(new Integer(source ? ip.getTarget() : ip.getSource()));
}
}
return list;
}
/** The set of target nodes of edges in this set. */
public Traversal.NodeSet getTargetNodeSet() {
NodeSet nodes = new NodeSet(edge.getTarget());
for (int i = 0; i < copies; i++) if (indices.get(i)) nodes.addIndex(i);
Iterator ips = indexPairs.iterator();
while (ips.hasNext()) {
IndexPair ip = (IndexPair) ips.next();
nodes.addIndex(ip.getTarget());
}
return nodes;
}
/** Add u^i -l->
v^j to this set. */
void addIndices(int i, int j) {
if (i == j) indices.set(i); else indexPairs.add(new IndexPair(i,j));
}
/** Is this set empty? */
boolean isEmpty() { return indices.isEmpty() && indexPairs.isEmpty(); }
String indicesToString() {
return indices +
(indexPairs.isEmpty() ? "" : " intercopy: " + indexPairs);
}
public boolean equals(Object o) {
return super.equals(o)
&& indices.equals(((EdgeSet) o).indices)
&& indexPairs.equals(((EdgeSet) o).indexPairs);
}
}
/** A pair of indices, representing the copy indices on the source
and target of an edge in the traversal graph. */
public class IndexPair {
protected int source, target;
IndexPair(int i, int j) { source = i; target = j; }
/** The copy index of the source of the edge. */
public int getSource() { return source; }
/** The copy index of the target of the edge. */
public int getTarget() { return target; }
public boolean equals(Object ip) {
return (ip instanceof IndexPair
&& ((IndexPair) ip).source == source
&& ((IndexPair) ip).target == target);
}
public int hashCode() {
return copies * source + target;
}
public String toString() {
return source + " -> " + target;
}
}
/** Keep track of the marks for a node by keeping three NodeSet
objects, one for each direction and one for the intersection. */
class NodeSets {
NodeSet forw, back, both;
NodeSets(Object v) {
forw = new NodeSet(v);
back = new NodeSet(v);
both = new NodeSet(v);
}
boolean hasIndex(int i, boolean forward) {
return (forward ? forw : back).hasIndex(i);
}
void addIndex(int i, boolean forward) {
(forward ? forw : back).addIndex(i);
}
boolean hasIndex(int i) { return both.hasIndex(i); }
void addIndex(int i) { both.addIndex(i); }
NodeSet getNodeSet() { return both.isEmpty() ? null : both; }
}
/** Keep track of the marks for an edge by keeping three EdgeSet
objects, one for each direction and one for the intersection. */
class EdgeSets {
EdgeSet forw, back, both;
EdgeSets(EdgeI e) {
forw = new EdgeSet(e);
back = new EdgeSet(e);
both = new EdgeSet(e);
}
boolean hasIndices(int i, int j, boolean forward) {
return (forward ? forw : back).hasIndices(i, j);
}
void addIndices(int i, int j, boolean forward) {
(forward ? forw : back).addIndices(i, j);
}
boolean hasIndices(int i, int j) { return both.hasIndices(i, j); }
void addIndices(int i, int j) { both.addIndices(i, j); }
EdgeSet getEdgeSet() { return both.isEmpty() ? null : both; }
}
/** A map from nodes to NodeSets objects. */
LinkedHashMap nodes = new LinkedHashMap();
/** A map from strings (edge keys) to EdgeSets objects. */
LinkedHashMap edges = new LinkedHashMap();
/** Mark node vi, in the forward direction if forward is true,
otherwise in the backward direction. Note: this must be idempotent! */
void mark(Object v, int i, boolean forward) {
NodeSets sets = (NodeSets) nodes.get(v);
if (sets == null) nodes.put(v, sets = new NodeSets(v));
sets.addIndex(i, forward);
// assumes we do forward before backward...
if (!forward && sets.hasIndex(i, true)) sets.addIndex(i);
}
/** Is node vi marked in the forward (backward if forward=false)
direction? */
boolean marked(Object v, int i, boolean forward) {
NodeSets sets = (NodeSets) nodes.get(v);
return sets != null && sets.hasIndex(i, forward);
}
/** Mark edge u^i -l->
v^j (where e = u -l->
v), in the forward
direction if forward is true, otherwise in the backward
direction. */
void mark(EdgeI e, int i, int j, boolean forward) {
String key = edgeKey(e);
EdgeSets sets = (EdgeSets) edges.get(key);
if (sets == null) edges.put(key, sets = new EdgeSets(e));
sets.addIndices(i, j, forward);
// assumes we do forward before backward...
if (!forward && sets.hasIndices(i, j, true)) sets.addIndices(i, j);
}
/** Is edge u^i -l->
v^j (where e = u -l->
v) marked in the forward
(backward if forward=false) direction? */
boolean marked(EdgeI e, int i, int j, boolean forward) {
EdgeSets sets = (EdgeSets) edges.get(edgeKey(e));
return sets != null && sets.hasIndices(i, j, forward);
}
/** The memoized list of NodeSet objects. */
List nodesList;
/** An unmodifiable list of NodeSet objects representing the nodes
in the traversal graph.
@see NodeSet */
public List getNodeSets() {
if (nodesList == null) {
nodesList = new ArrayList();
Iterator i = nodes.values().iterator();
while (i.hasNext()) {
NodeSets sets = (NodeSets) i.next();
NodeSet set = sets.getNodeSet();
if (set != null) nodesList.add(set);
}
nodesList = Collections.unmodifiableList(nodesList);
}
return nodesList;
}
/** The set of copies of class graph node v in the traversal graph,
or null if there are none. */
public Traversal.NodeSet getNodeSet(Object v) {
NodeSets sets = (NodeSets) nodes.get(v);
return sets == null ? null : sets.getNodeSet();
}
/** The memoized list of EdgeSet objects. */
List edgesList;
/** An unmodifiable list of EdgeSet objects representing the edges
in the traversal graph.
@see EdgeSet */
public List getEdgeSets() {
if (edgesList == null) {
edgesList = new ArrayList();
Iterator i = edges.values().iterator();
while (i.hasNext()) {
EdgeSets sets = (EdgeSets) i.next();
EdgeSet set = sets.getEdgeSet();
if (set != null) edgesList.add(set);
}
edgesList = Collections.unmodifiableList(edgesList);
}
return edgesList;
}
/** The set of copies of the class graph edge with the given key in
the traversal graph, or null if there are none. */
public Traversal.EdgeSet getEdgeSet(String key) {
EdgeSets sets = (EdgeSets) edges.get(key);
return sets == null ? null : sets.getEdgeSet();
}
/** A map from class graph nodes to NodeSet objects representing the
set of start tokens for that node. */
LinkedHashMap startSets = new LinkedHashMap();
/** A map from class graph nodes to NodeSet objects representing the
set of finish tokens for that node. */
LinkedHashMap finishSets = new LinkedHashMap();
/** Compute the set of start nodes in the traversal graph:
Ts is the set of nodes v such that
s* -> v is an edge in G''.
*/
void makeStartSet() throws TraversalException {
makeEndpointSet(startSets, sourceNodeSets.values());
}
/** Compute the set of finish nodes in the traversal graph:
Tf is the set of nodes v such that
v -> t* is an edge in G''.
*/
void makeFinishSet() throws TraversalException {
makeEndpointSet(finishSets, targetNodeSets.values());
}
void makeEndpointSet(Map endpointSets, Collection nodeSets) {
Iterator it = nodeSets.iterator();
while (it.hasNext()) {
NodeSet set = (NodeSet) it.next();
Object v = set.getNode();
NodeSet setInTG = (NodeSet) getNodeSet(v);
if (setInTG != null) {
setInTG = setInTG.intersection(set);
NodeSet endpointSet = (NodeSet) endpointSets.get(v);
if (endpointSet == null) {
endpointSet = setInTG;
} else {
endpointSet = endpointSet.union(setInTG);
}
endpointSets.put(v, endpointSet);
}
}
}
/** The memoized start set. */
List startSet;
/** The memoized finish set. */
List finishSet;
public List getStartSet() {
if (startSet == null) {
startSet = new ArrayList();
Iterator i = startSets.values().iterator();
while (i.hasNext()) {
NodeSet set = (NodeSet) i.next();
if (!set.isEmpty()) startSet.add(set);
}
startSet = Collections.unmodifiableList(startSet);
}
return startSet;
}
public Traversal.NodeSet getStartSet(Object v) {
NodeSet set = (NodeSet) startSets.get(v);
return (set == null || set.isEmpty()) ? null : set;
}
public List getFinishSet() {
if (finishSet == null) {
finishSet = new ArrayList();
Iterator i = finishSets.values().iterator();
while (i.hasNext()) {
NodeSet set = (NodeSet) i.next();
if (!set.isEmpty()) finishSet.add(set);
}
finishSet = Collections.unmodifiableList(finishSet);
}
return finishSet;
}
public Traversal.NodeSet getFinishSet(Object v) {
NodeSet set = (NodeSet) finishSets.get(v);
return (set == null || set.isEmpty()) ? null : set;
}
/** A string representation of the graph. Print nodes and edges in
each copy of the graph, along with edges between copies. */
public String toString() {
String s[][] = new String[copies][copies+1];
for (int i = 0; i < copies; i++) {
s[i][0] = "Copy " + i + ":\n";
s[i][0] += " Nodes:\n";
for (int j = 0; j < copies; j++) {
s[i][j+1] = "";
}
}
Iterator nodeSets = getNodeSets().iterator();
while (nodeSets.hasNext()) {
NodeSet nodeSet = (NodeSet) nodeSets.next();
for (int i = 0; i < copies; i++) {
if (nodeSet.hasIndex(i)) {
s[i][0] += " " + nodeSet.getNode() + "\n";
}
}
}
for (int i = 0; i < copies; i++) {
s[i][0] += " Edges:\n";
}
Iterator edgeSets = getEdgeSets().iterator();
while (edgeSets.hasNext()) {
EdgeSet edgeSet = (EdgeSet) edgeSets.next();
String edge = edgeKey(edgeSet.getEdge());
for (int i = 0; i < copies; i++) {
if (edgeSet.hasIndices(i, i)) {
s[i][0] += " " + edge + "\n";
}
}
Iterator ici = edgeSet.getIntercopyIndices().iterator();
while (ici.hasNext()) {
IndexPair ip = (IndexPair) ici.next();
s[ip.getSource()][ip.getTarget()+1] +=
" " + ip.getTarget() + ": " + edge + "\n";
}
}
for (int i = 0; i < copies; i++) {
s[i][0] += " Edges to other copies:\n";
}
String ss = "";
for (int i = 0; i < copies; i++) {
for (int j = 0; j < copies+1; j++) {
ss += s[i][j];
}
}
return "Start set: " + getStartSet() + "\n"
+ ss + "Finish set: " + getFinishSet();
}
}