view src/share/tools/IdealGraphVisualizer/GraphTextEditor/src/com/sun/hotspot/igv/graphtexteditor/TextTopComponent.java @ 3608:5a1a05d3a30b

IdealGraphVisualizer: prevent unnecessary updates of the text component when the current graph changes
author Peter Hofer <peter.hofer@jku.at>
date Wed, 02 Nov 2011 14:09:40 +0100
parents 6ccb95c97e6d
children abb55d24ba56
line wrap: on
line source

/*
 * Copyright (c) 2008, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */
package com.sun.hotspot.igv.graphtexteditor;

import com.sun.hotspot.igv.data.ChangedListener;
import com.sun.hotspot.igv.data.Properties;
import com.sun.hotspot.igv.texteditor.*;
import com.sun.hotspot.igv.data.InputGraph;
import com.sun.hotspot.igv.data.Pair;
import com.sun.hotspot.igv.data.Property;
import com.sun.hotspot.igv.graph.Diagram;
import com.sun.hotspot.igv.graph.services.DiagramProvider;
import com.sun.hotspot.igv.graphtotext.services.GraphToTextConverter;
import com.sun.hotspot.igv.selectioncoordinator.SelectionCoordinator;
import com.sun.hotspot.igv.structuredtext.MultiElement;
import com.sun.hotspot.igv.structuredtext.StructuredText;
import com.sun.hotspot.igv.util.LookupHistory;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.util.Collection;
import java.util.logging.Logger;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.JToolBar;
import org.netbeans.api.diff.Diff;
import org.netbeans.api.diff.DiffView;
import org.netbeans.api.diff.StreamSource;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;

/**
 * @author Thomas Wuerthinger
 * @author Peter Hofer
 */
final class TextTopComponent extends TopComponent implements LookupListener {

    private static TextTopComponent instance;
    private Lookup.Result result = null;
    private static final String PREFERRED_ID = "TextTopComponent";
    private Diagram lastDiagram;
    private TextEditor leftEditor;
    private TextEditor rightEditor;
    private TextEditor singleEditor;
    private JSplitPane splitPane;
    private CardLayout cardLayout;
    private JPanel cardLayoutPanel;
    private JComboBox sourceCombo;
    private boolean firstTimeSplitter = true;
    private JPanel textDiffPanel;

    private static final String TWO_GRAPHS_TEXT_DIFF = "twoGraphsTextDiff";
    private static final String TWO_GRAPHS = "twoGraphs";
    private static final String ONE_GRAPH = "oneGraph";
    private static final String NO_GRAPH = "noGraph";

    private static final String GRAPH_TEXT_REPRESENTATION = "< Graph Text Representation >";

    private DiagramProvider currentDiagramProvider;

    private TextTopComponent() {
        initComponents();
        setName(NbBundle.getMessage(TextTopComponent.class, "CTL_TextTopComponent"));
        setToolTipText(NbBundle.getMessage(TextTopComponent.class, "HINT_TextTopComponent"));

        setLayout(new BorderLayout());

        // Selector for displayed data
        JToolBar sourceSelectBar = new JToolBar();
        sourceSelectBar.setLayout(new BorderLayout());
        sourceSelectBar.setFloatable(false);
        sourceSelectBar.add(new JLabel("Show: "), BorderLayout.WEST);
        sourceCombo = new JComboBox();
        sourceCombo.addItem(GRAPH_TEXT_REPRESENTATION);
        sourceCombo.addItemListener(sourceSelectionListener);
        sourceSelectBar.add(sourceCombo, BorderLayout.CENTER);
        add(sourceSelectBar, BorderLayout.NORTH);

        // Card layout for three different views.
        cardLayout = new CardLayout();
        cardLayoutPanel = new JPanel(cardLayout);
        add(cardLayoutPanel, BorderLayout.CENTER);

        // No graph selected.
        JLabel noGraphLabel = new JLabel("No graph open.", JLabel.CENTER);
        noGraphLabel.setOpaque(true);
        noGraphLabel.setBackground(Color.WHITE);
        cardLayoutPanel.add(noGraphLabel, NO_GRAPH);

        // Single graph selected.
        singleEditor = new TextEditor();
        cardLayoutPanel.add(singleEditor.getComponent(), ONE_GRAPH);

        // Graph difference => show split pane with two graphs.
        splitPane = new JSplitPane();
        leftEditor = new TextEditor();
        rightEditor = new TextEditor();
        // Work around a problem with JSplitPane and the NetBeans editor:
        // setDividerLocation() doesn't work when the split pane has not been
        // layouted and painted yet. JSplitPane then initially uses a tiny width
        // for the left editor component, which causes the editor to calculate
        // invalid offsets and constantly throw exceptions, particularly on
        // mouse events. Thus, defer adding the two components and setting the
        // divider's location.
        splitPane.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                if (firstTimeSplitter && splitPane.getWidth() > 0) {
                    splitPane.setLeftComponent(leftEditor.getComponent());
                    splitPane.setRightComponent(rightEditor.getComponent());
                    splitPane.setDividerLocation(0.5);
                    firstTimeSplitter = false;
                }
            }
        });
        cardLayoutPanel.add(splitPane, TWO_GRAPHS);
        
        // Text difference => NetBeans diff view
        // Diff component is created and added on demand
        textDiffPanel = new JPanel(new BorderLayout());
        cardLayoutPanel.add(textDiffPanel, TWO_GRAPHS_TEXT_DIFF);
    }


    private StructuredText convert(InputGraph graph, Diagram diagram) {
        Collection<? extends GraphToTextConverter> converters = Lookup.getDefault().lookupAll(GraphToTextConverter.class);
        StructuredText text = null;
        if (converters.size() == 0) {
            text = new StructuredText(graph.getName());
            text.println("No graph-to-text converter exists!");
            return text;
        }

        for (GraphToTextConverter converter : converters) {
            if (converter.canConvert(graph)) {
                text = converter.convert(graph, diagram);
                if (text == null) {
                    text = new StructuredText(graph.getName());
                    text.println("Class " + converter.getClass().getName() + " misbehaved and returned null on graph-to-text conversion!");
                }
                return text;
            }
        }

        text = new StructuredText(graph.getName());
        text.println("No appropriate graph-to-text converter found!");
        return text;
    }

    private StructuredText createStructuredPlainText(String name, String text) {
        StructuredText structured = new StructuredText(name);
        MultiElement multi = new MultiElement();
        multi.print(text);
        structured.addChild(multi);
        return structured;
    }

    private ItemListener sourceSelectionListener = new ItemListener() {
        public void itemStateChanged(ItemEvent e) {
            if (e.getStateChange() == ItemEvent.SELECTED) {
                if (e.getItem() == GRAPH_TEXT_REPRESENTATION) {
                    displayDiagram(lastDiagram);
                } else {
                    displayGroupProperty(lastDiagram, (String) e.getItem());
                }
            }
        }
    };

    private void setDiagram(Diagram diagram) {
        if (diagram == lastDiagram) {
            // No change => return.
            return;
        }
        lastDiagram = diagram;

        // Rebuild combobox choices
        Object selection = sourceCombo.getSelectedItem();
        sourceCombo.removeAllItems();
        // NOTE: addItem() makes the first inserted item the selected item,
        //       so use insertItemAt() instead
        sourceCombo.insertItemAt(GRAPH_TEXT_REPRESENTATION, 0);
        if (diagram != null) {
            if (diagram.getGraph().getSourceGraphs() != null) {
                // Diff graph with source graphs with possibly different groups:
                // show properties from both graphs
                Pair<InputGraph, InputGraph> sourceGraphs = diagram.getGraph().getSourceGraphs();
                Properties props = new Properties(sourceGraphs.getLeft().getGroup().getProperties());
                if (sourceGraphs.getLeft().getGroup() != sourceGraphs.getRight().getGroup()) {
                    props.add(sourceGraphs.getRight().getGroup().getProperties());
                }
                for (Property p : props) {
                    sourceCombo.addItem(p.getName());
                }
            } else {
                // Single graph
                for (Property p : diagram.getGraph().getGroup().getProperties()) {
                    sourceCombo.addItem(p.getName());
                }
            }
        }
        // NOTE: The following triggers a display update.
        sourceCombo.setSelectedItem(selection);
        if (sourceCombo.getSelectedItem() == null) {
            // previously selected property doesn't exist in new graph's group:
            // default to show graph representation
            sourceCombo.setSelectedItem(GRAPH_TEXT_REPRESENTATION);
        }
    }

    private void displayGroupProperty(Diagram diagram, String property) {
        if (diagram == null) {
            showCard(NO_GRAPH);
        } else if (diagram.getGraph().getSourceGraphs() != null) {
            showCard(TWO_GRAPHS_TEXT_DIFF);
            textDiffPanel.removeAll();
            try {
                Pair<InputGraph, InputGraph> sourceGraphs = diagram.getGraph().getSourceGraphs();

                String ltext = sourceGraphs.getLeft().getGroup().getProperties().get(property);
                if (ltext == null) {
                    ltext = "";
                }
                StreamSource leftsrc = StreamSource.createSource("left", sourceGraphs.getLeft().getName(), "text/plain", new StringReader(ltext));

                String rtext = sourceGraphs.getRight().getGroup().getProperties().get(property);
                if (rtext == null) {
                    rtext = "";
                }
                StreamSource rightsrc = StreamSource.createSource("right", sourceGraphs.getRight().getName(), "text/plain", new StringReader(rtext));

                DiffView view = Diff.getDefault().createDiff(leftsrc, rightsrc);
                textDiffPanel.add(view.getComponent(), BorderLayout.CENTER);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            textDiffPanel.revalidate(); // required when card was visible before
        } else {
            showCard(ONE_GRAPH);
            String text = diagram.getGraph().getGroup().getProperties().get(property);
            singleEditor.setStructuredText(createStructuredPlainText(diagram.getGraph().getName(), text));
        }
    }

    private void displayDiagram(Diagram diagram) {
        if (diagram == null) {
            showCard(NO_GRAPH);
        } else if (diagram.getGraph().getSourceGraphs() != null) {
            showCard(TWO_GRAPHS);
            Pair<InputGraph, InputGraph> graphs = diagram.getGraph().getSourceGraphs();
            leftEditor.setStructuredText(convert(graphs.getLeft(), diagram));
            rightEditor.setStructuredText(convert(graphs.getRight(), diagram));

            // TODO: Hack to update view - remove
            SelectionCoordinator.getInstance().getHighlightedChangedEvent().fire();
        } else {
            showCard(ONE_GRAPH);
            StructuredText text = convert(diagram.getGraph(), diagram);
            singleEditor.setStructuredText(text);

            // TODO: Hack to update view - remove
            SelectionCoordinator.getInstance().getHighlightedChangedEvent().fire();
        }
    }

    private ChangedListener<DiagramProvider> diagramChangedListener = new ChangedListener<DiagramProvider>() {

        public void changed(DiagramProvider source) {
            setDiagram(source.getDiagram());
        }
        
    };

    private void setDiagramProvider(DiagramProvider provider) {
        if (provider == currentDiagramProvider) {
            return;
        }

        if (currentDiagramProvider != null) {
            currentDiagramProvider.getChangedEvent().removeListener(diagramChangedListener);
        }

        currentDiagramProvider = provider;

        if (currentDiagramProvider != null) {
            currentDiagramProvider.getChangedEvent().addListener(diagramChangedListener);
            setDiagram(currentDiagramProvider.getDiagram());
        } else {
            setDiagram(null);
        }
    }

    private void showCard(final String card) {
        cardLayout.show(cardLayoutPanel, card);
    }

    public void resultChanged(LookupEvent lookupEvent) {
        DiagramProvider p = Utilities.actionsGlobalContext().lookup(DiagramProvider.class);

        if (p == null) {
            p = LookupHistory.getLast(DiagramProvider.class);
        }

        setDiagramProvider(p);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        setLayout(new java.awt.BorderLayout());
    }// </editor-fold>//GEN-END:initComponents


    // Variables declaration - do not modify//GEN-BEGIN:variables
    // End of variables declaration//GEN-END:variables
    /**
     * Gets default instance. Do not use directly: reserved for *.settings files only,
     * i.e. deserialization routines; otherwise you could get a non-deserialized instance.
     * To obtain the singleton instance, use {@link findInstance}.
     */
    public static synchronized TextTopComponent getDefault() {
        if (instance == null) {
            instance = new TextTopComponent();
        }
        return instance;
    }

    /**
     * Obtain the TextTopComponent instance. Never call {@link #getDefault} directly!
     */
    public static synchronized TextTopComponent findInstance() {
        TopComponent win = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
        if (win == null) {
            Logger.getLogger(TextTopComponent.class.getName()).warning(
                    "Cannot find " + PREFERRED_ID + " component. It will not be located properly in the window system.");
            return getDefault();
        }
        if (win instanceof TextTopComponent) {
            return (TextTopComponent) win;
        }
        Logger.getLogger(TextTopComponent.class.getName()).warning(
                "There seem to be multiple components with the '" + PREFERRED_ID +
                "' ID. That is a potential source of errors and unexpected behavior.");
        return getDefault();
    }

    @Override
    public int getPersistenceType() {
        return TopComponent.PERSISTENCE_ALWAYS;
    }

    @Override
    public void componentOpened() {

        DiagramProvider p = LookupHistory.getLast(DiagramProvider.class);
        setDiagramProvider(p);

        Lookup.Template<DiagramProvider> tpl = new Lookup.Template<DiagramProvider>(DiagramProvider.class);
        result = Utilities.actionsGlobalContext().lookup(tpl);
        result.addLookupListener(this);
    }

    @Override
    public void componentClosed() {
        result.removeLookupListener(this);
        result = null;
        setDiagramProvider(null);
    }

    /** replaces this in object stream */
    @Override
    public Object writeReplace() {
        return new ResolvableHelper();
    }

    @Override
    protected String preferredID() {
        return PREFERRED_ID;
    }

    @Override
    public void requestActive() {
        super.requestActive();
        cardLayoutPanel.requestFocus();
    }

    final static class ResolvableHelper implements Serializable {

        private static final long serialVersionUID = 1L;

        public Object readResolve() {
            return TextTopComponent.getDefault();
        }
    }
}