org.prom5.analysis.performance.PerformanceAnalysisGUI.java Source code

Java tutorial

Introduction

Here is the source code for org.prom5.analysis.performance.PerformanceAnalysisGUI.java

Source

/***********************************************************
 *      This software is part of the ProM package          *
 *             http://www.processmining.org/               *
 *                                                         *
 *            Copyright (c) 2003-2006 TU/e Eindhoven       *
 *                and is licensed under the                *
 *            Common Public License, Version 1.0           *
 *        by Eindhoven University of Technology            *
 *           Department of Information Systems             *
 *                 http://is.tm.tue.nl                     *
 *                                                         *
 **********************************************************/

/*
 * Copyright (c) 2006 Eindhoven University of Technology
 * All rights reserved.
 */

package org.prom5.analysis.performance;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;

import org.apache.commons.math.stat.descriptive.SummaryStatistics;
import org.prom5.framework.log.LogReader;
import org.prom5.framework.log.LogReaderFactory;
import org.prom5.framework.models.Bag;
import org.prom5.framework.models.DotFileWriter;
import org.prom5.framework.models.ModelGraphPanel;
import org.prom5.framework.models.hlprocess.HLActivity;
import org.prom5.framework.models.hlprocess.HLChoice;
import org.prom5.framework.models.hlprocess.HLCondition;
import org.prom5.framework.models.hlprocess.HLTypes;
import org.prom5.framework.models.hlprocess.distribution.HLDistribution;
import org.prom5.framework.models.hlprocess.distribution.HLGeneralDistribution;
import org.prom5.framework.models.hlprocess.hlmodel.HLPetriNet;
import org.prom5.framework.models.petrinet.PetriNet;
import org.prom5.framework.models.petrinet.Transition;
import org.prom5.framework.models.petrinet.algorithms.logReplay.AnalysisGUI;
import org.prom5.framework.models.petrinet.algorithms.logReplay.AnalysisResult;
import org.prom5.framework.plugin.ProvidedObject;
import org.prom5.framework.plugin.Provider;
import org.prom5.framework.ui.DoubleClickTable;
import org.prom5.framework.ui.MainUI;
import org.prom5.framework.ui.Message;

import att.grappa.Edge;
import att.grappa.Element;
import att.grappa.GrappaAdapter;
import att.grappa.GrappaBox;
import att.grappa.GrappaPanel;
import att.grappa.GrappaPoint;
import att.grappa.Node;
import att.grappa.Subgraph;

/**
 * This class contains the GUI in which the the results of performance analysis
 * with Petri net is displayed.
 *
 * @author Peter T.G. Hornix (p.t.g.hornix@student.tue.nl)
 */
public class PerformanceAnalysisGUI extends AnalysisGUI implements Provider {

    // technical attributes
    private PetriNet inputPetriNet;
    private LogReader inputLog;
    private ExtendedLogReader extendedLog;
    private ExtendedPetriNet extendedPetriNet;
    private HashMap mapping;

    // petri net with simulation related information
    private HLPetriNet highLevelPN;

    /**
     * If this plugin is used multiple times, each time the simulation model to be
     * provided will have an incremented number (in order to distinguish them later when they
     * e.g., need to be joined)
     */
    private static int simulationModelCounter = 0;

    //For purpose of keeping track of which elements have been selected on the
    //grappaPanel
    private Element elt1 = null;
    private Element elt2 = null;

    private PerformanceLogReplayResult replayResult;

    //general settings
    private boolean manualSettings = false;
    private long timeDivider = 1000;
    private String timeSort = "seconds";
    private int decimalPlaces = 2;
    private ArrayList bounds;
    private ArrayList levelColors;
    private double fastestProcessPercentage = 25.0;
    private double slowestProcessPercentage = 25.0;
    private double fastestPlacePercentage = 25.0;
    private double slowestPlacePercentage = 25.0;
    private double fastestBetweenPercentage = 25.0;
    private double slowestBetweenPercentage = 25.0;
    private double fastestActivityPercentage = 25.0;
    private double slowestActivityPercentage = 25.0;
    //advanced settings;
    private int[] advancedSettings = { 1, 1, 1, 1, 0 };

    //map containing all transitions and places in selectionBox1
    private HashMap boxMap = new HashMap();
    //set of procss instances that have failed
    private HashSet failedInstances = new HashSet();
    //GUI components
    private JScrollPane tableContainer;
    private JScrollPane modelContainer;
    private JScrollPane metricsBottomContainer;
    private JScrollPane processContainer;
    private JPanel westPanel = new JPanel();
    private JPanel eastPanel = new JPanel();
    private JPanel levelPanel = new JPanel();
    private JPanel highPanel = new JPanel();
    private JPanel mediumPanel = new JPanel();
    private JPanel lowPanel = new JPanel();
    private JPanel metricsBottomPanel = new JPanel();
    private JPanel tablePanel = new JPanel(new BorderLayout());
    private JPanel selectionPanel = new JPanel();
    private JPanel bottomPanel = new JPanel();
    private JPanel centerPanel = new JPanel();
    private JPanel processTablePanel = new JPanel();
    private JPanel piButtonsPanel = new JPanel();
    private ModelGraphPanel grappaPanel;

    //waiting time level labels
    private JLabel waitingLabel = new JLabel("Waiting time:");
    private JLabel highLabel = new JLabel("High");
    private JLabel mediumLabel = new JLabel("Medium");
    private JLabel lowLabel = new JLabel("Low");
    //selection labels
    private JLabel selectLabel = new JLabel("Selected:");
    private JLabel andLabel = new JLabel("and:");
    //place labels
    private JLabel titleLabel = new JLabel("Performance information of the selected" + " place:");
    private JLabel numberObjectLabel = new JLabel("Frequency: ");
    private JLabel freqObjectLabel = new JLabel();
    private JLabel arrivalPlaceLabel = new JLabel("Arrival rate: ");
    private JLabel ratePlaceLabel = new JLabel();

    //process labels
    private JLabel processLabel = new JLabel("Process information:");
    private JLabel numberProcessLabel = new JLabel("Total number selected:");
    private JLabel casesProcessLabel = new JLabel("-");
    private JLabel properLabel = new JLabel("Number fitting:");
    private JLabel completedLabel = new JLabel("-");
    private JLabel arrivalProcessLabel = new JLabel("Arrival rate of selected:");
    private JLabel rateProcessLabel = new JLabel("-");

    //buttons
    private JButton updateButton = new JButton("Update");
    private JButton invertButton = new JButton("Invert Selection");
    private JButton changeSettingsButton = new JButton("Settings");
    private JButton changePercentagesButton = new JButton("Change Percentages");
    private JButton changeProcessPercButton = new JButton("<html>&nbsp &nbsp " + "Change<br> Percentages</html>");
    private JButton exportButton = new JButton("Export Time Metrics");
    private JButton exportProcessButton = new JButton(
            "<html>&nbsp &nbsp &nbsp " + "Export <br> Time-Metrics</html>");
    //tables
    private DoubleClickTable processInstanceIDsTable;
    private JTable placeTable = new JTable();
    private JTable transitionTable = new JTable();
    private JTable processTable = new JTable();
    private JTable activityTable = new JTable();

    //splitPanes
    private JSplitPane leftSplitPane;
    private JSplitPane rightSplitPane;

    //comboboxes
    private JComboBox sb1;
    private JComboBox sb2;

    /**
     * Calls the constructor of its superclass and initializes variables and
     * the UI
     *
     * @param analysisResults HashSet: contains the results of log replay
     * @param manual boolean: true if the user has manually selected performance
     * options. When performance values have been automatically calculated it
     * contains false
     * @param bnds ArrayList: the bounds of the bottleneck levels
     * @param cols ArrayList: the bottleneck colors
     * @param sort String: the timesort used
     * @param divider long: the divider belonging to the timesort
     * @param places int: the number of decimal places used
     * @param advanced int[]: contains the advanced settings, containing info on
     * how to deal with conformance
     */
    public PerformanceAnalysisGUI(Set<AnalysisResult> analysisResults, boolean manual, ArrayList bnds,
            ArrayList cols, long divider, String sort, int places, int[] advanced) {
        super(analysisResults);

        // increment static sim model counter for this plugin
        simulationModelCounter = simulationModelCounter + 1;

        Iterator results = analysisResults.iterator();
        while (results.hasNext()) {
            AnalysisResult currentResult = (AnalysisResult) results.next();
            if (currentResult != null) {
                replayResult = (PerformanceLogReplayResult) currentResult;
            } else {
                // an analysis result can be null if the user aborted the computation
                return;
            }
        }
        manualSettings = manual;
        timeDivider = divider;
        timeSort = sort;
        decimalPlaces = places;
        bounds = bnds;
        levelColors = cols;
        advancedSettings = advanced;
        inputLog = replayResult.inputLogReader;
        inputPetriNet = replayResult.inputPetriNet;
        // create the highLevelPetriNet
        highLevelPN = new HLPetriNet(inputPetriNet);
        // the highLevelPN covers the timing perspective and (some part of) the choices perspective
        highLevelPN.getHLProcess().getGlobalInfo().addPerspective(HLTypes.Perspective.TIMING_EXECTIME);
        highLevelPN.getHLProcess().getGlobalInfo().addPerspective(HLTypes.Perspective.TIMING_WAITTIME);
        highLevelPN.getHLProcess().getGlobalInfo().addPerspective(HLTypes.Perspective.TIMING_SOJTIME);
        highLevelPN.getHLProcess().getGlobalInfo().addPerspective(HLTypes.Perspective.CASE_GEN_SCHEME);
        highLevelPN.getHLProcess().getGlobalInfo().addPerspective(HLTypes.Perspective.CHOICE_FREQ);
        highLevelPN.getHLProcess().getGlobalInfo().addPerspective(HLTypes.Perspective.CHOICE_PROB);
        // set the name of the simulation model
        highLevelPN.getHLProcess().getGlobalInfo()
                .setName("Performance Simulation Model No." + simulationModelCounter);

        extendedLog = (ExtendedLogReader) replayResult.replayedLog;
        extendedPetriNet = (ExtendedPetriNet) replayResult.replayedPetriNet;
        obtainFailedInstances();
        initializeWaitingTimeLevels();
        displayProcessMetrics(extendedLog.getDiagnosticLogTraces());
        try {
            jbInit();
            registerGuiListener();
        } catch (Exception ex) {
            Message.add("Program exception while displaying performance analysis results:\n" + ex.toString(), 2);
            ex.printStackTrace();
        }
        // calculate the values for the simulation model
        calculateValuesForSimulationModel(extendedLog.getDiagnosticLogTraces(), extendedLog.getLogTraceIDs());
        //
        Iterator entries = mapping.entrySet().iterator();
        while (entries.hasNext()) {
            Map.Entry entr = (Map.Entry) entries.next();
            if (entr.getKey() instanceof ExtendedTransition) {
                ExtendedTransition transition = (ExtendedTransition) entr.getKey();
                ExtendedActivity act = transition.getAssociatedActivity();
                if (act != null) {
                    act.calculateMetrics(getSelectedInstanceIDs(), advancedSettings[3], failedInstances);
                    act.checkWhichMetricsToUse();
                    act.getArrivalRate();
                }
            }
        }
    }

    ///////////////////////INITIALIZATION METHODS//////////////////////////////

    /**
     * Actually builds the UI
     * @throws Exception
     */
    private void jbInit() throws Exception {
        //Initialize the table which contains the log traces
        processInstanceIDsTable = new DoubleClickTable(new ExtendedLogTable(), updateButton);
        // select all rows as at the beginning the results count for the whole log
        processInstanceIDsTable.getSelectionModel().addSelectionInterval(0, extendedLog.getSizeOfLog() - 1);

        tableContainer = new JScrollPane(processInstanceIDsTable);

        //initialize the westPanel (which contains the processInstanceIDsTable)
        westPanel.setPreferredSize(new Dimension(100, 300));
        westPanel.setLayout(new BorderLayout());
        westPanel.add(tableContainer, BorderLayout.CENTER);
        updateButton.setToolTipText("Update metrics to the selected process instances");
        westPanel.add(piButtonsPanel, BorderLayout.SOUTH);
        piButtonsPanel.setLayout(new BorderLayout());
        piButtonsPanel.add(updateButton, BorderLayout.CENTER);
        piButtonsPanel.add(invertButton, BorderLayout.SOUTH);

        //initialize the eastpanel (which contains the process-metrics)
        eastPanel.setPreferredSize(new Dimension(240, 390));
        eastPanel.setMinimumSize(new Dimension(240, 390));
        eastPanel.setLayout(new GridBagLayout());
        GridBagConstraints cons = new GridBagConstraints();
        cons.gridx = 0;
        cons.gridy = 0;
        cons.insets = new Insets(0, 5, 5, 0);
        cons.anchor = GridBagConstraints.WEST;
        processLabel.setFont(new Font("SansSerif", Font.BOLD, 16));
        eastPanel.add(processLabel, cons);
        cons.gridx = 0;
        cons.gridy = 1;
        cons.insets = new Insets(8, 8, 0, 0);
        eastPanel.add(numberProcessLabel, cons);
        casesProcessLabel.setText(extendedLog.getSizeOfLog() + " cases");
        casesProcessLabel.setFont(new Font("SansSerif", Font.PLAIN, 11));
        cons.gridx = 0;
        cons.gridy = 2;
        eastPanel.add(casesProcessLabel, cons);
        cons.gridx = 0;
        cons.gridy = 3;
        eastPanel.add(properLabel, cons);
        completedLabel.setFont(new Font("SansSerif", Font.PLAIN, 11));
        cons.gridx = 0;
        cons.gridy = 4;
        eastPanel.add(completedLabel, cons);
        cons.gridx = 0;
        cons.gridy = 5;
        eastPanel.add(arrivalProcessLabel, cons);
        rateProcessLabel.setFont(new Font("SansSerif", Font.PLAIN, 11));
        cons.gridx = 0;
        cons.gridy = 6;
        eastPanel.add(rateProcessLabel, cons);
        cons.gridx = 0;
        cons.gridy = 7;
        cons.gridwidth = 2;
        cons.insets = new Insets(15, 5, 0, 2);
        cons.ipadx = 300;
        eastPanel.add(processTablePanel, cons);
        cons.gridx = 0;
        cons.gridy = 8;
        cons.gridwidth = 1;
        cons.insets = new Insets(0, 5, 0, 0);
        cons.weightx = 1;
        cons.ipadx = 0;
        exportProcessButton.setPreferredSize(new Dimension(110, 42));
        exportProcessButton.setMaximumSize(new Dimension(110, 42));
        changeProcessPercButton.setPreferredSize(new Dimension(110, 42));
        changeProcessPercButton.setMaximumSize(new Dimension(110, 42));
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
        buttonPanel.setPreferredSize(new Dimension(240, 42));
        buttonPanel.add(changeProcessPercButton);
        buttonPanel.add(exportProcessButton);
        eastPanel.add(buttonPanel, cons);

        processContainer = new JScrollPane(eastPanel);
        processContainer.setPreferredSize(new Dimension(240, 390));
        //initialize the processTablePanel (which contains the table with throughput times)
        processTablePanel.setLayout(new BorderLayout());
        processTablePanel.setMinimumSize(new Dimension(240, 130));
        processTablePanel.setPreferredSize(new Dimension(240, 130));
        processTable.setRowSelectionAllowed(false);
        processTablePanel.add(processTable.getTableHeader(), BorderLayout.PAGE_START);
        processTablePanel.add(processTable, BorderLayout.CENTER);

        //initialize the grappaPanel (which contains the Petri net UI) and place
        //it on the centerPanel
        grappaPanel = replayResult.getVisualization(extendedLog.getLogTraceIDs());
        grappaPanel.addGrappaListener(new ExtendedGrappaAdapter());
        mapping = new HashMap();
        buildGraphMapping(mapping, grappaPanel.getSubgraph());
        modelContainer = new JScrollPane(grappaPanel);
        centerPanel.setLayout(new BorderLayout());
        centerPanel.add(modelContainer, BorderLayout.CENTER);

        //initialize the colored panels
        Color tCol = (Color) levelColors.get(0);
        lowPanel.setBackground(tCol);
        lowPanel.setBorder(BorderFactory.createEtchedBorder());
        lowPanel.setPreferredSize(new Dimension(25, 12));
        tCol = (Color) levelColors.get(1);
        mediumPanel.setBackground(tCol);
        mediumPanel.setBorder(BorderFactory.createEtchedBorder());
        mediumPanel.setPreferredSize(new Dimension(25, 12));
        tCol = (Color) levelColors.get(2);
        highPanel.setBackground(tCol);
        highPanel.setBorder(BorderFactory.createEtchedBorder());
        highPanel.setPreferredSize(new Dimension(25, 12));

        //initialize the levelPanel (which contains the waiting time settings)
        levelPanel.setBorder(BorderFactory.createRaisedBevelBorder());
        levelPanel.setLayout(new GridBagLayout());
        cons.gridx = 0;
        cons.gridy = 0;
        cons.gridwidth = 2;
        cons.insets = new Insets(2, 2, 0, 5);
        cons.weightx = 0;
        levelPanel.add(waitingLabel, cons);
        cons.gridx = 0;
        cons.gridy = 1;
        cons.anchor = GridBagConstraints.WEST;
        cons.insets = new Insets(2, 2, 0, 5);
        cons.gridwidth = 1;
        levelPanel.add(highPanel, cons);
        cons.gridx = 1;
        cons.gridy = 1;
        levelPanel.add(highLabel, cons);
        cons.gridx = 0;
        cons.gridy = 2;
        cons.insets = new Insets(1, 2, 0, 5);
        levelPanel.add(mediumPanel, cons);
        cons.gridx = 1;
        cons.gridy = 2;
        levelPanel.add(mediumLabel, cons);
        cons.gridx = 0;
        cons.gridy = 3;
        cons.insets = new Insets(2, 2, 2, 5);
        levelPanel.add(lowPanel, cons);
        cons.gridx = 1;
        cons.gridy = 3;
        levelPanel.add(lowLabel, cons);
        cons.gridx = 0;
        cons.gridy = 4;
        cons.gridwidth = 2;
        cons.insets = new Insets(2, 2, 2, 2);
        changeSettingsButton.setMnemonic(KeyEvent.VK_S);
        levelPanel.add(changeSettingsButton, cons);
        levelPanel.setBackground(new Color(220, 220, 220));

        //initialize the selectionPanel (which contains the selected transitions/place)
        initializeSelection();

        //initialize the metricsBottomPanel
        metricsBottomPanel.setLayout(new GridBagLayout());
        cons = new GridBagConstraints();
        cons.gridx = 0;
        cons.gridy = 0;
        cons.gridwidth = 5;
        cons.anchor = GridBagConstraints.WEST;
        cons.insets = new Insets(1, 2, 5, 0);
        titleLabel.setFont(new Font("SansSerif", Font.BOLD, 14));
        metricsBottomPanel.add(titleLabel, cons);
        cons.gridx = 0;
        cons.gridy = 1;
        cons.gridwidth = 1;
        cons.insets = new Insets(1, 2, 2, 0);
        metricsBottomPanel.add(numberObjectLabel, cons);
        cons.gridx = 1;
        cons.gridy = 1;
        cons.gridwidth = 1;
        freqObjectLabel.setFont(new Font("SansSerif", Font.PLAIN, 12));
        metricsBottomPanel.add(freqObjectLabel, cons);
        cons.gridx = 0;
        cons.gridy = 2;
        cons.gridwidth = 1;
        metricsBottomPanel.add(arrivalPlaceLabel, cons);
        cons.gridx = 1;
        cons.gridy = 2;
        cons.gridwidth = 1;
        ratePlaceLabel.setFont(new Font("SansSerif", Font.PLAIN, 12));
        metricsBottomPanel.add(ratePlaceLabel, cons);
        cons.gridx = 0;
        cons.gridy = 3;
        cons.gridwidth = 5;
        metricsBottomPanel.add(tablePanel, cons);
        cons.gridx = 0;
        cons.gridy = 4;
        cons.gridwidth = 2;
        changePercentagesButton.setPreferredSize(new Dimension(170, 25));
        metricsBottomPanel.add(changePercentagesButton, cons);
        cons.gridx = 2;
        cons.gridy = 4;
        cons.gridwidth = 2;
        metricsBottomPanel.add(exportButton, cons);
        //hide all place/transition metrics
        hideAllMetrics();
        metricsBottomContainer = new JScrollPane(metricsBottomPanel);
        metricsBottomContainer.setPreferredSize(new Dimension(550, 140));
        metricsBottomContainer.setMinimumSize(new Dimension(550, 140));
        metricsBottomContainer.setMaximumSize(new Dimension(550, 800));
        metricsBottomContainer.setBorder(BorderFactory.createEmptyBorder());

        //initialize the bottomPanel
        bottomPanel.setBorder(BorderFactory.createEtchedBorder());
        bottomPanel.setPreferredSize(new Dimension(650, 143));
        bottomPanel.setMinimumSize(new Dimension(650, 143));
        bottomPanel.setLayout(new GridBagLayout());
        GridBagConstraints con = new GridBagConstraints();
        con.gridx = 0;
        con.gridy = 0;
        con.gridwidth = 4;
        con.weightx = 2;
        bottomPanel.add(metricsBottomContainer, con);
        con.gridx = 4;
        con.gridy = 0;
        con.gridwidth = 1;
        con.weightx = 1;
        con.anchor = GridBagConstraints.EAST;
        bottomPanel.add(levelPanel, con);
        con.gridx = 5;
        con.gridy = 0; //con.gridwidth = 2;
        con.weightx = 1;
        con.anchor = GridBagConstraints.EAST;
        bottomPanel.add(selectionPanel, con);
        //Divide the upper panels by using splitPanes
        leftSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, westPanel, centerPanel);
        leftSplitPane.setDividerLocation(130);
        leftSplitPane.setOneTouchExpandable(true);
        rightSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftSplitPane, processContainer);
        rightSplitPane.setDividerLocation(rightSplitPane.getSize().width - rightSplitPane.getInsets().right
                - rightSplitPane.getDividerSize() - 240); //processContainer.getWidth());
        rightSplitPane.setOneTouchExpandable(true);
        rightSplitPane.setResizeWeight(1.0);
        rightSplitPane.setDividerSize(3);

        //set tooltips
        processTable.setToolTipText("Throughput time of the process, based on" + " the selected cases");
        rateProcessLabel.setToolTipText("Arrival rate of the selected cases");
        arrivalProcessLabel.setToolTipText("Arrival rate of the selected cases");
        completedLabel.setToolTipText(
                "Total number of the selected cases that" + "  have completed properly and successfully");
        properLabel.setToolTipText(
                "Total number of the selected cases that" + " have completed properly and successfully");
        casesProcessLabel.setToolTipText("Total number of cases selected in" + " the table on the left");
        numberProcessLabel.setToolTipText("Total number of cases selected in" + " the table on the left");
        changeSettingsButton.setToolTipText("Adjust the current waiting time" + " classifications");
        placeTable.setToolTipText("Time-metrics of the selected place, based on" + " the selected cases");
        transitionTable.setToolTipText(
                "Time cases spend in between the" + " selected transitions, based on the selected cases");
        activityTable.setToolTipText(
                "Time-metrics of activity related to the" + " selected transition, based on the selected cases");
        processInstanceIDsTable.setToolTipText("Selected cases");
        changePercentagesButton.setToolTipText("Change percentages of slow," + " fast and normal");
        changeProcessPercButton.setToolTipText("Change percentages of slow," + " fast and normal");
        exportButton.setToolTipText("Export the values of all measurements of"
                + " the metrics in the table above to a comma-seperated" + " text-file");
        exportProcessButton.setToolTipText("Export the value of the throughput"
                + " time of all selected process instances to a comma-seperated" + " text-file");
        //initialize the performanceAnalysisGUI
        this.setLayout(new BorderLayout());
        this.add(bottomPanel, BorderLayout.SOUTH);
        this.add(rightSplitPane, BorderLayout.CENTER);
        this.validate();
        this.repaint();
    }

    /**
     * Initializes the selectionPanel:
     * Fills one combobox with all transitions and places of the used Petri net,
     * and one combobox with only transitions.
     * Initially nothing is selected in both comboboxes and the second combobox
     * is disabled.
     */
    private void initializeSelection() {
        sb1 = new JComboBox();
        sb1.setMaximumSize(new Dimension(200, 20));
        //SteppedComboBoxUI allows the pop-up menu to be of different width
        //than the width of the combobox, here sb1, itself
        sb1.setUI(new SteppedComboBoxUI());
        sb1.setBorder(BorderFactory.createEtchedBorder());
        sb2 = new JComboBox();
        sb2.setUI(new SteppedComboBoxUI());
        sb2.setMaximumSize(new Dimension(200, 20));
        sb2.setBorder(BorderFactory.createEtchedBorder());

        //Fill the boxes
        sb1.addItem("-------------");
        sb2.addItem("-------------");
        //add all places of the Petri net to the first selection box
        Iterator it = extendedPetriNet.getPlaces().iterator();
        while (it.hasNext()) {
            Element current = (Element) it.next();
            ExtendedPlace ep = (ExtendedPlace) current;
            String placeString = "Place - " + ep.getIdentifier();
            sb1.addItem(placeString);
            if (boxMap.get(placeString) == null) {
                //and add the place to the boxMap if it is not already in it
                boxMap.put(placeString, current);
            }
        }
        //add all transitions of the Petri net to both selection boxes
        Iterator dit = extendedPetriNet.getTransitions().iterator();
        while (dit.hasNext()) {
            Element current = (Element) dit.next();
            ExtendedTransition et = (ExtendedTransition) current;
            try {
                String transString = "Transition - " + et.getLogEvent().getModelElementName() + " "
                        + et.getLogEvent().getEventType();

                if (boxMap.get(transString) == null) {
                    //place transition in sb1 & sb2
                    sb1.addItem(transString);
                    sb2.addItem(transString);
                    //and add the transition to the boxMap
                    boxMap.put(transString, current);
                } else {
                    //at least one transition with the same name within the boxMap
                    //count the number of transitions in the boxMap having this same name
                    Iterator keys = boxMap.keySet().iterator();
                    int number = 0;
                    while (keys.hasNext()) {
                        String key = (String) keys.next();
                        if (key.startsWith(transString)) {
                            number++;
                        }
                    }
                    //add current transition to the boxMap, with between brackets
                    //its number (>=1)
                    transString = "Transition - " + et.getLogEvent().getModelElementName() + " (" + number + ") "
                            + " " + et.getLogEvent().getEventType();
                    //place transition in sb1 & sb2
                    sb1.addItem(transString);
                    sb2.addItem(transString);
                    boxMap.put(transString, current);
                }
            } catch (NullPointerException ne) {
                /* Invisible transitions do not have a ModelElementName or
                   EventType, thus an exception occurs. It is caught here */
            }
        }
        //place objects on the selectionPanel
        selectionPanel.setLayout(new BoxLayout(selectionPanel, BoxLayout.Y_AXIS));
        selectionPanel.add(Box.createRigidArea(new Dimension(15, 10)));
        selectLabel.setFont(new Font("SansSerif", Font.BOLD, 14));
        selectionPanel.add(selectLabel);
        selectionPanel.add(Box.createRigidArea(new Dimension(5, 5)));
        selectionPanel.add(sb1);
        selectionPanel.add(Box.createRigidArea(new Dimension(5, 10)));
        andLabel.setForeground(Color.GRAY);
        selectionPanel.add(andLabel);
        selectionPanel.add(Box.createRigidArea(new Dimension(5, 5)));
        sb1.setBackground(Color.white);
        sb2.setBackground(Color.white);
        sb2.setEnabled(false);
        selectionPanel.add(sb2);
        selectionPanel.setBackground(new Color(220, 220, 220));
        selectLabel.setAlignmentX(LEFT_ALIGNMENT);
        sb1.setAlignmentX(LEFT_ALIGNMENT);
        andLabel.setAlignmentX(LEFT_ALIGNMENT);
        sb2.setAlignmentX(LEFT_ALIGNMENT);
        selectionPanel.setBorder(BorderFactory.createEtchedBorder());
        selectionPanel.setPreferredSize(new Dimension(240, 140));
        selectionPanel.setMinimumSize(new Dimension(240, 140));
    }

    private void obtainFailedInstances() {
        extendedLog.reset();
        while (extendedLog.hasNext()) {
            ExtendedLogTrace pi = (ExtendedLogTrace) extendedLog.next();
            if (!pi.hasSuccessfullyExecuted() || !pi.hasProperlyTerminated()) {
                failedInstances.add(pi.getName());
            }
        }
    }

    /**
     * Initializes the waiting time levels. If no manual peformance settings are
     * filled in by the user, standard settings are calculated and used.
     * Standard settings: approximately 33% low, 33% high, 33% medium level
     * note that a set is used instead of a list however, so if a time occurs
     * multiple times (this can happen easily with a waiting time of 0.0s for
     * instance) of such places only one is used, so the 33-33-33 estimation can
     * be quite wrong, though this is not considered to be a problem.
     */
    public void initializeWaitingTimeLevels() {
        if (!manualSettings) {
            //no manual settings are present
            TreeSet waitingTimes = new TreeSet();
            ListIterator it = extendedPetriNet.getPlaces().listIterator();
            while (it.hasNext()) {
                //place the mean waiting time of each place in the tree set
                ExtendedPlace p = (ExtendedPlace) it.next();
                p.calculateMetrics(extendedLog.getLogTraceIDs(), advancedSettings[1], failedInstances);
                if (p.getMeanWaitingTime() >= 0) {
                    //only add correct times
                    double waitTime = p.getMeanWaitingTime() / timeDivider;
                    waitingTimes.add(Double.valueOf(waitTime));
                }
            }
            int num = waitingTimes.size() / 3;
            //remove the first 'num' measurements and the last 'num' measurements
            //from waitingTimes
            for (int i = 0; i < num; i++) {
                //there should be at least one waiting time measurement remaining
                if (!(waitingTimes.size() < 2)) {
                    waitingTimes.remove(waitingTimes.first());
                    waitingTimes.remove(waitingTimes.last());
                }
            }

            //give new values to the bounds and the colors
            if (waitingTimes.size() != 0) {
                Double bnd = (Double) waitingTimes.first();
                bounds.set(0, bnd);
                bnd = (Double) waitingTimes.last();
                bounds.set(1, bnd);
            } else {
                //in case there are no valid waiting times
                waitingTimes.add(Double.valueOf(0));
                Double bnd = (Double) waitingTimes.first();
                bounds.set(0, bnd);
                bounds.set(1, bnd);
            }
            levelColors.set(0, Color.BLUE);
            levelColors.set(1, Color.YELLOW);
            levelColors.set(2, Color.MAGENTA);
        }
        extendedPetriNet.setAdvancedSettings(advancedSettings);
        extendedPetriNet.setBounds(bounds);
        extendedPetriNet.setLevelColors(levelColors);
        extendedPetriNet.setTimeDivider(timeDivider);
        extendedPetriNet.setFailedInstances(failedInstances);
    }

    /**
     * Connects user-interface objects with Listener methods
     */
    private void registerGuiListener() {
        // specify button action
        updateButton.addActionListener(new ActionListener() {
            // specify the action to be taken when pressing the button
            public void actionPerformed(ActionEvent e) {
                hideAllMetrics();
                updateResults();
                // update the timing information for the transitions in the hlPN
                Iterator entries = mapping.entrySet().iterator();
                while (entries.hasNext()) {
                    Map.Entry entr = (Map.Entry) entries.next();
                    if (entr.getKey() instanceof ExtendedTransition) {
                        ExtendedTransition transition = (ExtendedTransition) entr.getKey();
                        ExtendedActivity act = transition.getAssociatedActivity();
                        if (act != null) {
                            act.calculateMetrics(getSelectedInstanceIDs(), advancedSettings[3], failedInstances);
                            act.checkWhichMetricsToUse();
                            act.getArrivalRate();
                        }
                    }
                }
            }
        });
        invertButton.addActionListener(new ActionListener() {
            // specify the action to be taken when pressing the button
            public void actionPerformed(ActionEvent e) {
                invertSelectionStatus();
            }
        });
        changeSettingsButton.addActionListener(new ActionListener() {
            // specify the action to be taken when pressing the button
            public void actionPerformed(ActionEvent e) {
                //create and show the performance configuration screen
                createPerformanceOptionsScreen();
            }
        });
        changePercentagesButton.addActionListener(new ActionListener() {
            // specify the action to be taken when pressing the button
            public void actionPerformed(ActionEvent e) {
                changeObjectPercentages();
            }
        });
        exportButton.addActionListener(new ActionListener() {
            // specify the action to be taken when pressing the button
            public void actionPerformed(ActionEvent e) {
                exportObjectMetrics();
            }
        });
        exportProcessButton.addActionListener(new ActionListener() {
            // specify the action to be taken when pressing the button
            public void actionPerformed(ActionEvent e) {
                exportProcessMetrics();
            }
        });
        changeProcessPercButton.addActionListener(new ActionListener() {
            // specify the action to be taken when pressing the button
            public void actionPerformed(ActionEvent e) {
                changeProcessPercentages();
            }
        });
        sb1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                adjustToSelectedSb1();
            }
        });
        sb2.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                adjustToSelectedSb2();
            }
        });
    }

    /////////////////////// ADJUSTING METHODS///////////////////
    /**
     * Updates both the visualization and the performance metrics by taking only
     * the selected instances into account. If all instances have been selected,
     * the result corresponds to the initial state.
     */
    private void updateResults() {
        try {
            // check if selection is empty --> if so select whole log
            ListSelectionModel selectionModel = processInstanceIDsTable.getSelectionModel();
            if (selectionModel.isSelectionEmpty()) {
                selectionModel.addSelectionInterval(0, extendedLog.getSizeOfLog() - 1);
            }
            // invoke redraw of vizualization for selected process instances only..
            ArrayList selectedInstanceIDs = getSelectedInstanceIDs();
            ArrayList selectedInstances = getSelectedInstances();

            grappaPanel = replayResult.getVisualization(selectedInstanceIDs);
            grappaPanel.addGrappaListener(new ExtendedGrappaAdapter());
            mapping = new HashMap();
            buildGraphMapping(mapping, grappaPanel.getSubgraph());
            modelContainer = new JScrollPane(grappaPanel);
            centerPanel.removeAll();
            centerPanel.add(modelContainer, BorderLayout.CENTER);
            centerPanel.validate();
            centerPanel.repaint();
            //update process metrics and display them
            displayProcessMetrics(selectedInstances);
            //calculate the values for the simulation model
            calculateValuesForSimulationModel(getSelectedInstances(), getSelectedInstanceIDs());
            // update the provided object (visualization might have changed)
            extendedPetriNet = (ExtendedPetriNet) replayResult.replayedPetriNet;
            //Make sure that what was selected before the update, is now
            //selected again.
            if (elt1 instanceof ExtendedTransition) {
                //transition was selected, select it again in sb1
                ExtendedTransition trans = (ExtendedTransition) elt1;
                sb1.setSelectedItem("Transition - " + trans.getLogEvent().getModelElementName() + " "
                        + trans.getLogEvent().getEventType());
                if (elt2 instanceof ExtendedTransition) {
                    //another transition was selected too, select it again in sb2
                    trans = (ExtendedTransition) elt2;
                    sb2.setSelectedItem("Transition - " + trans.getLogEvent().getModelElementName() + " "
                            + trans.getLogEvent().getEventType());
                }
            } else if (elt1 instanceof ExtendedPlace) {
                //place was selected, select it in sb1
                ExtendedPlace place = (ExtendedPlace) elt1;
                sb1.setSelectedItem("Place - " + place.getIdentifier());
            } else {
                //nothing selected, set sb1 and sb2 to index 0
                sb1.setSelectedIndex(0);
                sb2.setSelectedIndex(0);
            }
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(this, "An internal error occured while\nupdating the visualization.");
            Message.add("Probably not found.\n" + ex.toString(), 2);
            ex.printStackTrace();
        }
    }

    /**
     * Adjusts the PerformanceAnalysisGUI to the set values of the performance
     * options.
     */
    private void adjustGUI() {
        tablePanel.removeAll();
        //update legenda
        Color bgColor = (Color) levelColors.get(0);
        lowPanel.setBackground(bgColor);
        bgColor = (Color) levelColors.get(1);
        mediumPanel.setBackground(bgColor);
        bgColor = (Color) levelColors.get(2);
        highPanel.setBackground(bgColor);
        //initialize waiting time levels
        initializeWaitingTimeLevels();
        //update the display results to the settings
        updateResults();
    }

    /**
     * Creates dialogs in which the user can change the percentages of
     * (place or time-in-between) measurements to be counted as fast-slow-normal
     * and adjusts the metrics to these percentages.
     */
    private void changeObjectPercentages() {
        if (elt1 instanceof ExtendedPlace) {
            //a place was selected
            //Create a dialog in which the user can fill in the percentage of
            //measurements that is counted as 'fast'
            String perc = JOptionPane.showInputDialog(this,
                    "Enter the percentage of measurements\n" + " that is to be counted as 'fast': ");
            if (perc != null) {
                //Cancel button was not pressed
                try {
                    fastestPlacePercentage = Double.parseDouble(perc);
                    //check if the entered value is between 0 and 100
                    if (fastestPlacePercentage < 0 || fastestPlacePercentage > 100) {
                        fastestPlacePercentage = 0;
                    }
                } catch (Exception e) {
                    //the entered string was not an integer-value
                    Message.add("Exception: " + e.toString(), 2);
                    fastestPlacePercentage = 0;
                }
                //Create a dialog in which the user can fill in the percentage of
                //measurements that is counted as 'slow'
                String perc2 = JOptionPane.showInputDialog(this,
                        "Enter the percentage of measurements\n" + " that is to be counted as 'slow': ");
                if (perc2 != null) {
                    //Cancel was not pressed
                    try {
                        slowestPlacePercentage = Double.parseDouble(perc2);
                        //check if the entered value is between 0 and 100 and if
                        //fastest + slowpercentage is below or equal to 100
                        if (slowestPlacePercentage < 0 || slowestPlacePercentage > 100
                                || (slowestPlacePercentage + fastestPlacePercentage) > 100) {
                            slowestPlacePercentage = 0;
                        }
                    } catch (Exception e) {
                        Message.add("Exception: " + e.toString(), 2);
                        slowestPlacePercentage = 0;
                    }
                }
                //display the place metrics
                displayPlaceMetrics((ExtendedPlace) elt1);
            }
        } else {
            if (elt2 instanceof ExtendedTransition) {
                //two transitions selected
                String perc = JOptionPane.showInputDialog(this,
                        "Enter the percentage of cases that is to be" + " counted as 'fast': ");
                if (perc != null) {
                    try {
                        fastestBetweenPercentage = Double.parseDouble(perc);
                        if (fastestBetweenPercentage < 0 || fastestBetweenPercentage > 100) {
                            fastestBetweenPercentage = 0;
                        }
                    } catch (Exception e) {
                        Message.add("Exception: " + e.toString(), 2);
                        fastestBetweenPercentage = 0;
                    }
                    String perc2 = JOptionPane.showInputDialog(this,
                            "Enter the percentage of cases that is to be" + " counted as 'slow': ");
                    if (perc2 != null) {
                        try {
                            slowestBetweenPercentage = Double.parseDouble(perc2);
                            if (slowestBetweenPercentage < 0 || slowestBetweenPercentage > 100
                                    || (slowestBetweenPercentage + fastestBetweenPercentage) > 100) {
                                slowestBetweenPercentage = 0;
                            }
                        } catch (Exception e) {
                            Message.add("Exception: " + e.toString(), 2);
                            slowestBetweenPercentage = 0;
                        }
                    }
                    ExtendedTransition otherTrans = (ExtendedTransition) elt1;
                    ExtendedTransition lastTrans = (ExtendedTransition) elt2;

                    //display the transition metrics
                    displayTransitionMetrics(lastTrans, otherTrans);
                }
            } else {
                //one transition selected
                //if not a place selected, then one or two transitions selected
                String perc = JOptionPane.showInputDialog(this,
                        "Enter the percentage of measurements that is to be" + " counted as 'fast': ");
                if (perc != null) {
                    try {
                        fastestActivityPercentage = Double.parseDouble(perc);
                        if (fastestActivityPercentage < 0 || fastestActivityPercentage > 100) {
                            fastestActivityPercentage = 0;
                        }
                    } catch (Exception e) {
                        Message.add("Exception: " + e.toString(), 2);
                        fastestActivityPercentage = 0;
                    }
                    String perc2 = JOptionPane.showInputDialog(this,
                            "Enter the percentage of measurements that is" + " to be counted as 'slow': ");
                    if (perc2 != null) {
                        try {
                            slowestActivityPercentage = Double.parseDouble(perc2);
                            if (slowestActivityPercentage < 0 || slowestActivityPercentage > 100
                                    || (slowestActivityPercentage + fastestActivityPercentage) > 100) {
                                slowestActivityPercentage = 0;
                            }
                        } catch (Exception e) {
                            Message.add("Exception: " + e.toString(), 2);
                            slowestActivityPercentage = 0;
                        }
                    }
                    ExtendedTransition trans = (ExtendedTransition) elt1;
                    //display the transition metrics
                    try {
                        displayActivityMetrics(trans.getAssociatedActivity());
                    } catch (NullPointerException npe) {
                        Message.add("No activity related to the selected transition.\n" + npe.toString(), 2);
                    }
                }
            }
        }
    }

    /**
     * Creates dialogs in which the user can change the percentages of
     * process instances to be counted as fast-slow-normal baed on their
     * throughput time.
     * It also adjusts the process metrics to these percentages.
     */
    private void changeProcessPercentages() {
        //Create a dialog in which the user can fill in the percentage of
        //cases that is to be counted as 'fast'
        String perc = JOptionPane.showInputDialog(this,
                "Enter the percentage of cases that is to be" + " counted as 'fast': ");
        if (perc != null) {
            try {
                fastestProcessPercentage = Double.parseDouble(perc);
                if (fastestProcessPercentage < 0 || fastestProcessPercentage > 100) {
                    fastestProcessPercentage = 0;
                }
            } catch (Exception e) {
                Message.add("Exception: " + e.toString(), 2);
                fastestProcessPercentage = 0;
            }
            //Create a dialog in which the user can fill in the percentage of
            //cases that is to be counted as 'slow'
            String perc2 = JOptionPane.showInputDialog(this,
                    "Enter the percentage of cases that is to be counted" + " as 'slow': ");
            if (perc2 != null) {
                //Cancel button was not pushed
                try {
                    slowestProcessPercentage = Double.parseDouble(perc2);
                    if (slowestProcessPercentage < 0 || slowestProcessPercentage > 100
                            || (slowestProcessPercentage + fastestProcessPercentage) > 100) {
                        slowestProcessPercentage = 0;
                    }
                } catch (Exception e) {
                    Message.add("Exception: " + e.toString(), 2);
                    slowestProcessPercentage = 0;
                }
            }
            //display the process metrics (based on the selected instances)
            displayProcessMetrics(getSelectedInstances());
        }
    }

    /**
     * Creates a filechooser where the user can select the (comma-seperated)
     * file that he wants to export to. If this file already exists, permission
     * to overwrite is requested, else a new file is created to which the
     * time-metrics of the selected place/transitions are exported.
     *
     */
    private void exportObjectMetrics() {
        //create and initialize the file chooser
        final JFileChooser fc = new JFileChooser();
        NameFilter filt1 = new NameFilter(".csv");
        NameFilter filt2 = new NameFilter(".txt");
        fc.addChoosableFileFilter(filt1);
        fc.addChoosableFileFilter(filt2);
        fc.setAcceptAllFileFilterUsed(false);
        fc.setFileFilter(filt1);
        //check whether a place is selected
        int result = fc.showDialog(this, "Export");
        if (result == 0) {
            //Export-button was pushed
            File selectedFile = fc.getSelectedFile();
            if (!(selectedFile.getName().endsWith(fc.getFileFilter().getDescription().substring(1)))) {
                //create new file, with filetype added to it
                selectedFile = new File(
                        selectedFile.getAbsolutePath() + fc.getFileFilter().getDescription().substring(1));
            }

            if (!selectedFile.exists()) {
                if (boxMap.get(sb1.getSelectedItem()) instanceof ExtendedPlace) {
                    //actually perform export of place time-metrics
                    exportPlaceMetrics(selectedFile);
                } else {
                    if (boxMap.get(sb2.getSelectedItem()) instanceof ExtendedTransition) {
                        //actually perform export of transition time-metrics
                        exportTransitionMetrics(selectedFile);
                    } else {
                        //actually perform export of activity time-metrics
                        exportActivityMetrics(selectedFile);
                    }
                }
            } else {
                //file already exist, open a confirm dialog containing a
                //'Yes' Button as well as a 'No' button
                int overwrite = JOptionPane.showConfirmDialog(this, "File already exists! Overwrite?",
                        "Confirm Dialog", 0);
                if (overwrite == 0) {
                    //user has selected Yes
                    if (boxMap.get(sb1.getSelectedItem()) instanceof ExtendedPlace) {
                        //actually perform export of place time-metrics
                        exportPlaceMetrics(selectedFile);
                    } else {
                        if (boxMap.get(sb2.getSelectedItem()) instanceof ExtendedTransition) {
                            //actually perform export of transition time-metrics
                            exportTransitionMetrics(selectedFile);
                        } else {
                            //actually perform export of activity time-metrics
                            exportActivityMetrics(selectedFile);
                        }
                    }
                }
            }
        }
    }

    /**
     * Creates a filechooser where the user can select the (comma-seperated)
     * file that he wants to export to. If this file already exists, permission
     * to overwrite is requested, else a new file is created to which the
     * throughput times of the selected process instances are exported.
     *
     */
    private void exportProcessMetrics() {
        //create and initialize the file chooser
        final JFileChooser fc = new JFileChooser();
        NameFilter filt1 = new NameFilter(".csv");
        NameFilter filt2 = new NameFilter(".txt");
        fc.addChoosableFileFilter(filt1);
        fc.addChoosableFileFilter(filt2);
        fc.setAcceptAllFileFilterUsed(false);
        fc.setFileFilter(filt1);
        //check whether a place is selected
        int result = fc.showDialog(this, "Export");
        if (result == 0) {
            //Export-button was pushed
            File selectedFile = fc.getSelectedFile();
            if (!(selectedFile.getName().endsWith(fc.getFileFilter().getDescription().substring(1)))) {
                //create new file, with filetype added to it
                selectedFile = new File(
                        selectedFile.getAbsolutePath() + fc.getFileFilter().getDescription().substring(1));
            }
            if (!selectedFile.exists()) {
                try {
                    //actually export the throughput times to the file
                    replayResult.exportToFile(getSelectedInstances(), selectedFile, timeDivider, timeSort,
                            advancedSettings[0]);
                } catch (IOException ex) {
                    Message.add("IO exception: " + ex.toString(), 2);
                }
            } else {
                //file already exist, open a confirm dialog containing a
                //'Yes' Button as well as a 'No' button
                int overwrite = JOptionPane.showConfirmDialog(this, "File already exists! Overwrite?",
                        "Confirm Dialog", 0);
                if (overwrite == 0) {
                    //user has selected Yes
                    try {
                        //actually export the throughput times to the file
                        replayResult.exportToFile(getSelectedInstances(), selectedFile, timeDivider, timeSort,
                                advancedSettings[0]);
                    } catch (IOException ex) {
                        Message.add("IO exception: " + ex.toString(), 2);
                    }
                }
            }
        }
    }

    /**
     * Exports the values of all measurements of the time-metrics (waiting,
     * synchronization, sojourn time) of the selected place to the file that is
     * given as parameter.
     * @param file File
     */
    private void exportPlaceMetrics(File file) {
        ExtendedPlace place = (ExtendedPlace) boxMap.get(sb1.getSelectedItem());
        try {
            place.exportToFile(getSelectedInstanceIDs(), file, timeDivider, timeSort, advancedSettings[1]);
        } catch (IOException ex) {
            Message.add("IO exception: " + ex.toString(), 2);
        }
    }

    /**
     * Exports the values of all measurements of the time-metrics (waiting,
     * synchronization, sojourn time) of the selected place to the file that is
     * given as parameter.
     * @param file File
     */
    private void exportTransitionMetrics(File file) {
        ExtendedTransition first = (ExtendedTransition) boxMap.get(sb1.getSelectedItem());
        ExtendedTransition second = (ExtendedTransition) boxMap.get(sb2.getSelectedItem());
        try {
            TransitionAnalysis ta = new TransitionAnalysis(first, second);
            ta.exportToFile(getSelectedInstanceIDs(), file, timeDivider, timeSort, advancedSettings[2]);
        } catch (IOException ex) {
            Message.add("IO exception: " + ex.toString(), 2);
        }
    }

    /**
     * Exports the values of all measurements of the time-metrics (waiting,
     * execution, sojourn time) of the selected activity to the file that is
     * given as parameter.
     * @param file File
     */
    private void exportActivityMetrics(File file) {
        ExtendedTransition trans = (ExtendedTransition) boxMap.get(sb1.getSelectedItem());
        try {
            trans.getAssociatedActivity().exportToFile(getSelectedInstanceIDs(), file, timeDivider, timeSort,
                    advancedSettings[3]);
        } catch (IOException ex) {
            Message.add("IO exception: " + ex.toString(), 2);
        }
    }

    /**
     * Adjusts the GUI to the object (a place, a transition or null) that is
     * selected in comboBox sb1
     * @todo Peter: check whether I can change this, so objects are highlighted
     * before calculation of metrics starts..
     */
    private void adjustToSelectedSb1() {
        //remove highlighting of all selected objects on the panel
        grappaPanel.unSelectAll();
        //hide all place/transition metrics
        hideAllMetrics();
        if (boxMap.get((String) sb1.getSelectedItem()) instanceof ExtendedTransition) {
            //a transition was selected in sb1, make sure sb2 is enabled
            sb2.setEnabled(true);
            andLabel.setForeground(Color.black);
            //store the selected transition in elt1
            elt1 = (Element) boxMap.get(sb1.getSelectedItem());
            Collection a = new Bag();
            a.add(elt1);
            if (boxMap.get((String) sb2.getSelectedItem()) instanceof ExtendedTransition) {
                //a transition was selected in sb2 as well, store it in elt2
                elt2 = (Element) boxMap.get(sb2.getSelectedItem());
                if (elt1 != elt2) {
                    a.add(elt2);
                    //display the transition metrics of the selected transitions
                    displayTransitionMetrics((ExtendedTransition) elt1, ((ExtendedTransition) elt2));
                }
            } else {
                if (sb2.getSelectedIndex() == 0 && elt2 == null) {
                    //display activity metrics if just one transition is selected
                    displayActivityMetrics(((ExtendedTransition) elt1).getAssociatedActivity());
                }
            }
            //highlight the transition(s) on the grappaPanel
            grappaPanel.selectElements(a);
        } else {
            //anything but a transition selected in sb1, disable sb2
            sb2.setEnabled(false);
            sb2.setSelectedIndex(0);
            andLabel.setForeground(Color.gray);
            if (boxMap.get((String) sb1.getSelectedItem()) instanceof ExtendedPlace) {
                //a place was selected in sb1, store it in elt1
                elt1 = (Element) boxMap.get(sb1.getSelectedItem());
                Collection a = new Bag();
                a.add(elt1);
                //highlight the selected place
                grappaPanel.selectElements(a);
                ExtendedPlace place = (ExtendedPlace) boxMap.get((String) sb1.getSelectedItem());
                if (place.getIdentifier() != "pstart" && place.getIdentifier() != "pend"
                        && place.getIdentifier() != "psource" && place.getIdentifier() != "psink") {
                    //display the metrics corresponding to the place in elt1
                    displayPlaceMetrics((ExtendedPlace) elt1);
                }
            }
        }
        //repaint the graph
        grappaPanel.getSubgraph().getGraph().repaint();
    }

    /**
     * Adjusts the GUI to the object (a place, a transition or null) that is
     * selected in comboBox sb2
     */
    private void adjustToSelectedSb2() {
        //remove all current highlighting
        grappaPanel.unSelectAll();
        //hide all place/transition metrics
        hideAllMetrics();
        //store the transition that is selected in sb1 in elt1
        elt1 = (Element) boxMap.get(sb1.getSelectedItem());
        Collection a = new Bag();
        a.add(elt1);
        if (boxMap.get((String) sb2.getSelectedItem()) instanceof ExtendedTransition) {
            //a transition is selected in sb2, store it in elt2
            elt2 = (Element) boxMap.get(sb2.getSelectedItem());
            a.add(elt2);
            if (elt2 != elt1) {
                //display the transition metrics of elt1 and elt2
                displayTransitionMetrics((ExtendedTransition) elt1, ((ExtendedTransition) elt2));
            }
        } else {
            elt2 = null;
            if (boxMap.get((String) sb1.getSelectedItem()) instanceof ExtendedTransition) {
                //display activity metrics if just one transition is selected
                displayActivityMetrics(((ExtendedTransition) elt1).getAssociatedActivity());
            }
        }

        //highlight elt1 and (if not null) elt2 in the graph
        grappaPanel.selectElements(a);
        grappaPanel.getSubgraph().getGraph().repaint();
    }

    /**
     * Creates a new frame, containing the current PerformanceConfiguration.
     * In the new frame, the user can change the current settings, after which
     * the PerformanceAnalysisGUI is updated.
     */
    private void createPerformanceOptionsScreen() {
        //initialize the settings screen
        JPanel firstPanel = new JPanel(new BorderLayout());
        JPanel secondPanel = new JPanel();
        JButton okButton = new JButton("Apply Changes");
        JButton cancelButton = new JButton("Cancel");
        String sel = (String) sb1.getSelectedItem();
        Dimension dim = new Dimension(380, 460);
        //create a new PerformanceConfiguration-object, containing the current
        //settings
        final PerformanceConfiguration performanceOptions = new PerformanceConfiguration(levelColors, bounds,
                timeSort, decimalPlaces, advancedSettings.clone());
        if (boxMap.get(sel) instanceof ExtendedPlace) {
            //adjust the performanceOptions to the settings of the selected place
            performanceOptions.changeDisplay((ExtendedPlace) boxMap.get(sel));
            dim = new Dimension(380, 525);
        } else {
            performanceOptions.changeDisplay(null);
        }
        JScrollPane scroll = new JScrollPane(performanceOptions);
        firstPanel.add(scroll, BorderLayout.CENTER);
        firstPanel.add(secondPanel, BorderLayout.SOUTH);
        secondPanel.setLayout(new BoxLayout(secondPanel, BoxLayout.X_AXIS));
        secondPanel.add(Box.createRigidArea(new Dimension(40, 0)));
        secondPanel.add(okButton);
        secondPanel.add(Box.createRigidArea(new Dimension(20, 0)));
        secondPanel.add(cancelButton);
        okButton.addActionListener(new ActionListener() {
            // the action to be taken when the OK-button is pressed
            public void actionPerformed(ActionEvent e) {
                if (performanceOptions.getAllSelected()) {
                    //settings should be applied to all places
                    timeDivider = performanceOptions.getTimeDivider();
                    timeSort = performanceOptions.getTimesort();
                    decimalPlaces = performanceOptions.getDecimalPlaces();
                    bounds = performanceOptions.getBoundaries();
                    levelColors = performanceOptions.getColors();
                    advancedSettings = performanceOptions.getAdvancedSettings().clone();
                    manualSettings = true;
                    ListIterator lit = extendedPetriNet.getPlaces().listIterator();
                    while (lit.hasNext()) {
                        ExtendedPlace p = (ExtendedPlace) lit.next();
                        p.setHasOwnSettings(false);
                    }
                } else {
                    //apply settings to the selected place only
                    performanceOptions.getSelectedPlace().setOwnSettings(performanceOptions.getBoundaries(),
                            performanceOptions.getColors());
                    timeDivider = performanceOptions.getTimeDivider();
                    timeSort = performanceOptions.getTimesort();
                    decimalPlaces = performanceOptions.getDecimalPlaces();
                    advancedSettings = performanceOptions.getAdvancedSettings().clone();
                    manualSettings = true;
                }
                //close all frames containing AdvancedSettings opened from performanceOptions
                performanceOptions.closeAdvancedFrames();
                //close the performanceOptions-frame
                MainUI.getInstance().getDesktop().getSelectedFrame().doDefaultCloseAction();
                //adjust the PerformanceAnalysisGUI
                adjustGUI();
            }
        });
        cancelButton.addActionListener(new ActionListener() {
            // the action to be taken when the cancel button has been pressed
            public void actionPerformed(ActionEvent e) {
                //close all frames containing AdvancedSettings opened from performanceOptions
                performanceOptions.closeAdvancedFrames();
                //close the PerformanceOptions-frame
                MainUI.getInstance().getDesktop().getSelectedFrame().doDefaultCloseAction();
            }
        });
        //show the panel in a new frame
        MainUI.getInstance().createFrame("Performance analysis options:", firstPanel);
        JInternalFrame frame = MainUI.getInstance().getDesktop().getSelectedFrame();
        frame.setSize(dim);
    }

    //////////////////PROCESS INSTANCE SELECTION RELATED METHODS///////////////////

    /**
     * Retrieves the current selection status based on table indices.
     * @return int[] an array of indices indicating those instances that are currently selected
     */
    private int[] getSelectionStatus() {
        return processInstanceIDsTable.getSelectedRows();
    }

    /**
     * Retrieves the current selection status in the form of the Process instance IDs.
     * @return ArrayList a list of Strings containing the IDs of the selected process instances
     */
    private ArrayList getSelectedInstanceIDs() {
        ArrayList selectedInstanceIDs = new ArrayList();
        int[] indexArray = getSelectionStatus();
        for (int i = 0; i < indexArray.length; i++) {
            selectedInstanceIDs.add(extendedLog.getLogTraceIDs().get(indexArray[i]));
        }
        return selectedInstanceIDs;
    }

    /**
     * Retrieves the current selection status in the form of the process instances itselves.
     * @return ArrayList a list of DiagnosticLogTraces
     */
    private ArrayList getSelectedInstances() {
        ArrayList selectedInstances = new ArrayList();
        int[] indexArray = getSelectionStatus();
        for (int i = 0; i < indexArray.length; i++) {
            selectedInstances.add(extendedLog.getDiagnosticLogTraces().get(indexArray[i]));
        }
        return selectedInstances;
    }

    /**
     * Inverts the current selection status of the table containing the process instances.
     * This means that instances that have been selected will result being unselected
     * and the other way round.
     */
    private void invertSelectionStatus() {
        ListSelectionModel selectionModel = processInstanceIDsTable.getSelectionModel();
        // step through the table
        for (int index = 0; index < extendedLog.getSizeOfLog(); index++) {
            if (selectionModel.isSelectedIndex(index) == true) {
                // if entry is currently selected --> deselect
                selectionModel.removeSelectionInterval(index, index);
            } else {
                // if entry is currently not selected --> select
                selectionModel.addSelectionInterval(index, index);
            }
        }
        hideAllMetrics();
        updateResults();
    }

    /////////////////DISPLAY METRICS METHODS/////////////////////

    /**
     * Retrieves the values of process-related metrics, based on the process
     * instances in piList and displays these on the UI.
     * @param piList ArrayList: Process instances
     */
    private void displayProcessMetrics(ArrayList piList) {
        try {
            //calculate the process metrics, based on the selected instances,
            //using the advancedSettings on how to deal with fitting.
            replayResult.calculateMetrics(piList, advancedSettings[0]);
            //initialize the processTable
            OneMetricTableModel ptm = new OneMetricTableModel();
            ptm.setHeadings("", "Throughput time (" + timeSort + ")");
            DefaultTableCellRenderer dtcr = new DefaultTableCellRenderer();
            dtcr.setBackground(new Color(235, 235, 235));
            processTable.setModel(ptm);
            processTable.setPreferredSize(new Dimension(240, 112));
            processTable.getColumnModel().getColumn(0).setPreferredWidth(50);
            processTable.getColumnModel().getColumn(0).setMaxWidth(150);
            processTable.getTableHeader().setFont(new Font("SansSerif", Font.BOLD, 12));
            processTable.getColumnModel().getColumn(0).setCellRenderer(dtcr);
            processTable.setBorder(BorderFactory.createEtchedBorder());
            //initialize labels
            arrivalProcessLabel.setText("Arrival rate:");
            rateProcessLabel.setText(formatString(replayResult.getArrivalRate() * timeDivider, decimalPlaces)
                    + " cases per " + timeSort.substring(0, timeSort.length() - 1));
            casesProcessLabel.setText(piList.size() + " cases");
            completedLabel.setText(replayResult.getProperFrequency() + " cases");

            //fill the first column of the process table
            processTable.setValueAt("avg", 0, 0);
            processTable.setValueAt("min", 1, 0);
            processTable.setValueAt("max", 2, 0);
            processTable.setValueAt("stdev", 3, 0);
            DecimalFormat df = new DecimalFormat("0.00");
            int[] sizes = replayResult.getSizes(fastestProcessPercentage, slowestProcessPercentage);
            processTable.setValueAt("fast " + df.format(fastestProcessPercentage) + "% (" + sizes[0] + ")", 4, 0);
            processTable.setValueAt("slow " + df.format(slowestProcessPercentage) + "%(" + sizes[1] + ")", 5, 0);
            processTable.setValueAt("normal " + df.format(100 - fastestProcessPercentage - slowestProcessPercentage)
                    + "%(" + sizes[2] + ")", 6, 0);

            //place calculated values in the table
            processTable.setValueAt(formatString(replayResult.getMeanThroughputTime() / timeDivider, decimalPlaces),
                    0, 1);
            processTable.setValueAt(
                    formatString((replayResult.getMinThroughputTime() * 1.0) / timeDivider, decimalPlaces), 1, 1);
            processTable.setValueAt(
                    formatString((replayResult.getMaxThroughputTime() * 1.0) / timeDivider, decimalPlaces), 2, 1);
            processTable.setValueAt(
                    formatString(replayResult.getStdevThroughputTime() / timeDivider, decimalPlaces), 3, 1);
            double[] avgTimes = replayResult.getAverageTimes(fastestProcessPercentage, slowestProcessPercentage);
            processTable.setValueAt(formatString(avgTimes[0] / timeDivider, decimalPlaces), 4, 1);
            processTable.setValueAt(formatString(avgTimes[1] / timeDivider, decimalPlaces), 5, 1);
            processTable.setValueAt(formatString(avgTimes[2] / timeDivider, decimalPlaces), 6, 1);
            processTable.revalidate();
        } catch (Exception ex) {
            Message.add("Program exception while calculating process metrics.\n" + ex.toString(), 2);
            ex.printStackTrace();
        }
    }

    /**
     * Calculates the metrics belonging to a place and displays the
     * results.
     * @param place ExtendedPlace: the place of which the values of metrics are
     * calculated.
     */
    private void displayPlaceMetrics(ExtendedPlace place) {
        ArrayList instanceIDList = getSelectedInstanceIDs();
        tablePanel.removeAll();
        tablePanel.add(placeTable.getTableHeader(), BorderLayout.PAGE_START);
        tablePanel.add(placeTable, BorderLayout.CENTER);
        try {
            //calculate place metrics
            place.calculateMetrics(instanceIDList, advancedSettings[1], failedInstances);
            //initialize the placeTable
            ThreeMetricsTableModel ptm = new ThreeMetricsTableModel();
            ptm.setHeadings("", "Waiting time (" + timeSort + ")", "Synchronization time (" + timeSort + ")",
                    "Sojourn time (" + timeSort + ")");
            placeTable.setModel(ptm);
            placeTable.setBorder(BorderFactory.createEtchedBorder());
            placeTable.setPreferredSize(new Dimension(480, 112));
            placeTable.setMinimumSize(new Dimension(480, 112));
            placeTable.getColumnModel().getColumn(0).setPreferredWidth(60);
            placeTable.getColumnModel().getColumn(0).setMaxWidth(150);
            placeTable.getColumnModel().getColumn(2).setPreferredWidth(100);
            placeTable.setRowSelectionAllowed(false);
            placeTable.getTableHeader().setFont(new Font("SansSerif", Font.BOLD, 12));
            DefaultTableCellRenderer dtcr = new DefaultTableCellRenderer();
            dtcr.setBackground(new Color(235, 235, 235));
            dtcr.setFont(new Font("SansSerif", Font.BOLD, 12));
            placeTable.getColumnModel().getColumn(0).setCellRenderer(dtcr);

            //place time metrics in the table
            placeTable.setValueAt(formatString(place.getMeanWaitingTime() / timeDivider, decimalPlaces), 0, 1);
            placeTable.setValueAt(formatString(place.getMeanSynchronizationTime() / timeDivider, decimalPlaces), 0,
                    2);
            placeTable.setValueAt(formatString(place.getMeanSojournTime() / timeDivider, decimalPlaces), 0, 3);
            placeTable.setValueAt(formatString((place.getMinWaitingTime() * 1.0) / timeDivider, decimalPlaces), 1,
                    1);
            placeTable.setValueAt(
                    formatString((place.getMinSynchronizationTime() * 1.0) / timeDivider, decimalPlaces), 1, 2);
            placeTable.setValueAt(formatString((place.getMinSojournTime() * 1.0) / timeDivider, decimalPlaces), 1,
                    3);
            placeTable.setValueAt(formatString((place.getMaxWaitingTime() * 1.0) / timeDivider, decimalPlaces), 2,
                    1);
            placeTable.setValueAt(
                    formatString((place.getMaxSynchronizationTime() * 1.0) / timeDivider, decimalPlaces), 2, 2);
            placeTable.setValueAt(formatString((place.getMaxSojournTime() * 1.0) / timeDivider, decimalPlaces), 2,
                    3);
            placeTable.setValueAt(formatString(place.getStdevWaitingTime() / timeDivider, decimalPlaces), 3, 1);
            placeTable.setValueAt(formatString(place.getStdevSynchronizationTime() / timeDivider, decimalPlaces), 3,
                    2);
            placeTable.setValueAt(formatString(place.getStdevSojournTime() / timeDivider, decimalPlaces), 3, 3);
            double[] avgTimes = place.getAvgTimes(fastestPlacePercentage, slowestPlacePercentage);
            placeTable.setValueAt(formatString(avgTimes[0] / timeDivider, decimalPlaces), 4, 1);
            placeTable.setValueAt(formatString(avgTimes[1] / timeDivider, decimalPlaces), 4, 2);
            placeTable.setValueAt(formatString(avgTimes[2] / timeDivider, decimalPlaces), 4, 3);
            placeTable.setValueAt(formatString(avgTimes[3] / timeDivider, decimalPlaces), 5, 1);
            placeTable.setValueAt(formatString(avgTimes[4] / timeDivider, decimalPlaces), 5, 2);
            placeTable.setValueAt(formatString(avgTimes[5] / timeDivider, decimalPlaces), 5, 3);
            placeTable.setValueAt(formatString(avgTimes[6] / timeDivider, decimalPlaces), 6, 1);
            placeTable.setValueAt(formatString(avgTimes[7] / timeDivider, decimalPlaces), 6, 2);
            placeTable.setValueAt(formatString(avgTimes[8] / timeDivider, decimalPlaces), 6, 3);
            DecimalFormat df = new DecimalFormat("0.00");
            int[] sizes = place.getSizes(fastestPlacePercentage, slowestPlacePercentage);
            placeTable.setValueAt("fast " + df.format(fastestPlacePercentage) + "%(" + sizes[0] + ")", 4, 0);
            placeTable.setValueAt("slow " + df.format(slowestPlacePercentage) + "%(" + sizes[1] + ")", 5, 0);
            placeTable.setValueAt("normal " + df.format(100 - fastestPlacePercentage - slowestPlacePercentage)
                    + "%(" + sizes[2] + ")", 6, 0);
            titleLabel.setText("Performance information of the selected place:");
            //display frequency of visits
            freqObjectLabel.setText(place.getFrequency() + " visits");
            //display the arrival rate
            ratePlaceLabel.setText(formatString(place.getArrivalRate() * timeDivider, decimalPlaces)
                    + " visits per " + timeSort.substring(0, timeSort.length() - 1));
            placeTable.getTableHeader().setVisible(true);
            titleLabel.setVisible(true);
            numberObjectLabel.setVisible(true);
            freqObjectLabel.setVisible(true);
            arrivalPlaceLabel.setVisible(true);
            ratePlaceLabel.setVisible(true);
            changePercentagesButton.setVisible(true);
            changePercentagesButton.setPreferredSize(new Dimension(170, 25));
            exportButton.setVisible(true);
            placeTable.repaint();
            metricsBottomPanel.revalidate();
        } catch (Exception ex) {
            Message.add("An internal error occured while calculating " + "place metrics.\n" + ex.toString(), 2);
            ex.printStackTrace();
        }
    }

    /**
     * Calculates and displays values of 'time in between' metrics of two
     * transitions.
     * @param lastTransition ExtendedTransition: one of the two transitions
     * @param otherTransition ExtendedTransition: one of the two transitions
     */
    private void displayTransitionMetrics(ExtendedTransition lastTransition, ExtendedTransition otherTransition) {
        //clear the table Panel
        tablePanel.removeAll();
        //create a transition analysis object
        TransitionAnalysis ta = new TransitionAnalysis(lastTransition, otherTransition);
        try {
            //calculate the time-metrics
            ta.calculateMetrics(getSelectedInstanceIDs(), advancedSettings[2], failedInstances);
            //initialize the transition time-metrics table
            OneMetricTableModel ttm = new OneMetricTableModel();
            ttm.setHeadings("", "Time in between (" + timeSort + ")");
            transitionTable = new JTable(ttm);
            transitionTable.setBorder(BorderFactory.createEtchedBorder());
            transitionTable.setPreferredSize(new Dimension(300, 112));
            transitionTable.setMinimumSize(new Dimension(280, 112));
            transitionTable.getColumnModel().getColumn(0).setPreferredWidth(80);
            transitionTable.getColumnModel().getColumn(0).setMaxWidth(150);
            transitionTable.setRowSelectionAllowed(false);
            transitionTable.getTableHeader().setFont(new Font("SansSerif", Font.BOLD, 12));
            DefaultTableCellRenderer dtcr = new DefaultTableCellRenderer();
            dtcr.setBackground(new Color(235, 235, 235));
            dtcr.setFont(new Font("SansSerif", Font.BOLD, 12));
            transitionTable.getColumnModel().getColumn(0).setCellRenderer(dtcr);
            //add the table to the tablePanel
            tablePanel.add(transitionTable.getTableHeader(), BorderLayout.PAGE_START);
            tablePanel.add(transitionTable, BorderLayout.CENTER);

            //frequency of process instances in which both transitions occur
            freqObjectLabel.setText(ta.getFrequency() + "" + " cases");
            //fill table with 'in-between-times'
            transitionTable.setValueAt(formatString(ta.getMeanTime() / timeDivider, decimalPlaces), 0, 1);
            transitionTable.setValueAt(formatString((ta.getMinTime() * 1.0) / timeDivider, decimalPlaces), 1, 1);
            transitionTable.setValueAt(formatString((ta.getMaxTime() * 1.0) / timeDivider, decimalPlaces), 2, 1);
            transitionTable.setValueAt(formatString(ta.getStdevTimeInBetween() / timeDivider, decimalPlaces), 3, 1);
            //place average of fastest traces in the table
            DecimalFormat df = new DecimalFormat("0.00");
            int[] sizes = ta.getSizes(fastestBetweenPercentage, slowestBetweenPercentage);
            transitionTable.setValueAt("fast " + df.format(fastestBetweenPercentage) + "%(" + sizes[0] + ")", 4, 0);
            transitionTable.setValueAt("slow " + df.format(slowestBetweenPercentage) + "%(" + sizes[1] + ")", 5, 0);
            transitionTable.setValueAt("normal "
                    + df.format(100 - fastestBetweenPercentage - slowestBetweenPercentage) + "%(" + sizes[2] + ")",
                    6, 0);
            double[] avgTimes = ta.getAverageTimes(fastestBetweenPercentage, slowestBetweenPercentage);
            transitionTable.setValueAt(formatString(avgTimes[0] / timeDivider, decimalPlaces), 4, 1);
            //place average of slowest traces in the table
            transitionTable.setValueAt(formatString(avgTimes[1] / timeDivider, decimalPlaces), 5, 1);
            //place average of other traces in the table
            transitionTable.setValueAt(formatString(avgTimes[2] / timeDivider, decimalPlaces), 6, 1);

            //make metrics visible
            changePercentagesButton.setVisible(true);
            exportButton.setVisible(true);
            titleLabel.setVisible(true);
            titleLabel.setText("Performance information of the selected transitions:");
            numberObjectLabel.setVisible(true);
            freqObjectLabel.setVisible(true);
            transitionTable.repaint();
            metricsBottomPanel.revalidate();
        } catch (Exception ex) {
            Message.add(
                    "An internal error occured while calculating" + " time in between metrics.\n" + ex.toString(),
                    2);
            ex.printStackTrace();
        }
    }

    /**
     * Calculates metrics related to activities, such as waiting time, execution
     * time and throughput time. and displays these on the metricsBottomPanel,
     *
     * @param activity ExtendedActivity: the activity involved
     */
    private void displayActivityMetrics(ExtendedActivity activity) {
        tablePanel.removeAll();
        try {
            //calculate time-metrics of the activity based on the selected instances
            activity.calculateMetrics(getSelectedInstanceIDs(), advancedSettings[3], failedInstances);
            activity.checkWhichMetricsToUse();
            //initialize the activityTable
            ThreeMetricsRowExtraTableModel atm = new ThreeMetricsRowExtraTableModel();
            atm.setHeadings("", "Waiting time (" + timeSort + ")", "Execution" + " time (" + timeSort + ")",
                    "Sojourn time (" + timeSort + ")");
            activityTable.setModel(atm);
            activityTable.setBorder(BorderFactory.createEtchedBorder());
            activityTable.setPreferredSize(new Dimension(480, 128));
            activityTable.setMinimumSize(new Dimension(480, 128));
            activityTable.getColumnModel().getColumn(0).setPreferredWidth(60);
            activityTable.getColumnModel().getColumn(0).setMaxWidth(150);
            DefaultTableCellRenderer dtcr = new DefaultTableCellRenderer();
            dtcr.setBackground(new Color(235, 235, 235));
            activityTable.getColumnModel().getColumn(0).setCellRenderer(dtcr);
            activityTable.setRowSelectionAllowed(false);
            activityTable.getTableHeader().setFont(new Font("SansSerif", Font.BOLD, 12));

            /*
            Iterator entries = mapping.entrySet().iterator();
            while (entries.hasNext()) {
               Map.Entry entr = (Map.Entry) entries.next();
               if (entr.getKey() instanceof ExtendedTransition) {
                  ExtendedTransition transition = (ExtendedTransition) entr.getKey();
                  ExtendedActivity act = transition.getAssociatedActivity();
                  if (act != null) {
              act.calculateMetrics(getSelectedInstanceIDs(), advancedSettings[3], failedInstances);
              act.checkWhichMetricsToUse();
              act.getArrivalRate();
                  }
               }
            }*/
            //fill table with the calculated activity time-metrics
            TableCellRenderer renderer = new CustomTableCellRenderer(activity.getBoundWaitingUsed(),
                    activity.getBoundExecutionUsed(), activity.getBoundSojournUsed());
            activityTable.setDefaultRenderer(Class.forName("java.lang.String"), renderer);
            activityTable.setValueAt(formatString(activity.getMeanWaitTime() / timeDivider, decimalPlaces), 0, 1);
            activityTable.setValueAt(formatString(activity.getMeanExecutionTime() / timeDivider, decimalPlaces), 0,
                    2);
            activityTable.setValueAt(formatString(activity.getMeanSojournTime() / timeDivider, decimalPlaces), 0,
                    3);
            activityTable.setValueAt(formatString(activity.getMinWaitTime() / timeDivider, decimalPlaces), 1, 1);
            activityTable.setValueAt(formatString(activity.getMinExecutionTime() / timeDivider, decimalPlaces), 1,
                    2);
            activityTable.setValueAt(formatString(activity.getMinSojournTime() / timeDivider, decimalPlaces), 1, 3);
            activityTable.setValueAt(formatString(activity.getMaxWaitTime() / timeDivider, decimalPlaces), 2, 1);
            activityTable.setValueAt(formatString(activity.getMaxExecutionTime() / timeDivider, decimalPlaces), 2,
                    2);
            activityTable.setValueAt(formatString(activity.getMaxSojournTime() / timeDivider, decimalPlaces), 2, 3);
            activityTable.setValueAt(formatString(activity.getStdevWaitTime() / timeDivider, decimalPlaces), 3, 1);
            activityTable.setValueAt(formatString(activity.getStdevExecutionTime() / timeDivider, decimalPlaces), 3,
                    2);
            activityTable.setValueAt(formatString(activity.getStdevSojournTime() / timeDivider, decimalPlaces), 3,
                    3);
            double[] avgWaitTimes = activity.getAvgWaitTimes(fastestActivityPercentage, slowestActivityPercentage);
            double[] avgExecutionTimes = activity.getAvgExecutionTimes(fastestActivityPercentage,
                    slowestActivityPercentage);
            double[] avgThroughputTimes = activity.getAvgSojournTimes(fastestActivityPercentage,
                    slowestActivityPercentage);
            activityTable.setValueAt(formatString(avgWaitTimes[0] / timeDivider, decimalPlaces), 4, 1);
            activityTable.setValueAt(formatString(avgExecutionTimes[0] / timeDivider, decimalPlaces), 4, 2);
            activityTable.setValueAt(formatString(avgThroughputTimes[0] / timeDivider, decimalPlaces), 4, 3);
            activityTable.setValueAt(formatString(avgWaitTimes[1] / timeDivider, decimalPlaces), 5, 1);
            activityTable.setValueAt(formatString(avgExecutionTimes[1] / timeDivider, decimalPlaces), 5, 2);
            activityTable.setValueAt(formatString(avgThroughputTimes[1] / timeDivider, decimalPlaces), 5, 3);
            activityTable.setValueAt(formatString(avgWaitTimes[2] / timeDivider, decimalPlaces), 6, 1);
            activityTable.setValueAt(formatString(avgExecutionTimes[2] / timeDivider, decimalPlaces), 6, 2);
            activityTable.setValueAt(formatString(avgThroughputTimes[2] / timeDivider, decimalPlaces), 6, 3);
            activityTable.setValueAt(activity.getFrequencyWait() + "", 7, 1);
            activityTable.setValueAt(activity.getFrequencyExecution() + "", 7, 2);
            activityTable.setValueAt(activity.getFrequencySojourn() + "", 7, 3);

            DecimalFormat df = new DecimalFormat("0.00");
            activityTable.setValueAt("fast " + df.format(fastestActivityPercentage) + "%", 4, 0);
            activityTable.setValueAt("slow " + df.format(slowestActivityPercentage) + "%", 5, 0);
            activityTable.setValueAt(
                    "normal " + df.format(100 - fastestActivityPercentage - slowestActivityPercentage) + "%", 6, 0);
            ratePlaceLabel.setText(formatString(activity.getArrivalRate() * timeDivider, decimalPlaces)
                    + " visits per " + timeSort.substring(0, timeSort.length() - 1));
            //add the table to the tablePanel
            tablePanel.add(activityTable.getTableHeader(), BorderLayout.PAGE_START);
            tablePanel.add(activityTable, BorderLayout.CENTER);
            //display the title
            titleLabel.setText("Performance information of activity " + activity.getName() + ":");
            titleLabel.setVisible(true);
            arrivalPlaceLabel.setVisible(true);
            ratePlaceLabel.setVisible(true);
            changePercentagesButton.setVisible(true);
            exportButton.setVisible(true);
            activityTable.repaint();
            metricsBottomPanel.revalidate();
        } catch (Exception ex) {
            Message.add("An internal error occured while calculating " + " activity metrics.\n" + ex.toString(), 2);
            ex.printStackTrace();
        }
    }

    /**
     * Hides all place/transition/activity-metrics
     */
    private void hideAllMetrics() {
        tablePanel.removeAll();
        exportButton.setVisible(false);
        changePercentagesButton.setVisible(false);
        titleLabel.setVisible(false);
        numberObjectLabel.setVisible(false);
        freqObjectLabel.setVisible(false);
        arrivalPlaceLabel.setVisible(false);
        ratePlaceLabel.setVisible(false);
        metricsBottomPanel.revalidate();
    }

    /**
     * Formats double values to display at most the given number of decimal places.
     * @param val double: value that is formatted to String
     * @param places int: number of decimal places displayed
     * @return String
     */
    public String formatString(double val, int places) {
        String cur;
        DecimalFormat df;
        double bound = Math.pow(10.0, (0 - places));
        String tempString = "0";
        for (int i = 0; i < places - 1; i++) {
            tempString += "#";
        }
        if ((val != 0.0) && (val < bound)) {
            //display scientific notation
            if (places == 0) {
                df = new DecimalFormat("0E0");
            } else {
                df = new DecimalFormat("0." + tempString + "E0");
            }
            cur = df.format(val);
        } else {
            if (places == 0) {
                df = new DecimalFormat("0");
            } else {
                df = new DecimalFormat("0." + tempString);
            }
            cur = df.format(val);
        }
        return cur;
    }
    ////////// INTERFACE IMPLEMENTATION RELATED METHODS //////////

    /**
     * Adds the particular time-metrics for the highlevelactivity of a transition to
     * the simulation model.
     * @param kindTimingInfo String <code>"waiting"</code> when the provided time-metrics deal about waiting time.
     * <code>execution</code> when the provided time-metrics deal about execution time and <code>"sojourn"</code> when
     * the provided time-metrics deal about sojourn time
     * @param activity HLActivity the highlevelactivity of a transition
     * @param mean double the mean
     * @param min double the minimum
     * @param max double the maximum
     * @param stDev double the standard deviation
     * @param constVal double the constant value
     * @param arrivalRate double the arrival rate
     */
    private void addTimingInfoForTransition(String kindTimingInfo, HLActivity activity, double mean, double min,
            double max, double stDev, double constVal, double arrivalRate) {
        // in case that NaN is provided for either mean, min, max or stDev the default value needs to be used
        double constFinal = 0.0;
        double meanFinal = 0.0;
        double varianceFinal = 0.0;
        double minFinal = 0.0;
        double maxFinal = 0.0;
        double intensityFinal = 0.0;
        if (!(Double.isNaN(mean) || Double.isInfinite(mean))) {
            meanFinal = mean;
        }
        if (!(Double.isNaN(min) || Double.isInfinite(min))) {
            minFinal = min;
        }
        if (!(Double.isNaN(max) || Double.isInfinite(max))) {
            maxFinal = max;
        }
        if (!(Double.isNaN(stDev) || Double.isInfinite(stDev))) {
            varianceFinal = stDev * stDev;
        }
        if (!(Double.isNaN(constVal) || Double.isInfinite(constVal))) {
            constFinal = constVal;
        }
        if (!(Double.isNaN(arrivalRate) || Double.isInfinite(arrivalRate))) {
            intensityFinal = arrivalRate;
        }

        HLGeneralDistribution dist = new HLGeneralDistribution(constFinal, meanFinal, varianceFinal, minFinal,
                maxFinal, intensityFinal, HLDistribution.DistributionEnum.NORMAL_DISTRIBUTION);

        if (kindTimingInfo.equals("waiting")) {
            activity.setWaitingTime(dist);
        } else if (kindTimingInfo.equals("execution")) {
            activity.setExecutionTime(dist);
        } else if (kindTimingInfo.equals("sojourn")) {
            activity.setSojournTime(dist);
        }
    }

    /**
     * Adds the time-metrics for the process and the transitions to the simulation model. Furthermore,
     * the possibility dependencies and frequency dependencies for a place with multiple outgoing arcs are added
     * to the simulation model
     * @param logTraces ArrayList list of process instances
     * @param logTraceIDs ArrayList list of IDs of the process instances
     */
    private void calculateValuesForSimulationModel(ArrayList logTraces, ArrayList logTraceIDs) {
        // calculateValuesForSimulationModel(extendedLog.getDiagnosticLogTraces(), extendedLog.getLogTraceIDs());
        // update the simulation model
        fillProcessHighLevelPN(logTraces);
        //fill the transitions in the simulation model with the calculated activity time-metrics
        fillTransitionsHighLevelPN(mapping.entrySet(), logTraceIDs);
        // add frequency and possibility dependencies to the simulation model
        addDependenciesToSimulationModel(mapping.entrySet(), logTraceIDs);
    }

    /**
     * Adds the frequency and possibility dependencies to the simulation model
     * @param entrySet Set the places and transitions in the petri net
     * @param logTraceIDs ArrayList list of IDs of the process instances
     */
    private void addDependenciesToSimulationModel(Set<Map.Entry> entrySet, ArrayList logTraceIDs) {
        // get the places which have more than one outgoing arc
        Iterator entries = entrySet.iterator();
        while (entries.hasNext()) {
            Map.Entry entr = (Map.Entry) entries.next();
            if (entr.getKey() instanceof ExtendedPlace) {
                ExtendedPlace place = (ExtendedPlace) entr.getKey();
                if (place.outDegree() > 1) {
                    // check whether in the process a choice exists for 'place'
                    HLChoice choice = highLevelPN.findChoice(place);
                    if (choice != null) {
                        Enumeration outEdges = place.outEdgeElements();
                        while (outEdges.hasMoreElements()) {
                            ExtendedPNEdge outEdge = (ExtendedPNEdge) outEdges.nextElement();

                            // TODO: check and remove

                            //                  HLChoice choice = null;
                            //                  boolean choiceCreated = false;
                            //                  Iterator<HLChoice> choicesIt = highLevelPN.getHLProcess().getChoices().iterator();
                            //                  while (choicesIt.hasNext()) {
                            //                     HLChoice choiceIt =  choicesIt.next();
                            //                     if (choiceIt.getChoiceNode().equals(place)) {
                            //                        // a highlevelchoice exists for this place in the process.
                            //                        choice = choiceIt;
                            //                        break;
                            //                     }
                            //                  }
                            //                  // the case that choice is still null, a highlevelchoice for 'place' does not exist
                            //                  // so, create a new one
                            //                  if (choice == null) {
                            //                     //choice = new HLChoice(place);
                            //                     choice = new HLChoice(place.getIdentifier(), highLevelPN.getHLProcess());
                            //                     choiceCreated = true;
                            //                  }

                            // add the choice based on frequencies
                            HLActivity activity = highLevelPN.findActivity((Transition) outEdge.getDest());
                            HLCondition cond = choice.getCondition(activity.getID());
                            if (outEdge.getFrequency(logTraceIDs, advancedSettings[1], failedInstances) > 0) {
                                cond.setFrequency(
                                        outEdge.getFrequency(logTraceIDs, advancedSettings[1], failedInstances));
                            }
                            // add the choice based on probabilities
                            cond.setProbability(
                                    outEdge.getProbability(
                                            logTraceIDs, place.getTotalOutEdgeFrequency(logTraceIDs,
                                                    advancedSettings[1], failedInstances),
                                            advancedSettings[1], failedInstances));

                            //                     // first check whether already a choice based on probabilities exists for this highlevelactivity
                            //                     HighLevelProbabilityDependency prob =  null;
                            //                     Iterator<HighLevelProbabilityDependency> probsIt = choice.getProbabilityDependencies().iterator();
                            //                     while (probsIt.hasNext()) {
                            //                        HighLevelProbabilityDependency probIt = probsIt.next();
                            //                        if (probIt.getSourceNode().equals(place) && probIt.getTargetNode().equals( (Transition) outEdge.getDest())) {
                            //                           prob = probIt;
                            //                           break;
                            //                        }
                            //                     }
                            //                     if (prob == null) {
                            //                        // prob is still null, create a new one
                            //                        prob = new HLProbabilityDependency(outEdge.getProbability(logTraceIDs, place.getTotalOutEdgeFrequency(logTraceIDs, advancedSettings[1], failedInstances),
                            //                                advancedSettings[1], failedInstances), place, (Transition) outEdge.getDest());
                            //                        choice.addProbabilityDependency(prob);
                            //                     }
                            //                     else {
                            //                        // set a new value for the probability
                            //                        prob.setProbability(outEdge.getProbability(logTraceIDs, place.getTotalOutEdgeFrequency(logTraceIDs, advancedSettings[1], failedInstances),
                            //                                advancedSettings[1], failedInstances));
                            //                     }
                            //                     // add the choice to the process
                            //                     if (choiceCreated) {
                            //                        highLevelPN.addChoice(choice);
                            //                     }
                        }
                    }
                }
            }
        }
    }

    /**
     * Add the calculated process time-metrics to the simulation model
     * @param logTraces ArrayList the process instances
     */
    private void fillProcessHighLevelPN(ArrayList logTraces) {
        try {
            replayResult.calculateMetrics(logTraces, advancedSettings[0]);
            // add the arrival rate to the HighLevelProcess model
            double arrivalRate = (double) replayResult.getArrivalRate() * (double) timeDivider;
            SummaryStatistics arrivalStats = replayResult.getArrivalStats();

            HLGeneralDistribution dist = new HLGeneralDistribution(arrivalStats.getMean() / (double) timeDivider,
                    arrivalStats.getMean() / (double) timeDivider,
                    arrivalStats.getVariance() / (double) timeDivider, arrivalStats.getMin() / (double) timeDivider,
                    arrivalStats.getMax() / (double) timeDivider, arrivalRate,
                    HLDistribution.DistributionEnum.EXPONENTIAL_DISTRIBUTION);

            highLevelPN.getHLProcess().getGlobalInfo().setCaseGenerationScheme(dist);
            // set the time unit of the HighLevelProcess model
            // different time strings: "milliseconds", "seconds", "minutes", "hours", "days", "weeks", "months", "years"
            if (timeSort.equals("milliseconds")) {
                highLevelPN.getHLProcess().getGlobalInfo().setTimeUnit(HLTypes.TimeUnit.MILLISECONDS);
            } else if (timeSort.equals("seconds")) {
                highLevelPN.getHLProcess().getGlobalInfo().setTimeUnit(HLTypes.TimeUnit.SECONDS);
            } else if (timeSort.equals("minutes")) {
                highLevelPN.getHLProcess().getGlobalInfo().setTimeUnit(HLTypes.TimeUnit.MINUTES);
            } else if (timeSort.equals("hours")) {
                highLevelPN.getHLProcess().getGlobalInfo().setTimeUnit(HLTypes.TimeUnit.HOURS);
            } else if (timeSort.equals("days")) {
                highLevelPN.getHLProcess().getGlobalInfo().setTimeUnit(HLTypes.TimeUnit.DAYS);
            } else if (timeSort.equals("weeks")) {
                highLevelPN.getHLProcess().getGlobalInfo().setTimeUnit(HLTypes.TimeUnit.WEEKS);
            } else if (timeSort.equals("months")) {
                highLevelPN.getHLProcess().getGlobalInfo().setTimeUnit(HLTypes.TimeUnit.MONTHS);
            } else if (timeSort.equals("years")) {
                highLevelPN.getHLProcess().getGlobalInfo().setTimeUnit(HLTypes.TimeUnit.YEARS);
            } else {
                // use default value
                highLevelPN.getHLProcess().getGlobalInfo().setTimeUnit(HLTypes.TimeUnit.HOURS);
            }
        } catch (Exception e) {
            Message.add("Program exception while calculating proces performance metrics for the "
                    + "simulation model:\n" + e.toString(), 2);
            e.printStackTrace();

        }
    }

    /**
     * Add the calculated activity time-metrics to the transitions in the simulation model
     * @param entrySet Set the transitions in the petri net
     * @param piList ArrayList list of process instances
     */
    private void fillTransitionsHighLevelPN(Set<Map.Entry> entrySet, ArrayList piList) {
        Iterator entries = entrySet.iterator();
        while (entries.hasNext()) {
            Map.Entry entr = (Map.Entry) entries.next();
            if (entr.getKey() instanceof ExtendedTransition) {
                ExtendedTransition transition = (ExtendedTransition) entr.getKey();
                if (!transition.isInvisibleTask()) {
                    ExtendedActivity activity = transition.getAssociatedActivity();
                    activity.calculateMetrics(piList, advancedSettings[3], failedInstances);
                    activity.checkWhichMetricsToUse();
                    // find the corresponding HLActivity for this transition
                    // TODO Anne: double check and remove
                    //HLActivity hlActivity = highLevelPN.findHLTransition(transition);
                    HLActivity hlActivity = highLevelPN.findActivity(transition);
                    //add the waiting time (if available)
                    addTimingInfoForTransition("waiting", hlActivity, activity.getMeanWaitTime() / timeDivider,
                            activity.getMinWaitTime() / timeDivider, activity.getMaxWaitTime() / timeDivider,
                            activity.getStdevWaitTime() / timeDivider, activity.getMeanWaitTime() / timeDivider,
                            ((double) 1 / (activity.getMeanWaitTime() / timeDivider)));
                    //add the execution time (if available)
                    addTimingInfoForTransition("execution", hlActivity,
                            activity.getMeanExecutionTime() / timeDivider,
                            activity.getMinExecutionTime() / timeDivider,
                            activity.getMaxExecutionTime() / timeDivider,
                            activity.getStdevExecutionTime() / timeDivider,
                            activity.getMeanExecutionTime() / timeDivider,
                            ((double) 1 / (activity.getMeanExecutionTime() / timeDivider)));
                    //add the sojourn time (at least the upperbound)
                    addTimingInfoForTransition("sojourn", hlActivity, activity.getMeanSojournTime() / timeDivider,
                            activity.getMinSojournTime() / timeDivider, activity.getMaxSojournTime() / timeDivider,
                            activity.getStdevSojournTime() / timeDivider,
                            activity.getMeanSojournTime() / timeDivider,
                            ((double) 1 / (activity.getMeanSojournTime() / timeDivider)));
                }
            }
        }
    }

    /**
     * Specifiy provided objects of the analysis that can be further
     * used to, e.g., export an item.
     * @return An Array containing provided objects
     */
    public ProvidedObject[] getProvidedObjects() {
        // if provided objects are asked before the GUI is actually created the log selection cannot be provided yet
        // furthermore the log selection is only offered if there is something selected
        try {
            if (processInstanceIDsTable != null
                    && processInstanceIDsTable.getSelectionModel().isSelectionEmpty() == false) {
                // fill the high level PN with simulation information
                //fillTransitionsHighLevelPN();
                ProvidedObject[] objects = { new ProvidedObject("Petri net model", new Object[] { inputPetriNet }),
                        new ProvidedObject("Performance Simulation Model No." + simulationModelCounter,
                                new Object[] { highLevelPN }),
                        new ProvidedObject("Performance visualization", new Object[] { new DotFileWriter() {
                            public void writeToDot(Writer bw) throws IOException {
                                extendedPetriNet.writeToDot(bw);
                            }
                        } }), new ProvidedObject("Whole Log", new Object[] { inputLog }),
                        new ProvidedObject("Log Selection",
                                new Object[] { LogReaderFactory.createInstance(inputLog, getSelectionStatus()) }) };
                return objects;
            } else {
                ProvidedObject[] objects = { new ProvidedObject("Petri net model", new Object[] { inputPetriNet }),
                        new ProvidedObject("Performance visualization", new Object[] { new DotFileWriter() {
                            public void writeToDot(Writer bw) throws IOException {
                                extendedPetriNet.writeToDot(bw);
                            }
                        } }), new ProvidedObject("Whole Log", new Object[] { inputLog }) };
                return objects;
            }
        } catch (Exception e) {
            System.err.println("Fatal error creating new log reader instance:");
            System.err.println("(" + this.getClass() + ")");
            e.printStackTrace();
            return null;
        }
    }

    ////////// GRAPPA RELATED METHODS //////////

    /**
     * Create a mapping from the Petri net graph structure back to the
     * Grappa nodes. Copied from the template GUI but not used so far.
     * @param mapping Map
     * @param subGraph Subgraph
     */
    private void buildGraphMapping(Map mapping, Subgraph subGraph) {
        Enumeration e = subGraph.nodeElements();
        while (e.hasMoreElements()) {
            Node n = (Node) e.nextElement();
            mapping.put(n.object, n);
        }
        e = subGraph.edgeElements();
        while (e.hasMoreElements()) {
            Edge n = (Edge) e.nextElement();
            mapping.put(n.object, n);
        }
        e = subGraph.subgraphElements();
        while (e.hasMoreElements()) {
            Subgraph n = (Subgraph) e.nextElement();
            buildGraphMapping(mapping, n);
        }
    }

    ////////// INTERNAL CLASS DEFINITIONS //////////

    /**
     * A custom listener class for the grappa graph panel.
     */
    public class ExtendedGrappaAdapter extends GrappaAdapter {

        /**
         * The method called when a mouse click occurs on an element of the
         * graph panel.
         * Difference with the same method in the GrappaAdapter-class is that
         * here certain metrics are displayed after clicking on a place or a
         * transition within the graph (Petri net), that is displayed on the
         * panel. Furthermore, here the user can only have one Element selected
         * at a time, except in case the selected Elements are transitions, the
         * user can select up to two of these.
         *
         * @param subg displayed subgraph where action occurred
         * @param elem subgraph element in which action occurred
         * @param pt the point where the action occurred (graph coordinates)
         * @param modifiers mouse modifiers in effect
         * @param clickCount count of mouse clicks that triggered this action
         * @param panel specific panel where the action occurred
         *
         */
        public void grappaClicked(Subgraph subg, Element elem, GrappaPoint pt, int modifiers, int clickCount,
                GrappaPanel panel) {
            if ((modifiers & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) {
                if ((clickCount == 1) && ((modifiers & InputEvent.CTRL_MASK) != InputEvent.CTRL_MASK)) {
                    // looks like Java has a single click occur on the way to a
                    // multiple click, so this code always executes (which is
                    // not necessarily a bad thing)
                    if (subg.getGraph().isSelectable()) {
                        if (modifiers == InputEvent.BUTTON1_MASK) {
                            // select element
                            if (elem == null) {
                                //called when user clicked below/above/next to Petri net
                                sb1.setSelectedIndex(0);
                                sb2.setSelectedIndex(0);
                                sb2.setEnabled(false);
                                andLabel.setForeground(Color.gray);
                            } else {
                                if (elem.object instanceof ExtendedTransition) {
                                    //the user has clicked on a transition on the panel
                                    ExtendedTransition temp = (ExtendedTransition) elem.object;
                                    if (temp.isInvisibleTask()) {
                                        JOptionPane.showMessageDialog(panel,
                                                "Invisible transitions cannot be selected!");
                                    } else {
                                        //only allow selection of visible tasks
                                        if (elt1 == null || elt1 instanceof ExtendedPlace) {
                                            //No other transition was selected
                                            elt1 = (Element) elem.object;
                                            elt2 = null;
                                            ExtendedTransition trans = (ExtendedTransition) elt1;
                                            String transName = "Transition - "
                                                    + trans.getLogEvent().getModelElementName() + " "
                                                    + trans.getLogEvent().getEventType();
                                            sb1.setSelectedItem(transName);
                                        } else {
                                            //At least one other transition was selected
                                            if (elt1 == elem.object) {
                                                //the clicked transition was already selected
                                                //in sb1
                                                if (elt2 == null) {
                                                    sb1.setSelectedIndex(0);
                                                    sb2.setSelectedIndex(0);
                                                    sb2.setEnabled(false);
                                                    andLabel.setForeground(Color.gray);
                                                } else {
                                                    elt1 = elt2;
                                                    ExtendedTransition trans = (ExtendedTransition) elt1;
                                                    String transName = "Transition - "
                                                            + trans.getLogEvent().getModelElementName() + " "
                                                            + trans.getLogEvent().getEventType();
                                                    sb1.setSelectedItem(transName);
                                                    sb2.setSelectedIndex(0);
                                                }
                                            } else if (elt2 == elem.object) {
                                                //the clicked transition was
                                                //already selected in sb2
                                                elt2 = null;
                                                sb2.setSelectedIndex(0);
                                            } else {
                                                //add clicked transition to the selection
                                                if (elt2 != null) {
                                                    JOptionPane.showMessageDialog(panel,
                                                            "Already two transitions selected."
                                                                    + "\nDeselect one first!");
                                                } else {
                                                    elt2 = (Element) elem.object;
                                                    ExtendedTransition trans1 = (ExtendedTransition) elt1;
                                                    String transName = "Transition - "
                                                            + trans1.getLogEvent().getModelElementName() + " "
                                                            + trans1.getLogEvent().getEventType();
                                                    ExtendedTransition trans2 = (ExtendedTransition) elt2;
                                                    sb1.setSelectedItem(transName);
                                                    transName = "Transition - "
                                                            + trans2.getLogEvent().getModelElementName() + " "
                                                            + trans2.getLogEvent().getEventType();
                                                    sb2.setSelectedItem(transName);
                                                }
                                            }
                                        }
                                    }
                                } else {
                                    // called when the user has clicked on anything but a
                                    // transition -> reset lastSelected and otherSelected
                                    if (elem.object instanceof ExtendedPlace) {
                                        //the user has clicked on a place on the panel
                                        if (elem.object == elt1) {
                                            //the place was already selected
                                            elt1 = null;
                                            elt2 = null;
                                            sb1.setSelectedIndex(0);
                                        } else {
                                            elt1 = (Element) elem.object;
                                            elt2 = null;
                                            ExtendedPlace place = (ExtendedPlace) elt1;
                                            sb1.setSelectedItem("Place - " + place.getIdentifier());
                                        }
                                    } else {
                                        //not a place, nor a transition selected
                                        elt1 = null;
                                        elt2 = null;
                                        sb1.setSelectedIndex(0);
                                    }
                                }
                            }
                        }
                    }
                } else {
                    // multiple clicks
                    // this code executes for each click beyond the first
                }
            }
        }

        /**
         * The method called when a mouse release occurs on a displayed subgraph.
         * Difference with the same method in the GrappaAdapter-class is that
         * here no elements in the displayed graph are selected after release.
         *
         * @param subg displayed subgraph where action occurred
         * @param elem subgraph element in which action occurred
         * @param pt the point where the action occurred (graph coordinates)
         * @param modifiers mouse modifiers in effect
         * @param pressedElem subgraph element in which the most recent mouse press occurred
         * @param pressedPt the point where the most recent mouse press occurred (graph coordinates)
         * @param pressedModifiers mouse modifiers in effect when the most recent mouse press occurred
         * @param outline enclosing box specification from the previous drag position (for XOR reset purposes)
         * @param panel specific panel where the action occurred
         */
        public void grappaReleased(Subgraph subg, Element elem, GrappaPoint pt, int modifiers, Element pressedElem,
                GrappaPoint pressedPt, int pressedModifiers, GrappaBox outline, GrappaPanel panel) {
            if (modifiers == InputEvent.BUTTON1_MASK && subg.getGraph().isSelectable()) {
                if (outline != null) {
                    // Outline is supplied in the coordinates of the canvas
                    panel.recordDragPoints();
                    subg.getGraph().paintImmediately();
                }
            } else if (modifiers == (InputEvent.BUTTON1_MASK | InputEvent.CTRL_MASK)
                    && subg.getGraph().isSelectable()) {
                if (outline != null) {
                    Graphics2D g2d = (Graphics2D) (panel.getGraphics());
                    AffineTransform orig = g2d.getTransform();
                    g2d.setTransform(panel.getTransform());
                    g2d.setXORMode(Color.darkGray);
                    g2d.draw(outline);
                    g2d.setPaintMode();
                    g2d.setTransform(orig);
                }
            }
        }
    }

    /**
     * Private data structure for the table containing the process instance IDs.
     */
    private class ExtendedLogTable extends AbstractTableModel {

        /**
         * Specify the headings for the columns.
         * @param col The column specified.
         * @return The heading of the respective column.
         */
        public String getColumnName(int col) {
            // heading of the first column
            return "Log Traces";
        }

        /**
         * Specify the number of rows.
         * @return The number of traces in the log.
         */
        public int getRowCount() {
            return extendedLog.getSizeOfLog();
        }

        /**
         * Specifiy the number of columns.
         * @return Always 1.
         */
        public int getColumnCount() {
            return 1;
        }

        /**
         * Method to fill a certain field of the table with contents.
         * @param row The specified row.
         * @param column The specified column.
         * @return The content to display at the table field specified.
         */
        public Object getValueAt(int row, int column) {
            // fill column with trace IDs
            return extendedLog.getLogTraceIDs().get(row);

        }
    }

    /**
     * Private data structure for tables containing three columns with metrics.
     */
    private static class ThreeMetricsTableModel extends AbstractTableModel {

        private String[] columnNames = { "", "Waiting time", "Synchronization time", "Sojourn time" };
        private Object[][] data = { { "avg", "", "", "" }, { "min", "", "", "" }, { "max", "", "", "" },
                { "stdev", "", "", "" }, { "fast", "", "", "" }, { "slow", "", "", "" }, { "normal", "", "", "" } };

        public int getColumnCount() {
            return columnNames.length;
        }

        public int getRowCount() {
            return data.length;
        }

        public String getColumnName(int col) {
            return columnNames[col];
        }

        public Object getValueAt(int row, int col) {
            return data[row][col];
        }

        public Class getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }

        /*
         * Cells can't be edited
         */
        public boolean isCellEditable(int row, int col) {
            return false;
        }

        /*
         * Set value at field[row, col] in the table
         * data can change.
         */
        public void setValueAt(Object value, int row, int col) {
            data[row][col] = value;
            fireTableCellUpdated(row, col);
        }

        public void setHeadings(String one, String two, String three, String four) {
            columnNames[0] = one;
            columnNames[1] = two;
            columnNames[2] = three;
            columnNames[3] = four;
        }
    }

    /**
     * Private data structure for tables containing three columns with metrics
     * and is different as ThreeMetricsTableModel in the sense that it contains
     * one extra row to store frequencies of measurements.
     */
    private static class ThreeMetricsRowExtraTableModel extends AbstractTableModel {

        private String[] columnNames = { "", "Waiting time", "Execution time", "Sojourn time" };
        private Object[][] data = { { "avg", "", "", "" }, { "min", "", "", "" }, { "max", "", "", "" },
                { "stdev", "", "", "" }, { "fast", "", "", "" }, { "slow", "", "", "" }, { "normal", "", "", "" },
                { "frequency", "", "", "" } };

        public int getColumnCount() {
            return columnNames.length;
        }

        public int getRowCount() {
            return data.length;
        }

        public String getColumnName(int col) {
            return columnNames[col];
        }

        public Object getValueAt(int row, int col) {
            return data[row][col];
        }

        public Class getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }

        /*
         * Cells can't be edited
         */
        public boolean isCellEditable(int row, int col) {
            return false;
        }

        /*
         * Set value at field[row, col] in the table
         * data can change.
         */
        public void setValueAt(Object value, int row, int col) {
            data[row][col] = value;
            fireTableCellUpdated(row, col);
        }

        public void setHeadings(String one, String two, String three, String four) {
            columnNames[0] = one;
            columnNames[1] = two;
            columnNames[2] = three;
            columnNames[3] = four;
        }
    }

    /**
     * Private data structure for tables containing one column with metrics
     * (e.g. throughput time)
     */
    private static class OneMetricTableModel extends AbstractTableModel {

        private String[] columnNames = { "", "Throughput time" };
        private Object[][] data = { { "avg", "" }, { "min", "" }, { "max", "" }, { "stdev", "" }, { "fast", "" },
                { "slow", "" }, { "normal", "" } };

        public int getColumnCount() {
            return columnNames.length;
        }

        public int getRowCount() {
            return data.length;
        }

        public String getColumnName(int col) {
            return columnNames[col];
        }

        public Object getValueAt(int row, int col) {
            return data[row][col];
        }

        public Class getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }

        public boolean isCellEditable(int row, int col) {
            return false;
        }

        /*
         * Set value at field[row, col] in the table
         * data can change.
         */
        public void setValueAt(Object value, int row, int col) {
            data[row][col] = value;
            fireTableCellUpdated(row, col);
        }

        public void setHeadings(String one, String two) {
            columnNames[0] = one;
            columnNames[1] = two;
        }
    }

    public static class NameFilter extends javax.swing.filechooser.FileFilter {
        private String description = "";

        /**
        * Constructor, sets description to suffix
        * @param suffix String
        */
        public NameFilter(String suffix) {
            description = suffix;
        }

        /**
        * Returns true if f is a directory or ends with description
        * @param f File
        * @return boolean
        */
        public boolean accept(File f) {
            if (f.isDirectory() || f.getName().endsWith(description)) {
                return true;
            } else {
                return false;
            }
        }

        public String getDescription() {
            return "*" + description;
        }

    }

    /**
     * Cell renderer, to color the font of cells red if they contain bound values
     * instead of exact values.
     */
    public static class CustomTableCellRenderer extends DefaultTableCellRenderer {
        private boolean firstColumn = false;
        private boolean secondColumn = false;
        private boolean thirdColumn = false;

        public CustomTableCellRenderer(boolean firstColumn, boolean secondColumn, boolean thirdColumn) {
            super();
            this.firstColumn = firstColumn;
            this.secondColumn = secondColumn;
            this.thirdColumn = thirdColumn;
        }

        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                boolean hasFocus, int row, int column) {
            Component cell = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
            if (column == 1 && firstColumn) {
                cell.setForeground(Color.red);
            } else if (column == 2 && secondColumn) {
                cell.setForeground(Color.red);
            } else if (column == 3 && thirdColumn) {
                cell.setForeground(Color.red);
            } else {
                cell.setForeground(Color.black);
            }
            return cell;

        }
    }

}