/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2017 
 * © 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.
 */
package bench;

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.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
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.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JRadioButton;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.SpringLayout;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import ilog.views.gantt.IlvGanttChart;
import ilog.views.gantt.IlvGanttModel;
import ilog.views.gantt.IlvHierarchyChart;
import ilog.views.gantt.IlvScheduleChart;
import ilog.views.gantt.action.IlvAction;
import ilog.views.gantt.action.IlvZoomToFitAction;
import ilog.views.gantt.model.IlvDefaultGanttModel;
import ilog.views.gantt.xml.IlvGanttDocumentReader;
import ilog.views.util.IlvImageUtil;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.IlvResourceUtil;
import ilog.views.util.convert.IlvConvert;
import ilog.views.util.styling.IlvStylingException;
import ilog.views.util.swing.IlvSwingUtil;
import shared.AbstractExample;
import shared.GanttCommand;
import shared.swing.ExampleFrame;
import xml.XMLFileChooser;
import xml.XMLGanttActions;

/**
 * This sample shows the performances of the Gantt component when loading data
 * of various sizes and using various graphic features. It also compares the
 * performances of the CSS engine with the Graphic Framework API when used for
 * graphic renderers.
 */
public class GanttBenchExample extends AbstractExample {

  {
    // This sample uses JViews Gantt features. When deploying an
    // application that includes this code, you need to be in possession
    // of a Rogue Wave JViews Gantt Deployment license.
    IlvProductUtil.DeploymentLicenseRequired(IlvProductUtil.JViews_Gantt_Deployment);
  }

  private static final int coldStartCount = 3;
  private JFrame frame;
  private String titleFormat;
  private ResourceBundle bundle;
  private IlvGanttChart gantt;
  private IlvScheduleChart schedule;
  private IlvDefaultGanttModel scheduleModel;

  /**
   * The various menu and toolbar actions.
   */
  protected IlvAction openAction;
  protected IlvAction saveAsAction;
  protected IlvAction exitAction;
  private XMLFileChooser fileChooser;
  private URL xmlUrl;

  // Initial capacities for the data model
  private int activityCapacity = 101;
  private int resourceCapacity = 101;
  private int reservationCapacity = 101;
  private int constraintCapacity = 201;

  // Object count from the created chart
  private int activityCount;
  private int resourceCount;
  private int reservationCount;
  private int constraintCount;

  // Object number from the combo boxes
  private int nbActivity;
  private int nbResource;
  private int avgReservation;
  private int avgConstraint;

  private boolean createThroughXML = false;
  private boolean loadFromXML = false;
  private boolean attachModelAfter = true;
  private JRadioButton cssRadio;
  private String cssFile;
  private URL selectedCSS;
  private String objectCountFormat;
  private String objectCountTooltipFormat;
  private String timeFormat;
  private String speedFormat;
  private String percentFormat;
  private String memoryFormat;
  private String memoryBarFormat;
  private String emptyBarString;
  private String emptyTextString;
  private String runningString;
  private JTextField objectCountText;
  private JTextField memoryText;
  private long memoryBefore;
  private JProgressBar memoryBar;
  private Color normalMemoryColor;
  private Color fullMemoryColor;

  private Counter ganttCreationCounter;
  private Counter ganttRenderingCounter;
  private Counter ganttPaintingCounter;
  private Counter ganttTotalCounter;
  private Counter scheduleCreationCounter;
  private Counter scheduleRenderingCounter;
  private Counter schedulePaintingCounter;
  private Counter scheduleTotalCounter;

  private JTextField messageText;
  private Color normalMessageColor;
  private Color errorMessageColor;
  private Icon nomarkIcon;
  private Icon markIcon;
  private int autoGCCount = 2;
  private ByteArrayOutputStream xmlByteStream;
  private long[] rab;
  private Cursor busyCursor;
  private boolean running;

  private GanttData ganttData = new GanttData();

  private Map<? extends Object, Object> helpProperties;

  /**
   * The main method, used when the sample is launched as an application.
   *
   * @param args
   *          The command line arguments.
   */
  public static void main(String[] args) {
    ExampleFrame.createAndShowGUI(GanttBenchExample.class);
  }

  /**
   * Returns the title of the example.
   *
   * @return The title of the example.
   */
  Override
  public String getTitle() {
    String noData = getString("NoGantt");
    return MessageFormat.format(titleFormat, noData);
  }

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

  /**
   * Constructor. Initializes strings, icons.
   */
  public GanttBenchExample() {
    bundle = IlvResourceUtil.getBundle("bench.bench", getLocale(), getClass().getClassLoader());

    titleFormat = getString("TitleFormat");
    objectCountFormat = getString("ObjectCountFormat");
    objectCountTooltipFormat = getString("ObjectCountTooltipFormat");
    timeFormat = getString("TimeFormat");
    speedFormat = getString("SpeedFormat");
    percentFormat = getString("PercentFormat");
    memoryFormat = getString("MemoryFormat");
    memoryBarFormat = getString("MemoryBarFormat");
    emptyBarString = getString("EmptyBar");
    emptyTextString = getString("EmptyText");
    runningString = getString("Running");

    try {
      nomarkIcon = new ImageIcon(IlvImageUtil.getImageFromFile(GanttBenchExample.class, "images/nomark.gif"));
      markIcon = new ImageIcon(IlvImageUtil.getImageFromFile(GanttBenchExample.class, "images/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("GanttBench." + key);
  }

  /**
   * Custom panel.
   */
  private class ParamPane extends JPanel {

    int rows = 0;

    int columns = 1;

    final int margin = 5;

    Dimension maxDimension = new Dimension(500, 20);

    public ParamPane(final String title, final String toolTip, int columns) {
      super(new SpringLayout());
      this.columns = columns;
      if (title != null && !title.equals("")) {
        setBorder(BorderFactory.createTitledBorder(title));
      } else {
        setBorder(BorderFactory.createEmptyBorder());
      }
      setToolTipText(toolTip);
    }

    public void addParam(JComponent component) {
      add(component);
      component.setMaximumSize(maxDimension);
      rows++;
    }

    public void addParam(String label, JComponent component) {
      JLabel labelObj = new JLabel(label);
      add(labelObj);
      labelObj.setLabelFor(component);
      add(component);
      component.setMaximumSize(maxDimension);
      rows++;
    }

    public void paramsAdded() {
      SpringUtilities.makeCompactGrid(this, rows, columns, margin, margin, margin, margin);
    }

  }

  /**
   * Initializes the example's user interface in the specified container.
   *
   * @param container
   *          The container that the example is running in. This will be the
   *          <code>JFrame</code> of the application.
   */
  Override
  public void init(Container container) {
    super.init(container);
    if (Runtime.getRuntime().maxMemory() < 200 * 1024 * 1024) {
      IlvSwingUtil.showErrorDialog(null, getString("MaxMemoryNotSet"));
        System.exit(1);
    }
    
    frame = (JFrame) SwingUtilities.getWindowAncestor(container);
    frame.addWindowListener(new WindowAdapter() {
      Override
      public void windowClosing(WindowEvent e) {
        clear();
      }
    });
    

    JPanel panel;
    JLabel label;
    JTextField text;

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

    // Creates the Gantt before creating the menus, as some actions need
    // the gantt as parameter.
    gantt = new IlvGanttChart();

    // Create the menu bar.
    JMenuBar menubar = createMenuBar();
    setHelpID(menubar, "Menus.helpID");
    // Add the menu bar.
    setJMenuBar(menubar);

    // Create the Gantt view.
    //
    JPanel ganttPanel = new JPanel(new BorderLayout());

    panel = new JPanel(new BorderLayout());
    ganttPanel.add(panel, BorderLayout.CENTER);
    ganttPanel.setBorder(BorderFactory.createTitledBorder(getString("GanttViewTitle")));
    ganttPanel.setToolTipText(getString("GanttView.ToolTip"));

    gantt.setPreferredSize(new Dimension(650, 300));
    panel.add(gantt, BorderLayout.CENTER);

    // Create the toolbar
    JToolBar ganttToolBar = createToolBar(gantt);
    panel.add(ganttToolBar, BorderLayout.WEST);

    // Create the Schedule view.
    //
    JPanel schedulePanel = new JPanel(new BorderLayout());

    panel = new JPanel(new BorderLayout());
    schedulePanel.add(panel, BorderLayout.CENTER);
    schedulePanel.setBorder(BorderFactory.createTitledBorder(getString("ScheduleViewTitle")));
    schedulePanel.setToolTipText(getString("ScheduleView.ToolTip"));

    schedule = new IlvScheduleChart();
    schedule.setPreferredSize(new Dimension(650, 300));
    panel.add(schedule, BorderLayout.CENTER);

    JToolBar gfToolbar = createToolBar(schedule);
    panel.add(gfToolbar, BorderLayout.WEST);

    // The splitter that contains the Gantt and Schedule views.
    //
    JSplitPane splitter = new JSplitPane(JSplitPane.VERTICAL_SPLIT, ganttPanel, schedulePanel);
    splitter.setDividerSize(2);
    container.add(splitter, BorderLayout.CENTER);
    splitter.setDividerLocation(0.5);

    // Create the parameter panel that lets the user choose the gantt data
    // size and style.
    //
    JPanel paramPanel = new JPanel(new GridLayout());
    paramPanel.setAlignmentY(JPanel.TOP_ALIGNMENT);
    setHelpID(paramPanel, "Panel.Params.helpID");
    container.add(paramPanel, BorderLayout.NORTH);

    // **********************************
    // First column of parameters panel:
    // - the "Run" button
    // - control loading XML data vs. populating data model by code.
    // - control option to load data model, then attach to charts vs. load
    // data model already attached to chart.
    // **********************************
    // **********************************

    ParamPane pane1 = new ParamPane("", "", 1);

    // The Create Gantt charts button
    JButton createButton = new JButton(getString("Create"));
    createButton.setToolTipText(getString("Create.ToolTip"));
    try {
      createButton.setIcon(new ImageIcon(IlvImageUtil.getImageFromFile(getClass(), "images/go.gif")));
    } catch (IOException e) {
      error(e);
    }
    createButton.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        createCharts();
      }
    });
    pane1.addParam(createButton);
    // The "Load from XML" check box
    JComboBox<String> loadXML = new JComboBox<String>(
        new String[] { getString("CreateByCode"), getString("CreateThroughXML"), getString("LoadFromSelectedXML") });
    loadXML.setToolTipText(getString("LoadFromXML.ToolTip"));
    loadXML.setSelectedIndex(createThroughXML ? 1 : 0);
    loadXML.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          SuppressWarnings("unchecked")
          int index = ((JComboBox<String>) e.getSource()).getSelectedIndex();
          createThroughXML = false;
          loadFromXML = false;
          if (index == 1) {
            createThroughXML = true;
          } else if (index == 2) {
            loadFromXML = true;
          }
          if (createThroughXML || loadFromXML) {
            ganttCreationCounter.setLabel("XMLReading");
            scheduleCreationCounter.setLabel("XMLReading");
          } else {
            ganttCreationCounter.setLabel("Creation");
            scheduleCreationCounter.setLabel("Creation");
          }
        }
      }
    });
    pane1.addParam(loadXML);
    // The "Attach model to chart after/before loading" check box
    JComboBox<String> attachAfter = new JComboBox<String>(
        new String[] { getString("AttachModel.After"), getString("AttachModel.Before") });
    attachAfter.setToolTipText(getString("AttachModel.ToolTip"));
    attachAfter.setSelectedIndex(attachModelAfter ? 0 : 1);
    attachAfter.addItemListener(new ItemListener() {
      Override
      SuppressWarnings("unchecked")
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          attachModelAfter = ((JComboBox<String>) e.getSource()).getSelectedIndex() == 0;
        }
      }
    });
    pane1.addParam(attachAfter);
    pane1.paramsAdded();
    paramPanel.add(pane1);

    // **********************************
    // 2nd column of parameters panel:
    // - provide option to set size on number of activities, resources,
    // avg reservations per resource, and avg constraints per activity
    // **********************************

    ParamPane pane2 = new ParamPane(getString("Data"), getString("Data.ToolTip"), 2);
    int maxSize = 100000;

    // The activity number combo.
    JComboBox<Integer> sizeCombo = new JComboBox<Integer>(new Integer[] { 10, 100, 1000, 10000 });
    pane2.addParam(getString("ActivityCount.Label"), sizeCombo);
    sizeCombo.setToolTipText(getString("ActivityCount.ToolTip"));
    sizeCombo.setSelectedIndex(0);
    sizeCombo.setEditable(true);
    sizeCombo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          nbActivity = (Integer) e.getItem();
        }
      }
    });
    nbActivity = 10;
    // The resource number combo.
    sizeCombo = new JComboBox<Integer>(new Integer[] { 10, 100, 1000, 10000 });
    pane2.addParam(getString("ResourceCount.Label"), sizeCombo);
    sizeCombo.setToolTipText(getString("ResourceCount.ToolTip"));
    sizeCombo.setSelectedIndex(0);
    sizeCombo.setEditable(true);
    sizeCombo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          nbResource = (Integer) e.getItem();
        }
      }
    });
    nbResource = 10;
    // The average reservations per resource number combo.
    sizeCombo = new JComboBox<Integer>(new Integer[] { 1, 2, 3, 5, 10 });
    pane2.addParam(getString("ReservationCount.Label"), sizeCombo);
    sizeCombo.setToolTipText(getString("ReservationCount.ToolTip"));
    sizeCombo.setSelectedIndex(0);
    sizeCombo.setEditable(true);
    sizeCombo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          avgReservation = (Integer) e.getItem();
        }
      }
    });
    avgReservation = 1;
    // The average constraints per activity number combo.
    sizeCombo = new JComboBox<Integer>(new Integer[] { 1, 2, 3, 5 });
    pane2.addParam(getString("ConstraintCount.Label"), sizeCombo);
    sizeCombo.setToolTipText(getString("ConstraintCount.ToolTip"));
    sizeCombo.setSelectedIndex(0);
    sizeCombo.setEditable(true);
    sizeCombo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          avgConstraint = (Integer) e.getItem();
        }
      }
    });
    avgConstraint = 1;
    pane2.paramsAdded();
    paramPanel.add(pane2);

    // **********************************
    // 3rd column of parameters panel:
    // - provide option to set the initial capacities for activities, resources,
    // reservations and constraints
    // **********************************

    ParamPane pane3 = new ParamPane(getString("InitialCapacity"), getString("InitialCapacity.ToolTip"), 2);
    // The activity capacity combo.
    JComboBox<Integer> capacityCombo = new JComboBox<Integer>(new Integer[] { 10, 101, 1000, 10000 });
    pane3.addParam(getString("ActivityCapacity.Label"), capacityCombo);
    capacityCombo.setToolTipText(getString("ActivityCapacity.ToolTip"));
    capacityCombo.setSelectedIndex(1);
    capacityCombo.setEditable(true);
    capacityCombo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          activityCapacity = (Integer) e.getItem();
        }
      }
    });
    activityCapacity = 101;
    // The resource capacity combo.
    capacityCombo = new JComboBox<Integer>(new Integer[] { 10, 101, 1000, 10000 });
    pane3.addParam(getString("ResourceCapacity.Label"), capacityCombo);
    capacityCombo.setToolTipText(getString("ResourceCapacity.ToolTip"));
    capacityCombo.setSelectedIndex(1);
    capacityCombo.setEditable(true);
    capacityCombo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          resourceCapacity = (Integer) e.getItem();
        }
      }
    });
    resourceCapacity = 101;
    // The reservations capacity combo.
    capacityCombo = new JComboBox<Integer>(new Integer[] { 10, 100, 211, 1000, 10000 });
    pane3.addParam(getString("ReservationCapacity.Label"), capacityCombo);
    capacityCombo.setToolTipText(getString("ReservationCapacity.ToolTip"));
    capacityCombo.setSelectedIndex(2);
    capacityCombo.setEditable(true);
    capacityCombo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          reservationCapacity = (Integer) e.getItem();
        }
      }
    });
    reservationCapacity = 211;
    // The constraints capacity combo.
    capacityCombo = new JComboBox<Integer>(new Integer[] { 10, 101, 1000, 10000 });
    pane3.addParam(getString("ConstraintCapacity.Label"), capacityCombo);
    capacityCombo.setToolTipText(getString("ConstraintCapacity.ToolTip"));
    capacityCombo.setSelectedIndex(1);
    capacityCombo.setEditable(true);
    capacityCombo.addItemListener(new ItemListener() {
      Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          constraintCapacity = (Integer) e.getItem();
        }
      }
    });
    constraintCapacity = 101;

    pane3.paramsAdded();
    paramPanel.add(pane3);

    // **********************************
    // 4th column of parameters panel:
    // - provide option to control the rendering
    // **********************************

    ParamPane pane4 = new ParamPane(getString("Rendering"), getString("Rendering.ToolTip"), 1);
    // Radio buttons to control the style sheets
    ButtonGroup cssGroup = new ButtonGroup();

    // No style sheet
    JRadioButton button = new JRadioButton(getString("No"));
    button.setToolTipText(getString("No.ToolTip"));
    button.setSelected(true);
    button.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        cssFile = null;
      }
    });
    cssGroup.add(button);
    pane4.addParam(button);

    // A basic one
    button = new JRadioButton(getString("Basic"));
    button.setToolTipText(getString("Basic.ToolTip"));
    button.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        try {
          cssFile = getResourceURL("data/basic.css").toString();
        } catch (IOException ex) {
          error(ex);
        }
      }
    });
    cssGroup.add(button);
    pane4.addParam(button);

    // A standard one
    button = new JRadioButton(getString("Standard"));
    button.setToolTipText(getString("Standard.ToolTip"));
    button.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        try {
          cssFile = getResourceURL("data/standard.css").toString();
        } catch (IOException ex) {
          error(ex);
        }
      }
    });
    cssGroup.add(button);
    pane4.addParam(button);

    // To choose one
    cssRadio = new JRadioButton(MessageFormat.format(getString("SelectedFormat"), getString("Selected.No")));
    cssRadio.setToolTipText(getString("Selected.ToolTip"));
    cssRadio.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        if (selectedCSS != null) {
          cssFile = selectedCSS.toString();
        } else {
          IlvSwingUtil.showWarningDialog(gantt, getString("Selected.No.Warning"));
        }
      }
    });
    cssGroup.add(cssRadio);
    pane4.addParam(cssRadio);

    // Button to select a new CSS file
    JButton loadCSS = new JButton(getString("LoadCSS"));
    loadCSS.setToolTipText(getString("LoadCSS.ToolTip"));
    loadCSS.addActionListener(new ActionListener() {
      final CSSFileChooser chooser = new CSSFileChooser();

      Override
      public void actionPerformed(ActionEvent e) {
        selectedCSS = getCSSFile();
        // Change the default one is cssRadio is selected
        if (cssRadio.isSelected()) {
          cssFile = selectedCSS.toString();
        }
      }

      /**
       * Prompts the user for a CSS file to open. This method will return
       * <code>null</code> if the user cancels, the selected file does not
       * exist, or any other problem is encountered.
       *
       * @return The URL file or <code>null</code>.
       */
      public URL getCSSFile() {
        // Choose a CSS file to open.
        String filename = chooser.showOpenDialog(gantt);
        if (filename == null) {
          return null;
        }

        // Get the URL of the selected file.
        URL url;
        try {
          url = new File(filename).toURI().toURL();
        } catch (MalformedURLException ex) {
          IlvSwingUtil.showErrorDialog(gantt, ex);
          return null;
        }

        // Set the file name on the radio button
        filename = filename.substring(filename.lastIndexOf(File.separatorChar) + 1);
        cssRadio.setText(MessageFormat.format(getString("SelectedFormat"), filename));
        return url;
      }
    });
    pane4.addParam(loadCSS);

    pane4.paramsAdded();
    paramPanel.add(pane4);

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

    // Status field
    JPanel bottomPanel = new JPanel(new BorderLayout());
    setHelpID(bottomPanel, "Panel.Results.helpID");
    container.add(bottomPanel, BorderLayout.SOUTH);

    JPanel messagePanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
    messageText = new JTextField(30);
    messageText.setEditable(false);
    messagePanel.add(messageText);
    bottomPanel.add(messagePanel, BorderLayout.CENTER);

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

    JPanel memoryPanel = new JPanel(new FlowLayout());
    // Number of objects
    text = new JTextField();
    text.setEditable(false);
    memoryPanel.add(text);
    objectCountText = text;

    displayCounts(maxSize);
    text.setPreferredSize(text.getPreferredSize());
    displayCounts(0);

    // Memory field
    label = new JLabel(getString("Memory"));
    memoryPanel.add(label);

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

    memoryBar = new JProgressBar();
    memoryBar.setToolTipText(getString("MemoryBar.ToolTip"));
    memoryBar.setStringPainted(true);

    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;
    }

    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");
      }
    });
    memoryPanel.add(gcButton);

    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;
      }
    });
    memoryPanel.add(autoGCCheckBox);

    bottomPanel.add(memoryPanel, BorderLayout.EAST);

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

    // ***********************
    // - Gantt chart section
    // ***********************

    ParamPane ganttResultPanel = new ParamPane(getString("GanttChartResult"), getString("GanttChartResult.ToolTip"), 2);
    setHelpID(ganttResultPanel, "Panel.Results.helpID");

    // Total time counter (created now, but added at the end)
    ganttTotalCounter = new Counter();
    // Chart reading/creation counter
    ganttCreationCounter = new Counter().add(((createThroughXML || loadFromXML) ? "XMLReading" : "Creation"),
        ganttResultPanel, ganttTotalCounter);
    // Rendering counter
    ganttRenderingCounter = new Counter().add("Rendering", ganttResultPanel, ganttTotalCounter);
    // Painting counter
    ganttPaintingCounter = new Counter().add("Painting", ganttResultPanel, ganttTotalCounter);
    // Add the total counter for Gantt Chart
    ganttTotalCounter.add("GanttChartTotal", ganttResultPanel, 10000);

    ganttResultPanel.paramsAdded();
    ganttPanel.add(ganttResultPanel, BorderLayout.EAST);

    // **************************
    // - Schedule Chart section
    // **************************

    ParamPane scheduleResultPanel = new ParamPane(getString("ScheduleChartResult"),
        getString("ScheduleChartResult.ToolTip"), 2);
    setHelpID(scheduleResultPanel, "Panel.Results.helpID");
    // Total time counter (created now, but added at the end)
    scheduleTotalCounter = new Counter();
    // Chart reading/creation counter
    scheduleCreationCounter = new Counter().add(((createThroughXML || loadFromXML) ? "XMLReading" : "Creation"),
        scheduleResultPanel, scheduleTotalCounter);
    // Rendering counter
    scheduleRenderingCounter = new Counter().add("Rendering", scheduleResultPanel, scheduleTotalCounter);
    // Painting counter
    schedulePaintingCounter = new Counter().add("Painting", scheduleResultPanel, scheduleTotalCounter);
    // Add the total counter for Schedule Chart
    scheduleTotalCounter.add("ScheduleChartTotal", scheduleResultPanel, 10000);

    scheduleResultPanel.paramsAdded();
    schedulePanel.add(scheduleResultPanel, BorderLayout.EAST);

    // *********************
    // 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();
      }
      System.out.println("");
      clear();
      autoGC();
    }

    updateMemory();

    showMessage("Ready");
  }

  private void displayCounts() {
    String s;
    s = MessageFormat.format(objectCountFormat, activityCount, resourceCount, reservationCount, constraintCount);
    objectCountText.setText(s);
    s = MessageFormat.format(objectCountTooltipFormat, activityCount, resourceCount, reservationCount, constraintCount);
    objectCountText.setToolTipText(s);
  }

  private void displayCounts(int maxSize) {
    String s;
    s = MessageFormat.format(objectCountFormat, maxSize, maxSize, maxSize, maxSize);
    objectCountText.setText(s);
    s = MessageFormat.format(objectCountTooltipFormat, maxSize, maxSize, maxSize, maxSize);
    objectCountText.setToolTipText(s);
  }

  /**
   * Creates the menubar.
   */
  private JMenuBar createMenuBar() {
    JMenuBar menubar = new JMenuBar();
    JMenu fileMenu = createFileMenu();
    if (fileMenu != null) {
      menubar.add(fileMenu);
    }
    JMenu helpMenu = createHelpMenu();
    if (helpMenu != null) {
      menubar.add(helpMenu);
    }
    return menubar;
  }

  /**
   * Creates the "File" menu.
   *
   * @return The File menu.
   */
  private JMenu createFileMenu() {
    JMenu menu = new JMenu(getString("Menu.File"));
    menu.setMnemonic(KeyEvent.VK_F);
    setStatusText(menu, getString("File.Operations"));
    fileChooser = new XMLFileChooser();

    if (isLocalFileAccessAllowed()) {
      // Menu item for "Open ...".
      openAction = new XMLGanttActions.OpenXMLAction(gantt) {
        Override
        public void actionPerformed(ActionEvent event) {
          xmlUrl = getXMLFile();
          if (xmlUrl != null) {
            createCharts();
          }
        }

        /**
         * Prompts the user for an XML data file to open. This method will
         * return <code>null</code> if the user cancels, the selected file does
         * not exist, or any other problem is encountered.
         *
         * @return The URL file or <code>null</code>.
         */
        public URL getXMLFile() {
          // Choose an XML file to open.
          String filename = fileChooser.showOpenDialog(gantt);
          if (filename == null) {
            return null;
          }

          // Get the URL of the selected file.
          URL url;
          try {
            url = new File(filename).toURI().toURL();
          } catch (MalformedURLException ex) {
            IlvSwingUtil.showErrorDialog(gantt, ex);
            return null;
          }

          // Parse the file.
          return url;
        }
      };
      addAction(menu, openAction);

      // Menu item for "Save ...".
      saveAsAction = new XMLGanttActions.SaveAsXMLAction(gantt);
      addAction(menu, saveAsAction);
    }

    // Menu item for "Exit".
    if (isExitAllowed()) {
      menu.addSeparator();
      exitAction = new GanttCommand.ExitAction();
      addAction(menu, exitAction);
    }

    setHelpID(menu, "Menu.File.helpID");
    return menu;
  }

  /**
   * Creates a toolbar for some actions on the charts
   */
  private JToolBar createToolBar(IlvHierarchyChart chart) {
    JToolBar toolbar = new JToolBar(JToolBar.VERTICAL);
    toolbar.setFloatable(false);
    addAction(toolbar, chart.getZoomOutAction());
    addAction(toolbar, chart.getZoomInAction());
    IlvZoomToFitAction zoomToFitAction = new IlvZoomToFitAction(chart, getString("ZoomToFit"),
        KeyStroke.getKeyStroke(KeyEvent.VK_F, KeyEvent.CTRL_MASK), getString("ZoomToFit"),
        getString("ZoomToFit.Description"));
    addAction(toolbar, zoomToFitAction);
    return toolbar;
  }

  /**
   * 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);
  }

  /**
   * The core method that creates the Gantt charts and measures the time spent
   * in the successive steps.
   */
  private void createCharts() {
    // Avoid restarting while we are waiting for the paint to be
    // finished.
    if (running) {
      return;
    }
    running = true;

    setCursor(busyCursor);

    // Set frame title
    if (loadFromXML && xmlUrl != null) {
      setFrameTitle(xmlUrl.toExternalForm());
    } else {
      setFrameTitle(getString("GenerateData"));
    }

    // 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();

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

      objectCountText.setText("");
      paintImmediately(objectCountText);

      autoGC();

      IlvDefaultGanttModel ganttModel = new IlvDefaultGanttModel(activityCapacity, resourceCapacity, constraintCapacity,
          reservationCapacity);

      // If we need to load from a selected XML file, make sure we have one
      if (loadFromXML && xmlUrl == null) {
        IlvSwingUtil.showErrorDialog(gantt, getString("NoSelectedXML"));
        return;
      }

      if (createThroughXML) {
        // We are first going to create the XML output stream from the data
        // Read the XML data file.
        // We do not record time for this.
        showMessage("GeneratingXML");
        ganttData.generateGanttDataFile(nbActivity, nbResource, avgReservation, avgConstraint);
      }

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

      if (!attachModelAfter) {
        gantt.setGanttModel(ganttModel);
      }

      if (createThroughXML) {
        String filename = ganttData.getFilename();

        if (filename != null) {
          setFrameTitle(ganttData.getFilename());

          // Read the XML data file.
          showMessage("ReadingXML");

          ganttCreationCounter.before();

          // Create an InputSource
          InputStream source = createXMLInputStream(filename);
          // Create the document
          Document document = null;
          IlvGanttDocumentReader docReader = new IlvGanttDocumentReader();
          try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            document = builder.parse(source);
          } catch (Exception ex) {
            IlvSwingUtil.showErrorDialog(gantt, ex);
          }

          // Read the document into the Gantt model.
          try {
            docReader.readGanttModel(document, ganttModel);
          } catch (Exception ex) {
            IlvSwingUtil.showErrorDialog(gantt, ex);
          }

          ganttCreationCounter.after();

          // Get the data model objects.
          activityCount = ganttData.getActivityCount();
          resourceCount = ganttData.getResourceCount();
          reservationCount = ganttData.getReservationCount();
          constraintCount = ganttData.getConstraintCount();
        }

      } else if (loadFromXML) {
        if (xmlUrl != null) {
          setFrameTitle(xmlUrl.getFile());

          // Read the XML data file.
          showMessage("ReadingXML");

          ganttCreationCounter.before();

          // Create an InputSource
          InputSource source = new InputSource(xmlUrl.toString());
          // Create the document
          Document document = null;
          IlvGanttDocumentReader docReader = new IlvGanttDocumentReader();
          try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            document = builder.parse(source);
          } catch (Exception ex) {
            IlvSwingUtil.showErrorDialog(gantt, ex);
          }

          // Read the document into the Gantt model.
          try {
            docReader.readGanttModel(document, ganttModel);
          } catch (Exception ex) {
            IlvSwingUtil.showErrorDialog(gantt, ex);
          }

          ganttCreationCounter.after();

          activityCount = 0;
          resourceCount = 0;
          reservationCount = 0;
          constraintCount = 0;
          // Get the data model objects.
          Iterator<?> iter = ganttModel.activityPreorderIterator();
          while (iter.hasNext()) {
            iter.next();
            activityCount++;
          }
          iter = ganttModel.resourcePreorderIterator();
          while (iter.hasNext()) {
            iter.next();
            resourceCount++;
          }
          iter = ganttModel.reservationIterator();
          while (iter.hasNext()) {
            iter.next();
            reservationCount++;
          }
          iter = ganttModel.constraintIterator();
          while (iter.hasNext()) {
            iter.next();
            constraintCount++;
          }
        }

      } else {
        // Not reading from XML (i.e., creating graph in-memory):
        showMessage("Creating");

        ganttCreationCounter.before();

        // Call the external GanttData class to populate the model.
        ganttData.populateModel(ganttModel, nbActivity, nbResource, avgReservation, avgConstraint);

        ganttCreationCounter.after();

        activityCount = ganttData.getActivityCount();
        resourceCount = ganttData.getResourceCount();
        reservationCount = ganttData.getReservationCount();
        constraintCount = ganttData.getConstraintCount();
      }

      // Display the number of objects
      displayCounts();
      paintImmediately(objectCountText);

      // OK, now we have a data model.
      if (attachModelAfter) {
        // Set the data model on the Gantt chart.
        // We don't measure this as it is fast.
        gantt.setGanttModel(ganttModel);
      }

      // Next, perform and measure the creation of graphic objects.
      showMessage("Rendering");

      ganttRenderingCounter.before();

      // Set the style sheets
      showMessage("ReadingCSS");
      if (cssFile == null) {
        gantt.setStyleSheets(null);
      } else {
        gantt.setStyleSheet(cssFile);
      }

      // Make sure to create all graphic objects.
      showMessage("ExpandingRows");
      gantt.expandAllRows();

      ganttRenderingCounter.after();

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

      ganttPaintingCounter.before();

      // Do a "fit-to-contents" to make sure we paint all graphic objects.
      zoomToFit(gantt);

      // 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 Gantt chart view has been painted.
          // We can stop the painting counter,
          //
          ganttPaintingCounter.after();

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

          // Finished for the Gantt chart case!
          //
          // *** Let's start the Schedule chart case.

          // Use another data model to start with a fresh one
          scheduleModel = new IlvDefaultGanttModel(activityCapacity, resourceCapacity, constraintCapacity,
              reservationCapacity);
          //
          // Record time at the beginning of everything.
          scheduleTotalCounter.before();

          if (!attachModelAfter) {
            schedule.setGanttModel(scheduleModel);
          }

          if (createThroughXML) {
            // The XML output stream has already be created
            String filename = ganttData.getFilename();
            if (filename != null) {

              // Read the XML data file.
              showMessage("ReadingXML");

              scheduleCreationCounter.before();

              // Create an InputSource
              InputStream source = null;
              try {
                source = createXMLInputStream(filename);
              } catch (IOException ex) {
                IlvSwingUtil.showErrorDialog(schedule, ex);
              }
              // Create the document
              Document document = null;
              IlvGanttDocumentReader docReader = new IlvGanttDocumentReader();
              try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                document = builder.parse(source);
              } catch (Exception ex) {
                IlvSwingUtil.showErrorDialog(schedule, ex);
              }

              // Read the document into the Gantt model.
              try {
                docReader.readGanttModel(document, scheduleModel);
              } catch (Exception ex) {
                IlvSwingUtil.showErrorDialog(schedule, ex);
              }

              scheduleCreationCounter.after();
            }

          } else if (loadFromXML) {
            if (xmlUrl != null) {
              // Read the XML data file.
              showMessage("ReadingXML");

              scheduleCreationCounter.before();

              // Create an InputSource
              InputSource source = new InputSource(xmlUrl.toString());
              // Create the document
              Document document = null;
              IlvGanttDocumentReader docReader = new IlvGanttDocumentReader();
              try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                document = builder.parse(source);
              } catch (Exception ex) {
                IlvSwingUtil.showErrorDialog(schedule, ex);
              }

              // Read the document into the Gantt model.
              try {
                docReader.readGanttModel(document, scheduleModel);
              } catch (Exception ex) {
                IlvSwingUtil.showErrorDialog(schedule, ex);
              }

              scheduleCreationCounter.after();
            }

          } else {
            // Not reading from XML (i.e., creating chart in-memory):
            showMessage("Creating");

            scheduleCreationCounter.before();

            // Call the external GanttData class to populate the model.
            ganttData.populateModel(scheduleModel, nbActivity, nbResource, avgReservation, avgConstraint);

            scheduleCreationCounter.after();
          }

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

          scheduleRenderingCounter.before();

          if (attachModelAfter) {
            // Set the data model on the Schedule chart. This causes the
            // engine to actually create the graphic objects that represent
            // the graph.
            schedule.setGanttModel(scheduleModel);
          }

          // Set the style sheets
          showMessage("ReadingCSS");

          try {
            if (cssFile == null) {
              schedule.setStyleSheets(null);
            } else {
              schedule.setStyleSheet(cssFile);
            }
          } catch (IlvStylingException e) {
            error(e);
          }

          // Make sure to create all graphic objects.
          showMessage("ExpandingRows");
          schedule.expandAllRows();

          scheduleRenderingCounter.after();

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

          schedulePaintingCounter.before();

          // Do a "fit-to-contents" to make sure we paint all graphic objects.
          zoomToFit(schedule);

          // Same trick: we do an invokeLater to continue execution
          // once all painting has been done.
          //
          SwingUtilities.invokeLater(new Runnable() {
            Override
            public void run() {
              // We get here after the Schedule Chart view has been painted.
              schedulePaintingCounter.after();
              scheduleTotalCounter.after();

              autoGC();

              showMessage("Ready");
              setCursor(null);
              running = false;

              updateMemory();

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

  private void zoomToFit(IlvHierarchyChart chart) {
    IlvZoomToFitAction zoomToFitAction = new IlvZoomToFitAction(chart, getString("ZoomToFit"),
        KeyStroke.getKeyStroke(KeyEvent.VK_F, KeyEvent.CTRL_MASK), getString("ZoomToFit"),
        getString("ZoomToFit.Description"));
    zoomToFitAction.perform();
  }

  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(gantt, MessageFormat.format(getString("NotEnoughMemory"), needed));
  }

  /**
   * Creates a gantt without displaying any results. Used at startup to "heat"
   * the JIT.
   */
  private void coldStart() {
    IlvGanttModel model = new IlvDefaultGanttModel();
    ganttData.populateModel(model, 10, 10, 1, 1);

    gantt.setGanttModel(model);
    schedule.setGanttModel(model);
  }

  /**
   * 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, usedMegs, 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, megs < 0 ? "" : "+", megs);
        memoryText.setText(memText);
      }
    });
    t.setRepeats(false);
    t.start();
  }

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

    if (gantt.getGanttModel() != null) {
      // ((IlvDefaultGanttModel) (gantt.getGanttModel())).clear();
      gantt.setGanttModel(null);
      paintImmediately(gantt);
    }
    if (schedule.getGanttModel() != null) {
      // ((IlvDefaultGanttModel) (schedule.getGanttModel())).clear();
      schedule.setGanttModel(null);
      paintImmediately(schedule);
    }

    // If there has been an XML stream created, delete it.
    String filename = ganttData.getFilename();
    if (filename != null) {
      deleteXMLFile(filename);
    }
  }

  /**
   * Displays an error box.
   */
  private void error(Throwable t) {
    IlvSwingUtil.showErrorDialog(gantt, 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 List<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, ParamPane pane, Counter total) {
      add(labelKey, pane, 100);
      this.total = total;
      total.percents.add(this);
      return this;
    }

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

      label = new JLabel(getString(labelKey));
      label.setIcon(nomarkIcon);
      String tooltip = getString(labelKey + ".ToolTip");
      label.setToolTipText(tooltip);

      text = new JTextField();
      text.setToolTipText(tooltip);
      text.setEditable(false);
      text.setColumns(6);

      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) {
      }

      JPanel p = new JPanel(new FlowLayout());
      p.add(text);
      p.add(bar);
      pane.addParam(getString(labelKey), p);
      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, 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) ((activityCount + resourceCount + reservationCount + constraintCount) * 1000 / time);
          bar.setValue(speed);
          s = MessageFormat.format(speedFormat, speed);
          bar.setString(s);

          for (Counter counter : percents) {
            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, 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 List<Counter> allCounters = new ArrayList<Counter>();

  /**
   * Resets all the counters.
   */
  void resetAllCounters() {
    for (Counter counter : allCounters) {
      counter.reset();
    }
  }

  /**
   * Clears all the counters.
   */
  void clearAllCounters() {
    for (Counter counter : allCounters) {
      counter.clear();
    }
  }

  /**
   * 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) {
      showMessage("GarbageCollecting");
      for (int i = 0; i < autoGCCount; i++) {
        System.gc();
      }
    }
  }

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

  private InputStream createXMLInputStream(String tmpXMLFile) throws IOException {
    InputStream in;
    if (createThroughXML) {
      in = new FileInputStream(tmpXMLFile);
    } else {
      in = new ByteArrayInputStream(xmlByteStream.toString().getBytes());
    }
    return new BufferedInputStream(in);
  }

  private void deleteXMLFile(String tmpXMLFile) {
    if (createThroughXML) {
      try {
        new File(tmpXMLFile).delete();
      } catch (Throwable t) {
      }
    } else {
      xmlByteStream = null;
    }
  }

  // ==========================================
  // Help Related Methods
  // ==========================================

  Override
  SuppressWarnings("unchecked")
  protected Map<String, Object> getHelpProperties() {
    return (Map<String, Object>) helpProperties;
  }

  Override
  protected void initHelpSystem() {
    helpProperties = new Properties();
    InputStream istream = getClass().getResourceAsStream("help/help.properties");
    if (istream != null) {
      try {
        ((Properties) helpProperties).load(istream);
      } catch (IOException e) {
        IlvSwingUtil.showErrorDialog(this, e);
        helpProperties.clear();
      }
    } else {
      IlvSwingUtil.showErrorDialog(this, "Help properties not found: help/help.properties");
    }
    super.initHelpSystem();
  }
}