public interface Attacher {

    static final String CURRENT_DIR = System.getProperty("user.dir");
    static final String DEPENDENCIES_DIR = CURRENT_DIR.endsWith("bin") ? CURRENT_DIR
            + "/../dependencies"
            : CURRENT_DIR + "/dependencies";
    static final String AGENT_JAR = DEPENDENCIES_DIR + "/btrace-agent.jar";
    static final String CLIENT_JAR = DEPENDENCIES_DIR + "/btrace-client.jar";
    static final String SCRIPT_PATH = CURRENT_DIR + '/'
            + ScriptGenerator.FILE_NAME + ".class";

    public void detach();

    public void start() throws IOException;

    public boolean isRunning();

    public String getPid();

    public String getName();

    public String getDescriptor();

} public class Playback implements Attacher, Runnable {

    private static final String DESCRIPTOR = "Playback";

    public enum Mode {
        PACKAGE, CLASS, METHOD
    }

    private static final Pattern RETURN_CALL_PATTERN = Pattern.compile("-.*");

    private volatile Mode mode;
    private volatile Map<Integer, StructMethod> selectedIds;

    private final File script;
    private final int tracesStart;
    private final int tracesLength;

    private volatile double playbackStart;
    private volatile double playbackEnd;
    private volatile TraceInfo previousTrace;
    private volatile LineNumberReader reader;
    private volatile boolean needScannerRestart;

    private final ExecutorService player;
    private volatile boolean running;
    private volatile boolean finished;
    private volatile long wait;

    public Playback(FileParser fp) throws FileNotFoundException {
        this.script = fp.getFile();
        reader = new LineNumberReader(new FileReader(script));

        mode = Mode.METHOD;
        selectedIds = Collections.emptyMap();

        tracesStart = fp.getTracesStart();
        tracesLength = fp.getTracesLength();

        playbackStart = 0d;
        playbackEnd = 1d;
        needScannerRestart = true;

        player = Executors.newSingleThreadExecutor();
        running = false;
        finished = false;
    }

    public File getScript() {
        return script;
    }

    public int getTracesStart() {
        return tracesStart;
    }

    public int getTracesLength() {
        return tracesLength;
    }

    public boolean isFinished() {
        return finished;
    }

    public Mode getMode() {
        return mode;
    }

    public void setMode(Mode mode, Map<Integer, StructMethod> map) {
        this.mode = mode;
        this.selectedIds = map;
    }

    public void setPlaybackStart(double playbackStart) {
        if (playbackStart < 0 || playbackStart > 1)
            throw new IllegalArgumentException(
                    Messages.get("playback.position.exception"));
        this.playbackStart = playbackStart;
        needScannerRestart = true;
        if (!isRunning())
            restartScanner();
    }

    public void setPlaybackEnd(double playbackEnd) {
        if (playbackEnd < 0 || playbackEnd > 1)
            throw new IllegalArgumentException(
                    Messages.get("playback.position.exception"));
        this.playbackEnd = playbackEnd;
    }

    public void setCurrentPos(double currentPos) {
        if (currentPos < 0 || currentPos > 1)
            throw new IllegalArgumentException(
                    Messages.get("playback.position.exception"));
        previousTrace = null;
        restartScannerToPosition((int) (tracesStart + tracesLength * currentPos));
    }

    public void setCurvesPerSecond(int curvesNo) {
        wait = 1000 / curvesNo;
    }

    public double getCurrentPosPercent() {
        if (reader.getLineNumber() <= tracesStart)
            return 0d;
        return (double) (reader.getLineNumber() - tracesStart + 1)
                / (tracesLength - 1);
    }

    @Override
    public void run() {
        previousTrace = null;
        while (running) {
            try {
                finished = false;
                previousTrace = readNextCall(previousTrace);
                Thread.sleep(wait);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private TraceInfo readNextCall(TraceInfo previousTrace)
            throws InterruptedException, IOException {
        String line;
        for (double posPc = getCurrentPosPercent(); (line = reader.readLine()) != null
                && posPc >= playbackStart && posPc <= playbackEnd; posPc = getCurrentPosPercent()) {
            Scanner scanner = new Scanner(line);
            boolean returnCall = scanner.hasNext(RETURN_CALL_PATTERN);
            int methodId = Math.abs(scanner.nextInt());
            long callTime = scanner.nextLong();
            long threadId = scanner.nextLong();

            TraceInfo trace = new TraceInfo(methodId, returnCall, callTime,
                    threadId);
            ScriptHandler.getInstance().sendTraceInfo(trace);

            if (mode == Mode.METHOD && returnFromMethod(trace, previousTrace)) {
                return trace;
            } else if (mode == Mode.CLASS
                    && returnFromClass(trace, previousTrace)) {
                return trace;
            } else if (mode == Mode.PACKAGE
                    && returnFromPackage(trace, previousTrace)) {
                return trace;
            }
        }

        // Can get to here only if did not return.
        needScannerRestart = true;
        running = false;
        finished = true;
        Thread.sleep(50);
        ScriptHandler.getInstance().sendEmptyCall();

        return previousTrace;
    }

    private boolean returnFromMethod(TraceInfo trace, TraceInfo previousTrace) {
        if (trace.isReturnCall() && previousTrace == null)
            return false;
        return selectedIds.containsKey(trace.getMethodId());
    }

    private boolean returnFromClass(TraceInfo trace, TraceInfo previousTrace) {
        if (trace.isReturnCall() && previousTrace == null)
            return false;

        StructMethod currentMethod = selectedIds.get(trace.getMethodId());
        if (previousTrace == null)
            return currentMethod != null;

        StructMethod previousMethod = selectedIds.get(previousTrace
                .getMethodId());
        if (currentMethod == null || previousMethod == null)
            return false;

        return !currentMethod.getParent().equals(previousMethod.getParent());
    }

    private boolean returnFromPackage(TraceInfo trace, TraceInfo previousTrace) {
        if (trace.isReturnCall() && previousTrace == null)
            return false;

        StructMethod currentMethod = selectedIds.get(trace.getMethodId());
        if (previousTrace == null)
            return currentMethod != null;

        StructMethod previousMethod = selectedIds.get(previousTrace
                .getMethodId());
        if (currentMethod == null || previousMethod == null)
            return false;

        StructComponent pckg = currentMethod.getParent().getParent();
        StructComponent previousPckg = previousMethod.getParent().getParent();

        if ((pckg == null && previousPckg != null)
                || (previousPckg == null && pckg != null))
            return true;

        return pckg != null && !pckg.equals(previousPckg);
    }

    @Override
    public void detach() {
        stop();
    }

    @Override
    public void start() {
        player.submit(this);
    }

    private void restartScanner() {
        if (needScannerRestart) {
            int end = (int) (tracesStart + tracesLength * playbackStart);
            configureCurrentPos(end);
        }
    }

    private void restartScannerToPosition(int position) {
        boolean playing = isRunning();
        configureCurrentPos(position);
        if (playing)
            play();
    }

    private void configureCurrentPos(int position) {
        running = false;

        if (reader.getLineNumber() >= position) {
            try {
                reader = new LineNumberReader(new FileReader(script));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }

        while (reader.getLineNumber() < position) {
            try {
                reader.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        needScannerRestart = false;
        ScriptHandler.getInstance().sendEmptyCall();
    }

    public void play() {
        restartScanner();
        running = true;
        start();
    }

    public void pause() {
        running = false;
    }

    public void stop() {
        pause();
        needScannerRestart = true;
        restartScanner();
    }

    @Override
    public boolean isRunning() {
        return running;
    }

    @Override
    public String getPid() {
        return "?";
    }

    @Override
    public String getName() {
        return script.getName();
    }

    @Override
    public String getDescriptor() {
        return DESCRIPTOR;
    }

} public class FileParser {

    private final StructComponent root;
    private final File file;
    private final int tracesStart;
    private final int tracesLength;
    private final TreePath[] treePaths;
    private final List<Integer> depths;
    private int maxDepth;

    public FileParser(File script) throws IOException, ClassNotFoundException {
        file = script;

        FileInputStream fis = new FileInputStream(file);
        ObjectInputStream ois = new ObjectInputStream(fis);
        treePaths = (TreePath[]) ois.readObject();

        root = ((ProjectTreeNode) treePaths[0].getLastPathComponent())
                .getRootStructComp();
        ois.close();
        fis.close();

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(treePaths);

        LineNumberReader lnr = new LineNumberReader(new FileReader(file));
        lnr.skip(bos.size());
        // Consume till line ends and TREEPATHS_TERMINATING_SEQUENCE
        lnr.readLine();
        lnr.readLine();

        String s;
        Matcher m = Pattern.compile(ScriptHandler.HEADER_TERMINATING_SEQUENCE)
                .matcher("");
        while ((s = lnr.readLine()) != null) {
            m.reset(s);
            if (m.matches())
                break;
        }

        tracesStart = lnr.getLineNumber();

        // Skip until the end reached
        depths = new ArrayList<Integer>(5000);
        int depth = 0;
        while ((s = lnr.readLine()) != null) {
            if (s.charAt(0) == '-') {
                if (depth > 0)
                    depth--;
            } else {
                depth++;
            }
            depths.add(depth);
            maxDepth = Math.max(maxDepth, depth);
        }

        tracesLength = lnr.getLineNumber() - tracesStart + 1;
    }

    public List<Integer> getDepths() {
        return depths;
    }

    public int getMaxDepth() {
        return maxDepth;
    }

    public int getTracesLength() {
        return tracesLength;
    }

    public int getTracesStart() {
        return tracesStart;
    }

    public StructComponent getRoot() {
        return root;
    }

    public TreePath[] getTreePaths() {
        return treePaths;
    }

    public File getFile() {
        return file;
    }

    private StructComponent buildPackagePathForClassName(String className) {
        String[] packages = getPackagesForClassName(className);
        StructComponent parent = root;
        for (String aPackage : packages) {
            StructStub stub = new StructStub(aPackage);
            StructPackage structPackage = new StructPackage(parent, stub);
            StructComponent foundChild;
            if ((foundChild = parent.getChild(structPackage)) != null) {
                parent = foundChild;
            } else {
                parent.addStructComponent(structPackage);
                parent = structPackage;
            }
        }
        return parent;
    }

    private String[] getPackagesForClassName(String className) {
        String[] packages = className.split("\\.");
        for (int i = 1; i < packages.length - 1; i++) {
            packages[i] = packages[i - 1] + '.' + packages[i];
        }
        return Arrays.copyOf(packages, packages.length - 1);
    }

} public class ScriptHandler extends Observable {

    private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat(
            "yyyy-MM-dd'T'HHmmssSSS");
    private static final ScriptHandler INSTANCE = new ScriptHandler();
    private static final int FLUSH_COUNT = 2000;
    public static final String TREEPATHS_TERMINATING_SEQUENCE = "treePathsEnd";
    public static final String HEADER_TERMINATING_SEQUENCE = "headerEnd";

    private File outputFile;
    private FileWriter fw;
    private int counter;

    private ScriptHandler() {
        configureNewFileWriter();
    }

    public static ScriptHandler getInstance() {
        return INSTANCE;
    }

    private void configureNewFileWriter() {
        try {
            close();
            outputFile = new File(DATE_FORMATTER.format(new Date()) + ".vis");
            outputFile.deleteOnExit();
            fw = new FileWriter(outputFile);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    private void close() {
        try {
            if (fw != null)
                fw.close();
            if (outputFile != null)
                outputFile.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void saveToFile(File dest, TreePath[] treePathToWrite)
            throws IOException {
        synchronized (outputFile) {
            FileOutputStream fis = new FileOutputStream(dest);
            ObjectOutputStream oos = new ObjectOutputStream(fis);
            if (treePathToWrite != null)
                oos.writeObject(treePathToWrite);
            oos.writeUTF(TREEPATHS_TERMINATING_SEQUENCE + '\n');
            oos.close();
            fis.close();

            flush();
            copyFile(outputFile, dest);
        }
    }

    public void writeScriptLine(String methodId, boolean returnCall,
                                String nanoTime, String threadId) {
        try {
            FileWriter oldFw = fw;
            TraceInfo trace;
            synchronized (outputFile) {
                // Check if created a new FileOutputStream if so do not write
                // anything to a newly created file - discard input.
                if (oldFw != fw)
                    return;

                if (returnCall)
                    oldFw.write('-');
                oldFw.write(methodId);
                oldFw.write(' ');
                oldFw.write(nanoTime);
                oldFw.write(' ');
                oldFw.write(threadId);
                oldFw.write('\n');
                if (++counter % FLUSH_COUNT == 0) {
                    flush();
                }
                trace = new TraceInfo(Integer.parseInt(methodId), returnCall,
                        Long.parseLong(nanoTime), Long.parseLong(threadId));
            }
            sendTraceInfo(trace);
        } catch (IOException e) {
            // This should never happen!
            e.printStackTrace();
        }
    }

    public void sendTraceInfo(TraceInfo trace) {
        if (trace != null) {
            setChanged();
            notifyObservers(trace);
        }
    }

    public void sendEmptyCall() {
        setChanged();
        notifyObservers();
    }

    private void flush() {
        counter = 0;
        try {
            synchronized (outputFile) {
                fw.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void writeScriptHeader(ScriptGenerator scriptGen) {
        try {
            synchronized (outputFile) {
                configureNewFileWriter();
                Map<StructMethod, Integer> map = scriptGen.getMethods();
                for (Entry<StructMethod, Integer> e : map.entrySet()) {
                    StructMethod m = e.getKey();
                    fw.write(String.format("%d %s %d %s %s %d%n", e.getValue(),
                            m.getParentName(), m.getParent().getAccessFlag(),
                            m.getName(), m.getDescriptor(), m.getAccessFlag()));
                }
                fw.write(HEADER_TERMINATING_SEQUENCE + "\n");
                flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void copyLines(File sourceFile, File destFile, long from,
                                 long to) throws IOException {
        if (from >= to)
            return;
        if (!destFile.exists()) {
            destFile.createNewFile();
        }

        LineNumberReader source = null;
        FileOutputStream destination = null;
        try {
            source = new LineNumberReader(new FileReader(sourceFile));
            destination = new FileOutputStream(destFile, true);

            // Skip not copied lines
            while (source.readLine() != null
                    && source.getLineNumber() < from)
                ;

            int flusher = 0;
            String line;
            while ((line = source.readLine()) != null
                    && source.getLineNumber() <= to) {
                destination.write(line.getBytes());
                destination.write('\n');
                flusher++;
                if (flusher >= 5000) {
                    destination.flush();
                    flusher = 0;
                }
            }
            destination.flush();
        } finally {
            if (source != null)
                source.close();
            if (destination != null)
                destination.close();
        }
    }

    public static void copyFile(File sourceFile, File destFile)
            throws IOException {
        if (!destFile.exists()) {
            destFile.createNewFile();
        }

        FileChannel source = null;
        FileChannel destination = null;
        try {
            source = new FileInputStream(sourceFile).getChannel();
            destination = new FileOutputStream(destFile, true).getChannel();
            destination.transferFrom(source, destination.size(), source.size());
        } finally {
            if (source != null) {
                source.close();
            }
            if (destination != null) {
                destination.close();
            }
        }
    }
} public final class Messages {
    private static final ResourceBundle messages;

    static {
        messages = ResourceBundle.getBundle(
                "travis.resources.messages", Locale.getDefault());
    }

    private Messages() {
    }

    public static String get(String key) {
        return messages.getString(key);
    }

    public static String format(String key, Object... args) {
        return MessageFormat.format(get(key), args);
    }
} public class AttacherPanel extends JPanel implements ActionListener {

    private static final long serialVersionUID = -4423926752303119759L;

    private final JButton attachButton;
    private final JButton startButton;
    private final JButton killAllButton;
    private final AttachersTable attachersTable;

    public AttacherPanel() {
        super(new MigLayout("fill, insets 0", "",
                "[growprio 0][growprio 0][growprio 100]"));

        attachButton = new JButton(Messages.get("attach"));
        attachButton.addActionListener(this);
        startButton = new JButton(Messages.get("start"));
        startButton.addActionListener(this);
        killAllButton = new JButton(Messages.get("kill.all"));
        killAllButton.addActionListener(this);

        attachersTable = new AttachersTable();
        JScrollPane scrollPane = new JScrollPane(attachersTable);

        this.add(attachButton, "span, split 2");
        this.add(startButton, "wrap");
        this.add(killAllButton, "span, wrap");
        this.add(scrollPane, "span, grow");
    }

    public AttachersTable getAttachersTable() {
        return attachersTable;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource().equals(attachButton)) {
            attachToProcess();
        } else if (e.getSource().equals(startButton)) {
            startNewProcess();
        } else if (e.getSource().equals(killAllButton)) {
            UIHelper.getInstance().askToKillAllIfConnected();
        }
    }

    private void attachToProcess() {
        JavaProcess process = getProcess();
        if (process == null)
            return;

        Attacher attacher = AttacherFactory.newAttacher(process);
        UIHelper.getInstance().addAndStartAttacher(attacher);
    }

    private JavaProcess getProcess() {
        Collection<JavaProcess> processes = JavaProcess.getVMList();
        if (processes.isEmpty()) {
            UIHelper.getInstance().displayMessage(
                    Messages.get("no.processes.found"),
                    UIHelper.MessageType.INFORMATION);
            return null;
        }

        Object[] possibilities = processes.toArray();
        JavaProcess jp = (JavaProcess) JOptionPane.showInputDialog(UIHelper
                .getInstance().getMainFrame(), Messages.get("process.choose") + '\n',
                Messages.get("process"), JOptionPane.PLAIN_MESSAGE, null,
                possibilities, possibilities[0]);

        if (jp != null) {
            return jp;
        }

        return null;
    }

    private void startNewProcess() {
        StructComponent root = UIHelper.getInstance().getProjectTree()
                .getRoot();
        NewProcessPane processPane = new NewProcessPane(
                root.getMainMethodComponents());
        if (!processPane.getValue().equals(JOptionPane.OK_OPTION))
            return;

        Attacher attacher = AttacherFactory.newAttacher(
                processPane.getJavaOptions(), processPane.getMain(),
                processPane.getProgramArgs(), processPane.getClassPath());
        UIHelper.getInstance().addAndStartAttacher(attacher);
    }

} public class MainFrame extends JFrame implements ActionListener {

    private static final long serialVersionUID = -5320515317070072419L;

    private static final int OPTION_KEY;

    static {
        boolean isMacOs = System.getProperty("os.name").matches("Mac.*");
        OPTION_KEY = isMacOs ? ActionEvent.META_MASK : ActionEvent.CTRL_MASK;
    }

    private final JMenuBar menuBar;
    private final JTabbedPane tabbedPane;

    private JMenuItem saveSubtrace;
    private JMenuItem saveAs;

    public MainFrame() {
        super(Messages.get("app.name"));
        this.setLayout(new MigLayout("fill, hidemode 2"));
        this.setBackground(Color.WHITE);

        menuBar = new JMenuBar();
        tabbedPane = new JTabbedPane();

        setupMenuBar();

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setMinimumSize(new Dimension(1125, 600));
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        this.setPreferredSize(d);
        this.pack();
    }

    public void populateFrame() {
        tabbedPane.addTab(Messages.get("monitors"), UIHelper.getInstance()
                .getAttacherPanel());
        tabbedPane.addTab(Messages.get("console"), UIHelper.getInstance()
                .getConsolePanel());

        JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
                UIHelper.getInstance().getTreePanel(), tabbedPane);

        this.add(splitPane, "w 20%!, grow");
        this.add(UIHelper.getInstance().getGraphLayeredPane(), "w 80%, grow");
        this.add(UIHelper.getInstance().getSettingsPane(), "w 20%!, grow");
        this.add(UIHelper.getInstance().getPlaybackPanel(), "south, h 50px!");

        validate();
        this.setVisible(true);
    }

    public void setAttachMode() {
        tabbedPane.setEnabledAt(0, true);
        tabbedPane.setSelectedIndex(0);

        UIHelper.getInstance().getPlaybackPanel().setEnabled(false);
        saveSubtrace.setEnabled(false);
        saveAs.setEnabled(true);
    }

    public void setPlaybackMode() {
        tabbedPane.setEnabledAt(0, false);
        tabbedPane.setSelectedIndex(1);

        UIHelper.getInstance().getPlaybackPanel().setEnabled(true);
        saveSubtrace.setEnabled(true);
        saveAs.setEnabled(false);
    }

    private void setupMenuBar() {
        // File
        JMenu menu = new JMenu(Messages.get("file"));
        menu.setMnemonic(KeyEvent.VK_F);
        menuBar.add(menu);

        JMenuItem menuItem = new JMenuItem(Messages.get("open.classpath"),
                KeyEvent.VK_O);
        menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
                OPTION_KEY));
        menuItem.addActionListener(this);
        menu.add(menuItem);

        menuItem = new JMenuItem(Messages.get("attach.classpath"),
                KeyEvent.VK_A);
        menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A,
                OPTION_KEY | ActionEvent.SHIFT_MASK));
        menuItem.addActionListener(this);
        menu.add(menuItem);

        menu.addSeparator();

        menuItem = new JMenuItem(Messages.get("open.file"), KeyEvent.VK_F);
        menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
                OPTION_KEY | ActionEvent.SHIFT_MASK));
        menuItem.addActionListener(this);
        menu.add(menuItem);

        saveAs = new JMenuItem(Messages.get("save.as"), KeyEvent.VK_S);
        saveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, OPTION_KEY));
        saveAs.addActionListener(this);
        menu.add(saveAs);

        menu.addSeparator();

        saveSubtrace = new JMenuItem(Messages.get("save.subtrace"),
                KeyEvent.VK_T);
        saveSubtrace.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T,
                OPTION_KEY));
        saveSubtrace.addActionListener(this);
        menu.add(saveSubtrace);

        // Graph
        menu = new JMenu(Messages.get("graph"));
        menu.setMnemonic(KeyEvent.VK_G);
        menuBar.add(menu);

        menuItem = new JMenuItem(Messages.get("reset.traces"), KeyEvent.VK_R);
        menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R,
                OPTION_KEY));
        menuItem.addActionListener(this);
        menu.add(menuItem);

        this.setJMenuBar(menuBar);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getActionCommand().equals(Messages.get("open.classpath"))) {
            UIHelper.getInstance().openClasspath();
        } else if (e.getActionCommand()
                .equals(Messages.get("attach.classpath"))) {
            UIHelper.getInstance().attachClasspath();
        } else if (e.getActionCommand().equals(Messages.get("open.file"))) {
            UIHelper.getInstance().openFile();
        } else if (e.getActionCommand().equals(Messages.get("save.as"))) {
            UIHelper.getInstance().saveFileAs();
        } else if (e.getActionCommand().equals(Messages.get("save.subtrace"))) {
            UIHelper.getInstance().saveSubtrace();
        } else if (e.getActionCommand().equals(Messages.get("reset.traces"))) {
            UIGraphicsHelper.getInstance().resetConnectionsAndRepaintGraph();
        }
    }

} public class ConsolePanel extends JPanel implements ActionListener, Printer {

    private static final long serialVersionUID = -8355796638620826169L;

    private final JTextArea console;
    private final JScrollPane pane;
    private final Timer scrollTimer;

    public ConsolePanel() {
        super(new MigLayout("fill, insets 0"));
        scrollTimer = new Timer(30, this);
        scrollTimer.setRepeats(false);

        ScriptPrinter.getInstance().addListeningPrinter(this);
        System.setErr(new ErrOut(System.err));

        console = new JTextArea();
        console.setEditable(false);
        console.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    JPopupMenu menu = new JPopupMenu();
                    JMenuItem item = new JMenuItem(
                            Messages.get("clear.console"));
                    item.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            console.setText("");
                        }
                    });
                    menu.add(item);
                    menu.show(e.getComponent(), e.getX(), e.getY());
                }
            }
        });
        pane = new JScrollPane(console);
        add(pane, "grow");
    }

    private class ErrOut extends PrintStream {

        public ErrOut(OutputStream out) {
            super(out);
        }

        @Override
        public void write(byte[] buf, int off, int len) {
            super.write(buf, off, len);
            console.append(new String(buf, off, len));
            scrollTimer.restart();
        }

    }

    @Override
    public void actionPerformed(ActionEvent e) {
        pane.getVerticalScrollBar().setValue(
                pane.getVerticalScrollBar().getMaximum());
    }

    @Override
    public void write(String s) {
        console.append(s);
        scrollTimer.restart();
    }

} public class PlaybackPanel extends JPanel implements ActionListener {

    private static final long serialVersionUID = -9083678108698554427L;

    private Playback playback;
    private final JButton playButton;
    private final JButton pauseButton;
    private final JButton stopButton;
    private final PlaybackProgress progress;
    private volatile JButton activeButton;

    public PlaybackPanel() {
        super(new MigLayout("aligny center, alignx center, insets 0"));

        playButton = Util.getButtonWithIcon("playSmall32");
        playButton.addActionListener(this);
        pauseButton = Util.getButtonWithIcon("pauseSmall32");
        pauseButton.addActionListener(this);
        stopButton = Util.getButtonWithIcon("stopSmall32");
        stopButton.addActionListener(this);

        add(playButton, "cell 0 0");
        activeButton = playButton;
        add(stopButton, "cell 1 0");

        progress = new PlaybackProgress(this);
        add(progress, "cell 2 0, w 90%, grow");
    }

    public PlaybackProgress getPlaybackProgress() {
        return progress;
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        activeButton.setEnabled(enabled);
        stopButton.setEnabled(enabled);
        progress.setVisible(enabled);
    }

    public Playback getPlayback() {
        return playback;
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        progress.repaint();

        if (playback != null && !playback.isRunning()
                && activeButton == pauseButton)
            togglePlayPause(pauseButton, playButton);
    }

    public void play() {
        togglePlayPause(playButton, pauseButton);
        if (playback != null) {
            if (playback.isFinished())
                UIGraphicsHelper.getInstance()
                        .resetConnectionsAndRepaintGraph();

            updatePlaybackSpeed();
            updatePlaybackMode(false);
            playback.play();
        }
    }

    public void updatePlaybackSpeed() {
        if (playback != null) {
            playback.setCurvesPerSecond(Settings.getInstance()
                    .getCurvesPerSec());
        }
    }

    public void updatePlaybackMode() {
        updatePlaybackMode(true);
    }

    private void updatePlaybackMode(boolean force) {
        if (playback != null) {
            Mode mode = Mode.PACKAGE;
            if (Settings.getInstance().isDrawingStruct(Settings.STRUCT_METHOD)) {
                mode = Mode.METHOD;
            } else if (Settings.getInstance().isDrawingStruct(
                    Settings.STRUCT_CLASS)) {
                mode = Mode.CLASS;
            }

            if (mode != playback.getMode() || force) {
                playback.setMode(mode, UIHelper.getInstance().getProjectTree()
                        .getSelectedMethodsIds());
            }
        }
    }

    public void pause() {
        togglePlayPause(pauseButton, playButton);
        if (playback != null)
            playback.pause();
    }

    private void togglePlayPause(JButton from, JButton to) {
        remove(from);
        add(to, "cell 0 0");
        to.requestFocusInWindow();
        activeButton = to;
        validate();
    }

    public void stop() {
        pause();
        if (playback != null)
            playback.stop();
        UIGraphicsHelper.getInstance().resetConnectionsAndRepaintGraph();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource().equals(playButton)) {
            play();
        } else if (e.getSource().equals(pauseButton)) {
            pause();
        } else if (e.getSource().equals(stopButton)) {
            stop();
        }
    }

    public void setFileParser(FileParser fp) {
        try {
            playback = AttacherFactory.newAttacher(fp);
            UIHelper.getInstance().startAttacher(playback);
            updatePlaybackMode(true);
            progress.setupPlaybackGraph(fp.getDepths(), fp.getMaxDepth());
            progress.resetPlaybackRange();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

} public class PlaybackProgress extends JPanel {

    private static final long serialVersionUID = -8725982120299231092L;

    private static final Color SELECTION = new Color(0xaa, 0xff, 0x99, 200);
    private static final Color CURRENT_POS = new Color(0x44, 0x44, 0xff);
    private static final int TOP_MARGIN = 3;

    private final PlaybackPanel playbackPanel;
    private final MouseHandler mouseHandler;

    private Image depthGraph;

    public PlaybackProgress(PlaybackPanel playbackPanel) {
        super();
        this.playbackPanel = playbackPanel;
        setBackground(new Color(0xdd, 0xdd, 0xdd));
        mouseHandler = new MouseHandler();
        addMouseListener(mouseHandler);
        addMouseMotionListener(mouseHandler);
    }

    public void setupPlaybackGraph(List<Integer> depths, int maxDepth) {
        if (maxDepth == 0) {
            depthGraph = null;
            return;
        }

        int red = (255 << 24) - (1 << 23);
        int w = getWidth();
        int h = getHeight() - TOP_MARGIN;
        int[] pix = new int[w * h];
        int maxIndex = depths.size() - 1;

        for (int i = 0; i < w; i++) {
            float widthPercent = (float) i / (w - 1);
            float depth = depths.get((int) (widthPercent * maxIndex));
            float depthPercent = depth / maxDepth;
            int height = (int) (h - h * depthPercent);
            for (int j = height; j < h; j++) {
                pix[j * w + i] = red;
            }
        }

        depthGraph = createImage(new MemoryImageSource(w, h, pix, 0, w));
    }

    public Point getMouseDragStart() {
        return mouseHandler.mouseDragStart;
    }

    public Point getMouseDragEnd() {
        return mouseHandler.mouseDragEnd;
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);

        if (getMouseDragStart() != null && getMouseDragEnd() != null) {
            g.setColor(SELECTION);
            int start = Math.min(getMouseDragStart().x, getMouseDragEnd().x);
            int end = Math.max(getMouseDragStart().x, getMouseDragEnd().x);
            g.fillRect(start, 0, end - start, getHeight());
        }

        if (depthGraph != null) {
            Graphics2D g2 = (Graphics2D) g;
            g2.drawImage(depthGraph, 0, TOP_MARGIN, null);
        }

        Playback playback;
        if ((playback = playbackPanel.getPlayback()) != null) {
            g.setColor(CURRENT_POS);
            double currentPost = playback.getCurrentPosPercent();
            g.fillRect((int) (getWidth() * currentPost) - 1, 0, 2, getHeight());
        }
    }

    public void resetPlaybackRange() {
        mouseHandler.actionPerformed(new ActionEvent(this, 0, ""));
    }

    public double getPlaybackStart() {
        if (getMouseDragStart() == null)
            return 0d;

        double start = Math.min(getMouseDragStart().getX(), getMouseDragEnd()
                .getX())
                / getWidth();
        start = start < 0 ? 0d : start;
        return start;
    }

    public double getPlaybackEnd() {
        if (getMouseDragEnd() == null)
            return 1d;

        double end = Math.max(getMouseDragStart().getX(), getMouseDragEnd()
                .getX())
                / getWidth();
        end = end > 1 ? 1d : end;
        return end;
    }

    private void updatePlaybackRange() {
        Playback playback;
        if ((playback = playbackPanel.getPlayback()) != null) {
            playback.setPlaybackStart(getPlaybackStart());
            playback.setPlaybackEnd(getPlaybackEnd());
        }
    }

    private void updateCurrentPosition() {
        Playback playback;
        if ((playback = playbackPanel.getPlayback()) != null) {
            UIGraphicsHelper.getInstance().resetConnectionsAndRepaintGraph();
            double pos = mouseHandler.mousePressCoord.getX() / getWidth();
            // Bit defensive ;)
            pos = pos < 0 ? 0 : pos;
            pos = pos > 1 ? 1 : pos;
            playback.setCurrentPos(pos);
        }
    }

    private class MouseHandler extends MouseAdapter implements ActionListener {
        private boolean dragged;
        private volatile Point mousePressCoord = new Point();
        private Point mouseDragStart;
        private Point mouseDragEnd;

        @Override
        public void mouseDragged(MouseEvent e) {
            if (Math.abs(mousePressCoord.x - e.getPoint().x) < 3)
                return;

            dragged = true;
            mouseDragStart = mousePressCoord;
            mouseDragEnd = e.getPoint();
            repaint();
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (!dragged) {
                if (e.getButton() == MouseEvent.BUTTON3) {
                    JPopupMenu menu = new JPopupMenu();
                    JMenuItem item = new JMenuItem(
                            Messages.get("clear.selection"));
                    item.addActionListener(this);
                    menu.add(item);
                    menu.show(e.getComponent(), e.getX(), e.getY());
                } else {
                    updateCurrentPosition();
                    repaint();
                }
            } else {
                updatePlaybackRange();
            }
        }

        @Override
        public void mousePressed(MouseEvent e) {
            dragged = false;
            mousePressCoord = e.getPoint();
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            mouseDragStart = null;
            mouseDragEnd = null;
            updatePlaybackRange();
            repaint();
        }
    }

} public class GraphLayeredPane extends JPanel {

    private static final long serialVersionUID = 2855872910533934574L;

    private final GraphPanel graph;
    private final GraphTooltip tooltip;

    public GraphLayeredPane(GraphPanel graph, GraphTooltip tooltip) {
        super(new BorderLayout());
        this.graph = graph;
        this.tooltip = tooltip;

        JLayeredPane layeredPane = new JLayeredPane();
        layeredPane.setOpaque(true);

        layeredPane.add(graph, JLayeredPane.DEFAULT_LAYER);
        layeredPane.add(tooltip, JLayeredPane.POPUP_LAYER);

        add(layeredPane, BorderLayout.CENTER);
    }

    @Override
    public void setBounds(int x, int y, int width, int height) {
        super.setBounds(x, y, width, height);

        graph.setBounds(0, 0, width, height);
        tooltip.setBounds(0, 0, width, height);
    }
} public class GraphPanel extends JPanel {

    private static final long serialVersionUID = -2719889219523140214L;
    private static final int MARGIN_X = 5;
    private static final int MARGIN_Y = 5;

    private final TreeRepresentation treeRep;
    private final ConnectionPainter connectPainter;

    private int alignX;
    private int alignY;
    private Dimension oldD;

    public GraphPanel() {
        super();
        setOpaque(true);

        treeRep = new TreeRepresentation();
        connectPainter = new ConnectionPainter(treeRep);
    }

    public TreeRepresentation getTreeRepRepresentation() {
        return treeRep;
    }

    public ConnectionPainter getConnectionPainter() {
        return connectPainter;
    }

    public void updateImage() {
        connectPainter.setNeedRepaint(true);
        int radius = Math.min(getWidth() - MARGIN_X * 2, getHeight() - MARGIN_Y
                * 2) / 2;
        treeRep.createImage(radius, Settings.getInstance().getGraphRotate());
        repaint();
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        if (oldD == null || !oldD.equals(getSize())) {
            oldD = getSize();
            int diameter = Math.min(getWidth() - MARGIN_X * 2, getHeight()
                    - MARGIN_Y * 2);
            alignX = (getWidth() - diameter) / 2;
            alignY = (getHeight() - diameter) / 2;
            updateImage();
        }

        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHints(Util.HINTS);
        g2.translate(alignX, alignY);

        g2.drawImage(treeRep.getImage(), null, 0, 0);

        g2.drawImage(connectPainter.getImage(getWidth(), getHeight()), null, 0,
                0);
    }

    public int getAlignX() {
        return alignX;
    }

    public int getAlignY() {
        return alignY;
    }

} public class GraphTooltip extends JPanel {

    private static final long serialVersionUID = 6214722087448317356L;

    private static final int MAX_CURVE_DIST = 2;
    private static final int MAX_CURVES_PER_SEC_FOR_PLAYBACK_TOOLTIP = 3;
    private static final Font FONT = new Font("Monospaced", Font.PLAIN, 12);
    private static final Color BACKGROUND = new Color(1f, 1f, 0.75f, 0.85f);
    private static final Color BACKGROUND_ALTERNATE = new Color(0.75f, 1f, 1f,
            0.85f);
    private static final Color SPLINE_COLOUR = Color.BLUE;
    private static final int MARGIN = 2;

    private volatile Point coord;
    private volatile String text;

    private final LinkedBlockingDeque<SplineData> splines;

    private ExecutionPoint playbackExecutionPoint;

    public GraphTooltip() {
        setOpaque(false);
        setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));

        splines = new LinkedBlockingDeque<SplineData>();

        addMouseMotionListener(new MouseListener());
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        if (coord == null)
            return;

        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHints(Util.HINTS);

        g2.setFont(FONT);
        FontMetrics fm = g2.getFontMetrics();
        int height = fm.getHeight() + fm.getDescent();
        Point tooltipCoord = new Point(coord);
        tooltipCoord.y -= fm.getDescent();

        if (playbackExecutionPoint != null) {
            String text = getTextForExecutionPoint(playbackExecutionPoint);
            Point epCoord = new Point(playbackExecutionPoint.getCenter());
            drawTooltip(fm, text, epCoord, g2, height, BACKGROUND);
        }

        if (text != null) {
            drawTooltip(fm, text, tooltipCoord, g2, height, BACKGROUND);
        } else if (!splines.isEmpty()) {
            GraphPanel graph = UIHelper.getInstance().getGraph();
            AffineTransform oldT = g2.getTransform();
            g2.translate(graph.getAlignX(), graph.getAlignY());

            for (SplineData sd : splines) {
                sd.getSpline().draw(g2, SPLINE_COLOUR);
            }
            g2.setTransform(oldT);

            if (splines.size() > 1) {
                String text = "Most recent first:";
                drawTooltip(fm, text, tooltipCoord, g2, height, BACKGROUND);
                tooltipCoord.y += height;
            }

            int i = 0;
            for (SplineData sd : splines) {
                String text = getTextForSpline(sd);

                if (i++ % 2 == 0)
                    drawTooltip(fm, text, tooltipCoord, g2, height, BACKGROUND);
                else
                    drawTooltip(fm, text, tooltipCoord, g2, height,
                            BACKGROUND_ALTERNATE);

                tooltipCoord.y += height;
            }
        }
    }

    private String getTextForExecutionPoint(ExecutionPoint ep) {
        Mode mode = getMode();
        String text = null;
        switch (mode) {
            case METHOD:
                text = ep.getComponentData().getComp().getTooltipFriendlyName()
                        + " THREAD: " + ep.getTrace().getThreadId();
                break;
            case CLASS:
                text = ep.getComponentData().getComp().getParent()
                        .getTooltipFriendlyName()
                        + " THREAD: " + ep.getTrace().getThreadId();
                break;
            case PACKAGE:
                text = ep.getComponentData().getComp().getParent().getParent()
                        .getTooltipFriendlyName()
                        + " THREAD: " + ep.getTrace().getThreadId();
                break;
            default:
                break;
        }

        return text;
    }

    private String getTextForSpline(SplineData sd) {
        Mode mode = getMode();
        String text = null;
        switch (mode) {
            case METHOD:
                text = "FROM " + sd.getSrc().getComp().getTooltipFriendlyName()
                        + " TO " + sd.getDest().getComp().getTooltipFriendlyName()
                        + " THREAD: " + sd.getDestTrace().getThreadId();
                break;
            case CLASS:
                text = "FROM "
                        + sd.getSrc().getComp().getParent()
                        .getTooltipFriendlyName()
                        + " TO "
                        + sd.getDest().getComp().getParent()
                        .getTooltipFriendlyName() + " THREAD: "
                        + sd.getDestTrace().getThreadId();
                break;
            case PACKAGE:
                text = "FROM "
                        + sd.getSrc().getComp().getParent().getParent()
                        .getTooltipFriendlyName()
                        + " TO "
                        + sd.getDest().getComp().getParent().getParent()
                        .getTooltipFriendlyName() + " THREAD: "
                        + sd.getDestTrace().getThreadId();
                break;
            default:
                break;
        }

        return text;
    }

    private Mode getMode() {
        Mode mode = Mode.PACKAGE;
        if (Settings.getInstance().isDrawingStruct(Settings.STRUCT_METHOD)) {
            mode = Mode.METHOD;
        } else if (Settings.getInstance().isDrawingStruct(
                Settings.STRUCT_CLASS)) {
            mode = Mode.CLASS;
        }
        return mode;
    }

    private void drawTooltip(FontMetrics fm, String text, Point tooltipCoord,
                             Graphics2D g2, int height, Color background) {
        int width = fm.stringWidth(text);
        int rectWidth = width + MARGIN * 2;

        Point coord = new Point(tooltipCoord);
        if (coord.x + rectWidth >= getWidth())
            coord.x -= coord.x + rectWidth - getWidth();

        g2.setColor(background);
        g2.fillRoundRect(coord.x,
                coord.y - height + fm.getDescent(), rectWidth, height,
                height / 2, height / 2);

        g2.setColor(Color.BLACK);
        g2.drawString(text, coord.x + MARGIN, coord.y);
    }

    public void displayExecutionPointTooltip(ExecutionPoint ep) {
        UIHelper helper = UIHelper.getInstance();
        if (helper.getMode() == UIHelper.Mode.PLAYBACK
                && Settings.getInstance().getCurvesPerSec() <= MAX_CURVES_PER_SEC_FOR_PLAYBACK_TOOLTIP) {
            playbackExecutionPoint = ep;
        } else {
            playbackExecutionPoint = null;
        }

        if (ep == null || ep.getComponentData() == null
                || ep.getCenter() == null) {
            playbackExecutionPoint = null;
        }
    }

    private class MouseListener extends MouseAdapter {
        @Override
        public void mouseEntered(MouseEvent e) {
        }

        @Override
        public void mouseExited(MouseEvent e) {
            text = null;
            splines.clear();
            repaint();
        }

        @Override
        public void mouseMoved(MouseEvent e) {
            text = null;
            splines.clear();

            GraphPanel graph = UIHelper.getInstance().getGraph();
            TreeRepresentation treeRep = graph.getTreeRepRepresentation();
            ConnectionPainter connPainter = graph.getConnectionPainter();

            coord = e.getPoint();
            Point p = new Point(e.getPoint().x - graph.getAlignX(),
                    e.getPoint().y - graph.getAlignY());

            ExecutionPoint ep = connPainter.getExecutionPoint();
            if (ep != null && ep.contains(p)) {
                text = ep.getComponentData().getComp().getTooltipFriendlyName()
                        + " THREAD: " + ep.getTrace().getThreadId();
                repaint();
                return;
            }

            ComponentData cd = treeRep.getElementForCoord(p);
            if (cd != null) {
                text = cd.getComp().getTooltipFriendlyName();
            } else {
                Collection<GraphBspline> gSplines = connPainter.getSplines();
                for (GraphBspline gSpline : gSplines) {
                    double distance;
                    if ((distance = gSpline.distanceFrom(p)) < MAX_CURVE_DIST) {
                        ComponentData[] methods = treeRep.getMethods();
                        ComponentData src = methods[gSpline.getCallerTrace()
                                .getMethodId()];
                        ComponentData dst = methods[gSpline.getCalleeTrace()
                                .getMethodId()];
                        splines.addFirst(new SplineData(gSpline, src, gSpline
                                .getCallerTrace(), dst, gSpline
                                .getCalleeTrace(), distance));
                    }
                }

            }

            repaint();
        }
    }

} public class ProjectTree implements ChangeListener {

    private final ScriptGenerator scriptGen;
    private final JTree tree;
    private final CheckBoxTreeCellRenderer renderer;
    private StructComponent root;
    private ProjectTreeNode rootNode;
    private volatile boolean changed;
    private volatile boolean needRebuildScript;

    private final Semaphore painterSemaphore = new Semaphore(1);
    private final ExecutorService painterWaiter = Executors
            .newSingleThreadExecutor();

    public ProjectTree() {
        scriptGen = new ScriptGenerator();
        tree = new JTree(new Object[]{});
        renderer = new CheckBoxTreeCellRenderer(tree);
        tree.setCellRenderer(renderer);
        renderer.addChangeListener(this);
        tree.addMouseListener(new TreePopupMenu(this));
        needRebuildScript = true;
        buildEmptyTree();
    }

    @Override
    public void stateChanged(ChangeEvent e) {
        if (!painterSemaphore.tryAcquire()) {
            return;
        }
        waitForMoreNotifications(10, e);
    }

    private void waitForMoreNotifications(final long waitTime,
                                          final ChangeEvent evt) {
        painterWaiter.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(waitTime);
                    repaintTree(evt);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    painterSemaphore.release();
                }
            }
        });
    }

    private void repaintTree(ChangeEvent e) {
        if (e.getSource().equals(renderer)) {
            changed = true;
            needRebuildScript = true;
            UIGraphicsHelper.getInstance().repaintTreeGraph();
            if (UIHelper.getInstance().getMode() == Mode.PLAYBACK)
                UIHelper.getInstance().updatePlaybackMode();
        }
    }

    public boolean isChanged() {
        return changed;
    }

    public void setChanged(boolean changed) {
        this.changed = changed;
    }

    public void consumeChenge() {
        changed = false;
    }

    public void buildEmptyTree() {
        Builder builder = new Builder();
        root = builder.build();
        rebuildTree();
    }

    public void setRoot(FileParser fp) {
        renderer.resetCheckedPaths();
        this.root = fp.getRoot();
        rebuildTree();
        restoreSelections(fp.getTreePaths());
    }

    public void buildTree(File directory) {
        renderer.resetCheckedPaths();
        Builder builder = new Builder();
        try {
            root = builder.build(directory);
            if (!isValidClassPath(root)) {
                if (UIHelper.getInstance().displayConfirmation(
                        Messages.get("incorrect.classpath")))
                    UIHelper.getInstance().openClasspath();
                return;
            }

            rebuildTree();
        } catch (Exception e) {
            UIHelper.getInstance().displayException(e);
        }
    }

    public void attachTree(final File directory) {
        treeAction(new TreeAction() {
            @Override
            public void act() {
                Builder builder = new Builder();
                try {
                    StructComponent classpathRoot = builder.build(directory);
                    if (!isValidClassPath(classpathRoot)) {
                        if (UIHelper.getInstance().displayConfirmation(
                                Messages.get("incorrect.classpath")))
                            UIHelper.getInstance().attachClasspath();
                        return;
                    }

                    root.addStructComponents(classpathRoot.getChildren());
                    rebuildTree();
                } catch (Exception e) {
                    UIHelper.getInstance().displayException(e);
                }
            }
        });
    }

    public void treeAction(TreeAction treeAction) {
        Enumeration<TreePath> expanded = getExpandedPaths();
        TreePath[] checkedPaths = getCheckedPaths();

        treeAction.act();

        restoreExpandedPathsAndSelections(expanded, checkedPaths);
    }

    private void restoreExpandedPathsAndSelections(
            Enumeration<TreePath> expanded, TreePath[] checkedPaths) {
        rebuildTree();

        restoreSelections(checkedPaths);
        restoreExpandedPaths(expanded);
    }

    private void restoreSelections(TreePath[] checkedPaths) {
        renderer.resetCheckedPaths();
        if (checkedPaths != null && checkedPaths.length > 0) {
            for (TreePath path : checkedPaths) {
                toggleChecked(path);
            }
        }
        renderer.fireStateChanged();
    }

    private void restoreExpandedPaths(Enumeration<TreePath> expanded) {
        if (expanded != null) {
            while (expanded.hasMoreElements()) {
                TreePath treePath = expanded.nextElement();
                tree.expandPath(treePath);
            }
        }
    }

    private Enumeration<TreePath> getExpandedPaths() {
        TreePath rootPath = new TreePath(rootNode);
        return tree.getExpandedDescendants(rootPath);
    }

    private boolean isValidClassPath(StructComponent root) {
        for (StructComponent comp : root.getChildren()) {
            if (comp.isPartOfClassPath()) {
                if (comp instanceof StructClass) {
                    if (!comp.getName().contains(".")) {
                        return true;
                    }
                } else if (comp instanceof StructPackage) {
                    return true;
                }
            }
        }
        return false;
    }

    public void rebuildTree() {
        changed = true;
        needRebuildScript = true;
        rootNode = new ProjectTreeNode(root);
        tree.setModel(new DefaultTreeModel(rootNode));
        tree.requestFocusInWindow();
    }

    public void generateScript() {
        setupScriptGenerator();
        scriptGen.buildScript();
    }

    public void setupScriptGenerator() {
        if (!needRebuildScript)
            return;

        TreePath[] checkedPaths = renderer.getCheckedPaths();
        Set<StructComponent> comps = new TreeSet<StructComponent>();
        for (TreePath tp : checkedPaths) {
            ProjectTreeNode node = (ProjectTreeNode) tp.getLastPathComponent();
            StructComponent comp = node.getUserObject();
            comps.add(comp);
        }
        try {
            scriptGen.generateForComponents(root, comps);
        } catch (IOException e) {
            UIHelper.getInstance().displayException(e);
        }

        needRebuildScript = false;
        // Set<StructComponent> comps2 = new TreeSet<StructComponent>();
        // comps2.add(root);
        // scriptGen.generateForComponents(root, comps2);
    }

    public JTree getTree() {
        return tree;
    }

    public StructComponent getRoot() {
        return root;
    }

    public ProjectTreeNode getRootNode() {
        return rootNode;
    }

    public boolean isSelected(ProjectTreeNode node) {
        TreePath path = new TreePath(node.getPath());
        return renderer.isChecked(path) || renderer.isPartiallyChecked(path);
    }

    public TreePath[] getCheckedPaths() {
        return renderer.getCheckedPaths();
    }

    public void toggleChecked(TreePath path) {
        renderer.toggleCheckedIgnoreChanged(path);
    }

    public int getTotalNumberOfSelectedMethods() {
        return getNumberOfSelectedMethods(rootNode);
    }

    public int getNumberOfSelectedMethods(ProjectTreeNode node) {
        int i = 0;
        if (node.getUserObject() instanceof StructMethod
                && Settings.getInstance().isDrawingMethod(node.getUserObject())) {
            TreePath path = new TreePath(node.getPath());
            if (renderer.isChecked(path))
                i++;
        } else {
            Enumeration<ProjectTreeNode> children = node.children();
            while (children.hasMoreElements()) {
                ProjectTreeNode child = children.nextElement();
                if (Settings.getInstance()
                        .isDrawingClass(child.getUserObject()))
                    i += getNumberOfSelectedMethods(child);
            }
        }
        return i;
    }

    public Map<Integer, StructMethod> getSelectedMethodsIds() {
        Map<Integer, StructMethod> idMap = new TreeMap<Integer, StructMethod>();

        Map<StructMethod, Integer> methods = getStructMethods();
        for (Entry<StructMethod, Integer> e : methods.entrySet()) {
            StructMethod method = e.getKey();
            ProjectTreeNode treeNode = ProjectTreeNode.findMethod(rootNode,
                    method);
            // ProjectTreeNode treeNode = rootNode.find(method);
            if (treeNode != null
                    && isSelected(treeNode)
                    && Settings.getInstance()
                    .isDrawingClass(method.getParent())
                    && Settings.getInstance().isDrawingMethod(method)) {
                idMap.put(e.getValue(), method);
            }
        }
        return idMap;
    }

    public Map<StructMethod, Integer> getStructMethods() {
        setupScriptGenerator();
        return scriptGen.getMethods();
    }

} public class TreePanel extends JPanel {

    private static final long serialVersionUID = -1199916802849919758L;

    private final ProjectTree tree;

    public TreePanel() {
        super(new MigLayout("fill, insets 0"));
        tree = new ProjectTree();

        JScrollPane treeView = new JScrollPane(tree.getTree());
        treeView.setPreferredSize(new Dimension(300, 500));
        this.add(treeView, "span, grow");
    }

    public ProjectTree getTree() {
        return tree;
    }

    public JTree getJTree() {
        return tree.getTree();
    }

    public void buildTree(File directory) {
        tree.buildTree(directory);
    }

    public void attachTree(File directory) {
        tree.attachTree(directory);
    }

} public class SettingsPane extends JTabbedPane implements Observer {

    private static final long serialVersionUID = -6658851623888731026L;

    private static final Settings SETS = Settings.getInstance();

    private final Set<SettingsTab> tabs;

    public SettingsPane() {
        super(JTabbedPane.LEFT);

        tabs = new HashSet<SettingsTab>();

        SettingsTab visibilityPanel = new VisibilityPanel();
        SettingsTab layersPanel = new LayersPanel();
        SettingsTab graphPanel = new GraphPanel();
        SettingsTab tracesPanel = new TracesPanel();
        SettingsTab colorsPanel = new ColorsPanel();

        addTab(Messages.get("visibility"), visibilityPanel);
        tabs.add(visibilityPanel);
        addTab(Messages.get("layers"), layersPanel);
        tabs.add(layersPanel);
        addTab(Messages.get("graph"), graphPanel);
        tabs.add(graphPanel);
        addTab(Messages.get("traces"), tracesPanel);
        tabs.add(tracesPanel);
        addTab(Messages.get("colors"), colorsPanel);
        tabs.add(colorsPanel);

        SETS.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        for (SettingsTab tab : tabs) {
            tab.updateValues();
        }
    }

}
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.io.File;
import java.io.IOException;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;
import travis.model.attach.Attacher;
import travis.model.attach.Playback;
import travis.model.script.FileParser;
import travis.model.script.ScriptHandler;
import travis.util.Messages;
import travis.view.AttacherPanel;
import travis.view.MainFrame;
import travis.view.console.ConsolePanel;
import travis.view.playback.PlaybackPanel;
import travis.view.playback.PlaybackProgress;
import travis.view.project.graph.GraphLayeredPane;
import travis.view.project.graph.GraphPanel;
import travis.view.project.graph.GraphTooltip;
import travis.view.project.tree.ProjectTree;
import travis.view.project.tree.TreePanel;
import travis.view.settings.SettingsPane;
        fc.setAcceptAllFileFilterUsed(false);

        attachers = new Vector<Attacher>();

        frame = new MainFrame();
        treePanel = new TreePanel();
        attacherPanel = new AttacherPanel();
        consolePanel = new ConsolePanel();
        graph = new GraphPanel();
        tooltip = new GraphTooltip();
        graphLayeredPane = new GraphLayeredPane(graph, tooltip);
        settingsPane = new SettingsPane();
        playbackPanel = new PlaybackPanel();

        mode = Mode.ATTACH;
    }

    public static UIHelper getInstance() {
        return INSTANCE;
    }

    public void populateFrame() {
        fc.setCurrentDirectory(new File(System.getProperty("user.dir")));
        currentDirectory = fc.getCurrentDirectory();
        frame.populateFrame();
        changeMode(Mode.ATTACH);
    }

    private void changeMode(Mode mode) {
        this.mode = mode;
        if (mode == Mode.ATTACH) {
            frame.setAttachMode();
        } else if (mode == Mode.PLAYBACK) {
            frame.setPlaybackMode();
        }
    }

    public Mode getMode() {
        return mode;
    }

    public Vector<Attacher> getAttachers() {
        return new Vector<Attacher>(attachers);
    }

    public void killAllAttachers() {
        synchronized (attachers) {
            for (Attacher attacher : attachers) {
                attacher.detach();
            }
            attachers.clear();
        }
        repaintAttachersTable();
    }

    public void startAttacher(Attacher attacher) {
        getProjectTree().setupScriptGenerator();

        try {
            attacher.start();
        } catch (IOException e) {
            displayException(e);
        }
    }

    public void addAndStartAttacher(final Attacher attacher) {
        final JDialog dialog = getAndShowProgressBarWindow(Messages
                .get("attaching"));
        dispatcher.submit(new Runnable() {
            @Override
            public void run() {
                attachers.add(attacher);
                getProjectTree().generateScript();

                try {
                    attacher.start();
                } catch (IOException e) {
                    displayException(e);
                }
                repaintAttachersTable();

                dialog.setVisible(false);
            }
        });
    }

    public void removeAndKillAttacher(Attacher attacher) {
        attacher.detach();
        attachers.remove(attacher);
        repaintAttachersTable();
    }

    public ConsolePanel getConsolePanel() {
        return consolePanel;
    }

    public File getCurrentDirectory() {
        return currentDirectory;
    }

    public void setCurrentDirectory(File currentDirectory) {
        this.currentDirectory = currentDirectory;
    }

    public void updatePlaybackMode() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                playbackPanel.updatePlaybackMode();
            }
        });
    }

    public void updatePlaybackSpeed() {
        playbackPanel.updatePlaybackSpeed();
    }

    public TreePanel getTreePanel() {
        return treePanel;
    }

    public ProjectTree getProjectTree() {