de.gebit.integrity.eclipse.views.IntegrityTestRunnerView.java Source code

Java tutorial

Introduction

Here is the source code for de.gebit.integrity.eclipse.views.IntegrityTestRunnerView.java

Source

/*******************************************************************************
 * Copyright (c) 2013 Rene Schneider, GEBIT Solutions GmbH and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package de.gebit.integrity.eclipse.views;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.HyperlinkGroup;
import org.eclipse.ui.forms.IFormColors;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.Form;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.part.ViewPart;

import com.google.inject.Inject;

import de.gebit.integrity.eclipse.Activator;
import de.gebit.integrity.eclipse.actions.BreakpointAction;
import de.gebit.integrity.eclipse.actions.JumpToLinkAction;
import de.gebit.integrity.eclipse.controls.ProgressBar;
import de.gebit.integrity.eclipse.running.TestActionConfigurationDialog;
import de.gebit.integrity.eclipse.search.SetListSearch;
import de.gebit.integrity.remoting.IntegrityRemotingConstants;
import de.gebit.integrity.remoting.client.IntegrityRemotingClient;
import de.gebit.integrity.remoting.client.IntegrityRemotingClientListener;
import de.gebit.integrity.remoting.entities.setlist.SetList;
import de.gebit.integrity.remoting.entities.setlist.SetListEntry;
import de.gebit.integrity.remoting.entities.setlist.SetListEntryAttributeKeys;
import de.gebit.integrity.remoting.entities.setlist.SetListEntryResultStates;
import de.gebit.integrity.remoting.entities.setlist.SetListEntryTypes;
import de.gebit.integrity.remoting.transport.Endpoint;
import de.gebit.integrity.remoting.transport.enums.ExecutionCommands;
import de.gebit.integrity.remoting.transport.enums.ExecutionStates;
import de.gebit.integrity.remoting.transport.enums.TestRunnerCallbackMethods;
import de.gebit.integrity.remoting.transport.messages.ExecutionStateMessage;
import de.gebit.integrity.remoting.transport.messages.IntegrityRemotingVersionMessage;
import de.gebit.integrity.remoting.transport.messages.SetListBaselineMessage;
import de.gebit.integrity.ui.linking.IntegrityURLResolver;
import de.gebit.integrity.utils.DateUtil;

/**
 * The Integrity Test Runner Eclipse Plugin main view.
 * 
 * @author Rene Schneider - initial API and implementation
 */
public class IntegrityTestRunnerView extends ViewPart {

    /**
     * The ID of the view as specified by the extension.
     */
    public static final String ID = "de.gebit.integrity.eclipse.views.IntegrityTestRunnerView";

    /**
     * The prefix for Integrity URLs. Required to add to links provided by {@link SetListEntry} instances, which don't
     * include the prefix to save some space.
     */
    protected static final String INTEGRITY_URL_PREFIX = "integrity://";

    /**
     * Height of the result text fields.
     */
    private static final int RESULT_TEXTFIELD_HEIGHT = 60;

    /**
     * Height of larger tables in the result (variables, tabletests).
     */
    private static final int RESULT_TABLE_HEIGHT = 230;

    /**
     * Height of each text field for extended results in text form.
     */
    private static final int EXTENDED_RESULT_TEXTFIELD_HEIGHT = 60;

    /**
     * Space around images in extended result fields.
     */
    private static final int EXTENDED_RESULT_IMAGE_SPACING = 6;

    /**
     * Space between each extended result field.
     */
    private static final int EXTENDED_RESULT_SPACING = 5;

    /**
     * The "magic search term" for failed test/calls.
     */
    private static final String MAGIC_SEARCH_TERM_FAILURES = "Tests/Calls with failures or exceptions";

    /**
     * The Integrity URL resolver.
     */
    @Inject
    private IntegrityURLResolver urlResolver;

    /**
     * The sash form used to split the screen in one half for the tree, and another for the details view.
     */
    private SashForm sashForm;

    /**
     * The container for the tree.
     */
    private Form treeContainer;

    /**
     * This container is placed below the tree and contains the UI elements of the tree search function.
     */
    private Composite searchContainer;

    /**
     * The text input field for the tree search.
     */
    private Text searchTextField;

    /**
     * A flag used to determine whether the text field just gained the focus.
     */
    private boolean searchTextFieldJustGainedFocus;

    /**
     * The "search for error/exception" button for the tree search.
     */
    private Button searchErrorButton;

    /**
     * The image for the {@link #searchErrorButton}.
     */
    private Image searchErrorButtonEnabledImage;

    /**
     * The "previous result" button for the tree search.
     */
    private Button searchLeftButton;

    /**
     * The image for the {@link #searchLeftButton}.
     */
    private Image searchLeftButtonEnabledImage;

    /**
     * The "next result" button for the tree search.
     */
    private Button searchRightButton;

    /**
     * The image for the {@link #searchRightButton}.
     */
    private Image searchRightButtonEnabledImage;

    /**
     * The label denoting the current position and number of results during searching in the tree.
     */
    private Label searchPositionLabel;

    /**
     * The test execution tree viewer.
     */
    private TreeViewer treeViewer;

    /**
     * Status flag which is used to signal whether the tree viewer is currently scrolled by the user (using the vertical
     * scrollbar).
     */
    private boolean treeViewerIsScrolledManually;

    /**
     * The drawer to colorize the test execution tree.
     */
    private TestTreeContentDrawer viewerContentDrawer;

    /**
     * The container for the detail information.
     */
    private Composite detailsContainer;

    /**
     * The scrolling form for the details.
     */
    private ScrolledForm details;

    /**
     * The hyperlink group for the fixture link.
     */
    private HyperlinkGroup fixtureLinkGroup;

    /**
     * The link that allows to jump to a specific fixture method.
     */
    private Hyperlink fixtureLink;

    /**
     * The label displaying whether a command is executed on a fork.
     */
    private Label forkLabel;

    /**
     * The container for the detail information groups.
     */
    private Composite detailGroups;

    /**
     * The variable section.
     */
    private Section variableSection;

    /**
     * The composite for the variable table.
     */
    private Composite variableComposite;

    /**
     * The table of defined variables.
     */
    private TableViewer variableTable;

    /**
     * The section for parameters.
     */
    private Section parameterSection;

    /**
     * The composite for the parameter table.
     */
    private Composite parameterComposite;

    /**
     * The table with all defined parameters.
     */
    private TableViewer parameterTable;

    /**
     * The section that gets the result info.
     */
    private Section resultSection;

    /**
     * The composite containing result UI elements.
     */
    private Composite resultComposite;

    /**
     * The label for the first (actual) result.
     */
    private Label resultLine1Name;

    /**
     * The text field for the first (actual) result.
     */
    private Text resultLine1Text;

    /**
     * The container for the first (actual) result text field, which adds a color border around it.
     */
    private Composite resultLine1Border;

    /**
     * The label for the second (expected) result.
     */
    private Label resultLine2Name;

    /**
     * The text field for the second (expected) result.
     */
    private Text resultLine2Text;

    /**
     * The container for the second (expected) result text field, which adds a color border around it.
     */
    private Composite resultLine2Border;

    /**
     * The label for the third (extended result) result value.
     */
    private Label resultLine3Name;

    /**
     * The container for the third (extended result) result value field. This one will be dynamically filled with text
     * fields or whatever else for displaying the varying number of extended result data objects.
     */
    private Composite resultLine3Border;

    /**
     * The container for the result table.
     */
    private Composite resultTableComposite;

    /**
     * The result table (for multi-result tests).
     */
    private TableViewer resultTable;

    /**
     * The container for the variable update table.
     */
    private Composite varUpdateTableComposite;

    /**
     * The variable update table (for calls with multi-variable updates).
     */
    private TableViewer varUpdateTable;

    /**
     * The color for results of successful tests.
     */
    private Color resultSuccessColor;

    /**
     * The color for results of failed tests.
     */
    private Color resultFailureColor;

    /**
     * The neutral color for results.
     */
    private Color resultNeutralColor;

    /**
     * The color of exception results.
     */
    private Color resultExceptionColor;

    /**
     * The background color of the search container.
     */
    private Color searchContainerColor;

    /**
     * The image used to display successful test results.
     */
    private Image resultSuccessIconImage;

    /**
     * The image used to display failed test results.
     */
    private Image resultFailureIconImage;

    /**
     * The image used to display exception test results.
     */
    private Image resultExceptionIconImage;

    /**
     * The container to display the success icon.
     */
    private Label resultSuccessIcon;

    /**
     * The container to display the failure icon.
     */
    private Label resultFailureIcon;

    /**
     * The container to display the exception icon.
     */
    private Label resultExceptionIcon;

    /**
     * The success count.
     */
    private Label resultSuccessCountLabel;

    /**
     * The failure count.
     */
    private Label resultFailureCountLabel;

    /**
     * The exception count.
     */
    private Label resultExceptionCountLabel;

    /**
     * The progress bar displaying the test execution progress.
     */
    private ProgressBar executionProgress;

    /**
     * The background color for table test results (successful tests).
     */
    private Color resultTableSuccessColor;

    /**
     * The background color for failed table test results (failed tests).
     */
    private Color resultTableFailureColor;

    /**
     * The action that connects to a test runner.
     */
    private Action connectToTestRunnerAction;

    /**
     * The action that allows test continuation.
     */
    private Action playAction;

    /**
     * The action that allows to pause a running test execution.
     */
    private Action pauseAction;

    /**
     * The action for single-stepping test execution steps.
     */
    private Action stepIntoAction;

    /**
     * The action for stepping over suite calls.
     */
    private Action stepOverAction;

    /**
     * The action that runs a predefined launch config and connects to the test runner automatically.
     */
    private Action executeTestAction;

    /**
     * The action that runs a predefined launch config in debug mode and connects to the test runner automatically.
     */
    private Action executeDebugTestAction;

    /**
     * The action allowing to shut down a running test execution.
     */
    private Action shutdownAction;

    /**
     * The action allowing to configure what is executed with {@link #executeTestAction}.
     */
    private Action configureTestAction;

    /**
     * The action which expands all nodes one level further.
     */
    private Action expandAllAction;

    /**
     * The action which collapses all nodes.
     */
    private Action collapseAllAction;

    /**
     * The action which toggles the scroll lock function.
     */
    private Action scrollLockAction;

    /**
     * Whether scroll lock is active.
     */
    private boolean scrollLockActive;

    /**
     * The last level of node expansion.
     */
    private int lastExpansionLevel;

    /**
     * The search engine instance used to search the setlist tree.
     */
    private SetListSearch setListSearch;

    /**
     * The currently valid setlist tree search result. May be null if no search result is available.
     */
    private List<SetListEntry> currentSearchResult;

    /**
     * The current position in the setlist tree search result. May be null if no current position is available.
     */
    private Integer currentSearchResultPosition;

    /**
     * The remoting client instance.
     */
    private IntegrityRemotingClient client;

    /**
     * The currently used set list instance.
     */
    private SetList setList;

    /**
     * The set of breakpoints currently in use.
     */
    private Set<Integer> breakpointSet = Collections.synchronizedSet(new HashSet<Integer>());

    /**
     * The launch configuration to run when the start button in the view is pressed.
     */
    private ILaunchConfiguration launchConfiguration;

    /**
     * The constructor.
     */
    public IntegrityTestRunnerView() {
    }

    /**
     * This is a callback that will allow us to create the viewer and initialize it.
     */
    // SUPPRESS CHECKSTYLE MethodLength
    public void createPartControl(final Composite aParent) {
        aParent.setLayout(new FillLayout());

        final FormToolkit tempToolkit = new FormToolkit(aParent.getDisplay());

        resultSuccessColor = new Color(Display.getCurrent(), 0, 94, 13);
        resultFailureColor = new Color(Display.getCurrent(), 190, 0, 0);
        resultNeutralColor = new Color(Display.getCurrent(), 0, 0, 0);
        resultExceptionColor = new Color(Display.getCurrent(), 204, 163, 0);

        resultTableSuccessColor = new Color(Display.getCurrent(), 205, 255, 222);
        resultTableFailureColor = new Color(Display.getCurrent(), 255, 130, 130);

        searchContainerColor = new Color(Display.getCurrent(), 220, 220, 220);

        resultSuccessIconImage = Activator.getImageDescriptor("icons/suite_success_big.png").createImage();
        resultFailureIconImage = Activator.getImageDescriptor("icons/suite_failure_big.png").createImage();
        resultExceptionIconImage = Activator.getImageDescriptor("icons/suite_exception_big.png").createImage();

        sashForm = new SashForm(aParent, SWT.HORIZONTAL | SWT.SMOOTH);

        treeContainer = new Form(sashForm, SWT.NONE);
        treeContainer.setText("Not connected");
        treeContainer.getBody().setLayout(new FormLayout());
        treeContainer.setBackground(tempToolkit.getColors().getBackground());
        treeContainer.setForeground(tempToolkit.getColors().getColor(IFormColors.TITLE));
        treeContainer.setFont(JFaceResources.getHeaderFont());
        tempToolkit.decorateFormHeading(treeContainer);

        executionProgress = new ProgressBar(treeContainer.getBody(), SWT.NONE);
        FormData tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 0);
        tempFormData.right = new FormAttachment(100, 0);
        tempFormData.top = new FormAttachment(0, 0);
        tempFormData.bottom = new FormAttachment(0, 16);
        executionProgress.setLayoutData(tempFormData);

        treeViewer = new TreeViewer(treeContainer.getBody(), SWT.VIRTUAL | SWT.H_SCROLL | SWT.V_SCROLL);
        treeViewer.setUseHashlookup(true);
        treeViewer.setContentProvider(new TestTreeContentProvider(treeViewer));
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 0);
        tempFormData.right = new FormAttachment(100, 0);
        tempFormData.top = new FormAttachment(executionProgress, 0);
        tempFormData.bottom = new FormAttachment(100, -18);
        treeViewer.getTree().setLayoutData(tempFormData);
        treeViewer.getTree().getVerticalBar().addSelectionListener(new SelectionListener() {
            public void widgetDefaultSelected(SelectionEvent anEvent) {
                // nothing to do
            }

            public void widgetSelected(SelectionEvent anEvent) {
                if (anEvent.detail == SWT.NONE) {
                    treeViewerIsScrolledManually = false;
                } else if (anEvent.detail == SWT.DRAG) {
                    treeViewerIsScrolledManually = true;
                }
            }
        });

        searchContainer = new Composite(treeContainer.getBody(), SWT.NONE);
        searchContainer.setLayout(new FormLayout());
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 0);
        tempFormData.right = new FormAttachment(100, 0);
        tempFormData.top = new FormAttachment(treeViewer.getControl(), 0);
        tempFormData.height = 18;
        searchContainer.setLayoutData(tempFormData);
        searchContainer.setBackground(searchContainerColor);

        searchErrorButtonEnabledImage = Activator.getImageDescriptor("icons/search_failed_enabled.gif")
                .createImage();

        searchErrorButton = new Button(searchContainer, SWT.FLAT);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 0);
        tempFormData.width = 16;
        tempFormData.top = new FormAttachment(0, 2);
        tempFormData.height = 16;
        searchErrorButton.setImage(searchErrorButtonEnabledImage);
        searchErrorButton.setLayoutData(tempFormData);
        searchErrorButton.addListener(SWT.Selection, new Listener() {

            @Override
            public void handleEvent(Event anEvent) {
                // repeated clicks shall just jump to the next result
                if (!performTreeSearchForFailures()) {
                    jumpToNextSearchResult();
                }
            }
        });

        searchTextField = new Text(searchContainer, SWT.SINGLE);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 16);
        tempFormData.right = new FormAttachment(100, -100);
        tempFormData.top = new FormAttachment(0, 2);
        tempFormData.height = 16;
        searchTextField.setText("");
        searchTextField.setLayoutData(tempFormData);
        searchTextField.addModifyListener(new ModifyListener() {

            @Override
            public void modifyText(ModifyEvent anEvent) {
                performTreeSearch();
            }
        });
        searchTextField.addFocusListener(new FocusListener() {

            @Override
            public void focusLost(FocusEvent anEvent) {
                searchTextFieldJustGainedFocus = false;
            }

            @Override
            public void focusGained(FocusEvent anEvent) {
                searchTextFieldJustGainedFocus = true;
            }
        });
        searchTextField.addMouseListener(new MouseListener() {

            @Override
            public void mouseUp(MouseEvent anEvent) {
                // Perform a full selection of all entered text if there is text AND the text field just gained the
                // focus.
                if (searchTextFieldJustGainedFocus && searchTextField.getText().length() > 0) {
                    searchTextField.selectAll();
                }
                searchTextFieldJustGainedFocus = false;
            }

            @Override
            public void mouseDown(MouseEvent anEvent) {
                // ignored
            }

            @Override
            public void mouseDoubleClick(MouseEvent anEvent) {
                // ignored
            }
        });

        searchLeftButtonEnabledImage = Activator.getImageDescriptor("icons/search_prev_enabled.gif").createImage();

        searchLeftButton = new Button(searchContainer, SWT.FLAT);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(searchTextField, 0);
        tempFormData.width = 16;
        tempFormData.top = new FormAttachment(0, 2);
        tempFormData.height = 16;
        searchLeftButton.setImage(searchLeftButtonEnabledImage);
        searchLeftButton.setLayoutData(tempFormData);
        searchLeftButton.addListener(SWT.Selection, new Listener() {

            @Override
            public void handleEvent(Event anEvent) {
                jumpToPreviousSearchResult();
            }
        });

        searchRightButtonEnabledImage = Activator.getImageDescriptor("icons/search_next_enabled.gif").createImage();

        searchRightButton = new Button(searchContainer, SWT.FLAT);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(searchLeftButton, 0);
        tempFormData.width = 16;
        tempFormData.top = new FormAttachment(0, 2);
        tempFormData.height = 16;
        searchRightButton.setImage(searchRightButtonEnabledImage);
        searchRightButton.setLayoutData(tempFormData);
        searchRightButton.addListener(SWT.Selection, new Listener() {

            @Override
            public void handleEvent(Event anEvent) {
                jumpToNextSearchResult();
            }
        });

        searchPositionLabel = new Label(searchContainer, SWT.NONE);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(searchRightButton, 0);
        tempFormData.right = new FormAttachment(100, 0);
        tempFormData.top = new FormAttachment(0, 2);
        tempFormData.height = 16;
        searchPositionLabel.setBackground(searchContainerColor);
        searchPositionLabel.setLayoutData(tempFormData);
        searchPositionLabel.setText("");
        searchPositionLabel.setAlignment(SWT.CENTER);

        detailsContainer = new Composite(sashForm, SWT.NONE);
        detailsContainer.setLayout(new FillLayout());

        details = new ScrolledForm(detailsContainer, SWT.V_SCROLL | SWT.H_SCROLL);
        details.setExpandHorizontal(true);
        details.setExpandVertical(true);
        details.setBackground(tempToolkit.getColors().getBackground());
        details.setForeground(tempToolkit.getColors().getColor(IFormColors.TITLE));
        details.setFont(JFaceResources.getHeaderFont());
        details.getBody().setLayout(new FormLayout());
        tempToolkit.decorateFormHeading(details.getForm());

        fixtureLinkGroup = new HyperlinkGroup(aParent.getDisplay());
        fixtureLink = new Hyperlink(details.getBody(), SWT.NONE);
        fixtureLink.setBackground(details.getBackground());
        fixtureLink.setText("");
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 5);
        tempFormData.right = new FormAttachment(100, -5);
        tempFormData.top = new FormAttachment(0, 3);
        tempFormData.height = 10;
        fixtureLink.setLayoutData(tempFormData);
        fixtureLink.addHyperlinkListener(new HyperlinkAdapter() {
            public void linkActivated(HyperlinkEvent anEvent) {
                jumpToJavaMethod(anEvent.getLabel());
            }
        });
        fixtureLinkGroup.add(fixtureLink);

        forkLabel = new Label(details.getBody(), SWT.NONE);
        forkLabel.setText("");
        forkLabel.setBackground(tempToolkit.getColors().getBackground());
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 5);
        tempFormData.right = new FormAttachment(100, -5);
        tempFormData.top = new FormAttachment(fixtureLink, 3);
        tempFormData.height = 14;
        forkLabel.setLayoutData(tempFormData);

        detailGroups = new Composite(details.getBody(), SWT.NONE);
        detailGroups.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));
        GridLayout tempGridLayout = new GridLayout(1, true);
        tempGridLayout.verticalSpacing = 10;
        detailGroups.setLayout(tempGridLayout);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 5);
        tempFormData.right = new FormAttachment(100, -5);
        tempFormData.top = new FormAttachment(forkLabel, 3);
        tempFormData.bottom = new FormAttachment(100, 0);
        detailGroups.setLayoutData(tempFormData);

        resultSection = tempToolkit.createSection(detailGroups, Section.TITLE_BAR | Section.EXPANDED);
        resultSection.setText("Result");
        GridData tempGridData = new GridData();
        tempGridData.minimumHeight = 10;
        tempGridData.horizontalIndent = 5;
        tempGridData.grabExcessHorizontalSpace = true;
        tempGridData.horizontalAlignment = GridData.FILL;
        resultSection.setLayoutData(tempGridData);
        resultSection.setLayout(new FillLayout());

        resultComposite = tempToolkit.createComposite(resultSection);
        tempToolkit.paintBordersFor(resultComposite);
        resultSection.setClient(resultComposite);
        resultComposite.setLayout(new FormLayout());

        parameterSection = tempToolkit.createSection(detailGroups, Section.TITLE_BAR | Section.EXPANDED);
        parameterSection.setText("Parameters");
        tempGridData = new GridData();
        tempGridData.minimumHeight = SWT.DEFAULT;
        tempGridData.heightHint = 130;
        tempGridData.horizontalIndent = 5;
        tempGridData.grabExcessHorizontalSpace = true;
        tempGridData.horizontalAlignment = GridData.FILL;
        parameterSection.setLayoutData(tempGridData);
        parameterSection.setLayout(new FillLayout());

        parameterComposite = tempToolkit.createComposite(parameterSection);
        tempToolkit.paintBordersFor(parameterComposite);
        parameterSection.setClient(parameterComposite);
        parameterComposite.setLayout(new FillLayout());

        parameterTable = new TableViewer(parameterComposite, SWT.FULL_SELECTION);
        parameterTable.setContentProvider(new ArrayContentProvider());
        configureTable(parameterTable);

        variableSection = tempToolkit.createSection(detailGroups, Section.TITLE_BAR | Section.EXPANDED);
        variableSection.setText("Variable definitions");
        tempGridData = new GridData();
        tempGridData.minimumHeight = SWT.DEFAULT;
        tempGridData.heightHint = 150;
        tempGridData.horizontalIndent = 5;
        tempGridData.grabExcessHorizontalSpace = true;
        tempGridData.horizontalAlignment = GridData.FILL;
        variableSection.setLayoutData(tempGridData);
        variableSection.setLayout(new FillLayout());

        variableComposite = tempToolkit.createComposite(variableSection);
        tempToolkit.paintBordersFor(variableComposite);
        variableSection.setClient(variableComposite);
        variableComposite.setLayout(new FillLayout());

        variableTable = new TableViewer(variableComposite, SWT.FULL_SELECTION);
        variableTable.setContentProvider(new ArrayContentProvider());
        configureTable(variableTable);

        resultComposite = tempToolkit.createComposite(resultSection);
        tempToolkit.paintBordersFor(resultComposite);
        resultSection.setClient(resultComposite);
        resultComposite.setLayout(new FormLayout());

        resultTableComposite = tempToolkit.createComposite(resultComposite);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 5);
        tempFormData.right = new FormAttachment(100, -5);
        tempFormData.top = new FormAttachment(resultComposite, 10);
        tempFormData.bottom = new FormAttachment(resultTableComposite, RESULT_TABLE_HEIGHT, SWT.TOP);
        resultTableComposite.setLayoutData(tempFormData);
        resultTableComposite.setLayout(new FillLayout());

        resultTable = new TableViewer(resultTableComposite, SWT.FULL_SELECTION);
        resultTable.setContentProvider(new ArrayContentProvider());
        configureResultTable(resultTable);

        varUpdateTableComposite = tempToolkit.createComposite(resultComposite);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 5);
        tempFormData.right = new FormAttachment(100, -5);
        tempFormData.top = new FormAttachment(resultComposite, 10);
        tempFormData.bottom = new FormAttachment(varUpdateTableComposite, RESULT_TABLE_HEIGHT, SWT.TOP);
        varUpdateTableComposite.setLayoutData(tempFormData);
        varUpdateTableComposite.setLayout(new FillLayout());

        varUpdateTable = new TableViewer(varUpdateTableComposite, SWT.FULL_SELECTION);
        varUpdateTable.setContentProvider(new ArrayContentProvider());
        configureVarUpdateTable(varUpdateTable);

        resultLine1Name = new Label(resultComposite, SWT.WRAP);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 5);
        tempFormData.right = new FormAttachment(100, -5);
        tempFormData.top = new FormAttachment(0, 4);
        resultLine1Name.setLayoutData(tempFormData);

        resultLine1Border = new Composite(resultComposite, SWT.NONE);
        resultLine1Border.setForeground(resultNeutralColor);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 5);
        tempFormData.right = new FormAttachment(100, -5);
        tempFormData.top = new FormAttachment(resultLine1Name, 2, SWT.BOTTOM);
        tempFormData.bottom = new FormAttachment(resultLine1Border, RESULT_TEXTFIELD_HEIGHT, SWT.TOP);
        resultLine1Border.setLayoutData(tempFormData);
        FillLayout tempFill = new FillLayout();
        tempFill.marginHeight = 1;
        tempFill.marginWidth = 1;
        resultLine1Border.setLayout(tempFill);
        configureTextFieldBorder(resultLine1Border);

        resultLine1Text = new Text(resultLine1Border, SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
        resultLine1Text.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));

        resultLine2Name = new Label(resultComposite, SWT.WRAP);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 5);
        tempFormData.right = new FormAttachment(100, -5);
        tempFormData.top = new FormAttachment(resultLine1Border, 6);
        resultLine2Name.setLayoutData(tempFormData);

        resultLine2Border = new Composite(resultComposite, SWT.NONE);
        resultLine2Border.setForeground(resultNeutralColor);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 5);
        tempFormData.right = new FormAttachment(100, -5);
        tempFormData.top = new FormAttachment(resultLine2Name, 2, SWT.BOTTOM);
        tempFormData.bottom = new FormAttachment(resultLine2Border, RESULT_TEXTFIELD_HEIGHT, SWT.TOP);
        resultLine2Border.setLayoutData(tempFormData);
        tempFill = new FillLayout();
        tempFill.marginHeight = 1;
        tempFill.marginWidth = 1;
        resultLine2Border.setLayout(tempFill);
        configureTextFieldBorder(resultLine2Border);

        resultLine2Text = new Text(resultLine2Border, SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
        resultLine2Text.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));

        resultLine3Name = new Label(resultComposite, SWT.WRAP);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 5);
        tempFormData.right = new FormAttachment(100, -5);
        tempFormData.top = new FormAttachment(resultLine2Border, 6);
        resultLine3Name.setLayoutData(tempFormData);
        resultLine3Name.setText("Extended Result Data");

        resultLine3Border = new Composite(resultComposite, SWT.NONE);
        resultLine3Border.setForeground(resultNeutralColor);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(0, 5);
        tempFormData.right = new FormAttachment(100, -5);
        tempFormData.top = new FormAttachment(resultLine3Name, 2, SWT.BOTTOM);
        tempFormData.bottom = new FormAttachment(resultLine3Border, 0, SWT.TOP);
        resultLine3Border.setLayoutData(tempFormData);
        resultLine3Border.setLayout(new FormLayout());
        configureTextFieldBorder(resultLine3Border);

        resultFailureIcon = new Label(resultComposite, SWT.NONE);
        resultFailureIcon.setImage(resultFailureIconImage);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(50, -24);
        tempFormData.top = new FormAttachment(0, 10);
        tempFormData.width = 48;
        tempFormData.height = 48;
        resultFailureIcon.setLayoutData(tempFormData);

        resultSuccessIcon = new Label(resultComposite, SWT.NONE);
        resultSuccessIcon.setImage(resultSuccessIconImage);
        tempFormData = new FormData();
        tempFormData.right = new FormAttachment(resultFailureIcon, -16);
        tempFormData.top = new FormAttachment(0, 10);
        tempFormData.width = 48;
        tempFormData.height = 48;
        resultSuccessIcon.setLayoutData(tempFormData);

        resultExceptionIcon = new Label(resultComposite, SWT.NONE);
        resultExceptionIcon.setImage(resultExceptionIconImage);
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(resultFailureIcon, 16);
        tempFormData.top = new FormAttachment(0, 10);
        tempFormData.width = 48;
        tempFormData.height = 48;
        resultExceptionIcon.setLayoutData(tempFormData);

        resultSuccessCountLabel = new Label(resultComposite, SWT.CENTER);
        resultSuccessCountLabel.setText("123");
        resultSuccessCountLabel.setForeground(resultSuccessColor);
        FontData[] tempFontData = resultSuccessCountLabel.getFont().getFontData();
        tempFontData[0].setHeight(16);
        tempFontData[0].setStyle(SWT.BOLD);
        resultSuccessCountLabel.setFont(new Font(Display.getCurrent(), tempFontData[0]));
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(resultSuccessIcon, -32, SWT.CENTER);
        tempFormData.top = new FormAttachment(resultSuccessIcon, 4);
        tempFormData.width = 64;
        tempFormData.height = 24;
        resultSuccessCountLabel.setLayoutData(tempFormData);

        resultFailureCountLabel = new Label(resultComposite, SWT.CENTER);
        resultFailureCountLabel.setText("123");
        resultFailureCountLabel.setForeground(resultFailureColor);
        tempFontData = resultFailureCountLabel.getFont().getFontData();
        tempFontData[0].setHeight(16);
        tempFontData[0].setStyle(SWT.BOLD);
        resultFailureCountLabel.setFont(new Font(Display.getCurrent(), tempFontData[0]));
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(resultFailureIcon, -32, SWT.CENTER);
        tempFormData.top = new FormAttachment(resultFailureIcon, 4);
        tempFormData.width = 64;
        tempFormData.height = 24;
        resultFailureCountLabel.setLayoutData(tempFormData);

        resultExceptionCountLabel = new Label(resultComposite, SWT.CENTER);
        resultExceptionCountLabel.setText("123");
        resultExceptionCountLabel.setForeground(resultExceptionColor);
        tempFontData = resultExceptionCountLabel.getFont().getFontData();
        tempFontData[0].setHeight(16);
        tempFontData[0].setStyle(SWT.BOLD);
        resultExceptionCountLabel.setFont(new Font(Display.getCurrent(), tempFontData[0]));
        tempFormData = new FormData();
        tempFormData.left = new FormAttachment(resultExceptionIcon, -32, SWT.CENTER);
        tempFormData.top = new FormAttachment(resultExceptionIcon, 4);
        tempFormData.width = 64;
        tempFormData.height = 24;
        resultExceptionCountLabel.setLayoutData(tempFormData);

        PlatformUI.getWorkbench().getHelpSystem().setHelp(treeViewer.getControl(),
                "de.gebit.integrity.eclipse.viewer");
        attachTreeInteractionListeners();
        makeActions();
        hookContextMenu();
        contributeToActionBars();
        updateDetailPanel(null, null);
    }

    private void configureTable(final TableViewer aTable) {
        aTable.getTable().setHeaderVisible(true);
        aTable.getTable().setLinesVisible(true);

        TableViewerColumn tempColumn = new TableViewerColumn(aTable,
                SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
        tempColumn.getColumn().setText("Name");
        tempColumn.getColumn().setWidth(150);
        tempColumn.getColumn().setResizable(true);
        tempColumn.getColumn().setMoveable(false);
        tempColumn.setLabelProvider(new ColumnLabelProvider() {
            @Override
            public String getText(Object anElement) {
                SetListEntry tempEntry = (SetListEntry) anElement;
                return (String) tempEntry.getAttribute(SetListEntryAttributeKeys.NAME);
            }
        });

        tempColumn = new TableViewerColumn(aTable, SWT.NONE);
        tempColumn.getColumn().setText("Value");
        tempColumn.getColumn().setWidth(150);
        tempColumn.getColumn().setResizable(true);
        tempColumn.getColumn().setMoveable(false);
        tempColumn.setLabelProvider(new ColumnLabelProvider() {
            @Override
            public void update(ViewerCell aCell) {
                SetListEntry tempEntry = (SetListEntry) aCell.getElement();
                aCell.setText((String) tempEntry.getAttribute(SetListEntryAttributeKeys.VALUE));
            }
        });
        // Make the value column editable, mostly to be able to copy out results. See issue #77
        tempColumn.setEditingSupport(new EditingSupport(aTable) {

            @Override
            protected void setValue(Object anElement, Object aValue) {
                // not supported, we don't really want to support editing of result values
            }

            @Override
            protected Object getValue(Object anElement) {
                SetListEntry tempEntry = (SetListEntry) anElement;
                return (String) tempEntry.getAttribute(SetListEntryAttributeKeys.VALUE);
            }

            @Override
            protected CellEditor getCellEditor(Object anElement) {
                return new TextCellEditor(aTable.getTable());
            }

            @Override
            protected boolean canEdit(Object anElement) {
                return true;
            }
        });
    }

    private void configureResultTable(final TableViewer aTable) {
        aTable.getTable().setHeaderVisible(true);
        aTable.getTable().setLinesVisible(true);

        TableViewerColumn tempColumn = new TableViewerColumn(aTable,
                SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
        tempColumn.getColumn().setText("Name");
        tempColumn.getColumn().setWidth(150);
        tempColumn.getColumn().setResizable(true);
        tempColumn.getColumn().setMoveable(false);
        tempColumn.setLabelProvider(new ColumnLabelProvider() {
            @Override
            public String getText(Object anElement) {
                SetListEntry tempEntry = (SetListEntry) anElement;
                return (String) tempEntry.getAttribute(SetListEntryAttributeKeys.NAME);
            }

            @Override
            public Color getBackground(Object anElement) {
                SetListEntry tempEntry = (SetListEntry) anElement;
                Boolean tempSuccess = (Boolean) tempEntry
                        .getAttribute(SetListEntryAttributeKeys.RESULT_SUCCESS_FLAG);
                if (tempSuccess == null) {
                    return super.getBackground(anElement);
                } else if (tempSuccess) {
                    return resultTableSuccessColor;
                } else {
                    return resultTableFailureColor;
                }
            }
        });

        tempColumn = new TableViewerColumn(aTable, SWT.NONE);
        tempColumn.getColumn().setText("Result");
        tempColumn.getColumn().setWidth(150);
        tempColumn.getColumn().setResizable(true);
        tempColumn.getColumn().setMoveable(false);
        tempColumn.setLabelProvider(new ColumnLabelProvider() {
            @Override
            public void update(ViewerCell aCell) {
                SetListEntry tempEntry = (SetListEntry) aCell.getElement();
                aCell.setText((String) tempEntry.getAttribute(SetListEntryAttributeKeys.VALUE));
            }

            @Override
            public Color getBackground(Object anElement) {
                SetListEntry tempEntry = (SetListEntry) anElement;
                Boolean tempSuccess = (Boolean) tempEntry
                        .getAttribute(SetListEntryAttributeKeys.RESULT_SUCCESS_FLAG);
                if (tempSuccess == null) {
                    return super.getBackground(anElement);
                } else if (tempSuccess) {
                    return resultTableSuccessColor;
                } else {
                    return resultTableFailureColor;
                }
            }
        });
        // Make the value column editable, mostly to be able to copy out results. See issue #77
        tempColumn.setEditingSupport(new EditingSupport(aTable) {

            @Override
            protected void setValue(Object anElement, Object aValue) {
                // not supported, we don't really want to support editing of result values
            }

            @Override
            protected Object getValue(Object anElement) {
                SetListEntry tempEntry = (SetListEntry) anElement;
                return (String) tempEntry.getAttribute(SetListEntryAttributeKeys.VALUE);
            }

            @Override
            protected CellEditor getCellEditor(Object anElement) {
                return new TextCellEditor(aTable.getTable());
            }

            @Override
            protected boolean canEdit(Object anElement) {
                return true;
            }
        });

        tempColumn = new TableViewerColumn(aTable, SWT.NONE);
        tempColumn.getColumn().setText("Expected");
        tempColumn.getColumn().setWidth(150);
        tempColumn.getColumn().setResizable(true);
        tempColumn.getColumn().setMoveable(false);
        tempColumn.setLabelProvider(new ColumnLabelProvider() {
            @Override
            public void update(ViewerCell aCell) {
                SetListEntry tempEntry = (SetListEntry) aCell.getElement();
                aCell.setText((String) tempEntry.getAttribute(SetListEntryAttributeKeys.EXPECTED_RESULT));
            }

            @Override
            public Color getBackground(Object anElement) {
                SetListEntry tempEntry = (SetListEntry) anElement;
                Boolean tempSuccess = (Boolean) tempEntry
                        .getAttribute(SetListEntryAttributeKeys.RESULT_SUCCESS_FLAG);
                if (tempSuccess == null) {
                    return super.getBackground(anElement);
                } else if (tempSuccess) {
                    return resultTableSuccessColor;
                } else {
                    return resultTableFailureColor;
                }
            }
        });
        // Make the value column editable, mostly to be able to copy out results. See issue #77
        tempColumn.setEditingSupport(new EditingSupport(aTable) {

            @Override
            protected void setValue(Object anElement, Object aValue) {
                // not supported, we don't really want to support editing of result values
            }

            @Override
            protected Object getValue(Object anElement) {
                SetListEntry tempEntry = (SetListEntry) anElement;
                return (String) tempEntry.getAttribute(SetListEntryAttributeKeys.EXPECTED_RESULT);
            }

            @Override
            protected CellEditor getCellEditor(Object anElement) {
                return new TextCellEditor(aTable.getTable());
            }

            @Override
            protected boolean canEdit(Object anElement) {
                return true;
            }
        });
    }

    private void configureVarUpdateTable(final TableViewer aTable) {
        aTable.getTable().setHeaderVisible(true);
        aTable.getTable().setLinesVisible(true);

        TableViewerColumn tempColumn = new TableViewerColumn(aTable,
                SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
        tempColumn.getColumn().setText("Result");
        tempColumn.getColumn().setWidth(150);
        tempColumn.getColumn().setResizable(true);
        tempColumn.getColumn().setMoveable(false);
        tempColumn.setLabelProvider(new ColumnLabelProvider() {
            @Override
            public String getText(Object anElement) {
                SetListEntry tempEntry = (SetListEntry) anElement;
                return (String) tempEntry.getAttribute(SetListEntryAttributeKeys.PARAMETER_NAME);
            }
        });

        tempColumn = new TableViewerColumn(aTable, SWT.NONE);
        tempColumn.getColumn().setText("Variable");
        tempColumn.getColumn().setWidth(150);
        tempColumn.getColumn().setResizable(true);
        tempColumn.getColumn().setMoveable(false);
        tempColumn.setLabelProvider(new ColumnLabelProvider() {
            @Override
            public String getText(Object anElement) {
                SetListEntry tempEntry = (SetListEntry) anElement;
                return (String) tempEntry.getAttribute(SetListEntryAttributeKeys.VARIABLE_NAME);
            }
        });

        tempColumn = new TableViewerColumn(aTable, SWT.NONE);
        tempColumn.getColumn().setText("Value");
        tempColumn.getColumn().setWidth(150);
        tempColumn.getColumn().setResizable(true);
        tempColumn.getColumn().setMoveable(false);
        tempColumn.setLabelProvider(new ColumnLabelProvider() {
            @Override
            public String getText(Object anElement) {
                SetListEntry tempEntry = (SetListEntry) anElement;
                return (String) tempEntry.getAttribute(SetListEntryAttributeKeys.VALUE);
            }
        });
    }

    private void configureTextFieldBorder(final Composite aBorder) {
        aBorder.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent anEvent) {
                GC tempGC = anEvent.gc;
                tempGC.setForeground(aBorder.getForeground());
                Rectangle tempRect = aBorder.getBounds();

                tempGC.drawRectangle(0, 0, tempRect.width - 1, tempRect.height - 1);
            }
        });
    }

    private void attachTreeInteractionListeners() {
        treeViewer.addSelectionChangedListener(new ISelectionChangedListener() {

            @Override
            public void selectionChanged(SelectionChangedEvent anEvent) {
                if (anEvent.getSelection().isEmpty()) {
                    updateDetailPanel(null, null);
                } else {
                    if (anEvent.getSelection() instanceof TreeSelection) {
                        TreeSelection tempSelection = (TreeSelection) anEvent.getSelection();
                        if (tempSelection.getFirstElement() instanceof SetListEntry) {
                            SetListEntry tempEntry = (SetListEntry) tempSelection.getFirstElement();
                            if (tempEntry.getType() == SetListEntryTypes.COMMENT) {
                                updateDetailPanel(null, null);
                            } else {
                                updateDetailPanel(tempEntry,
                                        (ILabelProvider) ((TreeViewer) anEvent.getSource()).getLabelProvider());
                            }
                        }
                    }
                }
            }
        });

        treeViewer.addDoubleClickListener(new IDoubleClickListener() {

            @Override
            public void doubleClick(DoubleClickEvent anEvent) {
                if (anEvent.getSelection() instanceof TreeSelection) {
                    TreeSelection tempSelection = (TreeSelection) anEvent.getSelection();
                    if (tempSelection.getFirstElement() instanceof SetListEntry) {
                        SetListEntry tempEntry = (SetListEntry) tempSelection.getFirstElement();
                        String tempLink = (String) tempEntry.getAttribute(SetListEntryAttributeKeys.LINK);
                        if (tempLink != null) {
                            urlResolver.parseURL(INTEGRITY_URL_PREFIX + tempLink);
                        }
                    }
                }
            }
        });
    }

    private void hookContextMenu() {
        MenuManager tempMenuMgr = new MenuManager("#PopupMenu");
        tempMenuMgr.setRemoveAllWhenShown(true);
        tempMenuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager aManager) {
                IntegrityTestRunnerView.this.fillContextMenu(aManager);
            }
        });
        Menu tempMenu = tempMenuMgr.createContextMenu(treeViewer.getControl());
        treeViewer.getControl().setMenu(tempMenu);
        getSite().registerContextMenu(tempMenuMgr, treeViewer);
    }

    private void contributeToActionBars() {
        IActionBars tempBars = getViewSite().getActionBars();
        fillLocalPullDown(tempBars.getMenuManager());
        fillLocalToolBar(tempBars.getToolBarManager());
    }

    private void fillLocalPullDown(IMenuManager aManager) {
        aManager.add(executeTestAction);
        aManager.add(executeDebugTestAction);
        aManager.add(shutdownAction);
        aManager.add(configureTestAction);
        aManager.add(new Separator());
        aManager.add(playAction);
        aManager.add(pauseAction);
        aManager.add(stepIntoAction);
        aManager.add(new Separator());
        aManager.add(connectToTestRunnerAction);
    }

    private void fillContextMenu(IMenuManager aManager) {
        if (treeViewer.getSelection().isEmpty()) {
            return;
        }

        if (treeViewer.getSelection() instanceof IStructuredSelection) {
            IStructuredSelection tempSelection = (IStructuredSelection) treeViewer.getSelection();
            if (tempSelection.getFirstElement() instanceof SetListEntry) {
                final SetListEntry tempEntry = (SetListEntry) tempSelection.getFirstElement();

                if (tempEntry.getType() == SetListEntryTypes.TEST
                        || tempEntry.getType() == SetListEntryTypes.CALL) {
                    if (breakpointSet.contains(tempEntry.getId())) {
                        aManager.add(new BreakpointAction(tempEntry.getId(), "Remove Breakpoint",
                                "Removes the breakpoint from the selected step.") {
                            public void run() {
                                if (client == null || !client.isActive()) {
                                    showMessage(
                                            "Sorry, but breakpoints can only be added or removed while connected "
                                                    + "to a (running or paused) test runner instance!");
                                } else {
                                    client.deleteBreakpoint(tempEntry.getId());
                                }
                            }
                        });
                    } else {
                        aManager.add(new BreakpointAction(tempEntry.getId(), "Add Breakpoint",
                                "Adds a breakpoint to the selected step.") {
                            public void run() {
                                if (client == null || !client.isActive()) {
                                    showMessage(
                                            "Sorry, but breakpoints can only be added or removed while connected "
                                                    + "to a (running or paused) test runner instance!");
                                } else {
                                    client.createBreakpoint(tempEntry.getId());
                                }
                            }
                        });
                    }
                }

                String tempLink = (String) tempEntry.getAttribute(SetListEntryAttributeKeys.LINK);
                if (tempLink != null) {
                    aManager.add(new JumpToLinkAction(INTEGRITY_URL_PREFIX + tempLink, "Jump to Script",
                            "Jumps to the position of this element in the test scripts.") {
                        public void run() {
                            urlResolver.parseURL(getURL());
                        }
                    });
                }
            }
        }
    }

    private void fillLocalToolBar(IToolBarManager aManager) {
        // These are still in development...
        aManager.add(executeTestAction);
        aManager.add(executeDebugTestAction);
        aManager.add(shutdownAction);
        aManager.add(configureTestAction);
        aManager.add(new Separator());
        aManager.add(expandAllAction);
        aManager.add(collapseAllAction);
        aManager.add(scrollLockAction);
        aManager.add(new Separator());
        aManager.add(playAction);
        aManager.add(pauseAction);
        aManager.add(stepIntoAction);
        aManager.add(stepOverAction);
        aManager.add(new Separator());
        aManager.add(connectToTestRunnerAction);
    }

    private void makeActions() {
        connectToTestRunnerAction = new Action() {
            private String lastHostname = "localhost";

            public void run() {
                if (client == null || !client.isActive()) {
                    InputDialog tempDialog = new InputDialog(getSite().getShell(), "Connect to test runner",
                            "Please enter the hostname or IP address to connect to", lastHostname, null);
                    if (tempDialog.open() == IStatus.OK && tempDialog.getValue() != null
                            && tempDialog.getValue().length() > 0) {
                        lastHostname = tempDialog.getValue();
                        String tempHost = lastHostname;
                        int tempPort = IntegrityRemotingConstants.DEFAULT_PORT;
                        if (tempHost.contains(":")) {
                            try {
                                tempPort = Integer.parseInt(tempHost.substring(tempHost.indexOf(':') + 1));
                            } catch (NumberFormatException exc) {
                                showMessage("The port number given is illegal.");
                                return;
                            } catch (IndexOutOfBoundsException exc) {
                                showMessage("No port number given.");
                                return;
                            }
                            tempHost = tempHost.substring(0, tempHost.indexOf(':'));
                        }
                        connectToTestRunnerAsync(tempHost, tempPort);
                    }
                } else {
                    disconnectFromTestRunner();
                }
            }
        };

        playAction = new Action() {
            public void run() {
                client.controlExecution(ExecutionCommands.RUN);
                updateStatus("Continuing test execution...");
            }
        };
        playAction.setText("Start or continue test execution");
        playAction.setToolTipText("Continues test execution if currently paused.");
        playAction.setImageDescriptor(Activator.getImageDescriptor("icons/play_enabled.gif"));
        playAction.setDisabledImageDescriptor(Activator.getImageDescriptor("icons/play_disabled.gif"));

        pauseAction = new Action() {
            public void run() {
                client.controlExecution(ExecutionCommands.PAUSE);
                updateStatus("Pausing test execution...");
            }
        };
        pauseAction.setText("Pause test execution");
        pauseAction
                .setToolTipText("Interrupts test execution; the currently running test will be finished though.");
        pauseAction.setImageDescriptor(Activator.getImageDescriptor("icons/pause_enabled.gif"));
        pauseAction.setDisabledImageDescriptor(Activator.getImageDescriptor("icons/pause_disabled.gif"));

        stepIntoAction = new Action() {
            public void run() {
                client.controlExecution(ExecutionCommands.STEP_INTO);
                updateStatus("Executing single step...");
            }
        };
        stepIntoAction.setText("Single step / step into");
        stepIntoAction.setToolTipText("Executes a single test or call.");
        stepIntoAction.setImageDescriptor(Activator.getImageDescriptor("icons/stepinto_enabled.gif"));
        stepIntoAction.setDisabledImageDescriptor(Activator.getImageDescriptor("icons/stepinto_disabled.gif"));

        stepOverAction = new Action() {
            @SuppressWarnings("unchecked")
            public void run() {
                SetListEntry tempTarget = null;
                SetListEntry tempCurrentSuite = setList.getParent(setList.getEntryInExecution());
                if (tempCurrentSuite != null) {
                    SetListEntry tempOuterSuite = setList.getParent(tempCurrentSuite);
                    while (tempOuterSuite != null && tempTarget == null) {
                        List<Integer> tempSetupStatements = (List<Integer>) tempOuterSuite
                                .getAttribute(SetListEntryAttributeKeys.SETUP);
                        List<Integer> tempSuiteStatements = (List<Integer>) tempOuterSuite
                                .getAttribute(SetListEntryAttributeKeys.STATEMENTS);
                        List<Integer> tempTeardownStatements = (List<Integer>) tempOuterSuite
                                .getAttribute(SetListEntryAttributeKeys.TEARDOWN);
                        List<Integer> tempAllStatements = new LinkedList<Integer>();
                        if (tempSetupStatements != null) {
                            tempAllStatements.addAll(tempSetupStatements);
                        }
                        if (tempSuiteStatements != null) {
                            tempAllStatements.addAll(tempSuiteStatements);
                        }
                        if (tempTeardownStatements != null) {
                            tempAllStatements.addAll(tempTeardownStatements);
                        }

                        int tempPos = tempAllStatements.indexOf(tempCurrentSuite.getId()) + 1;
                        if (tempPos == 0 || tempPos >= tempAllStatements.size()) {
                            tempOuterSuite = setList.getParent(tempOuterSuite);
                        } else {
                            tempTarget = setList.resolveReference(tempAllStatements.get(tempPos));
                        }
                    }
                }

                if (tempTarget != null) {
                    while (tempTarget != null && tempTarget.getType() != SetListEntryTypes.CALL
                            && tempTarget.getType() != SetListEntryTypes.TEST) {
                        tempTarget = setList.resolveReference(tempTarget.getId() + 1);
                    }
                }

                if (tempTarget != null) {
                    client.createBreakpoint(tempTarget.getId());
                    client.controlExecution(ExecutionCommands.RUN);
                    updateStatus("Executing until end of current suite...");
                } else {
                    client.controlExecution(ExecutionCommands.RUN);
                    updateStatus("Continuing test execution...");
                }
            }
        };
        stepOverAction.setText("Single step / step into");
        stepOverAction.setToolTipText("Places a breakpoint after the current suite call and continues execution.");
        stepOverAction.setImageDescriptor(Activator.getImageDescriptor("icons/stepover_enabled.gif"));
        stepOverAction.setDisabledImageDescriptor(Activator.getImageDescriptor("icons/stepover_disabled.gif"));

        executeTestAction = new Action() {
            public void run() {
                if (launchConfiguration != null) {
                    try {
                        executeTestAction.setEnabled(false);
                        executeDebugTestAction.setEnabled(false);
                        final ILaunch tempLaunch = launchConfiguration.launch(ILaunchManager.RUN_MODE, null);
                        new AutoConnectThread(tempLaunch).start();
                    } catch (CoreException exc) {
                        showException(exc);
                    }
                }
            }
        };
        executeTestAction.setText("Launch test application");
        executeTestAction.setImageDescriptor(Activator.getImageDescriptor("icons/exec_enabled.gif"));

        executeDebugTestAction = new Action() {
            public void run() {
                if (launchConfiguration != null) {
                    try {
                        executeTestAction.setEnabled(false);
                        executeDebugTestAction.setEnabled(false);
                        final ILaunch tempLaunch = launchConfiguration.launch(ILaunchManager.DEBUG_MODE, null);
                        new AutoConnectThread(tempLaunch).start();
                    } catch (CoreException exc) {
                        showException(exc);
                    }
                }
            }
        };
        executeDebugTestAction.setText("Launch test application (debug mode)");
        executeDebugTestAction.setImageDescriptor(Activator.getImageDescriptor("icons/exec_debug_enabled.gif"));
        updateLaunchButtonState();

        shutdownAction = new Action() {
            public void run() {
                client.requestShutdown();
                updateStatus("Requested immediate shutdown...");
            };
        };
        shutdownAction.setText("Shutdown running test application");
        shutdownAction.setToolTipText("Requests the test application to shut down immediately.");
        shutdownAction.setImageDescriptor(Activator.getImageDescriptor("icons/shutdown.gif"));
        shutdownAction.setEnabled(false);

        configureTestAction = new Action() {
            @Override
            public void run() {
                TestActionConfigurationDialog tempDialog = new TestActionConfigurationDialog(getSite().getShell());
                if (tempDialog.open() == Dialog.OK) {
                    launchConfiguration = tempDialog.getSelectedConfiguration();
                    updateLaunchButtonState();
                }
            }
        };
        configureTestAction.setText("Configure test application");
        configureTestAction.setToolTipText("Configures the test run configuration(s) to launch.");
        configureTestAction.setImageDescriptor(Activator.getImageDescriptor("icons/exec_config_enabled.gif"));

        expandAllAction = new Action() {
            @Override
            public void run() {
                lastExpansionLevel++;
                ((TestTreeContentProvider) treeViewer.getContentProvider()).expandToLevel(lastExpansionLevel + 1);
            }
        };
        expandAllAction.setText("Expand all (one level)");
        expandAllAction.setToolTipText("Expands all nodes one level deeper (except table tests).");
        expandAllAction.setImageDescriptor(Activator.getImageDescriptor("icons/expandall.gif"));

        collapseAllAction = new Action() {
            @Override
            public void run() {
                lastExpansionLevel = 0;
                treeViewer.collapseAll();
            }
        };
        collapseAllAction.setText("Collapse all");
        collapseAllAction.setToolTipText("Collapses all nodes.");
        collapseAllAction.setImageDescriptor(Activator.getImageDescriptor("icons/collapseall.gif"));

        scrollLockAction = new Action("Toggle scroll lock", IAction.AS_CHECK_BOX) {
            @Override
            public void run() {
                scrollLockActive = isChecked();
            };
        };
        scrollLockAction.setToolTipText("Toggles the scroll lock setting.");
        scrollLockAction.setImageDescriptor(Activator.getImageDescriptor("icons/scrolllock.gif"));

        updateActionStatus(null);
    }

    private void updateLaunchButtonState() {
        String tempText;
        boolean tempEnabled;
        if (launchConfiguration != null) {
            tempText = "Launches the test run configuration '" + launchConfiguration.getName() + "'";
            tempEnabled = true;
        } else {
            tempText = "Launches the test run configuration";
            tempEnabled = false;
        }

        executeTestAction.setEnabled(tempEnabled);
        executeTestAction.setToolTipText(tempText);
        executeDebugTestAction.setEnabled(tempEnabled);
        executeDebugTestAction.setToolTipText(tempText + " (debug mode)");
    }

    private boolean isConnected() {
        return (client != null && client.isActive());
    }

    private void updateActionStatus(final ExecutionStates anExecutionState) {
        Runnable tempRunnable = updateActionStatusRunnable(anExecutionState);
        if (Display.getCurrent() != null) {
            Display.getCurrent().syncExec(tempRunnable);
        } else {
            Display.getDefault().asyncExec(tempRunnable);
        }
    }

    private Runnable updateActionStatusRunnable(final ExecutionStates anExecutionState) {
        return new Runnable() {

            @Override
            public void run() {
                if (!isConnected()) {
                    connectToTestRunnerAction.setText("Connect to test runner");
                    connectToTestRunnerAction.setToolTipText("Connects to a local or remote test runner");
                    connectToTestRunnerAction.setImageDescriptor(Activator.getImageDescriptor("icons/connect.gif"));
                    playAction.setEnabled(false);
                    pauseAction.setEnabled(false);
                    stepIntoAction.setEnabled(false);
                    stepOverAction.setEnabled(false);
                    shutdownAction.setEnabled(false);
                } else {
                    connectToTestRunnerAction.setText("Disconnect from test runner");
                    connectToTestRunnerAction
                            .setToolTipText("Disconnects the client from the currently connected test runner");
                    connectToTestRunnerAction
                            .setImageDescriptor(Activator.getImageDescriptor("icons/disconnect.gif"));
                    if (anExecutionState == null) {
                        playAction.setEnabled(false);
                        pauseAction.setEnabled(false);
                        stepIntoAction.setEnabled(false);
                        stepOverAction.setEnabled(false);
                        shutdownAction.setEnabled(false);
                    } else {
                        shutdownAction.setEnabled(true);
                        switch (anExecutionState) {
                        case BLOCKED:
                            playAction.setEnabled(true);
                            pauseAction.setEnabled(false);
                            stepIntoAction.setEnabled(true);
                            stepOverAction.setEnabled(true);
                            updateStatusRunnable("Waiting for execution start").run();
                            break;
                        case PAUSED:
                            playAction.setEnabled(true);
                            pauseAction.setEnabled(false);
                            stepIntoAction.setEnabled(true);
                            stepOverAction.setEnabled(true);
                            updateStatusRunnable(
                                    determineIntermediateTestResultStatusString("Paused test execution (", ")"))
                                            .run();
                            break;
                        case RUNNING:
                            playAction.setEnabled(false);
                            pauseAction.setEnabled(true);
                            stepIntoAction.setEnabled(false);
                            stepOverAction.setEnabled(false);
                            updateStatusRunnable(determineIntermediateTestResultStatusString("Running tests: ", ""))
                                    .run();
                            break;
                        case FINALIZING:
                            playAction.setEnabled(false);
                            pauseAction.setEnabled(false);
                            stepIntoAction.setEnabled(false);
                            stepOverAction.setEnabled(false);
                            updateStatusRunnable(
                                    determineIntermediateTestResultStatusString("Finalizing test results (", ")"))
                                            .run();
                            break;
                        case ENDED:
                            playAction.setEnabled(false);
                            pauseAction.setEnabled(false);
                            stepIntoAction.setEnabled(false);
                            stepOverAction.setEnabled(false);
                            updateStatusRunnable(
                                    determineIntermediateTestResultStatusString("Test execution finished (", ")"))
                                            .run();
                            break;
                        default:
                            break;
                        }
                    }
                }
            }
        };
    }

    private String determineIntermediateTestResultStatusString(String aPrefix, String aSuffix) {
        StringBuilder tempBuilder = new StringBuilder(aPrefix);

        if (setList != null) {
            tempBuilder.append(setList.getNumberOfEntriesInResultState(SetListEntryResultStates.SUCCESSFUL));
            tempBuilder.append(" successful, ");
            tempBuilder.append(setList.getNumberOfEntriesInResultState(SetListEntryResultStates.FAILED));
            tempBuilder.append(" failures, ");
            tempBuilder.append(setList.getNumberOfEntriesInResultState(SetListEntryResultStates.EXCEPTION));
            tempBuilder.append(" exceptions");
        }

        tempBuilder.append(aSuffix);
        return tempBuilder.toString();
    }

    private void hideResultComposite(Composite aComposite) {
        aComposite.setVisible(false);
        ((FormData) aComposite.getLayoutData()).bottom.offset = 0;
    }

    private void showResultComposite(Composite aComposite, int aHeight) {
        aComposite.setVisible(true);
        ((FormData) aComposite.getLayoutData()).bottom.offset = aHeight;
    }

    // SUPPRESS CHECKSTYLE MethodLength
    private void updateDetailPanel(SetListEntry anEntry, ILabelProvider aProvider) {
        fixtureLink.setVisible(false);
        forkLabel.setVisible(false);
        hideResultComposite(resultTableComposite);
        hideResultComposite(varUpdateTableComposite);
        resultLine1Name.setVisible(false);
        hideResultComposite(resultLine1Border);
        resultLine1Text.setText("");
        resultLine2Name.setVisible(false);
        hideResultComposite(resultLine2Border);
        resultLine3Name.setVisible(false);
        resultLine3Border.setVisible(false);
        for (Control tempChild : resultLine3Border.getChildren()) {
            tempChild.dispose();
        }

        resultSuccessIcon.setVisible(false);
        resultFailureIcon.setVisible(false);
        resultExceptionIcon.setVisible(false);
        resultSuccessCountLabel.setVisible(false);
        resultFailureCountLabel.setVisible(false);
        resultExceptionCountLabel.setVisible(false);
        if (anEntry == null) {
            details.setText("Details");
            details.setImage(null);
        } else {
            details.setImage(aProvider.getImage(anEntry));
            if (anEntry.getType() == SetListEntryTypes.SUITE) {
                details.setText((String) anEntry.getAttribute(SetListEntryAttributeKeys.NAME));
            } else {
                details.setText((String) anEntry.getAttribute(SetListEntryAttributeKeys.DESCRIPTION));
                if (anEntry.getType() == SetListEntryTypes.RESULT) {
                    fixtureLink.setText(
                            (String) setList.getParent(anEntry).getAttribute(SetListEntryAttributeKeys.FIXTURE));
                } else {
                    fixtureLink.setText((String) anEntry.getAttribute(SetListEntryAttributeKeys.FIXTURE));
                }
                fixtureLink.setVisible(true);
            }

            String[] tempForkName = setList.getForkExecutingEntry(anEntry);
            if (tempForkName != null) {
                String tempLabelText = "executed on fork '" + tempForkName[0] + "'";
                if (tempForkName[1] != null) {
                    tempLabelText += " (" + tempForkName[1] + ")";
                }
                forkLabel.setText(tempLabelText);
                forkLabel.setVisible(true);
            }

            List<SetListEntry> tempVariables = setList.resolveReferences(anEntry,
                    SetListEntryAttributeKeys.VARIABLE_DEFINITIONS);
            if (tempVariables.size() > 0) {
                variableTable.setInput(tempVariables);
            } else {
                variableTable.setInput(null);
            }

            List<SetListEntry> tempParameters = setList.resolveReferences(anEntry,
                    SetListEntryAttributeKeys.PARAMETERS);
            if (tempParameters.size() > 0) {
                parameterTable.setInput(tempParameters);
            } else {
                parameterTable.setInput(null);
            }

            SetListEntry tempResultEntry = null;
            if (anEntry.getType() == SetListEntryTypes.TABLETEST) {
                // High-level table test summary information is stored in the
                // tabletest entry itself
                tempResultEntry = anEntry;
            } else if (anEntry.getType() == SetListEntryTypes.RESULT) {
                // result entries are results by themselves ;-)
                tempResultEntry = anEntry;
            } else {
                tempResultEntry = setList.resolveReferences(anEntry, SetListEntryAttributeKeys.RESULT).get(0);
            }

            if (tempResultEntry != null) {
                switch (anEntry.getType()) {
                case SUITE:
                case TABLETEST:
                    if (tempResultEntry.getAttribute(SetListEntryAttributeKeys.SUCCESS_COUNT) != null) {
                        int tempSuccessCount = (Integer) tempResultEntry
                                .getAttribute(SetListEntryAttributeKeys.SUCCESS_COUNT);
                        int tempFailureCount = (Integer) tempResultEntry
                                .getAttribute(SetListEntryAttributeKeys.FAILURE_COUNT);
                        int tempExceptionCount = (Integer) tempResultEntry
                                .getAttribute(SetListEntryAttributeKeys.TEST_EXCEPTION_COUNT)
                                + (Integer) tempResultEntry
                                        .getAttribute(SetListEntryAttributeKeys.CALL_EXCEPTION_COUNT);

                        resultSuccessCountLabel.setText(Integer.toString(tempSuccessCount));
                        resultFailureCountLabel.setText(Integer.toString(tempFailureCount));
                        resultExceptionCountLabel.setText(Integer.toString(tempExceptionCount));

                        resultSuccessIcon.setVisible(true);
                        resultFailureIcon.setVisible(true);
                        resultExceptionIcon.setVisible(true);
                        resultSuccessCountLabel.setVisible(true);
                        resultFailureCountLabel.setVisible(true);
                        resultExceptionCountLabel.setVisible(true);
                    } else {
                        resultLine1Name.setText("No results available - please run the tests first.");
                        resultLine1Name.setVisible(true);
                    }
                    break;
                case TEST:
                case RESULT:
                    @SuppressWarnings("unchecked")
                    List<SetListEntry> tempComparisonEntries = setList.resolveReferences(
                            ((List<Integer>) tempResultEntry.getAttribute(SetListEntryAttributeKeys.COMPARISONS)));

                    if (tempComparisonEntries.size() > 1) {
                        resultTable.setInput(tempComparisonEntries);
                        showResultComposite(resultTableComposite, RESULT_TABLE_HEIGHT);
                    } else {
                        SetListEntry tempComparisonEntry = tempComparisonEntries.get(0);
                        resultLine2Name.setText("Expected value: ");
                        resultLine2Text.setText((String) tempComparisonEntry
                                .getAttribute(SetListEntryAttributeKeys.EXPECTED_RESULT));
                        resultLine2Border.setForeground(resultNeutralColor);
                        resultLine2Name.setVisible(true);
                        showResultComposite(resultLine2Border, RESULT_TEXTFIELD_HEIGHT);

                        if (tempResultEntry.getAttribute(SetListEntryAttributeKeys.EXCEPTION) != null) {
                            resultLine1Name.setText("Exception occurred while running the test fixture:");
                            resultLine1Text.setText(
                                    (String) tempResultEntry.getAttribute(SetListEntryAttributeKeys.EXCEPTION));
                            resultLine1Border.setForeground(resultExceptionColor);
                            resultLine1Name.setVisible(true);
                            showResultComposite(resultLine1Border, RESULT_TEXTFIELD_HEIGHT);
                        } else {
                            if (tempComparisonEntry
                                    .getAttribute(SetListEntryAttributeKeys.RESULT_SUCCESS_FLAG) != null) {
                                resultLine1Name.setText("Result returned by the test fixture: ");
                                String tempResult = (String) tempComparisonEntry
                                        .getAttribute(SetListEntryAttributeKeys.VALUE);
                                resultLine1Text.setText(tempResult == null ? "null" : tempResult);
                                if (tempComparisonEntry
                                        .getAttribute(SetListEntryAttributeKeys.RESULT_SUCCESS_FLAG) != null) {
                                    if (Boolean.TRUE.equals(tempComparisonEntry
                                            .getAttribute(SetListEntryAttributeKeys.RESULT_SUCCESS_FLAG))) {
                                        resultLine1Border.setForeground(resultSuccessColor);
                                    } else {
                                        resultLine1Border.setForeground(resultFailureColor);
                                    }
                                }
                                showResultComposite(resultLine1Border, RESULT_TEXTFIELD_HEIGHT);
                            } else {
                                resultLine1Name.setText("No result available - please run the tests first.");
                            }
                            resultLine1Name.setVisible(true);
                        }
                    }
                    break;
                case CALL:
                    if (tempResultEntry.getAttribute(SetListEntryAttributeKeys.RESULT_SUCCESS_FLAG) != null) {
                        if (tempResultEntry.getAttribute(SetListEntryAttributeKeys.EXCEPTION) != null) {
                            resultLine1Name.setText("Exception occurred while running the test fixture:");
                            resultLine1Text.setText(
                                    (String) tempResultEntry.getAttribute(SetListEntryAttributeKeys.EXCEPTION));
                            resultLine1Border.setForeground(resultExceptionColor);
                            showResultComposite(resultLine1Border, RESULT_TEXTFIELD_HEIGHT);
                        } else {
                            List<SetListEntry> tempVarUpdates = setList.resolveReferences(tempResultEntry,
                                    SetListEntryAttributeKeys.VARIABLE_UPDATES);

                            if (tempVarUpdates.size() == 1) {
                                String tempResultValue = (String) tempVarUpdates.get(0)
                                        .getAttribute(SetListEntryAttributeKeys.VALUE);
                                String tempTargetVariable = (String) tempVarUpdates.get(0)
                                        .getAttribute(SetListEntryAttributeKeys.VARIABLE_NAME);
                                if (tempTargetVariable != null) {
                                    tempResultValue += "  " + tempTargetVariable;
                                }
                                resultLine1Name.setText("Result returned by the fixture:");
                                resultLine1Text.setText(tempResultValue);
                                resultLine1Border.setForeground(resultNeutralColor);
                                showResultComposite(resultLine1Border, RESULT_TEXTFIELD_HEIGHT);
                            } else if (tempVarUpdates.size() > 1) {
                                varUpdateTable.setInput(tempVarUpdates);
                                showResultComposite(varUpdateTableComposite, RESULT_TABLE_HEIGHT);
                            } else {
                                resultLine1Name.setText("No result returned by the fixture.");
                            }
                        }
                    } else {
                        resultLine1Name.setText("No result available - please run the tests first.");
                    }
                    resultLine1Name.setVisible(true);
                    break;
                default:
                    break;
                }
            }

            Object[] tempExtendedResults = (Object[]) anEntry
                    .getAttribute(SetListEntryAttributeKeys.EXTENDED_RESULT_DATA);
            if (tempExtendedResults != null && tempExtendedResults.length > 0) {
                int tempSize = 0;

                for (Object tempExtendedResultArray : tempExtendedResults) {
                    if (tempSize > 0) {
                        tempSize += EXTENDED_RESULT_SPACING;
                    }

                    tempSize += createExtendedResultContainer(resultLine3Border, tempSize + 1,
                            (Object[]) tempExtendedResultArray);
                }

                resultLine3Name.setVisible(true);
                resultLine3Border.setVisible(true);
                showResultComposite(resultLine3Border, tempSize + 2);
                resultComposite.layout(true, true);
            } else {
                hideResultComposite(resultLine3Border);
            }
        }

        details.layout(true, true);
        details.reflow(false);
        details.redraw();
    }

    private int createExtendedResultContainer(Composite aTargetComposite, int aStartingPosition,
            Object[] someExtendedResultData) {
        String tempTitle = (String) someExtendedResultData[0];
        Object tempData = someExtendedResultData[1];

        int tempTitleSize = 0;
        int tempContentSize = 0;
        Control tempContentContainer = null;

        // First, create the title.
        if (tempTitle != null) {
            tempTitleSize = 18;

            Composite tempResultTitleContainer = new Composite(aTargetComposite, SWT.NONE);
            tempResultTitleContainer.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_BLACK));

            FormData tempFormData = new FormData();
            tempFormData.left = new FormAttachment(0, 1);
            tempFormData.right = new FormAttachment(100, 1);
            tempFormData.top = new FormAttachment(aTargetComposite, aStartingPosition);
            tempFormData.bottom = new FormAttachment(aTargetComposite, aStartingPosition + tempTitleSize,
                    SWT.BOTTOM);
            tempResultTitleContainer.setLayoutData(tempFormData);
            FillLayout tempFillLayout = new FillLayout();
            tempFillLayout.marginWidth = 2;
            tempFillLayout.marginHeight = 2;
            tempResultTitleContainer.setLayout(tempFillLayout);
            tempResultTitleContainer.setVisible(true);

            Label tempResultTitleField = new Label(tempResultTitleContainer, SWT.BOLD);
            tempResultTitleField.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));
            tempResultTitleField.setText(tempTitle);
            tempResultTitleField.setVisible(true);
        }

        // Now, see what we have to display, create the appropriate control and set everything up
        if (tempData instanceof String) { // This covers strings and hypertext strings
            Text tempResultTextField = new Text(aTargetComposite, SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
            tempResultTextField.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));
            tempResultTextField.setText((String) tempData);

            tempContentSize = EXTENDED_RESULT_TEXTFIELD_HEIGHT;
            tempContentContainer = tempResultTextField;
        } else if (tempData instanceof byte[]) { // Covers images
            Image tempImage = new Image(Display.getCurrent(), new ByteArrayInputStream((byte[]) tempData));
            Label tempImageLabel = new Label(aTargetComposite, SWT.NONE);
            tempImageLabel.setImage(tempImage);
            tempImageLabel.setAlignment(SWT.CENTER);

            tempContentSize = tempImage.getBounds().height + EXTENDED_RESULT_IMAGE_SPACING;
            tempContentContainer = tempImageLabel;
        }

        // Finally, place the control onto its parent
        if (tempContentContainer != null) {
            FormData tempFormData = new FormData();
            tempFormData.left = new FormAttachment(0, 1);
            tempFormData.right = new FormAttachment(100, -1);
            tempFormData.top = new FormAttachment(aTargetComposite, aStartingPosition + tempTitleSize);
            tempFormData.bottom = new FormAttachment(aTargetComposite,
                    aStartingPosition + tempTitleSize + tempContentSize, SWT.BOTTOM);
            tempContentContainer.setLayoutData(tempFormData);
            tempContentContainer.setVisible(true);
        }

        return tempTitleSize + tempContentSize;
    }

    private void showMessage(final String aMessage) {
        Runnable tempRunnable = new Runnable() {
            @Override
            public void run() {
                MessageDialog.openInformation(treeViewer.getControl().getShell(), "Integrity Test Control",
                        aMessage);
            }
        };

        Display.getDefault().asyncExec(tempRunnable);
    }

    private void showException(final Exception anException) {
        Runnable tempRunnable = new Runnable() {
            @Override
            public void run() {
                MessageDialog.openError(treeViewer.getControl().getShell(), "Integrity Test Runner",
                        anException.getLocalizedMessage());
            }
        };

        Display.getDefault().asyncExec(tempRunnable);
    }

    private void updateStatus(final String aStatus) {
        Display.getDefault().asyncExec(updateStatusRunnable(aStatus));
    }

    private Runnable updateStatusRunnable(final String aStatus) {
        return new Runnable() {

            @Override
            public void run() {
                treeContainer.setText(aStatus);
            }
        };
    }

    /**
     * Passing the focus request to the viewer's control.
     */
    public void setFocus() {
        treeViewer.getControl().setFocus();
    }

    private void connectToTestRunnerAsync(final String aHost, final int aPort) {
        new Thread("Test Runner Connect Thread") {

            @Override
            public void run() {
                try {
                    connectToTestRunner(aHost, aPort);
                    return;
                } catch (UnknownHostException exc) {
                    showMessage("Target host name '" + aHost + "' could not be resolved.");
                } catch (IOException exc) {
                    showMessage("Error while connecting to '" + aHost + "': " + exc.getMessage());
                }
            }

        }.start();
    }

    private void connectToTestRunner(final String aHost, final int aPort) throws UnknownHostException, IOException {
        updateStatus("Connecting...");
        boolean tempSuccessful = false;
        try {
            client = new IntegrityRemotingClient(aHost, aPort, new RemotingListener(), null);
            tempSuccessful = true;
            updateStatus("Connected, downloading test data...");
        } finally {
            if (!tempSuccessful) {
                updateStatus("Not connected");
            }
        }
    }

    private void disconnectFromTestRunner() {
        client.close();
        client = null;
        updateActionStatus(null);
        updateStatus("Not connected");
    }

    private void jumpToJavaMethod(String aJavaClassAndMethod) {
        Matcher tempMatcher = Pattern.compile("([^#]*)\\.([^#]*)#(.*)").matcher(aJavaClassAndMethod);
        if (tempMatcher.matches()) {
            final String tempPackageName = tempMatcher.group(1);
            String tempClassName = tempMatcher.group(2);
            final String tempMethodName = tempMatcher.group(3);

            SearchPattern tempPattern = SearchPattern.createPattern(tempClassName, IJavaSearchConstants.TYPE,
                    IJavaSearchConstants.DECLARATIONS,
                    SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE);
            IJavaSearchScope tempScope = SearchEngine.createWorkspaceScope();
            SearchRequestor tempRequestor = new SearchRequestor() {

                @Override
                public void acceptSearchMatch(SearchMatch aMatch) throws CoreException {
                    IType tempType = (IType) aMatch.getElement();
                    if (tempPackageName.equals(tempType.getPackageFragment().getElementName())) {
                        for (IMethod tempMethod : tempType.getMethods()) {
                            if (tempMethodName.equals(tempMethod.getElementName())) {
                                JavaUI.openInEditor(tempMethod);
                                return;
                            }
                        }
                    }
                }
            };

            SearchEngine tempSearchEngine = new SearchEngine();
            try {
                tempSearchEngine.search(tempPattern,
                        new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }, tempScope,
                        tempRequestor, null);
            } catch (CoreException exc) {
                exc.printStackTrace();
            }
        }
    }

    private void performTreeSearch() {
        String tempSearchQuery = searchTextField.getText();

        if (tempSearchQuery.length() < 3) {
            clearTreeSearchResult();
        } else {
            prepareTreeSearch();

            currentSearchResultPosition = null;

            if (MAGIC_SEARCH_TERM_FAILURES.equals(tempSearchQuery)) {
                currentSearchResult = setListSearch.findUnsuccessfulEntries(false);
            } else {
                currentSearchResult = setListSearch.findEntries(tempSearchQuery);
            }

            if (currentSearchResult.size() > 0) {
                currentSearchResultPosition = 0;
                jumpToCurrentSearchResult();
            }
            updateTreeSearchLabel();
        }
    }

    private boolean performTreeSearchForFailures() {
        if (MAGIC_SEARCH_TERM_FAILURES.equals(searchTextField.getText())) {
            return false;
        }

        searchTextField.setText(MAGIC_SEARCH_TERM_FAILURES);
        performTreeSearch();

        return true;
    }

    private void prepareTreeSearch() {
        if (setListSearch == null) {
            setListSearch = new SetListSearch(setList);
        }
    }

    private void clearTreeSearchResult() {
        currentSearchResultPosition = null;
        currentSearchResult = null;
        searchPositionLabel.setText("");
    }

    private void updateTreeSearchLabel() {
        if (currentSearchResult == null) {
            searchPositionLabel.setText("");
        } else {
            if (currentSearchResult.size() == 0) {
                searchPositionLabel.setText("no matches");
            } else {
                String tempCurrentPosition = (currentSearchResultPosition == null ? "?"
                        : Integer.toString(currentSearchResultPosition + 1));
                searchPositionLabel.setText(tempCurrentPosition + " / " + currentSearchResult.size());
            }
        }
    }

    private void jumpToPreviousSearchResult() {
        if (currentSearchResult != null && currentSearchResultPosition != null) {
            currentSearchResultPosition--;
            if (currentSearchResultPosition <= 0) {
                currentSearchResultPosition = 0;
            }
            jumpToCurrentSearchResult();
        }
    }

    private void jumpToNextSearchResult() {
        if (currentSearchResult != null && currentSearchResultPosition != null) {
            currentSearchResultPosition++;
            if (currentSearchResultPosition >= currentSearchResult.size()) {
                currentSearchResultPosition = 0;
            }
            jumpToCurrentSearchResult();
        }
    }

    private void jumpToCurrentSearchResult() {
        if (currentSearchResultPosition != null && currentSearchResult != null && currentSearchResultPosition >= 0
                && currentSearchResultPosition < currentSearchResult.size()) {
            SetListEntry tempEntry = currentSearchResult.get(currentSearchResultPosition);
            treeViewer.setSelection(new StructuredSelection(tempEntry));
            updateTreeSearchLabel();
        }
    }

    private void updateSetList(SetList aNewSetList) {
        setList = aNewSetList;
        breakpointSet.clear();
        setListSearch = null;

        Display.getDefault().asyncExec(new Runnable() {
            @Override
            public void run() {
                searchTextField.setText("");
                treeViewer.setSelection(null);
                treeViewer.setInput(null);

                executionProgress.setSetList(setList);
                executionProgress.redraw();

                // the following will automatically dispose the old provider!
                treeViewer.setLabelProvider(
                        new TestTreeLabelProvider(setList, breakpointSet, Display.getCurrent(), treeViewer));

                // the drawer must be manually disposed
                if (viewerContentDrawer != null) {
                    viewerContentDrawer.dispose(treeViewer.getTree());
                }
                viewerContentDrawer = new TestTreeContentDrawer(setList, breakpointSet, Display.getCurrent());
                viewerContentDrawer.attachToTree(treeViewer.getTree());

                treeViewer.setInput(setList);

                ((TestTreeContentProvider) treeViewer.getContentProvider()).expandToLevel(lastExpansionLevel + 1);

                updateStatus("Connected and ready");
            }
        });
    }

    private abstract class StatusUpdateRunnable implements Runnable {

        /**
         * Whether this update may be skipped to improve performance.
         */
        private boolean skippable;

        public StatusUpdateRunnable(boolean aSkippable) {
            skippable = aSkippable;
        }

        public boolean isSkippable() {
            return skippable;
        }

    }

    private class RemotingListener implements IntegrityRemotingClientListener {

        /**
         * Used to measure the connected time.
         */
        private long connectionTimestamp;

        @Override
        public void onConnectionSuccessful(IntegrityRemotingVersionMessage aRemoteVersion, Endpoint anEndpoint) {
            // request set list baseline and execution state
            anEndpoint.sendMessage(new SetListBaselineMessage(null));
            anEndpoint.sendMessage(new ExecutionStateMessage(null));
            updateActionStatus(client.getExecutionState());
            connectionTimestamp = System.nanoTime();

            // Initiate the status update timer. See issue #80 and timer javadoc documentation.
            statusUpdateTimer = new Timer("Integrity Test Runner Status Update Timer");
            statusUpdateTimerTask = new TimerTask() {

                private Timer associatedTimer = statusUpdateTimer;

                @Override
                public void run() {
                    StatusUpdateRunnable tempRunnable = statusUpdateQueue.poll();
                    if (tempRunnable == null) {
                        if (statusUpdateTimerTask != this) {
                            associatedTimer.cancel();
                        }
                        return;
                    }

                    StatusUpdateRunnable tempLastRunnable;
                    do {
                        tempLastRunnable = tempRunnable;
                        if (!tempLastRunnable.isSkippable()) {
                            break;
                        }

                        tempRunnable = statusUpdateQueue.poll();
                    } while (tempRunnable != null);

                    Display.getDefault().syncExec(tempLastRunnable);
                }
            };
            statusUpdateTimer.scheduleAtFixedRate(statusUpdateTimerTask, 0, STATUS_UPDATE_INTERVAL);
        }

        @Override
        public void onVersionMismatch(IntegrityRemotingVersionMessage aRemoteVersion, Endpoint anEndpoint) {
            // TODO Auto-generated method stub

        }

        @Override
        public void onBaselineReceived(SetList aSetList, Endpoint anEndpoint) {
            updateSetList(aSetList);
        }

        @Override
        public void onExecutionStateUpdate(final ExecutionStates aState, Endpoint anEndpoint) {
            statusUpdateQueue.add(new StatusUpdateRunnable(false) {

                @Override
                public void run() {
                    updateActionStatusRunnable(aState).run();
                }
            });
        }

        @Override
        public void onConnectionLost(Endpoint anEndpoint) {
            client = null;
            statusUpdateQueue.add(new StatusUpdateRunnable(false) {

                @Override
                public void run() {
                    executionProgress.redraw();
                    updateActionStatusRunnable(null).run();
                    updateStatusRunnable(determineIntermediateTestResultStatusString("Test Runner disconnected (",
                            ") after " + DateUtil.convertNanosecondTimespanToHumanReadableFormat(
                                    System.nanoTime() - connectionTimestamp, true, false))).run();
                }
            });

            statusUpdateTimer = null;
            statusUpdateTimerTask = null;
        }

        /**
         * This queue is used to queue asynchronous status display update requests (status text and execution progress
         * bar). Certain updates can be skipped for better performance. See issue #80.
         */
        private LinkedBlockingQueue<StatusUpdateRunnable> statusUpdateQueue = new LinkedBlockingQueue<StatusUpdateRunnable>();

        /**
         * The timer used to process status update queue entries. Gets started and stopped with Integrity test
         * executions.
         */
        private Timer statusUpdateTimer;

        /**
         * The task used by the {@link #statusUpdateTimer}.
         */
        private TimerTask statusUpdateTimerTask;

        /**
         * The interval in milliseconds with which the status shall be updated. Maximum number of updates per second can
         * directly be calculated from this.
         */
        private static final int STATUS_UPDATE_INTERVAL = 200;

        @Override
        public void onSetListUpdate(final SetListEntry[] someUpdatedEntries,
                final Integer anEntryInExecutionReference, Endpoint anEndpoint) {
            setList.integrateUpdates(someUpdatedEntries);
            if (anEntryInExecutionReference != null) {
                setList.setEntryInExecutionReference(anEntryInExecutionReference);
            }
            Display.getDefault().syncExec(new Runnable() {
                @Override
                public void run() {
                    for (SetListEntry tempEntry : someUpdatedEntries) {
                        switch (tempEntry.getType()) {
                        case RESULT:
                            treeViewer.update(setList.getParent(tempEntry), null);
                            treeViewer.update(tempEntry, null);
                            break;
                        default:
                            treeViewer.update(tempEntry, null);
                        }
                    }

                    if (anEntryInExecutionReference != null) {
                        List<SetListEntry> tempExecutionPath = setList.getEntriesInExecution();
                        for (SetListEntry tempEntry : tempExecutionPath) {
                            treeViewer.update(tempEntry, null);
                        }

                        if (!scrollLockActive && !treeViewerIsScrolledManually && tempExecutionPath.size() > 0) {
                            treeViewer.reveal(tempExecutionPath.get(0));
                        }
                    }
                }
            });

            final String tempStatusUpdate = determineIntermediateTestResultStatusString("Running tests: ", "");
            statusUpdateQueue.add(new StatusUpdateRunnable(true) {

                @Override
                public void run() {
                    executionProgress.redraw();
                    updateStatusRunnable(tempStatusUpdate).run();
                }
            });
        }

        @Override
        public void onConfirmCreateBreakpoint(final Integer anEntryReference, Endpoint anEndpoint) {
            if (anEntryReference == null) {
                // We are only interested in real breakpoints, not pause-at-next-instructions.
                return;
            }
            breakpointSet.add(anEntryReference);
            Display.getDefault().syncExec(new Runnable() {
                @Override
                public void run() {
                    treeViewer.update(setList.resolveReference(anEntryReference), null);
                }
            });
        }

        @Override
        public void onConfirmRemoveBreakpoint(final Integer anEntryReference, Endpoint anEndpoint) {
            if (anEntryReference == null) {
                // We are only interested in real breakpoints, not pause-at-next-instructions.
                return;
            }
            breakpointSet.remove(anEntryReference);
            Display.getDefault().syncExec(new Runnable() {
                @Override
                public void run() {
                    treeViewer.update(setList.resolveReference(anEntryReference), null);
                }
            });
        }

        @Override
        public void onTestRunnerCallbackMessageRetrieval(String aCallbackClassName,
                TestRunnerCallbackMethods aMethod, Serializable[] someData) {
            // not used in this context
        }

        @Override
        public void onVariableUpdateRetrieval(String aVariableName, Serializable aValue) {
            // not used in this context
        }

        @Override
        public void onAbortExecution(String anAbortExecutionMessage, String anAbortExecutionStackTrace) {
            // not used in this context
        }
    }

    private class AutoConnectThread extends Thread {

        /**
         * The launch to supervise.
         */
        private ILaunch launch;

        /**
         * Creates a new instance.
         */
        public AutoConnectThread(ILaunch aLaunch) {
            super("Integrity Autoconnect Thread");
            launch = aLaunch;
        }

        @Override
        public void run() {
            boolean tempSuccess = false;

            while (!launch.isTerminated()) {
                try {
                    if (!tempSuccess && !isConnected()) {
                        // try to connect at least once
                        connectToTestRunner("localhost", IntegrityRemotingConstants.DEFAULT_PORT);
                        tempSuccess = true;
                    } else {
                        // Now we'll wait until the launch has terminated
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException exc) {
                            // don't care
                        }
                    }
                } catch (UnknownHostException exc) {
                    // exceptions are expected, that just means we need to retry
                } catch (IOException exc) {
                    // exceptions are expected, that just means we need to retry
                }
            }
            executeTestAction.setEnabled(true);
            executeDebugTestAction.setEnabled(true);
        };
    }
}