/*
 * Licensed Materials - Property of Perforce Software, Inc. 
 * © Copyright Perforce Software, Inc. 2014, 2021 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.ResourceBundle;
import java.util.Vector;

import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.Border;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.html.HTMLEditorKit;

import ilog.views.IlvGrapher;
import ilog.views.IlvManagerView;
import ilog.views.IlvRect;
import ilog.views.IlvTransformer;
import ilog.views.diagrammer.IlvDiagrammer;
import ilog.views.diagrammer.application.IlvDiagrammerAction;
import ilog.views.diagrammer.application.IlvDiagrammerHelpMenu;
import ilog.views.diagrammer.application.IlvDiagrammerMenu;
import ilog.views.diagrammer.application.IlvDiagrammerToolBar;
import ilog.views.diagrammer.application.IlvDiagrammerViewBar;
import ilog.views.diagrammer.datasource.IlvXMLDataSource;
import ilog.views.diagrammer.project.IlvDiagrammerProject;
import ilog.views.sdm.IlvSDMEngine;
import ilog.views.sdm.IlvSDMModel;
import ilog.views.sdm.event.SDMGraphLayoutRendererEvent;
import ilog.views.sdm.event.SDMGraphLayoutRendererListener;
import ilog.views.sdm.model.IlvDefaultSDMModel;
import ilog.views.sdm.model.IlvPreorderEnumeration;
import ilog.views.sdm.renderer.graphlayout.IlvLinkLayoutRenderer;
import ilog.views.sdm.util.IlvXMLConnector;
import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.util.IlvImageUtil;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.convert.IlvConvert;
import ilog.views.util.internal.IlvURLUtil;
import ilog.views.util.swing.IlvSwingUtil;

/**
 * This sample shows the performances of the Diagrammer component when loading
 * graphs of various sizes and using various graphic features. It also compares
 * the performances of the SDM engine with the Graphic Framework API.
 */
public class DiagrammerBench extends JRootPane {
  private static final int coldStartCount = 3;
  private static final String tmpIVLFile = "data/tmp.ivl";
  private JFrame frame;
  private String titleFormat;
  private ResourceBundle bundle;
  private URL baseURL;
  private IlvDiagrammer diagrammer;
  private JButton createButton;
  private IlvManagerView gfView;
  private IlvJScrollManagerView gfScrollView;
  private Container contentPane;
  private int graphSize = 3; // 10^3 = 1000;
  private boolean subgraphs;
  private boolean intergraphLinks;
  private String nodeLinkCountFormat;
  private String nodeLinkCountTooltipFormat;
  private String sizeFormat;
  private String timeFormat;
  private String speedFormat;
  private String percentFormat;
  private String memoryFormat;
  private String memoryBarFormat;
  private String ivlRatioPercentFormat;
  private String ivlRatioFormatFaster;
  private String ivlRatioFormatSlower;
  private String emptyBarString;
  private String emptyTextString;
  private String runningString;
  private int nodeCount;
  private int linkCount;
  private JTextField nodeLinkCountText;
  private JTextField memoryText;
  private long memoryBefore;
  private JProgressBar memoryBar;
  private Color normalMemoryColor;
  private Color fullMemoryColor;
  private Counter graphCreationCounter;
  private Counter renderingCounter;
  private Counter nodeLayoutCounter;
  private Counter linkLayoutCounter;
  private Counter paintingCounter;
  private Counter totalCounter;
  private Counter ivlCounter;
  private Counter ivlReadCounter;
  private Counter ivlPaintCounter;
  private JTextField ivlPercentText;
  private boolean usePercentText = false;
  private JTextField ivlRatioText;
  private JTextField messageText;
  private Color normalMessageColor;
  private Color errorMessageColor;
  private JComboBox<String> fillCombo;
  private JComboBox<String> strokeCombo;
  private JComboBox<String> glinkCombo;
  private JCheckBox intergraphLinksCheckBox;
  /*
   * private JCheckBox savePositionsCheckBox; private JCheckBox
   * layoutOnZoomCheckBox; private JCheckBox connectToShapeNodesCheckBox;
   * private JCheckBox connectToShapeLinksCheckBox;
   */
  private JCheckBox compareWithIVLCheckBox;
  private boolean loadFromXML = true;
  private boolean compareWithIVL = true;
  private Icon nomarkIcon;
  private Icon markIcon;
  private IlvDiagrammerProject project;
  private JFileChooser fileChooser;
  private URL[] savedCSS;
  private int autoGCCount = 2;
  private Window helpWindow;
  private JEditorPane helpPane;
  private ByteArrayOutputStream ivlByteStream;
  // private sun.misc.Perf perf;
  // private long frequency;
  private long[] rab;
  private Cursor busyCursor;
  private boolean ivlError;
  private boolean running;

  private GraphData graphData = new GraphData();

  /**
   * The main method, used when the sample is launched as an application.
   */
  public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        DiagrammerBench bench = new DiagrammerBench();
        JFrame frame = new JFrame();
        bench.frame = frame;
        bench.setFrameTitle(bench.getString("NoDiagram"));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        try {
          bench.init(frame.getContentPane(), new URL("file:./"));
        } catch (MalformedURLException mue) {
          bench.error(mue);
        }
        frame.pack();
        frame.setLocation(100, 100);
        frame.setVisible(true);
      }
    });
  }

  private void setFrameTitle(String s) {
    if (frame != null) {
      frame.setTitle(MessageFormat.format(titleFormat, new Object[] { s }));
    }
  }


  /**
   * Constructor. Initializes strings, icons.
   */
  public DiagrammerBench() {
    // This sample uses JViews Diagrammer features. When deploying an
    // application that includes this code, you need to be in possession
    // of a Perforce JViews Diagrammer Deployment license.
    IlvProductUtil.DeploymentLicenseRequired(IlvProductUtil.JViews_Diagrammer_Deployment);

    bundle = ResourceBundle.getBundle("bench");

    titleFormat = getString("TitleFormat");
    sizeFormat = getString("SizeFormat");
    nodeLinkCountFormat = getString("NodeLinkCountFormat");
    nodeLinkCountTooltipFormat = getString("NodeLinkCountTooltipFormat");
    timeFormat = getString("TimeFormat");
    speedFormat = getString("SpeedFormat");
    percentFormat = getString("PercentFormat");
    memoryFormat = getString("MemoryFormat");
    memoryBarFormat = getString("MemoryBarFormat");
    ivlRatioPercentFormat = getString("IVLRatioPercentFormat");
    ivlRatioFormatFaster = getString("IVLRatioFormatFaster");
    ivlRatioFormatSlower = getString("IVLRatioFormatSlower");
    emptyBarString = getString("EmptyBar");
    emptyTextString = getString("EmptyText");
    runningString = getString("Running");

    try {
      nomarkIcon = new ImageIcon(IlvImageUtil.getImageFromFile(getClass(), "nomark.gif"));
      markIcon = new ImageIcon(IlvImageUtil.getImageFromFile(getClass(), "mark.gif"));
    } catch (IOException e) {
      error(e);
    }

    busyCursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
  }

  /**
   * Utility method to read an internationalized message.
   */
  private String getString(String key) {
    return bundle.getString("DiagrammerBench." + key);
  }

  /**
   * Initializes the GUI.
   * 
   * @param contentPane
   *          The container of the application.
   */
  public void init(Container contentPane, URL baseURL) {
    if (Runtime.getRuntime().maxMemory() < 200 * 1024 * 1024) {
      IlvSwingUtil.showErrorDialog(null, getString("MaxMemoryNotSet"));
      System.exit(1);
    }

    JPanel panel;
    JLabel label;
    JComboBox<String> combo;
    JTextField text;

    this.contentPane = contentPane;
    this.baseURL = baseURL;

    contentPane.setLayout(new BorderLayout(2, 2));

    // Create the menu bar.
    //
    JMenuBar menubar = new JMenuBar();

    // We reuse some Diagrammer actions, just to reuse the strings and
    // icons, but we redefine the actual action listener code.
    //
    IlvDiagrammerMenu fileMenu = new IlvDiagrammerMenu("Diagrammer.Menu.File");
    fileMenu.addAction(IlvDiagrammerAction.open);
    fileMenu.addAction(IlvDiagrammerAction.saveAs);
    fileMenu.addAction(IlvDiagrammerAction.refresh);
    fileMenu.addSeparator();
    fileMenu.addAction(IlvDiagrammerAction.exit);
    menubar.add(fileMenu);

    IlvDiagrammerAction.open.setHandler(new IlvDiagrammerAction.Handler() {
      Override
      public void perform(IlvDiagrammerAction action, IlvDiagrammer diagrammer, ActionEvent event) throws Exception {
        if (fileChooser == null) {
          fileChooser = new JFileChooser();
          fileChooser.setFileFilter(new FileFilter() {
            Override
            public boolean accept(File f) {
              return f.isDirectory() || f.getName().endsWith(IlvDiagrammerProject.suffix);
            }

            Override
            public String getDescription() {
              return getString("IDPRFileDescription");
            }
          });
          fileChooser.setCurrentDirectory(new File("."));
        }
        if (fileChooser.showOpenDialog(DiagrammerBench.this.contentPane) == JFileChooser.APPROVE_OPTION) {
          URL url = fileChooser.getSelectedFile().toURI().toURL();
          project = new IlvDiagrammerProject(url);
          if (!(project.getDataSource() instanceof IlvXMLDataSource)) {
            throw new RuntimeException(getString("NotAnXMLDataSource"));
          }
          createGraph();
        }
      }

      Override
      public void update(IlvDiagrammerAction action, IlvDiagrammer diagrammer) throws Exception {
        action.setEnabled(true);
      }
    });
    IlvDiagrammerAction.saveAs.setHandler(new IlvDiagrammerAction.Handler() {
      Override
      public void perform(IlvDiagrammerAction action, IlvDiagrammer diagrammer, ActionEvent event) throws Exception {
        IlvDiagrammerProject project = DiagrammerBench.this.project;

        if (project == null) {
          project = new IlvDiagrammerProject();
          URL dataURL = getDataFile(false);
          IlvXMLDataSource dataSource = new IlvXMLDataSource();
          dataSource.setDataURL(dataURL);
          project.setDataSource(dataSource);
          URL[] styleSheets = diagrammer.getStyleSheets();
          if (styleSheets.length > 0) {
            project.setStyleSheet(styleSheets[0]);
            for (int i = 1; i < styleSheets.length; i++) {
              project.addStyleSheet(styleSheets[i]);
            }
          }
        }

        diagrammer.setProject(project);

        super.perform(action, diagrammer, event);

        // Re-save XML and full CSS with the same base name as the
        // project.
        //
        URL projectURL = project.getProjectURL();

        String base = projectURL.toExternalForm();
        int dot = base.lastIndexOf('.');
        if (dot > 0) {
          base = base.substring(0, dot);
        }

        URL xmlURL = new URL(base + ".xml");
        ((IlvXMLDataSource) project.getDataSource()).setDataURL(xmlURL);
        project.getDataSource().write(diagrammer);

        URL cssURL = new URL(base + ".css");
        BufferedWriter out = new BufferedWriter(new FileWriter(cssURL.getFile()));
        URL[] css = project.getStyleSheets();
        for (int i = 0; i < css.length; i++) {
          copyCSS(out, css[i]);
        }
        out.close();
        project.setStyleSheet(cssURL);

        project.write(projectURL);

        diagrammer.setProject(null);
      }

      private void copyCSS(BufferedWriter out, URL css) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(IlvURLUtil.prepare(css).openStream()));
        out.write("// " + css.toExternalForm() + "\n\n");
        String line;
        while ((line = in.readLine()) != null) {
          line = line.trim();
          if (line.startsWith("import")) {
            String importName = line.substring(7).trim();
            if (importName.startsWith("\""))
              importName = importName.substring(1);
            if (importName.endsWith("\""))
              importName = importName.substring(0, importName.length() - 1);
            URL importCSS = new URL(css, importName);
            copyCSS(out, importCSS);
          } else {
            out.write(line);
          }
          out.write("\n");
        }
        in.close();
        out.write("\n");
      }

      Override
      public void update(IlvDiagrammerAction action, IlvDiagrammer diagrammer) throws Exception {
        action.setEnabled(diagrammer != null && diagrammer.canPrint());
      }
    });
    IlvDiagrammerAction.exit.setHandler(new IlvDiagrammerAction.Handler() {
      Override
      public void update(IlvDiagrammerAction action, IlvDiagrammer diagrammer) throws Exception {
        action.setEnabled(true);
      }
    });
    IlvDiagrammerAction.refresh.setHandler(new IlvDiagrammerAction.Handler() {
      Override
      public void perform(IlvDiagrammerAction action, IlvDiagrammer diagrammer, ActionEvent event) throws Exception {
        createGraph();
      }
    });

    IlvDiagrammerMenu helpMenu = new IlvDiagrammerHelpMenu();
    menubar.add(helpMenu);

    IlvDiagrammerAction.help.setHandler(new IlvDiagrammerAction.Handler() {
      Override
      public void perform(IlvDiagrammerAction action, IlvDiagrammer diagrammer, ActionEvent event) throws Exception {
        showHelp("top");
      }
    });

    IlvDiagrammerAction.about.putValue(IlvDiagrammerAction.ABOUT_APPLICATION_NAME, getString("AboutApplicationName"));

    ((JComponent) contentPane).getRootPane().setJMenuBar(menubar);

    // Create the SDM view:
    //
    JPanel sdmPanel = new JPanel(new BorderLayout());

    panel = new JPanel(new BorderLayout());
    sdmPanel.add(panel, BorderLayout.CENTER);

    JLabel sdmTitle = new JLabel(getString("SDMViewTitle"));
    sdmTitle.setToolTipText(getString("SDMView.ToolTip"));
    JPanel sdmTitlePanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
    sdmTitlePanel.add(sdmTitle);
    panel.add(sdmTitlePanel, BorderLayout.NORTH);

    diagrammer = new IlvDiagrammer();
    try {
      diagrammer.setStyleSheet(getClass().getResource("default.css"));
    } catch (Exception e) {
      error(e);
    }
    diagrammer.setScrollable(true);
    diagrammer.setPreferredSize(new Dimension(500, 200));
    panel.add(diagrammer, BorderLayout.CENTER);

    IlvDiagrammerViewBar sdmToolbar = new IlvDiagrammerViewBar(JToolBar.VERTICAL);
    sdmToolbar.removeAction(IlvDiagrammerAction.select);
    sdmToolbar.setFloatable(false);
    panel.add(sdmToolbar, BorderLayout.WEST);

    // Create the GF view:
    //
    JPanel gfPanel = new JPanel(new BorderLayout());

    panel = new JPanel(new BorderLayout());
    gfPanel.add(panel, BorderLayout.CENTER);

    JLabel gfTitle = new JLabel(getString("GFViewTitle"));
    gfTitle.setToolTipText(getString("GFView.ToolTip"));
    JPanel gfTitlePanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
    gfTitlePanel.add(gfTitle);
    panel.add(gfTitlePanel, BorderLayout.NORTH);

    IlvDiagrammer gfDiagrammer = new IlvDiagrammer();
    gfDiagrammer.setScrollable(true);
    gfView = gfDiagrammer.getView();
    gfScrollView = (IlvJScrollManagerView) gfView.getParent();
    gfDiagrammer.setPreferredSize(new Dimension(500, 200));
    panel.add(gfDiagrammer, BorderLayout.CENTER);

    GFToolBar gfToolbar = new GFToolBar(sdmToolbar.getActions(), gfDiagrammer);
    gfToolbar.setFloatable(false);
    panel.add(gfToolbar, BorderLayout.WEST);

    // The splitter that contains the SDM and GF views:
    //
    JSplitPane splitter = new JSplitPane(JSplitPane.VERTICAL_SPLIT, sdmPanel, gfPanel);
    splitter.setDividerSize(2);
    contentPane.add(splitter, BorderLayout.CENTER);
    splitter.setDividerLocation(0.5);

    // Create the parameter panel,that lets the user choose the graph size and
    // style.
    //
    JPanel paramPanel = new JPanel(new GridBagLayout());
    contentPane.add(paramPanel, BorderLayout.NORTH);

    GridBagConstraints c = new GridBagConstraints();
    c.gridx = 0;
    c.gridy = 0;
    c.insets = new Insets(3, 3, 3, 3);
    c.weightx = 1;
    c.weighty = 1;
    c.fill = GridBagConstraints.NONE;

    // *********************************
    // First line of parameters panel:
    // *********************************

    // The Create Graph button:
    //
    createButton = new JButton(getString("Create"));
    createButton.setToolTipText(getString("Create.ToolTip"));
    try {
      createButton.setIcon(new ImageIcon(IlvImageUtil.getImageFromFile(getClass(), "go.gif")));
    } catch (IOException e) {
      error(e);
    }
    createButton.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        project = null;
        createGraph();
      }
    });
    c.fill = GridBagConstraints.BOTH;
    c.gridheight = 2;
    paramPanel.add(createButton, c);
    c.gridheight = 1;
    c.fill = GridBagConstraints.NONE;

    c.gridx++;

    // Column headers:
    //
    label = new JLabel(getString("GraphData"));
    label.setToolTipText(getString("GraphData.ToolTip"));
    paramPanel.add(label, c);

    c.gridx++;

    label = new JLabel(getString("Nodes"));
    label.setToolTipText(getString("Nodes.ToolTip"));
    paramPanel.add(label, c);

    c.gridx++;

    label = new JLabel(getString("Links"));
    label.setToolTipText(getString("Links.ToolTip"));
    paramPanel.add(label, c);

    c.gridx++;

    label = new JLabel(getString("NodeLayout"));
    label.setToolTipText(getString("NodeLayout.ToolTip"));
    paramPanel.add(label, c);

    c.gridx++;

    label = new JLabel(getString("LinkLayout"));
    label.setToolTipText(getString("LinkLayout.ToolTip"));
    paramPanel.add(label, c);

    c.fill = GridBagConstraints.HORIZONTAL;

    c.gridy++;
    c.gridx = 1; // First column = create button.

    // The graph size combo:
    //

    final JComboBox<String> sizeCombo = new JComboBox<String>(
        new String[] { MessageFormat.format(sizeFormat, new Object[] { "10" }),
            MessageFormat.format(sizeFormat, new Object[] { "100" }),
            MessageFormat.format(sizeFormat, new Object[] { "1000" }),
            MessageFormat.format(sizeFormat, new Object[] { "10000" }),
        /*
         * Too big. MessageFormat.format(sizeFormat, new Object[] { "100000" }),
         */
        });

    int maxSize = 10000; // Used to compute size of node/link count text field.

    sizeCombo.setToolTipText(getString("Size.ToolTip"));
    sizeCombo.setSelectedIndex(graphSize - 1);
    sizeCombo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          graphSize = sizeCombo.getSelectedIndex() + 1;
        }
      }
    });
    paramPanel.add(sizeCombo, c);

    c.gridx++;

    // The main node style combo:
    //
    final JComboBox<String> nodeCombo = combo = createStyleCombo(
        new String[] { "Rectangle", "Text", "Shape", "GeneralNode", "Composite" },
        new String[] { "rect.css", "text.css", "shape.css", "gnode.css", "composite.css" });
    nodeCombo.setToolTipText(getString("NodeType.ToolTip"));
    combo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        SuppressWarnings("unchecked")
        int selIdx = ((JComboBox<String>) e.getSource()).getSelectedIndex();
        if (e.getStateChange() == ItemEvent.SELECTED && selIdx == 0) {
          if (fillCombo.getSelectedIndex() > 1)
            fillCombo.setSelectedIndex(1);
        }
        if (e.getStateChange() == ItemEvent.SELECTED && selIdx < 2) {
          strokeCombo.setSelectedIndex(0);
          strokeCombo.setEnabled(false);
        } else {
          strokeCombo.setEnabled(true);
        }
      }
    });
    paramPanel.add(combo, c);

    c.gridx++;

    // The main link style combo:
    //
    final JComboBox<String> linkCombo = combo = createStyleCombo(new String[] { "Polyline", "GeneralLink" },
        new String[] { "polylink.css", "glink.css" });
    linkCombo.setToolTipText(getString("LinkType.ToolTip"));
    combo.addItemListener(new ItemListener() {
      Override
      SuppressWarnings("unchecked")
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED && ((JComboBox<String>) e.getSource()).getSelectedIndex() == 0) {
          if (glinkCombo.getSelectedIndex() > 1)
            glinkCombo.setSelectedIndex(1);
        }
      }
    });
    paramPanel.add(combo, c);

    c.gridx++;

    // The node layout combo:
    //
    final JComboBox<String> nodeLayoutCombo = combo = createStyleCombo(new String[] { "None", "Hierarchical", "Tree" },
        new String[] { "nonodelayout.css", "hierarchical.css", "tree.css", });
    nodeLayoutCombo.setToolTipText(getString("NodeLayoutType.ToolTip"));
    /*
     * combo.addItemListener(new ItemListener() { public void
     * itemStateChanged(ItemEvent e) { if (e.getStateChange() ==
     * ItemEvent.SELECTED && ((JComboBox) e.getSource()).getSelectedIndex() ==
     * 0) { savePositionsCheckBox.setEnabled(false);
     * connectToShapeNodesCheckBox.setEnabled(false); //if
     * (!diagrammer.isAutomaticLinkLayout()) //
     * compareWithIVLCheckBox.setEnabled(true); } else {
     * savePositionsCheckBox.setEnabled(true);
     * connectToShapeNodesCheckBox.setEnabled(true);
     * //compareWithIVLCheckBox.setSelected(false);
     * //compareWithIVLCheckBox.setEnabled(false); } } });
     */
    paramPanel.add(combo, c);

    c.gridx++;

    // The link layout combo:
    //
    final JComboBox<String> linkLayoutCombo = combo = createStyleCombo(
        new String[] { "None", "Hierarchical", "Short", "Long" },
        new String[] { "nolinklayout.css", "hlink.css", "shortlink.css", "longlinks.css", });
    linkLayoutCombo.setToolTipText(getString("LinkLayoutType.ToolTip"));
    /*
     * combo.addItemListener(new ItemListener() { public void
     * itemStateChanged(ItemEvent e) { if (e.getStateChange() ==
     * ItemEvent.SELECTED && ((JComboBox) e.getSource()).getSelectedIndex() ==
     * 0) { layoutOnZoomCheckBox.setEnabled(false);
     * connectToShapeLinksCheckBox.setEnabled(false); //if
     * (!diagrammer.isAutomaticNodeLayout()) //
     * compareWithIVLCheckBox.setEnabled(true); } else {
     * layoutOnZoomCheckBox.setEnabled(true);
     * connectToShapeLinksCheckBox.setEnabled(true);
     * //compareWithIVLCheckBox.setSelected(false);
     * //compareWithIVLCheckBox.setEnabled(false); } } });
     */
    paramPanel.add(combo, c);

    // Hierarchical link routing works only if node layout is hierarchical.
    //
    linkLayoutCombo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED && linkLayoutCombo.getSelectedIndex() == 1
            && nodeLayoutCombo.getSelectedIndex() == 2) {
          nodeLayoutCombo.setSelectedIndex(1);
        }
      }
    });

    c.gridy++;
    c.gridx = 0;

    // *********************************
    // Second line of parameters panel:
    // *********************************

    // The "Load from XML" check box:
    //
    JComboBox<String> loadXML = new JComboBox<String>(
        new String[] { getString("LoadFromXML"), getString("CreateByCode") });
    loadXML.setToolTipText(getString("LoadFromXML.ToolTip"));
    loadXML.setSelectedIndex(loadFromXML ? 0 : 1);
    loadXML.addItemListener(new ItemListener() {
      Override
      SuppressWarnings("unchecked")
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          loadFromXML = ((JComboBox<String>) e.getSource()).getSelectedIndex() == 0;
          if (loadFromXML)
            graphCreationCounter.setLabel("XMLReading");
          else
            graphCreationCounter.setLabel("Creation");
        }
      }
    });
    paramPanel.add(loadXML, c);

    c.gridx++;

    // The subgraphs check box:
    //
    JCheckBox subgraphsCheckBox = createStyleCheckBox("SubGraphs", "subgraph.css");
    subgraphsCheckBox.setToolTipText(getString("SubGraphs.ToolTip"));
    subgraphsCheckBox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        subgraphs = e.getStateChange() == ItemEvent.SELECTED;
        intergraphLinksCheckBox.setEnabled(subgraphs);
      }
    });
    paramPanel.add(subgraphsCheckBox, c);

    c.gridx++;

    // The fill style combo for nodes:
    //
    fillCombo = combo = createStyleCombo(new String[] { "NoColoring", "ColorFill", "GradientFill", },
        new String[] { "nocolor.css", "colorfill.css", "gradientfill.css", });
    fillCombo.setToolTipText(getString("NodeFill.ToolTip"));
    fillCombo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED && nodeCombo.getSelectedIndex() == 0
            && fillCombo.getSelectedIndex() > 1) {
          IlvSwingUtil.showInformationDialog(DiagrammerBench.this.contentPane, getString("InvalidCombination"));
          fillCombo.setSelectedIndex(1);
        }
      }
    });
    combo.setSelectedIndex(1);
    paramPanel.add(combo, c);

    c.gridx++;

    // The link mode combo:
    //
    glinkCombo = combo = createStyleCombo(new String[] { "NoColoring", "UnicolorMode", "GradientMode", "NeonMode", },
        new String[] { "nocolorl.css", "unicolorglink.css", "gradientglink.css", "neonglink.css", });
    glinkCombo.setToolTipText(getString("LinkFill.ToolTip"));
    glinkCombo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED && linkCombo.getSelectedIndex() == 0
            && glinkCombo.getSelectedIndex() > 1) {
          IlvSwingUtil.showInformationDialog(DiagrammerBench.this.contentPane, getString("InvalidCombination"));
          glinkCombo.setSelectedIndex(1);
        }
      }
    });
    combo.setSelectedIndex(1);
    paramPanel.add(combo, c);

    c.gridx++;

    /*
     * // The "Update X/Y" check box for node layout: // savePositionsCheckBox =
     * createStyleCheckBox("SavePositions", "savepositions.css");
     * savepositionsCheckBox.setToolTipText(getString("SavePositions.ToolTip"));
     * savePositionsCheckBox.setEnabled(false);
     * paramPanel.add(savePositionsCheckBox, c);
     */

    c.gridx++;

    /*
     * // The "perform layout on zoom" check box for link layout: //
     * layoutOnZoomCheckBox = createStyleCheckBox("LayoutOnZoom",
     * "layoutonzoom.css");
     * layoutOnZoomCheckBox.setToolTipText(getString("LayoutOnZoom.ToolTip"));
     * layoutOnZoomCheckBox.setEnabled(false);
     * paramPanel.add(layoutOnZoomCheckBox, c);
     */

    c.gridy++;
    c.gridx = 0;

    // *********************************
    // Thrid line of parameters panel:
    // *********************************

    // The "Compare with GF" check box:
    //
    compareWithIVLCheckBox = new JCheckBox(getString("CompareWithIVL"));
    compareWithIVLCheckBox.setToolTipText(getString("CompareWithIVL.ToolTip"));
    compareWithIVLCheckBox.setSelected(compareWithIVL);
    compareWithIVLCheckBox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        compareWithIVL = e.getStateChange() == ItemEvent.SELECTED;
      }
    });
    paramPanel.add(compareWithIVLCheckBox, c);

    c.gridx++;

    // The check box for intergraph links:
    //
    intergraphLinksCheckBox = new JCheckBox(getString("IntergraphLinks"));
    intergraphLinksCheckBox.setToolTipText(getString("IntergraphLinks.ToolTip"));
    intergraphLinksCheckBox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        intergraphLinks = e.getStateChange() == ItemEvent.SELECTED;
      }
    });
    intergraphLinksCheckBox.setEnabled(false);
    paramPanel.add(intergraphLinksCheckBox, c);

    c.gridx++;

    // The node stroke combo:
    //
    strokeCombo = combo = createStyleCombo(new String[] { "DefaultStroke", "ThickStroke", "DashedStroke", },
        new String[] { "defaultstroke.css", "thickstroke.css", "dashedstroke.css", });
    strokeCombo.setToolTipText(getString("NodeStroke.ToolTip"));
    strokeCombo.setEnabled(false);
    paramPanel.add(combo, c);

    c.gridx++;

    // The link width combo:
    //
    combo = createStyleCombo(new String[] { "DefaultLink", "LinkWidth5", "LinkWidth10", },
        new String[] { "defaultlinkwidth.css", "linkwidth5.css", "linkwidth10.css", });
    combo.setToolTipText(getString("LinkWidth.ToolTip"));
    paramPanel.add(combo, c);

    c.gridx++;

    /*
     * // "Connect to shape" for node layout: // connectToShapeNodesCheckBox =
     * createStyleCheckBox("ConnectToShape", "connecttoshapenodes.css");
     * connectToShapeNodesCheckBox.setToolTipText(getString(
     * "ConnectToShape.ToolTip"));
     * connectToShapeNodesCheckBox.setEnabled(false);
     * paramPanel.add(connectToShapeNodesCheckBox, c);
     */

    c.gridx++;

    /*
     * // "Connect to shape" for link layout: // connectToShapeLinksCheckBox =
     * createStyleCheckBox("ConnectToShape", "connecttoshapelinks.css");
     * connectToShapeLinksCheckBox.setToolTipText(getString(
     * "ConnectToShape.ToolTip"));
     * connectToShapeLinksCheckBox.setEnabled(false);
     * paramPanel.add(connectToShapeLinksCheckBox, c);
     */

    // *************************
    // End of parameters panel
    // *************************

    // Status field:
    //
    JPanel bottomPanel = new JPanel(new GridBagLayout());
    contentPane.add(bottomPanel, BorderLayout.SOUTH);

    c.gridx = 0;
    c.gridy = 0;

    messageText = new JTextField();
    messageText.setEditable(false);
    c.weightx = 1;
    bottomPanel.add(messageText, c);
    c.weightx = 0;

    normalMessageColor = messageText.getForeground();
    try {
      errorMessageColor = (Color) IlvConvert.convert(getString("ErrorMessageColor"), Color.class);
    } catch (Exception ex) {
      errorMessageColor = Color.red;
    }

    c.gridx++;

    // Number of nodes/links:
    //

    text = new JTextField();
    text.setEditable(false);
    bottomPanel.add(text, c);
    nodeLinkCountText = text;

    nodeCount = maxSize;
    linkCount = maxSize;
    displayCounts();
    text.setPreferredSize(text.getPreferredSize());
    nodeCount = 0;
    linkCount = 0;
    displayCounts();

    c.gridx++;

    // Memory field:
    //
    label = new JLabel(getString("Memory"));
    bottomPanel.add(label, c);

    c.gridx++;

    text = new JTextField();
    text.setToolTipText(getString("MemoryText.ToolTip"));
    text.setEditable(false);
    text.setColumns(3);
    bottomPanel.add(text, c);
    memoryText = text;

    c.gridx++;

    memoryBar = new JProgressBar();
    memoryBar.setToolTipText(getString("MemoryBar.ToolTip"));
    memoryBar.setStringPainted(true);
    bottomPanel.add(createHelpPanel(memoryBar, "memory"), c);

    try {
      normalMemoryColor = (Color) IlvConvert.convert(getString("NormalMemoryColor"), Color.class);
    } catch (Exception ex) {
      normalMemoryColor = memoryBar.getForeground();
    }
    try {
      fullMemoryColor = (Color) IlvConvert.convert(getString("FullMemoryColor"), Color.class);
    } catch (Exception ex) {
      fullMemoryColor = Color.red;
    }

    c.gridx++;

    JButton gcButton = new JButton(getString("RunGC"));
    gcButton.setToolTipText(getString("RunGC.ToolTip"));
    gcButton.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        showMessage("GarbageCollecting");
        System.gc();
        updateMemory();
        showMessage("Ready");
      }
    });
    bottomPanel.add(gcButton, c);

    c.gridx++;

    JCheckBox autoGCCheckBox = new JCheckBox(getString("AutoGC"));
    autoGCCheckBox.setToolTipText(getString("AutoGC.ToolTip"));
    autoGCCheckBox.setSelected(autoGCCount > 0);
    autoGCCheckBox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        autoGCCount = e.getStateChange() == ItemEvent.SELECTED ? 2 : 0;
      }
    });
    bottomPanel.add(autoGCCheckBox, c);

    c.weightx = 1;

    // *****************
    // Results panel:
    // *****************

    JPanel resultPanel, sdmResultPanel, gfResultPanel;

    Border resultBorder = BorderFactory.createEmptyBorder(0, 3, 0, 0);

    // *****************
    // - SDM section:
    // *****************

    sdmResultPanel = resultPanel = new JPanel(new GridBagLayout());
    resultPanel.setBorder(resultBorder);
    sdmPanel.add(resultPanel, BorderLayout.EAST);

    JSeparator sep;

    c.gridx = 0;
    c.gridy = 0;

    // SDM section:
    //
    label = new JLabel(getString("UsingSDM"));
    label.setToolTipText(getString("UsingSDM.ToolTip"));
    c.gridwidth = GridBagConstraints.REMAINDER;
    resultPanel.add(label, c);
    c.gridwidth = 1;

    c.gridx = 0;
    c.gridy++;

    // Total time counter (created now, but added at the end).
    //
    totalCounter = new Counter();

    // Graph reading/creation counter:
    //
    graphCreationCounter = new Counter().add((loadFromXML ? "XMLReading" : "Creation"), resultPanel, c, totalCounter);
    c.gridx = 0;
    c.gridy++;

    // Rendering counter:
    //
    renderingCounter = new Counter().add("Rendering", resultPanel, c, totalCounter);
    c.gridx = 0;
    c.gridy++;

    // Node layout counter:
    //
    nodeLayoutCounter = new Counter().add("NodeLayoutCounter", resultPanel, c, totalCounter);
    c.gridx = 0;
    c.gridy++;

    // Link layout counter:
    //
    linkLayoutCounter = new Counter().add("LinkLayoutCounter", resultPanel, c, totalCounter);
    c.gridx = 0;
    c.gridy++;

    // Painting counter:
    //
    paintingCounter = new Counter().add("Painting", resultPanel, c, totalCounter);

    c.gridx = 0;
    c.gridy++;

    // Add the total counter for SDM:
    //
    totalCounter.add("SDMTotal", resultPanel, c, 10000);

    // *****************
    // - GF section:
    // *****************

    gfResultPanel = resultPanel = new JPanel(new GridBagLayout());
    resultPanel.setBorder(resultBorder);
    gfPanel.add(resultPanel, BorderLayout.EAST);

    c.gridx = 0;
    c.gridy = 0;

    label = new JLabel(getString("UsingGF"));
    label.setToolTipText(getString("UsingGF.ToolTip"));
    c.gridwidth = GridBagConstraints.REMAINDER;
    resultPanel.add(label, c);
    c.gridwidth = 1;

    c.gridx = 0;
    c.gridy++;

    // Total GF time counter:
    //
    ivlCounter = new Counter();

    // IVL reading counter:
    //
    ivlReadCounter = new Counter().add("IVLReading", resultPanel, c, ivlCounter);

    c.gridx = 0;
    c.gridy++;

    // IVL painting counter:
    //
    ivlPaintCounter = new Counter().add("Painting", resultPanel, c, ivlCounter);

    c.gridx = 0;
    c.gridy++;

    ivlCounter.add("GFTotal", resultPanel, c, 10000);

    c.gridx = 0;
    c.gridy++;

    sep = new JSeparator(JSeparator.HORIZONTAL);
    c.gridwidth = GridBagConstraints.REMAINDER;
    resultPanel.add(sep, c);
    c.gridwidth = 1;

    c.gridx = 0;
    c.gridy++;

    // SDM/GF ratio:
    //
    label = new JLabel(getString("SDMGFRatio"));
    label.setToolTipText(getString("SDMGFRatio.ToolTip"));
    resultPanel.add(label, c);

    if (usePercentText) {
      c.gridx++;

      ivlPercentText = new JTextField();
      ivlPercentText.setEditable(false);
      resultPanel.add(ivlPercentText, c);
    }

    c.gridx++;

    ivlRatioText = new JTextField();
    ivlRatioText.setToolTipText(getString("SDMGFRatio.ToolTip"));
    ivlRatioText.setEditable(false);
    c.gridwidth = GridBagConstraints.REMAINDER;
    resultPanel.add(createHelpPanel(ivlRatioText, "comparing_sdm_and_gf"), c);
    c.gridwidth = 1;

    c.gridx = 0;
    c.gridy++;

    // Make sure both result panels have the same width.
    //
    Dimension sdmSize = sdmResultPanel.getPreferredSize();
    Dimension gfSize = gfResultPanel.getPreferredSize();
    int w = Math.max(sdmSize.width, gfSize.width);
    sdmSize.width = gfSize.width = w;
    sdmResultPanel.setPreferredSize(sdmSize);
    gfResultPanel.setPreferredSize(gfSize);

    // *********************
    // End of GUI creation.
    // *********************

    // Cold start: we load the graph a few times
    // at startup, to "heat" the JIT.
    //
    if (coldStartCount > 0) {
      // System.out.print("cold starting...");
      for (int i = 0; i < coldStartCount; i++) {
        System.out.print(".");
        coldStart();
        autoGC();
      }
      deleteIVLFile();
      System.out.println("");
      clear();
      autoGC();
    }

    updateMemory();

    showMessage("Ready");
  }

  private void displayCounts() {
    String s;
    s = MessageFormat.format(nodeLinkCountFormat, new Object[] { Integer.valueOf(nodeCount), Integer.valueOf(linkCount), });
    nodeLinkCountText.setText(s);
    s = MessageFormat.format(nodeLinkCountTooltipFormat,
        new Object[] { Integer.valueOf(nodeCount), Integer.valueOf(linkCount), });
    nodeLinkCountText.setToolTipText(s);
  }

  /**
   * Creates a check box to add/remove a style sheet.
   */
  private JCheckBox createStyleCheckBox(String labelKey, final String css) {
    JCheckBox subgraphsCheckBox = new JCheckBox(getString(labelKey));
    subgraphsCheckBox.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        URL url = getClass().getResource(css);
        IlvSDMModel model = diagrammer.getEngine().getModel();
        diagrammer.getEngine().setModel(null, false);
        try {
          if (e.getStateChange() == ItemEvent.SELECTED) {
            diagrammer.addStyleSheet(url);
          } else {
            diagrammer.removeStyleSheet(url);
          }
        } catch (Exception e1) {
          error(e1);
        } finally {
          diagrammer.getEngine().setModel(model, false);
        }
      }
    });
    return subgraphsCheckBox;
  }

  /**
   * Creates a combo box to add/remove one of several style sheets.
   */
  private JComboBox<String> createStyleCombo(String[] keys, final String[] css) {
    final Vector<String> v = new Vector<String>();
    for (int i = 0; i < keys.length; i++) {
      v.add(getString(keys[i]));
    }
    JComboBox<String> combo = new JComboBox<String>(v);
    combo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(final ItemEvent e) {
        IlvSDMModel model = diagrammer.getEngine().getModel();
        try {
          diagrammer.getEngine().setModel(null, false);
          int index = v.indexOf(e.getItem());
          URL url = getClass().getResource(css[index]);
          if (e.getStateChange() == ItemEvent.SELECTED) {
            diagrammer.addStyleSheet(url);
          } else {
            diagrammer.removeStyleSheet(url);
          }
        } catch (Exception ex) {
          error(ex);
        } finally {
          diagrammer.getEngine().setModel(model, false);
        }
      }
    });

    try {
      diagrammer.addStyleSheet(getClass().getResource(css[0]));
    } catch (Exception e) {
      error(e);
    }

    return combo;
  }

  /**
   * Displays a message or a message in the status field.
   */
  private void showMessage(String key, boolean error) {
    messageText.setText(getString(key + "Message"));
    if (error)
      messageText.setForeground(errorMessageColor);
    else
      messageText.setForeground(normalMessageColor);
    messageText.setCaretPosition(0);
    messageText.setToolTipText(messageText.getText());
    paintImmediately(messageText);
  }

  /**
   * Displays a message in the status field.
   */
  private void showMessage(String key) {
    showMessage(key, false);
  }

  /**
   * Displays an error in the status field.
   */
  private void showError(String key) {
    showMessage(key, true);
  }

  /**
   * A listener used to "spy" the node/link layout renderers.
   */
  private class LayoutListener implements SDMGraphLayoutRendererListener {
    Override
    public void performLayoutStarted(SDMGraphLayoutRendererEvent event) {
      if (renderingCounter.startTime != 0) {
        renderingCounter.after();
      }
      if (event.getSource() instanceof IlvLinkLayoutRenderer) {
        showMessage("LinkLayout");
        linkLayoutCounter.before();
      } else {
        showMessage("NodeLayout");
        nodeLayoutCounter.before();
      }
    }

    Override
    public void performLayoutEnded(SDMGraphLayoutRendererEvent event) {
      if (event.getSource() instanceof IlvLinkLayoutRenderer) {
        linkLayoutCounter.after();
      } else {
        nodeLayoutCounter.after();
      }
    }
  }

  /**
   * The core method that creates the graph and measures the time spent in the
   * successive steps.
   */
  private void createGraph() {
    // Avoid restarting while we are waiting for the paint to be
    // finished.
    //

    if (running)
      return;
    running = true;

    ((JComponent) contentPane).getRootPane().setCursor(busyCursor);

    // Set frame title:
    //
    if (project != null) {
      setFrameTitle(project.getProjectURL().toExternalForm());
    } else {
      int n = (int) Math.pow(10, graphSize);
      setFrameTitle(MessageFormat.format(sizeFormat, new Object[] { String.valueOf(n) }));
    }

    // Allocate a bit of memory, to make sure we have some
    // spare memory if we hit an OutOfMemoryError.
    //
    rab = new long[1024 * 1024];
    rab[0] = 0;

    try {
      // Clear current graphs.
      //
      clear();

      // If we loaded a project last time, reset the CSS as defined by
      // the parameters panel.
      //
      if (savedCSS != null)
        restoreCSS();
      savedCSS = null;

      // Reset counters, clear extr fields.
      //
      resetAllCounters();

      nodeLinkCountText.setText("");
      paintImmediately(nodeLinkCountText);
      /*
       * memoryText.setText(""); paintImmediately(memoryText);
       */

      autoGC();

      IlvSDMModel model = new IlvDefaultSDMModel();

      if (loadFromXML && project == null) {
        getDataFile(true); // creates the file if necessary
      }

      // Record time at the beginning of everything.
      //
      totalCounter.before();

      if (loadFromXML || project != null) {

        if (project != null) {
          // Reading a project: read the style sheets
          // (Note that we don't measure this, but it is fast).
          //
          showMessage("ReadingCSS");

          savedCSS = diagrammer.getStyleSheets();

          URL[] styleSheets = project.getStyleSheets();
          if (styleSheets != null && styleSheets.length > 0) {
            diagrammer.setStyleSheet(styleSheets[0]);
            for (int i = 1; i < styleSheets.length; i++) {
              diagrammer.addStyleSheet(styleSheets[i]);
            }
          }
        }

        // Read the XML data file: we use a separate XML connector,
        // not the Diagrammer's, because we want to measure just
        // the time spent reading the XML, and not yet the time spent
        // to render the graph.
        //
        showMessage("ReadingXML");

        IlvXMLConnector connector = new IlvXMLConnector();

        graphCreationCounter.before();

        URL dataURL;
        if (project == null) {
          // We are not loading a project: the data file is something like
          // "data/1000.xml".
          dataURL = getDataFile(true);
        } else {
          // We are loading a project: the data file is the project's data
          // source
          // file.
          dataURL = ((IlvXMLDataSource) project.getDataSource()).getDataURL();
        }
        // Read the file...
        //
        InputStream in = IlvURLUtil.prepare(dataURL).openStream();
        if (project == null) {
          connector.readXML(model, new BufferedReader(new InputStreamReader(in)), false, null);
        } else {
          IlvSDMEngine tmpEngine = new IlvSDMEngine(new IlvGrapher(), model);
          tmpEngine.setRenderer(null);
          tmpEngine.readXML(new BufferedReader(new InputStreamReader(in)));
          model = tmpEngine.getModel();
          tmpEngine.setModel(null, false);
        }
        graphCreationCounter.after();

        // Count the nodes and links.
        //
        nodeCount = 0;
        linkCount = 0;
        Enumeration<?> objs = new IlvPreorderEnumeration(model);
        while (objs.hasMoreElements()) {
          Object o = objs.nextElement();
          if (diagrammer.isLink(o))
            linkCount++;
          else
            nodeCount++;
        }
      } else {
        // Not reading from XML (i.e., creating graph in-memory):
        //
        showMessage("Creating");

        graphCreationCounter.before();

        // Call the external GraphData class to create the graph.
        //
        graphData.createGraph(model, graphSize, subgraphs, intergraphLinks);

        graphCreationCounter.after();

        nodeCount = graphData.getNodeCount();
        linkCount = graphData.getLinkCount();
      }

      // Display the number of nodes/links:
      //
      displayCounts();
      paintImmediately(nodeLinkCountText);

      // Install the layout listeners to be notified when the layouts
      // start/end.
      //
      final LayoutListener layoutListener = new LayoutListener();
      if (diagrammer.getEngine().getNodeLayoutRenderer() != null) {
        diagrammer.getEngine().getNodeLayoutRenderer().addGraphLayoutRendererListener(layoutListener);
      }
      if (diagrammer.getEngine().getLinkLayoutRenderer() != null) {
        diagrammer.getEngine().getLinkLayoutRenderer().addGraphLayoutRendererListener(layoutListener);
      }

      // OK, now we have a data model.
      // Next, perform and measure the creation of graphic objects.
      //
      showMessage("Rendering");

      renderingCounter.before();

      // Set the data model on the SDM engine. This causes the
      // engine to actually create the graphic objects that represent
      // the graph.
      //
      diagrammer.getEngine().setModel(model, true);

      // If there was a node or link layout, the rendering counter
      // has already been stopped (we know this because startTime has
      // been reset to 0). If not, stop the rendering counter now.
      //
      if (renderingCounter.startTime != 0)
        renderingCounter.after();

      // If node/link layout was not performed, call the counter
      // anyway to set its time to 0.
      //
      if (!diagrammer.isAutomaticNodeLayout()) {
        nodeLayoutCounter.before();
        nodeLayoutCounter.after();
      }
      if (!diagrammer.isAutomaticLinkLayout()) {
        linkLayoutCounter.before();
        linkLayoutCounter.after();
      }

      // Graphic objects are now created.
      // Next, we want to measure the painting time.
      //
      showMessage("Painting");

      paintingCounter.before();

      // Do a "fit-to-contents" to make sure we paint all graphic objects.
      //
      diagrammer.getView().fitTransformerToContent(new Insets(10, 10, 10, 10));

      // To measure the painting time, we queue an invokeLater call.
      // We assume here that the invokeLater call will be handled after
      // the paint event, which should have been queued already.
      //
      SwingUtilities.invokeLater(new Runnable() {
        Override
        public void run() {
          // We get there after the SDM view has been painted.
          // We can stop the painting counter,
          //
          paintingCounter.after();

          // .. and also the total time counter (since painting is the last
          // thing to measure).
          totalCounter.after();

          // Finished for the SDM case!
          //
          // let's start the GF case.
          //
          ivlError = false;

          if (compareWithIVL) {
            try {
              // Write the SDm graph to an IVL file
              // (this is not measured).
              //
              showMessage("WritingIVL");

              OutputStream out = createIVLOutputStream();

              diagrammer.getEngine().getGrapher().write(out);

              // Read back the IVL file (this is what we measure).
              //
              showMessage("ReadingIVL");

              ivlCounter.before();

              ivlReadCounter.before();

              InputStream in = createIVLInputStream();

              gfView.getManager().read(in);

              out.close();
              in.close();
              deleteIVLFile();

              gfView.repaint();

              ivlReadCounter.after();

              // Measure time for painting the IVL view.
              //
              showMessage("Painting");

              ivlPaintCounter.before();
              gfView.fitTransformerToContent(new Insets(10, 10, 10, 10));
              gfView.repaint();
            } catch (OutOfMemoryError oome) {
              outOfMemory();
              ivlReadCounter.clear();
              ivlPaintCounter.clear();
              ivlCounter.clear();
            } catch (Exception e) {
              showError("ErrorInIVL");
              ivlError = true;
              ivlReadCounter.clear();
              ivlPaintCounter.clear();
              ivlCounter.clear();
            }
          } else {
            ivlReadCounter.clear();
            ivlPaintCounter.clear();
            ivlCounter.clear();
          }

          // Same trick: we do an invokeLater to continue execution
          // once all painting has been done.
          //
          SwingUtilities.invokeLater(new Runnable() {
            Override
            public void run() {
              if (compareWithIVL && !ivlError) {
                // We get here after the GF view has been painted.
                //
                ivlPaintCounter.after();

                ivlCounter.after();

                // Compute the ratio between SDM and GF times.
                // Note that we eliminate the node/link layout times,
                // which are never performed when we read the IVL.
                //
                float ratio = (float) ivlCounter.time
                    / (float) (totalCounter.time - nodeLayoutCounter.time - linkLayoutCounter.time);

                if (usePercentText) {
                  ivlPercentText.setText(
                      MessageFormat.format(ivlRatioPercentFormat, new Object[] { Integer.valueOf((int) (ratio * 100)) }));
                }

                String ratioText;
                if (ratio < 1) {
                  ratioText = MessageFormat.format(ivlRatioFormatFaster, new Object[] { Float.valueOf(1 / ratio) });
                } else {
                  ratioText = MessageFormat.format(ivlRatioFormatSlower, new Object[] { Float.valueOf(ratio) });
                }
                ivlRatioText.setText(ratioText);
                paintImmediately(ivlRatioText);
              }

              // autoGC();

              // Cleanup layout renderer listeners:
              //
              if (diagrammer.getEngine().getNodeLayoutRenderer() != null) {
                diagrammer.getEngine().getNodeLayoutRenderer().removeGraphLayoutRendererListener(layoutListener);
              }
              if (diagrammer.getEngine().getLinkLayoutRenderer() != null) {
                diagrammer.getEngine().getLinkLayoutRenderer().removeGraphLayoutRendererListener(layoutListener);
              }

              autoGC();

              if (!ivlError)
                showMessage("Ready");

              ((JComponent) contentPane).getRootPane().setCursor(null);

              running = false;

              updateMemory();

              // All done.
            }
          });
        }
      });
    } catch (OutOfMemoryError oome) {
      outOfMemory();
      return;
    } catch (Exception ex) {
      error(ex);
    } finally {
      ((JComponent) contentPane).getRootPane().setCursor(null);
      running = false;
    }
  }

  private void outOfMemory() {
    // On OutOfmemoryErrors, we try to display an error box.
    // For this, we free up the spare memory that we reserved
    // at the beginning:
    //
    rab = null;

    // Try to give the user an idea of how much memory he should
    // give to the VM:
    //
    long needed = 2 * Runtime.getRuntime().maxMemory() / 1024 / 1024;
    needed = ((needed + 99) / 100) * 100;
    IlvSwingUtil.showErrorDialog(contentPane,
        MessageFormat.format(getString("NotEnoughMemory"), new Object[] { Long.valueOf(needed) }));
  }

  /**
   * Restores a set of saved style sheets.
   */
  private void restoreCSS() {
    try {
      if (savedCSS != null && savedCSS.length > 0) {
        diagrammer.setStyleSheet(savedCSS[0]);
        for (int i = 1; i < savedCSS.length; i++) {
          diagrammer.addStyleSheet(savedCSS[i]);
        }
      }
    } catch (Exception ioex) {
      error(ioex);
    }
  }

  /**
   * Creates a graph without displaying any results. Used at startup to "heat"
   * the JIT.
   */
  private void coldStart() {
    try {
      IlvSDMModel model = new IlvDefaultSDMModel();
      graphData.createGraph(model, graphSize, subgraphs, intergraphLinks);
      IlvXMLConnector connector = new IlvXMLConnector();
      URL dataURL = getDataFile(true);
      InputStream in = dataURL.openStream();
      connector.readXML(model, new BufferedReader(new InputStreamReader(in)), false, null);
      diagrammer.getEngine().setModel(model, true);
      OutputStream ivlout = createIVLOutputStream();
      diagrammer.getEngine().getGrapher().write(ivlout);
      ivlout.close();
      InputStream ivlin = createIVLInputStream();
      gfView.getManager().read(ivlin);
      ivlin.close();
      BufferedImage image = new BufferedImage(500, 200, BufferedImage.TYPE_INT_ARGB);
      IlvTransformer t = new IlvTransformer();
      IlvTransformer.computeTransformer(diagrammer.getEngine().getGrapher().computeBBox(null),
          new IlvRect(0, 0, 500, 200), t);
      Graphics2D imageGraphics = image.createGraphics();
      diagrammer.getEngine().getGrapher().draw(imageGraphics, t);
      IlvTransformer.computeTransformer(gfView.getManager().computeBBox(null), new IlvRect(0, 0, 500, 200), t);
      gfView.getManager().draw(imageGraphics, t);
    } catch (Exception e) {
    }
  }

  /**
   * Updates the memory fields.
   */
  private void updateMemory() {
    Timer t = new Timer(500, new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        long total = Runtime.getRuntime().totalMemory();
        long free = Runtime.getRuntime().freeMemory();
        int totalMegs = (int) (total / (1024 * 1024));
        int usedMegs = (int) ((total - free) / (1024 * 1024));
        memoryBar.setMaximum(totalMegs);
        memoryBar.setValue(usedMegs);
        memoryBar.setString(
            MessageFormat.format(memoryBarFormat, new Object[] { Integer.valueOf(usedMegs), Integer.valueOf(totalMegs) }));
        if ((float) usedMegs / (float) totalMegs > 0.8)
          memoryBar.setForeground(fullMemoryColor);
        else
          memoryBar.setForeground(normalMemoryColor);

        if (memoryBefore == 0)
          memoryBefore = total - free;
        long memory = total - free - memoryBefore;
        if (memory < 0) {
          memoryBefore += memory;
          memory = 0;
        }
        int megs = (int) (memory / (1024 * 1024));
        String memText = MessageFormat.format(memoryFormat, new Object[] { megs < 0 ? "" : "+", Integer.valueOf(megs) });
        memoryText.setText(memText);
      }
    });
    t.setRepeats(false);
    t.start();
  }

  /**
   * Clears the views.
   */
  private void clear() {
    showMessage("Clearing");

    diagrammer.getEngine().setModel(new IlvDefaultSDMModel());
    paintImmediately(diagrammer);

    gfView.getManager().deleteAll(true);
    paintImmediately(gfScrollView);
  }

  /**
   * Displays an error box.
   */
  private void error(Throwable t) {
    IlvSwingUtil.showErrorDialog(contentPane, t);
  }

  /**
   * This class represents a counter. There are master counters, and
   * sub-counters that are used to measure times of individual steps.
   */
  private class Counter {
    private long startTime;

    private long time;

    private JLabel label;

    private JTextField text;

    private JProgressBar bar;

    private Counter total;

    private ArrayList<Counter> percents = new ArrayList<Counter>();

    /**
     * Adds the Swing components for a counter: this is the version for a
     * sub-counter. The "total" parameter is the master counter.
     */
    public Counter add(String labelKey, Container panel, GridBagConstraints c, Counter total) {
      add(labelKey, panel, c, 100);
      this.total = total;
      total.percents.add(this);
      label.setPreferredSize(label.getPreferredSize());
      return this;
    }

    /**
     * Adds the Swing components for a counter: this is the version for a master
     * counter.
     */
    public Counter add(String labelKey, Container panel, GridBagConstraints c, int max) {
      allCounters.add(this);

      label = new JLabel(getString(labelKey));
      label.setIcon(nomarkIcon);
      String tooltip = getString(labelKey + ".ToolTip");
      label.setToolTipText(tooltip);
      panel.add(label, c);
      c.gridx++;
      text = new JTextField();
      text.setToolTipText(tooltip);
      text.setEditable(false);
      text.setColumns(6);
      panel.add(text, c);
      c.gridx++;
      bar = new JProgressBar(0, max);
      bar.setToolTipText(tooltip);
      bar.setStringPainted(true);
      try {
        String barColor = getString(labelKey + "Color");
        Color color = (Color) IlvConvert.convert(barColor, Color.class);
        bar.setForeground(color);
      } catch (Exception ex) {
      }
      panel.add(bar, c);
      return this;
    }

    /**
     * This method is called to record the time before executing the task
     * measured by this counter.
     */
    private void before() {
      if (total != null)
        mark();
      startTime = getCurrentTime();
      time = -1;
    }

    /**
     * Displays the mark in the counter's label, to show that the counter is
     * active.
     */
    private void mark() {
      label.setIcon(markIcon);
      paintImmediately(label);
    }

    /**
     * Sets the label of the counter.
     */
    private void setLabel(String key) {
      label.setText(getString(key));
      paintImmediately(label);
      String tooltip = getString(key + ".ToolTip");
      label.setToolTipText(tooltip);
      text.setToolTipText(tooltip);
      bar.setToolTipText(tooltip);
    }

    /**
     * This method must be called after executing the task measured by this
     * counter. It measures the time, and updates the Swing components of the
     * counter. If this is a master counter, it also updates the percentages of
     * all sub-counters.
     */
    private void after() {
      if (total != null) {
        time = getCurrentTime() - startTime;
        if (total.time == -1)
          total.time = time;
        else
          total.time += time;
      }

      String s = MessageFormat.format(timeFormat, new Object[] { Long.valueOf(time) });
      text.setText(s);
      paintImmediately(text);

      if (total == null) {
        if (time == 0) {
          bar.setValue(Integer.MAX_VALUE);
          bar.setString(getString("TooFast"));
        } else {
          int speed = (int) ((nodeCount + linkCount) * 1000 / time);
          bar.setValue(speed);
          s = MessageFormat.format(speedFormat, new Object[] { Integer.valueOf(speed) });
          bar.setString(s);

          for (int i = 0; i < percents.size(); i++) {
            Counter counter = (Counter) percents.get(i);
            counter.percentOfTotal();
          }
        }
        paintImmediately(bar);
      }

      startTime = 0;

      if (total != null)
        unmark();
    }

    /**
     * Hides the mark in the counter's label, to show that the counter is not
     * active any more.
     */
    private void unmark() {
      label.setIcon(nomarkIcon);
      paintImmediately(label);
    }

    /**
     * Displays the percentage of time spent in a sub-counter with respect to
     * its master counter.
     */
    private void percentOfTotal() {
      if (total != null) {
        if (!text.getText().equals(emptyTextString)) {
          int percent = (int) (time * 100 / total.time);
          bar.setValue(percent);
          String s = MessageFormat.format(percentFormat, new Object[] { Integer.valueOf(percent) });
          bar.setString(s);
        } else {
          bar.setValue(0);
          bar.setString(emptyBarString);
        }
        paintImmediately(bar);
      }
    }

    /**
     * Resets the counter.
     */
    private void reset() {
      text.setText(emptyTextString);
      paintImmediately(text);
      bar.setValue(0);
      bar.setString(runningString);
      paintImmediately(bar);
      startTime = 0;
      time = 0;
    }

    /**
     * Clears the counter.
     */
    private void clear() {
      unmark();
      text.setText(emptyTextString);
      paintImmediately(text);
      bar.setValue(0);
      bar.setString(emptyBarString);
      paintImmediately(bar);
      startTime = 0;
      time = 0;
    }
  }

  private static ArrayList<Counter> allCounters = new ArrayList<Counter>();

  /**
   * Resets all the counters.
   */
  void resetAllCounters() {
    for (int i = 0; i < allCounters.size(); i++) {
      Counter counter = (Counter) allCounters.get(i);
      counter.reset();
    }
    if (usePercentText) {
      ivlPercentText.setText("");
      paintImmediately(ivlPercentText);
    }
    ivlRatioText.setText("");
    paintImmediately(ivlRatioText);
  }

  /**
   * Clears all the counters.
   */
  void clearAllCounters() {
    for (int i = 0; i < allCounters.size(); i++) {
      Counter counter = (Counter) allCounters.get(i);
      counter.clear();
    }
    if (usePercentText) {
      ivlPercentText.setText("");
      paintImmediately(ivlPercentText);
    }
    ivlRatioText.setText("");
    paintImmediately(ivlRatioText);
  }

  /**
   * Utility method to "paint immediately" a Swing component.
   */
  private void paintImmediately(JComponent c) {
    c.paintImmediately(0, 0, c.getWidth(), c.getHeight());
  }

  /**
   * Runs the GC "autoGCCount" times.
   */
  private void autoGC() {
    if (autoGCCount > 0) {
      if (!ivlError)
        showMessage("GarbageCollecting");
      for (int i = 0; i < autoGCCount; i++) {
        System.gc();
      }
    }
  }

  // Displays the Help window.
  //
  private void showHelp(final String anchor) {
    if (helpWindow == null) {
      Frame frame = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, contentPane);
      helpWindow = new JDialog(frame, getString("HelpTitle"));
      helpPane = new JEditorPane();
      helpPane.setEditorKit(new HTMLEditorKit());
      helpPane.setEditable(false);
      URL helpURL = getClass().getResource(getString("HelpFile"));
      try {
        helpURL = new URL(helpURL.toExternalForm() + "#" + anchor);
        helpPane.setPage(helpURL);
      } catch (IOException e) {
        error(e);
      }
      helpPane.addHyperlinkListener(new HyperlinkListener() {
        Override
        public void hyperlinkUpdate(HyperlinkEvent e) {
          if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
            String anchor = e.getURL().toExternalForm();
            int sharp = anchor.lastIndexOf('#');
            if (sharp >= 0)
              anchor = anchor.substring(sharp + 1);
            helpPane.scrollToReference(anchor);
          }
        }
      });
      JScrollPane scroller = new JScrollPane(helpPane);
      ((JDialog) helpWindow).getContentPane().add(scroller);
      helpWindow.setSize(600, 500);
      Point p = contentPane.getLocationOnScreen();
      helpWindow.setLocation(p.x + 200, p.y + 100);
    }
    helpPane.scrollToReference(anchor);
    helpWindow.setVisible(true);
  }

  private JPanel createHelpPanel(JComponent component, final String anchor) {
    JPanel panel = new JPanel(new GridBagLayout());
    GridBagConstraints c2 = new GridBagConstraints();
    c2.gridx = 0;
    c2.gridy = 0;
    c2.weightx = 1;
    c2.insets = new Insets(0, 0, 0, 5);
    c2.fill = GridBagConstraints.HORIZONTAL;
    panel.add(component, c2);
    c2.gridx++;
    c2.weightx = 0;
    c2.insets = new Insets(0, 0, 0, 0);
    IlvDiagrammerAction.help.init();
    JButton button = new JButton((Icon) IlvDiagrammerAction.help.getValue(Action.SMALL_ICON));
    button.setToolTipText(getString("ExplainThis"));
    button.setMargin(new Insets(0, 0, 0, 0));
    button.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        showHelp(anchor);
      }
    });
    panel.add(button, c2);
    return panel;
  }

  /**
   * Returns the current time in milliseconds.
   */
  private long getCurrentTime() {
    // More precise timings could be obtained with System.nanoTime().
    return System.currentTimeMillis();
  }

  private OutputStream createIVLOutputStream() throws IOException {
    OutputStream out;
    if (loadFromXML)
      out = new FileOutputStream(tmpIVLFile);
    else
      out = ivlByteStream = new ByteArrayOutputStream();
    return new BufferedOutputStream(out);
  }

  private InputStream createIVLInputStream() throws IOException {
    InputStream in;
    if (loadFromXML)
      in = new FileInputStream(tmpIVLFile);
    else
      in = new ByteArrayInputStream(ivlByteStream.toString().getBytes());
    return new BufferedInputStream(in);
  }

  private void deleteIVLFile() {
    if (loadFromXML) {
      try {
        new File(tmpIVLFile).delete();
      } catch (Throwable t) {
      }
    } else {
      ivlByteStream = null;
    }
  }

  /**
   * This class implements a Diagrammer view bar that acts on the Graphic
   * Framework view.
   */
  private class GFToolBar extends IlvDiagrammerToolBar {
    GFToolBar(Action[] sdmActions, final IlvDiagrammer gfDiagrammer) {
      super(null, JToolBar.VERTICAL);

      addAction(new IlvDiagrammerAction(null) {
        Override
        public void perform(ActionEvent e, IlvDiagrammer diagrammer) throws Exception {
          gfDiagrammer.zoomIn();
        }

        Override
        protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception {
          return gfDiagrammer.canZoomIn();
        }
      });
      addAction(new IlvDiagrammerAction(null) {
        Override
        public void perform(ActionEvent e, IlvDiagrammer diagrammer) throws Exception {
          gfDiagrammer.zoomOut();
        }

        Override
        protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception {
          return gfDiagrammer.canZoomOut();
        }
      });
      addAction(new IlvDiagrammerAction.ToggleAction(null) {
        Override
        protected boolean isSelected(IlvDiagrammer diagrammer) throws Exception {
          return gfDiagrammer.isZoomMode();
        }

        Override
        protected void setSelected(IlvDiagrammer diagrammer, boolean selected) throws Exception {
          gfDiagrammer.setZoomMode(selected);
        }

        Override
        protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception {
          return true;
        }
      });
      addAction(new IlvDiagrammerAction(null) {
        Override
        public void perform(ActionEvent e, IlvDiagrammer diagrammer) throws Exception {
          gfDiagrammer.resetZoom();
        }

        Override
        protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception {
          return gfDiagrammer.canResetZoom();
        }
      });
      addAction(new IlvDiagrammerAction(null) {
        Override
        public void perform(ActionEvent e, IlvDiagrammer diagrammer) throws Exception {
          gfDiagrammer.fitToContents();
        }

        Override
        protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception {
          return true;
        }
      });
      addAction(new IlvDiagrammerAction.ToggleAction(null) {
        Override
        protected boolean isSelected(IlvDiagrammer diagrammer) throws Exception {
          return gfDiagrammer.isPanMode();
        }

        Override
        protected void setSelected(IlvDiagrammer diagrammer, boolean selected) throws Exception {
          gfDiagrammer.setPanMode(selected);
        }

        Override
        protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception {
          return true;
        }
      });

      Action[] actions = getActions();
      for (int i = 0; i < actions.length; i++) {
        actions[i].getValue(Action.SMALL_ICON);
        actions[i].putValue(Action.SMALL_ICON, sdmActions[i].getValue(Action.SMALL_ICON));
        actions[i].putValue(Action.SHORT_DESCRIPTION, sdmActions[i].getValue(Action.SHORT_DESCRIPTION));
        actions[i].putValue(Action.LONG_DESCRIPTION, sdmActions[i].getValue(Action.LONG_DESCRIPTION));
      }
    }
  }

  private URL getDataFile(boolean create) throws Exception {
    URL dataURL = new URL(DiagrammerBench.this.baseURL, "data/" + (int) Math.pow(10, graphSize) + (subgraphs ? "s" : "")
        + (subgraphs && intergraphLinks ? "i" : "") + ".xml");
    if (create) {
      try {
        dataURL.openStream().close();
      } catch (Exception ex) {
        // File does not exist, try to create it:
        //
        showMessage("GeneratingDataFile");
        graphData.generateGraphFile(graphSize, subgraphs, intergraphLinks);
      }
    }
    return dataURL;
  }
}