/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2008, Benedikt Huber (benedikt.huber@gmail.com)
* Copyright (C) 2010, Stefan Hepp (stefan@stefant.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jopdesign.common.graphutils;
import com.jopdesign.common.AppInfo;
import com.jopdesign.common.ClassInfo;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.SimpleGraph;
import org.jgrapht.graph.Subgraph;
import org.jgrapht.traverse.DepthFirstIterator;
import org.jgrapht.traverse.TopologicalOrderIterator;
import java.io.IOException;
import java.io.Writer;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* TypeGraph based on JGraphT. Supports Interfaces.
*
* <p>Modifications to the class hierarchy in {@link AppInfo} are not propagated to
* this graph.</p>
*
* @author Benedikt Huber (benedikt.huber@gmail.com)
* @author Stefan Hepp (stefan@stefant.org)
*/
public class TypeGraph extends DefaultDirectedGraph<ClassInfo, DefaultEdge> {
private static final long serialVersionUID = 1L;
private ClassInfo rootNode;
/**
* Create and build a new type graph which mirrors the class hierarchy in {@link AppInfo}, using
* {@code java.lang.Object} as root. Changes to the class hierarchy in {@link AppInfo} or to
* {@link ClassInfo} are not propagated to this graph once it is created.
*/
public TypeGraph() {
super(DefaultEdge.class);
AppInfo ai = AppInfo.getSingleton();
build(ai, ai.getClassInfo("java.lang.Object"));
}
public ClassInfo getRootNode() {
return rootNode;
}
private void build(AppInfo ai, ClassInfo rootNode) {
this.rootNode = rootNode;
Collection<ClassInfo> classes = ai.getClassInfos();
for(ClassInfo ci : classes) {
this.addVertex(ci);
if(ci.getSuperClassInfo() != null) {
if(ci.isInterface() && ! ci.getSuperClassInfo().isInterface()) {
// Interface extends java.lang.Object, ignored
} else {
this.addVertex(ci.getSuperClassInfo());
this.addEdge(ci.getSuperClassInfo() ,ci);
}
}
/* interface edges */
for (String ifaceClass : ci.getInterfaceNames()) {
ClassInfo iface = ai.getClassInfo(ifaceClass);
assert iface != null : ("TypeGraph.build: Interface "+ifaceClass+" not found");
this.addVertex(iface);
this.addEdge(iface ,ci);
}
}
}
/**
* Subtypes (including the type itself) of the type denoted by the given class info
* @param base
* @return
*/
public List<ClassInfo> getSubtypes(ClassInfo base) {
List<ClassInfo> subTypes = getStrictSubtypes(base);
subTypes.add(base);
return subTypes;
}
/**
* Subtypes (not including the type itself) of the type denoted by the given class info
* @param base
* @return
*/
public List<ClassInfo> getStrictSubtypes(ClassInfo base) {
DepthFirstIterator<ClassInfo, DefaultEdge> iter = new DepthFirstIterator<ClassInfo, DefaultEdge>(this,base);
iter.setCrossComponentTraversal(false);
List<ClassInfo> subTypes = new LinkedList<ClassInfo>();
iter.next();
while(iter.hasNext()) {
subTypes.add(iter.next());
}
return subTypes;
}
/**
* Top down traversal, using TopologicalOrderIterator
* @return a top-down traversal of the type graph
*/
public List<ClassInfo> topDownTraversal() {
List<ClassInfo> vertices = new LinkedList<ClassInfo>();
TopologicalOrderIterator<ClassInfo, DefaultEdge> topoIter =
new TopologicalOrderIterator<ClassInfo, DefaultEdge>(this);
while(topoIter.hasNext()) {
vertices.add(topoIter.next());
}
return vertices;
}
/**
* Bottom up traversal, using TopologicalOrderIterator
* @return a bottom-up traversal of the type graph
*/
public List<ClassInfo> bottomUpTraversal() {
List<ClassInfo> vertices = topDownTraversal();
Collections.reverse(vertices);
return vertices;
}
/**
* Compute a the maximum level of each class
* If the longest subtype chain
* {@code Object <: T1 <: T2 <: ... <: T @} has length l+1,
* T has a maximum level of l.
* @return
*/
public Map<ClassInfo,Integer> getLevels() {
Map<ClassInfo, Integer> levels = new HashMap<ClassInfo, Integer>();
for(ClassInfo v : topDownTraversal()) {
int level = 0;
for(DefaultEdge parentEdge : incomingEdgesOf(v))
{
level = Math.max(level, 1 + levels.get(getEdgeSource(parentEdge)));
}
levels.put(v,level);
}
return levels;
}
/**
* Compute, for each type, the set of subtypes, using a single
* bottom-up traversal.
* @return
*/
public Map<ClassInfo, Set<ClassInfo>> getSubtypeSets() {
Map<ClassInfo, Set<ClassInfo>> subtypeMap = new HashMap<ClassInfo, Set<ClassInfo>>();
for(ClassInfo v : bottomUpTraversal()) {
Set<ClassInfo> subtypes = new HashSet<ClassInfo>();
for(DefaultEdge kidEdge : outgoingEdgesOf(v))
{
ClassInfo subtype = getEdgeTarget(kidEdge);
subtypes.addAll(subtypeMap.get(subtype));
subtypes.add(subtype);
}
subtypeMap.put(v,subtypes);
}
return subtypeMap;
}
/**
* Compute, for each type, the set of subtypes, using a single
* top-down traversal
* @return
*/
public Map<ClassInfo, Set<ClassInfo>> getSupertypeSets() {
Map<ClassInfo, Set<ClassInfo>> supertypeMap = new HashMap<ClassInfo, Set<ClassInfo>>();
for(ClassInfo v : topDownTraversal()) {
Set<ClassInfo> supertypes = new HashSet<ClassInfo>();
for(DefaultEdge parentEdge : incomingEdgesOf(v))
{
ClassInfo supertype = getEdgeSource(parentEdge);
supertypes.addAll(supertypeMap.get(supertype));
supertypes.add(supertype);
}
supertypeMap.put(v,supertypes);
}
return supertypeMap;
}
/** Compute the interface conflict graph.<br/>
* Two distinct types A and B conflict if either {@code A <: B} or
* {@code B <: A}, or if A and B share a common descendant.
* Algorithmically, assume A conflicts with B.
* Then either<ul>
* <li/>A is in the <emph>subtype set</emph> of B or vice versa
* <li/>A and B are ancestors of some join node T.
* </ul>
* A join node is a type which is the root of a single inheritance hierarchy,
* but has more than one parent (think: the leaves of the multiple inheritance
* hierarchy).
* <p>
* For detailed information see
* <quote>Vitek, J., Horspool, R. N., and Krall, A. 1997. Efficient type inclusion tests.
* SIGPLAN Not. 32, 10 (Oct. 1997), 142-157.</quote>
* </p>
*
* @return
*/
public SimpleGraph<ClassInfo, DefaultEdge> getInterfaceConflictGraph() {
SimpleGraph<ClassInfo, DefaultEdge> conflicts = new SimpleGraph<ClassInfo, DefaultEdge>(DefaultEdge.class);
Map<ClassInfo, Set<ClassInfo>> ancestors = getSupertypeSets();
Map<ClassInfo, Set<ClassInfo>> subtypes = getSubtypeSets();
for(ClassInfo v : topDownTraversal()) {
if(v.isInterface()) conflicts.addVertex(v);
}
for(ClassInfo a : conflicts.vertexSet()) {
for(ClassInfo b: subtypes.get(a)) {
if(b.isInterface()) conflicts.addEdge(a,b);
}
}
for(ClassInfo joinNode : getJoinNodes()) {
for(ClassInfo a: ancestors.get(joinNode)) {
if(! a.isInterface()) continue;
if(joinNode.isInterface()) conflicts.addEdge(joinNode,a);
for(ClassInfo b: ancestors.get(joinNode)) {
if(b.isInterface() && ! a.equals(b)) conflicts.addEdge(a,b);
}
}
}
return conflicts;
}
/** Compute the set of join nodes<br/>
*
* See <quote>Vitek, J., Horspool, R. N., and Krall, A. 1997. Efficient type inclusion tests.
* SIGPLAN Not. 32, 10 (Oct. 1997), 142-157.</quote>
* @return a map from types to join nodes
*/
public Set<ClassInfo> getJoinNodes() {
Set<ClassInfo> joinNodes = new HashSet<ClassInfo>();
Set<ClassInfo> miNodes = new HashSet<ClassInfo>();
for(ClassInfo v : bottomUpTraversal()) {
boolean hasJoinSub = false;
for(DefaultEdge descEdge : outgoingEdgesOf(v))
{
if(miNodes.contains(getEdgeTarget(descEdge))) {
hasJoinSub = true;
}
}
boolean isMiNode = hasJoinSub;
if(inDegreeOf(v) > 1 && ! hasJoinSub) {
joinNodes.add(v);
isMiNode = true;
}
if(isMiNode) {
miNodes.add(v);
}
}
return joinNodes;
}
/**
* Assign relative numbering for single inheritance constant time typecheck
* <ul>
* <li/>A <: B or A = B iff B.low <= A.low <= B.high
* <li/> {@code () ==> [l,l]}
* <li/> {@code ( [l,h1], [h1+1, h2], ..., [h{n-1}+1,hn] ) ==> [l,hn]}
* </ul>
*
* @return
*/
public Map<ClassInfo,Pair<Integer,Integer>> getRelativeNumbering() {
Map<ClassInfo, Pair<Integer, Integer>> rn = new HashMap<ClassInfo, Pair<Integer,Integer>>();
assignRelativeNumbering(rootNode, rn, 0);
return rn;
}
private int assignRelativeNumbering(ClassInfo node, Map<ClassInfo, Pair<Integer, Integer>> rn, int low) {
int next = low;
for(DefaultEdge subEdge : outgoingEdgesOf(node)) {
ClassInfo sub = getEdgeTarget(subEdge);
if(sub.isInterface()) continue;
next = assignRelativeNumbering(sub,rn,next+1);
}
rn.put(node, new Pair<Integer,Integer>(low,next));
return next;
}
/**
* Bucket assignment for interfaces.
* Two interfaces may not share a bucket if they have a common subtype.
* Uses a simple highest-degree first graph coloring heuristic
* @param maxBucketEntries
* @return
*/
public Map<ClassInfo,Integer> computeInterfaceBuckets(int maxBucketEntries) {
Map<ClassInfo, Integer> bucketMap = new HashMap<ClassInfo, Integer>();
SimpleGraph<ClassInfo, DefaultEdge> conflictGraph = getInterfaceConflictGraph();
List<ClassInfo> ifaces = new LinkedList<ClassInfo>();
for(ClassInfo v : vertexSet()) {
if(! v.isInterface()) continue;
ifaces.add(v);
}
Collections.sort(ifaces, new ReverseSubtypeCountComparator(getSubtypeSets()));
BitSet fullBuckets = new BitSet();
int[] count = new int[ifaces.size()];
for(ClassInfo iface : ifaces) {
BitSet used = (BitSet) fullBuckets.clone();
for(DefaultEdge conflictEdge: conflictGraph.edgesOf(iface)) {
ClassInfo conflictNode = conflictGraph.getEdgeSource(conflictEdge);
// FIXME: ugly JGraphT idiom ?
if(conflictNode == iface) conflictNode = conflictGraph.getEdgeTarget(conflictEdge);
if(bucketMap.containsKey(conflictNode)) {
used.set(bucketMap.get(conflictNode));
}
}
int color = used.nextClearBit(0);
count[color]++;
if(count[color] == maxBucketEntries) {
fullBuckets.set(color);
}
bucketMap.put(iface,color);
}
return bucketMap ;
}
private static class ReverseSubtypeCountComparator implements Comparator<ClassInfo> {
private Map<ClassInfo,Set<ClassInfo>> subTypes;
public ReverseSubtypeCountComparator(Map<ClassInfo, Set<ClassInfo>> subtypeSets) {
this.subTypes = subtypeSets;
}
public int compare(ClassInfo o1, ClassInfo o2) {
return new Integer(subTypes.get(o2).size()).compareTo(subTypes.get(o1).size());
}
}
/**
* Write the type graph in DOT format.
* @param w
* @param classFilter If non-null, only classes matching this prefix will be exported
* @throws IOException
*/
public void exportDOT(Writer w, String classFilter) throws IOException {
Set<ClassInfo> subset = null;
if(classFilter != null) {
subset = new HashSet<ClassInfo>();
subset.add(this.rootNode);
for(ClassInfo ci : this.vertexSet()) {
if(ci.getClassName().startsWith(classFilter)) {
while(ci.getSuperClassInfo() != null) {
subset.add(ci);
ci = ci.getSuperClassInfo();
}
}
}
}
Subgraph<ClassInfo, DefaultEdge, TypeGraph> subgraph =
new Subgraph<ClassInfo, DefaultEdge, TypeGraph>(this,subset);
AdvancedDOTExporter<ClassInfo, DefaultEdge> exporter = new AdvancedDOTExporter<ClassInfo, DefaultEdge>(
new TgNodeLabeller(),
null
);
exporter.exportDOTDiGraph(w, subgraph);
}
private class TgNodeLabeller extends AdvancedDOTExporter.DefaultNodeLabeller<ClassInfo> {
@Override public String getLabel(ClassInfo ci) {
return ci.getClassName();
}
@Override public boolean setAttributes(ClassInfo ci, Map<String, String> ht) {
boolean labelled = super.setAttributes(ci, ht);
if(ci.isInterface()) {
ht.put("shape","ellipse");
}
return labelled;
}
}
// --------------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------
// TESTING using faked ClassInfos
// --------------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------
/*
public static TypeGraph readTestTypeGraph(String file) throws IOException {
TypeGraph testTypeGraph = new TypeGraph();
Map<String,ClassInfo> classes = new HashMap<String, ClassInfo>();
BufferedReader fr = new BufferedReader(new FileReader(new File(file)));
testTypeGraph.rootNode = new FakeClassInfo("java.lang.Object",null,new String[0],false);
classes.put("java.lang.Object",testTypeGraph.rootNode);
String l;
while(null != (l=fr.readLine())) {
String[] typeInfo = l.split("\\s+");
if(typeInfo.length <= 2) continue;
boolean isIface = typeInfo[0].equals("I");
String obj = typeInfo[1];
String sup = typeInfo[2];
String[] ifaces = Arrays.copyOfRange(typeInfo, 3, typeInfo.length);
ClassInfo ci;
if(isIface) {
ci = new FakeClassInfo(obj,sup,ifaces,true);
} else {
ci = new FakeClassInfo(obj,sup,ifaces,false);
}
classes.put(obj,ci);
}
// fix super ref
for(ClassInfo ci : classes.values()) {
if(ci.clazz.getClassName().equals("java.lang.Object")) continue;
String sup = ci.clazz.getSuperclassName();
ci.superClass = classes.get(sup);
}
testTypeGraph.build(classes);
return testTypeGraph;
}
private TypeGraph() {
super(DefaultEdge.class);
}
private static class FakeClassInfo extends ClassInfo {
private FakeClassInfo(String name, String sup, String[] ifaces, boolean isIFace) {
super(null,null);
initClazz(name, sup, ifaces, isIFace);
}
private void initClazz(String name, String sup, String[] ifaces, boolean isIFace) {
ConstantPoolGen cgp = new ConstantPoolGen();
int cthis = cgp.addClass(name);
int csup;
if(sup != null) csup = cgp.addClass(sup);
else csup = 0;
int[] ifaceIds = new int[ifaces.length];
for(int i = 0;i < ifaces.length; i++) {
ifaceIds[i] = cgp.addClass(ifaces[i]);
}
ConstantPool cp = cgp.getConstantPool();
int flags = 0x0001 | 0x0020; // public
if(isIFace) flags |= 0x0200;
this.clazz = new JavaClass(cthis,csup,"test",49,0,flags,cp,ifaceIds,new Field[0],new Method[0], new Attribute[0]);
}
}
*/
}