pku.sei.checkedcoverage.jung.ShowJungGraph.java Source code

Java tutorial

Introduction

Here is the source code for pku.sei.checkedcoverage.jung.ShowJungGraph.java

Source

/** License information:
 *    Component: checkedcoverage-jung
 *    Package:   pku.sei.checkedcoverage.jung
 *    Class:     ShowJungGraph
 *    Filename:  checkedcoverage-jung/src/main/java/de/unisb/cs/st/checkedcoverage/jung/ShowJungGraph.java
 *
 * This file is part of the checkedcoverage tool, developed by Clemens Hammacher at Saarland University.
 * See http://www.st.cs.uni-saarland.de/checkedcoverage/ for more information.
 *
 * This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
 * To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a
 * letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
 */
package pku.sei.checkedcoverage.jung;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import javax.swing.JFrame;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.collections15.Transformer;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import pku.sei.checkedcoverage.common.classRepresentation.Instruction;
import pku.sei.checkedcoverage.common.classRepresentation.InstructionInstance;
import pku.sei.checkedcoverage.common.classRepresentation.InstructionType;
import pku.sei.checkedcoverage.common.classRepresentation.instructions.MethodInvocationInstruction;
import pku.sei.checkedcoverage.common.progress.ConsoleProgressMonitor;
import pku.sei.checkedcoverage.common.progress.ProgressMonitor;
import pku.sei.checkedcoverage.slicing.SliceVisitor;
import pku.sei.checkedcoverage.slicing.Slicer;
import pku.sei.checkedcoverage.slicing.SlicingCriterion;
import pku.sei.checkedcoverage.traceResult.ThreadId;
import pku.sei.checkedcoverage.traceResult.TraceResult;
import pku.sei.checkedcoverage.variables.ArrayElement;
import pku.sei.checkedcoverage.variables.LocalVariable;
import pku.sei.checkedcoverage.variables.ObjectField;
import pku.sei.checkedcoverage.variables.StackEntry;
import pku.sei.checkedcoverage.variables.StaticField;
import pku.sei.checkedcoverage.variables.Variable;
import edu.uci.ics.jung.algorithms.layout.KKLayout;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode;
import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;

/**
 * This class implements a main method which creates a graph showing the dependences
 * leading to the dynamic slice, and visualizes it.
 *
 * @author Clemens Hammacher
 *
 * @param <VertexType> the type of the vertices in the graph
 */
public class ShowJungGraph<VertexType> {

    private final TraceResult trace;
    private final List<ProgressMonitor> progressMonitors = new ArrayList<ProgressMonitor>(1);
    private final List<SliceVisitor> sliceVisitors = new ArrayList<SliceVisitor>(1);

    private Transformer<InstructionInstance, VertexType> vertexTransformer;
    private Transformer<VertexType, String> vertexLabelTransformer;
    private Transformer<VertexType, String> vertexTooltipTransformer;
    private int maxLevel;

    public ShowJungGraph(TraceResult trace, Transformer<InstructionInstance, VertexType> vertexTransformer) {
        this.trace = trace;
        this.vertexTransformer = vertexTransformer;
    }

    public static void main(String[] args) throws InterruptedException {
        Options options = createOptions();
        CommandLineParser parser = new GnuParser();
        CommandLine cmdLine;

        try {
            cmdLine = parser.parse(options, args, true);
        } catch (ParseException e) {
            System.err.println("Error parsing the command line arguments: " + e.getMessage());
            return;
        }

        if (cmdLine.hasOption('h')) {
            printHelp(options, System.out);
            System.exit(0);
        }

        String[] additionalArgs = cmdLine.getArgs();
        if (additionalArgs.length != 2) {
            printHelp(options, System.err);
            System.exit(-1);
        }
        File traceFile = new File(additionalArgs[0]);
        String slicingCriterionString = additionalArgs[1];

        Long threadId = null;
        if (cmdLine.hasOption('t')) {
            try {
                threadId = Long.parseLong(cmdLine.getOptionValue('t'));
            } catch (NumberFormatException e) {
                System.err.println("Illegal thread id: " + cmdLine.getOptionValue('t'));
                System.exit(-1);
            }
        }

        TraceResult trace;
        try {
            trace = TraceResult.readFrom(traceFile);
        } catch (IOException e) {
            System.err.format("Could not read the trace file \"%s\": %s%n", traceFile, e);
            System.exit(-1);
            return;
        }

        List<SlicingCriterion> sc = null;
        try {
            sc = SlicingCriterion.parseAll(slicingCriterionString, trace.getReadClasses());
        } catch (IllegalArgumentException e) {
            System.err.println("Error parsing slicing criterion: " + e.getMessage());
            System.exit(-1);
            return;
        }

        List<ThreadId> threads = trace.getThreads();
        if (threads.size() == 0) {
            System.err.println("The trace file contains no tracing information.");
            System.exit(-1);
        }

        ThreadId tracing = null;
        for (ThreadId t : threads) {
            if (threadId == null) {
                if ("main".equals(t.getThreadName())
                        && (tracing == null || t.getJavaThreadId() < tracing.getJavaThreadId()))
                    tracing = t;
            } else if (t.getJavaThreadId() == threadId.longValue()) {
                tracing = t;
            }
        }

        if (tracing == null) {
            System.err.println(threadId == null ? "Couldn't find the main thread."
                    : "The thread you specified was not found.");
            System.exit(-1);
            return;
        }

        Transformer<InstructionInstance, Object> transformer;
        Transformer<Object, String> vertexLabelTransformer;
        Transformer<Object, String> vertexTooltipTransformer;

        String granularity = cmdLine.getOptionValue("granularity");
        if (granularity == null || "instance".equals(granularity)) {
            transformer = new Transformer<InstructionInstance, Object>() {
                public InstructionInstance transform(InstructionInstance inst) {
                    return inst;
                }
            };
            vertexLabelTransformer = new Transformer<Object, String>() {
                public String transform(Object inst) {
                    return getShortInstructionText(((InstructionInstance) inst).getInstruction());
                }
            };
            vertexTooltipTransformer = new Transformer<Object, String>() {
                public String transform(Object inst) {
                    return getInstructionTooltip(((InstructionInstance) inst).getInstruction());
                }
            };
        } else if ("instruction".equals(granularity)) {
            transformer = new Transformer<InstructionInstance, Object>() {
                public Instruction transform(InstructionInstance inst) {
                    return inst.getInstruction();
                }
            };
            vertexLabelTransformer = new Transformer<Object, String>() {
                public String transform(Object inst) {
                    return getShortInstructionText(((Instruction) inst));
                }
            };
            vertexTooltipTransformer = new Transformer<Object, String>() {
                public String transform(Object inst) {
                    return getInstructionTooltip(((Instruction) inst));
                }
            };
        } else if ("line".equals(granularity)) {
            transformer = new Transformer<InstructionInstance, Object>() {
                public Line transform(InstructionInstance inst) {
                    return new Line(inst.getInstruction().getMethod(), inst.getInstruction().getLineNumber());
                }
            };
            vertexLabelTransformer = new Transformer<Object, String>() {
                public String transform(Object inst) {
                    Line line = (Line) inst;
                    return line.getMethod().getName() + ":" + line.getLineNr();
                }
            };
            vertexTooltipTransformer = new Transformer<Object, String>() {
                public String transform(Object inst) {
                    Line line = (Line) inst;
                    return "Line " + line.getLineNr() + " in method " + line.getMethod().getReadClass().getName()
                            + "." + line.getMethod();
                }
            };
        } else {
            System.err.println("Illegal granularity specification: " + granularity);
            System.exit(-1);
            return;
        }

        int maxLevel = Integer.MAX_VALUE;
        if (cmdLine.hasOption("maxlevel")) {
            try {
                maxLevel = Integer.parseInt(cmdLine.getOptionValue("maxlevel"));
            } catch (NumberFormatException e) {
                System.err.println("Argument to \"maxlevel\" must be an integer.");
                System.exit(-1);
                return;
            }
        }

        long startTime = System.nanoTime();
        ShowJungGraph<Object> showGraph = new ShowJungGraph<Object>(trace, transformer);
        showGraph.setMaxLevel(maxLevel);
        showGraph.setVertexLabelTransformer(vertexLabelTransformer);
        showGraph.setVertexTooltipTransformer(vertexTooltipTransformer);
        if (cmdLine.hasOption("progress"))
            showGraph.addProgressMonitor(new ConsoleProgressMonitor());
        boolean multithreaded;
        if (cmdLine.hasOption("multithreaded")) {
            String multithreadedStr = cmdLine.getOptionValue("multithreaded");
            multithreaded = ("1".equals(multithreadedStr) || "true".equals(multithreadedStr));
        } else {
            multithreaded = Runtime.getRuntime().availableProcessors() > 1;
        }

        DirectedGraph<Object, SliceEdge<Object>> graph = showGraph.getGraph(tracing, sc, multithreaded);
        long endTime = System.nanoTime();

        System.out.format((Locale) null, "%nSlice graph consists of %d nodes.%n", graph.getVertexCount());
        System.out.format((Locale) null, "Computation took %.2f seconds.%n", 1e-9 * (endTime - startTime));

        showGraph.displayGraph(graph);
    }

    private void setMaxLevel(int maxLevel) {
        this.maxLevel = maxLevel;
    }

    public void displayGraph(DirectedGraph<VertexType, SliceEdge<VertexType>> graph) {
        JFrame mainFrame = new JFrame("slice graph display");
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Layout<VertexType, SliceEdge<VertexType>> layout = new KKLayout<VertexType, SliceEdge<VertexType>>(graph);
        final VisualizationViewer<VertexType, SliceEdge<VertexType>> viewer = new VisualizationViewer<VertexType, SliceEdge<VertexType>>(
                layout);

        viewer.getRenderContext().setVertexLabelTransformer(this.vertexLabelTransformer);
        viewer.setVertexToolTipTransformer(this.vertexTooltipTransformer);
        viewer.getRenderContext().setEdgeLabelTransformer(new Transformer<SliceEdge<VertexType>, String>() {
            public String transform(SliceEdge<VertexType> edge) {
                Variable var = edge.getVariable();
                if (var instanceof ArrayElement) {
                    ArrayElement elem = (ArrayElement) var;
                    return "arr" + elem.getArrayId() + "[" + elem.getArrayIndex() + "]";
                } else if (var instanceof LocalVariable) {
                    LocalVariable localVar = (LocalVariable) var;
                    if (localVar.getVarName() != null)
                        return localVar.getVarName();
                    return "local" + localVar.getVarIndex();
                } else if (var instanceof StackEntry) {
                    return "stack";
                } else if (var instanceof ObjectField) {
                    ObjectField objField = (ObjectField) var;
                    return "obj" + objField.getObjectId() + "." + objField.getFieldName();
                } else if (var instanceof StaticField) {
                    StaticField objField = (StaticField) var;
                    String ownerInternalClassName = objField.getOwnerInternalClassName();
                    String simpleClassName = getSimpleClassName(ownerInternalClassName);
                    return simpleClassName + "." + objField.getFieldName();
                } else {
                    assert var == null;
                    return "";
                }
            }
        });
        viewer.setEdgeToolTipTransformer(new Transformer<SliceEdge<VertexType>, String>() {
            public String transform(SliceEdge<VertexType> edge) {
                Variable var = edge.getVariable();
                if (var instanceof ArrayElement) {
                    ArrayElement elem = (ArrayElement) var;
                    return "Array element " + elem.getArrayIndex() + " of array #" + elem.getArrayId();
                } else if (var instanceof LocalVariable) {
                    LocalVariable localVar = (LocalVariable) var;
                    if (localVar.getVarName() != null)
                        return "Local variable \"" + localVar.getVarName() + "\"";
                    return "Local variable #" + localVar.getVarIndex();
                } else if (var instanceof StackEntry) {
                    return "Dependency over the operand stack";
                } else if (var instanceof ObjectField) {
                    ObjectField objField = (ObjectField) var;
                    return "Field \"" + objField.getFieldName() + "\" of object #" + objField.getObjectId();
                } else if (var instanceof StaticField) {
                    StaticField objField = (StaticField) var;
                    String ownerInternalClassName = objField.getOwnerInternalClassName();
                    return "Static field \"" + objField.getFieldName() + "\" of class \""
                            + Type.getType(ownerInternalClassName).getClassName();
                } else {
                    assert var == null;
                    return "Control dependency";
                }
            }
        });
        viewer.getRenderContext().setEdgeDrawPaintTransformer(new Transformer<SliceEdge<VertexType>, Paint>() {
            public Paint transform(SliceEdge<VertexType> edge) {
                return edge.getVariable() == null ? Color.red : Color.black;
            }
        });

        viewer.getRenderer().getVertexLabelRenderer().setPosition(Position.CNTR);
        viewer.getRenderContext().setVertexShapeTransformer(new Transformer<VertexType, Shape>() {
            public Shape transform(VertexType inst) {
                Font font = viewer.getFont();
                String text = viewer.getRenderContext().getVertexLabelTransformer().transform(inst);
                FontRenderContext fontRenderContext = ((Graphics2D) viewer.getGraphics()).getFontRenderContext();
                Rectangle2D bounds = font.getStringBounds(text, fontRenderContext);
                return new RoundRectangle2D.Double(-.6 * bounds.getWidth(), -.6 * bounds.getHeight(),
                        1.2 * bounds.getWidth(), 1.2 * bounds.getHeight(), 8, 8);
            }
        });

        viewer.setBackground(Color.white);

        DefaultModalGraphMouse<InstructionInstance, SliceEdge<VertexType>> mouse = new DefaultModalGraphMouse<InstructionInstance, SliceEdge<VertexType>>();
        mouse.setMode(Mode.ANNOTATING);
        viewer.setGraphMouse(mouse);

        mainFrame.getContentPane().add(viewer);
        mainFrame.pack();

        mainFrame.setVisible(true);
    }

    protected static String getShortInstructionText(Instruction inst) {
        if (inst.getType() == InstructionType.METHODINVOCATION) {
            MethodInvocationInstruction mtdInv = (MethodInvocationInstruction) inst;
            String type;
            switch (mtdInv.getOpcode()) {
            case Opcodes.INVOKEVIRTUAL:
                type = "INVOKEVIRTUAL";
                break;
            case Opcodes.INVOKESPECIAL:
                type = "INVOKESPECIAL";
                break;
            case Opcodes.INVOKESTATIC:
                type = "INVOKESTATIC";
                break;
            case Opcodes.INVOKEINTERFACE:
                type = "INVOKEINTERFACE";
                break;
            default:
                assert false;
                type = "--ERROR--";
            }

            return type + ' ' + getSimpleClassName(mtdInv.getInvokedInternalClassName()) + '.'
                    + mtdInv.getInvokedMethodName();
        }
        return inst.toString();
    }

    protected static String getInstructionTooltip(Instruction insn) {
        String location = insn.getMethod().getReadClass() + "." + insn.getMethod() + ":" + insn.getLineNumber();

        if (insn.getType() == InstructionType.METHODINVOCATION) {
            return insn.toString() + " at " + location;
        }

        return location;
    }

    public void addProgressMonitor(ProgressMonitor progressMonitor) {
        this.progressMonitors.add(progressMonitor);
    }

    public void addSliceVisitor(SliceVisitor sliceVisitor) {
        this.sliceVisitors.add(sliceVisitor);
    }

    public DirectedGraph<VertexType, SliceEdge<VertexType>> getGraph(ThreadId threadId, List<SlicingCriterion> sc,
            boolean multithreaded) throws InterruptedException {

        CreateJungGraphSliceVisitor<VertexType> visitor = new CreateJungGraphSliceVisitor<VertexType>(
                this.vertexTransformer, this.maxLevel);

        Slicer slicer = new Slicer(this.trace);
        for (ProgressMonitor mon : this.progressMonitors)
            slicer.addProgressMonitor(mon);
        slicer.addSliceVisitor(visitor);

        slicer.process(threadId, sc, multithreaded);
        return visitor.getGraph();
    }

    protected static String getSimpleClassName(String internalClassName) {
        int lastSlashPos = internalClassName.lastIndexOf('/');
        String simpleClassName = lastSlashPos == -1 ? internalClassName
                : internalClassName.substring(lastSlashPos + 1);
        return simpleClassName;
    }

    public void setVertexLabelTransformer(Transformer<VertexType, String> vertexLabelTransformer) {
        this.vertexLabelTransformer = vertexLabelTransformer;
    }

    public void setVertexTooltipTransformer(Transformer<VertexType, String> vertexTooltipTransformer) {
        this.vertexTooltipTransformer = vertexTooltipTransformer;
    }

    public void setVertexTransformer(Transformer<InstructionInstance, VertexType> vertexTransformer) {
        this.vertexTransformer = vertexTransformer;
    }

    @SuppressWarnings("static-access")
    private static Options createOptions() {
        Options options = new Options();
        options.addOption(OptionBuilder.isRequired(false).withArgName("threadid").hasArg(true)
                .withDescription("thread id to select for slicing (default: main thread)").withLongOpt("threadid")
                .create('t'));
        options.addOption(OptionBuilder.isRequired(false).hasArg(false)
                .withDescription("show progress while computing the slice graph").withLongOpt("progress")
                .create('p'));
        options.addOption(OptionBuilder.isRequired(false).hasArg(false).withDescription("print this help and exit")
                .withLongOpt("help").create('h'));
        options.addOption(OptionBuilder.isRequired(false).hasArg(true).withArgName("value").withDescription(
                "process the trace in a multithreaded way (pass 'true' or '1' to enable, anything else to disable). Default is true iff we have more than one processor")
                .withLongOpt("multithreaded").create('m'));
        options.addOption(OptionBuilder.isRequired(false).hasArg(true).withArgName("instance|instruction|line")
                .withDescription("defines the granularity of the created graph. default is instance")
                .withLongOpt("granularity").create('g'));
        options.addOption(OptionBuilder.isRequired(false).hasArg(true).withArgName("int")
                .withDescription("defines the maximum distance from the slicing criterion to visualize")
                .withLongOpt("maxlevel").create('l'));
        return options;
    }

    private static void printHelp(Options options, PrintStream out) {
        out.println("Usage: " + ShowJungGraph.class.getSimpleName() + " [<options>] <file> <slicing criterion>");
        out.println("where <file> is the input trace file");
        out.println("      <slicing criterion> has the form <loc>[(<occ>)]:<var>[,<loc>[(<occ>)]:<var>]*");
        out.println("      <options> may be one or more of");
        HelpFormatter formatter = new HelpFormatter();
        PrintWriter pw = new PrintWriter(out, true);
        formatter.printOptions(pw, 120, options, 5, 3);
    }

}