org.eclipse.buildship.ui.view.execution.FailureDialog.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.buildship.ui.view.execution.FailureDialog.java

Source

/*
 * Copyright (c) 2015 the original author or authors.
 * 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
 *
 * Contributors:
 *     Etienne Studer & Dont Csiks (Gradle Inc.) - initial API and implementation and initial documentation
 */

package org.eclipse.buildship.ui.view.execution;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import org.eclipse.buildship.core.CorePlugin;
import org.eclipse.buildship.core.GradlePluginsRuntimeException;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.browser.IWebBrowser;
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
import org.gradle.tooling.Failure;
import org.gradle.tooling.events.FailureResult;
import org.gradle.tooling.events.FinishEvent;

import java.net.URI;
import java.util.List;

/**
 * Dialog presenting a list of {@link Failure} instances.
 */
public final class FailureDialog extends Dialog {

    private static final String FAILURE_DETAILS_URL_PREFIX = "org.gradle.api.GradleException: There were failing tests. See the report at: "; //$NON-NLS-1$

    private final String title;
    private final ImmutableList<FailureItem> failureItems;

    private Label operationNameText;
    private Text messageText;
    private Text detailsText;
    private Label urlLabel;
    private Link urlLink;
    private Button backButton;
    private Button nextButton;
    private Button copyButton;
    private Clipboard clipboard;

    private int selectionIndex;

    public FailureDialog(Shell parent, String title, List<FinishEvent> failureEvents) {
        super(parent);
        this.title = Preconditions.checkNotNull(title);
        this.failureItems = FailureItem.from(failureEvents);
        setShellStyle(SWT.DIALOG_TRIM | SWT.RESIZE | SWT.APPLICATION_MODAL);
    }

    @Override
    protected void configureShell(Shell shell) {
        super.configureShell(shell);
        shell.setText(this.title);
    }

    @Override
    protected Control createDialogArea(Composite parent) {
        Composite container = (Composite) super.createDialogArea(parent);
        GridData containerGridData = new GridData(SWT.FILL, SWT.FILL, true, true);
        containerGridData.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH);
        container.setLayoutData(containerGridData);
        container.setLayout(new GridLayout(5, false));

        Label operationNameLabel = new Label(container, SWT.NONE);
        operationNameLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1));
        operationNameLabel.setText(ExecutionViewMessages.Dialog_Failure_Operation_Label);

        this.operationNameText = new Label(container, SWT.NONE);
        GridData operationNameLayoutData = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1);
        this.operationNameText.setLayoutData(operationNameLayoutData);

        this.backButton = new Button(container, SWT.FLAT | SWT.CENTER);
        this.backButton.setToolTipText(ExecutionViewMessages.Dialog_Failure_Back_Tooltip);
        this.backButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
        this.backButton.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_BACK));

        this.nextButton = new Button(container, SWT.FLAT | SWT.CENTER);
        this.nextButton.setToolTipText(ExecutionViewMessages.Dialog_Failure_Next_Tooltip);
        this.nextButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
        this.nextButton
                .setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_FORWARD));

        this.copyButton = new Button(container, SWT.FLAT | SWT.CENTER);
        this.copyButton.setToolTipText(ExecutionViewMessages.Dialog_Failure_Copy_Details_Tooltip);
        this.copyButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
        this.copyButton.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_COPY));

        Label messageLabel = new Label(container, SWT.NONE);
        messageLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1));
        messageLabel.setText(ExecutionViewMessages.Dialog_Failure_Message_Label);

        this.messageText = new Text(container, SWT.BORDER);
        GridData messageTextLayoutData = new GridData(SWT.FILL, SWT.CENTER, true, false, 4, 1);
        this.messageText.setLayoutData(messageTextLayoutData);
        this.messageText.setEditable(false);

        Label detailsLabel = new Label(container, SWT.NONE);
        detailsLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1));
        detailsLabel.setText(ExecutionViewMessages.Dialog_Failure_Details_Label);

        this.detailsText = new Text(container, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
        GridData detailsTextGridData = new GridData(SWT.FILL, SWT.FILL, true, true, 4, 1);
        detailsTextGridData.heightHint = 200;
        this.detailsText.setLayoutData(detailsTextGridData);
        this.detailsText.setEditable(false);

        this.urlLabel = new Label(container, SWT.NONE);
        this.urlLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
        this.urlLabel.setText(ExecutionViewMessages.Dialog_Failure_Link_Label);

        this.urlLink = new Link(container, SWT.NONE);
        this.urlLink.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 4, 1));

        this.clipboard = new Clipboard(parent.getDisplay());

        initSelectionIndex();
        initEventListeners();
        update();

        return container;
    }

    private void initSelectionIndex() {
        this.selectionIndex = this.failureItems.isEmpty() ? -1 : 0;
    }

    private void initEventListeners() {
        this.backButton.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                FailureDialog.this.selectionIndex--;
                update();
            }
        });

        this.nextButton.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                FailureDialog.this.selectionIndex++;
                update();
            }
        });

        this.copyButton.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                Object[] data = { FailureDialog.this.detailsText.getText() };
                Transfer[] dataTypes = { TextTransfer.getInstance() };
                FailureDialog.this.clipboard.setContents(data, dataTypes);
            }
        });

        this.urlLink.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent event) {
                String url = (String) FailureDialog.this.urlLink.getData();
                try {
                    // if there is a browser with the same url then reuse it, otherwise open a new one
                    IWorkbenchBrowserSupport browserSupport = PlatformUI.getWorkbench().getBrowserSupport();
                    IWebBrowser browser = browserSupport.createBrowser(IWorkbenchBrowserSupport.AS_EDITOR, url,
                            null, url);
                    browser.openURL(URI.create(url).toURL());
                    close();
                } catch (Exception e) {
                    String message = String.format("Cannot open browser editor for %s.", url);
                    CorePlugin.logger().error(message, e);
                    throw new GradlePluginsRuntimeException(message, e);
                }
            }
        });
    }

    @SuppressWarnings("RedundantTypeArguments")
    private void update() {
        Optional<FailureItem> failureItem = this.selectionIndex == -1 ? Optional.<FailureItem>absent()
                : Optional.of(this.failureItems.get(this.selectionIndex));
        Optional<Failure> failure = failureItem.isPresent() ? failureItem.get().failure
                : Optional.<Failure>absent();

        this.operationNameText.setText(
                failureItem.isPresent() ? OperationDescriptorRenderer.renderVerbose(failureItem.get().event) : ""); //$NON-NLS-1$

        this.messageText.setText(failure.isPresent() ? Strings.nullToEmpty(failure.get().getMessage()) : ""); //$NON-NLS-1$
        this.messageText.setEnabled(failureItem.isPresent());

        this.detailsText.setText(failure.isPresent() ? collectDetails(failure.get()) : ""); //$NON-NLS-1$
        this.detailsText.setEnabled(failureItem.isPresent());

        this.backButton.setEnabled(this.selectionIndex > 0);
        this.nextButton.setEnabled(this.selectionIndex < this.failureItems.size() - 1);
        this.copyButton.setEnabled(
                failureItem.isPresent() && failure.isPresent() && failure.get().getDescription() != null);

        Optional<String> testReportUrl = findTestReportUrl(failure);
        this.urlLabel.setVisible(testReportUrl.isPresent());
        this.urlLink.setVisible(testReportUrl.isPresent());
        this.urlLink.setText(testReportUrl.isPresent() ? "<a>Test Summary</a>" : "");
        this.urlLink.setData(testReportUrl.isPresent() ? testReportUrl.get() : null);

        // force redraw since different failures can have different number of lines in the message
        this.operationNameText.getParent().layout(true);
    }

    private Optional<String> findTestReportUrl(Optional<Failure> failure) {
        if (failure.isPresent()) {
            String description = failure.get().getDescription();
            int beginIndex = description.indexOf(FAILURE_DETAILS_URL_PREFIX);
            if (beginIndex >= 0) {
                int endIndex = description.indexOf('\n', beginIndex);
                String url = description.substring(beginIndex + FAILURE_DETAILS_URL_PREFIX.length(), endIndex);
                return Optional.of(url);
            }
        }
        return Optional.absent();
    }

    private String collectDetails(Failure failure) {
        return collectDetailsRecursively(failure);
    }

    private String collectDetailsRecursively(Failure failure) {
        StringBuilder result = new StringBuilder();
        result.append(Strings.nullToEmpty(failure.getDescription()));
        List<? extends Failure> causes = failure.getCauses();
        if (!causes.isEmpty()) {
            result.append('\n').append(ExecutionViewMessages.Dialog_Failure_Root_Cause_Label).append(' ');
            for (Failure cause : causes) {
                result.append(collectDetailsRecursively(cause));
            }
        }
        return result.toString();
    }

    @Override
    protected void createButtonsForButtonBar(Composite parent) {
        createButton(parent, IDialogConstants.OK_ID, IDialogConstants.CLOSE_LABEL, false);
    }

    @Override
    public boolean close() {
        if (this.clipboard != null) {
            this.clipboard.dispose();
            this.clipboard = null;
        }
        return super.close();
    }

    /**
     * Represents a failure item shown in the failure dialog. One finish event can have multiple
     * failures and so for each failure of each event we show a failure item in the failure dialog.
     */
    private static final class FailureItem {

        private final FinishEvent event;
        private final Optional<Failure> failure;

        private FailureItem(FinishEvent event, Optional<Failure> failure) {
            this.event = event;
            this.failure = failure;
        }

        private static ImmutableList<FailureItem> from(final FinishEvent event) {
            List<? extends Failure> failures = ((FailureResult) event.getResult()).getFailures();
            ImmutableList<FailureItem> failureItems = FluentIterable.from(failures)
                    .transform(new Function<Failure, FailureItem>() {

                        @Override
                        public FailureItem apply(Failure failure) {
                            return new FailureItem(event, Optional.of(failure));
                        }
                    }).toList();
            return failureItems.isEmpty() ? ImmutableList.of(new FailureItem(event, Optional.<Failure>absent()))
                    : failureItems;
        }

        private static ImmutableList<FailureItem> from(List<FinishEvent> events) {
            ImmutableList.Builder<FailureItem> failureItems = ImmutableList.builder();
            for (FinishEvent event : events) {
                failureItems.addAll(from(event));
            }
            return failureItems.build();
        }

    }

}