Java tutorial
/* * Copyright 2015 Nokia Solutions and Networks * Licensed under the Apache License, Version 2.0, * see license.txt file for details. */ package org.robotframework.ide.eclipse.main.plugin.views.execution; import static com.google.common.collect.Lists.newArrayList; import java.util.ArrayList; import java.util.List; import java.util.Optional; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import org.eclipse.e4.ui.di.Focus; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TreePath; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.ViewerColumnsFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CLabel; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IViewPart; import org.eclipse.ui.menus.IMenuService; import org.eclipse.ui.services.IEvaluationService; import org.rf.ide.core.execution.agent.Status; import org.robotframework.ide.eclipse.main.plugin.RedImages; import org.robotframework.ide.eclipse.main.plugin.RedPlugin; import org.robotframework.ide.eclipse.main.plugin.launch.RobotTestExecutionService; import org.robotframework.ide.eclipse.main.plugin.launch.RobotTestExecutionService.RobotTestExecutionListener; import org.robotframework.ide.eclipse.main.plugin.launch.RobotTestExecutionService.RobotTestsLaunch; import org.robotframework.ide.eclipse.main.plugin.views.execution.ExecutionStatusStore.ExecutionProgressListener; import org.robotframework.ide.eclipse.main.plugin.views.execution.ExecutionStatusStore.ExecutionTreeElementListener; import org.robotframework.ide.eclipse.main.plugin.views.execution.handler.ExecutionViewPropertyTester; import org.robotframework.ide.eclipse.main.plugin.views.execution.handler.GoToFileHandler.E4GoToFileHandler; import org.robotframework.ide.eclipse.main.plugin.views.execution.handler.ShowFailedOnlyHandler; import org.robotframework.red.graphics.ColorsManager; import org.robotframework.red.graphics.ImagesManager; import org.robotframework.red.swt.SimpleProgressBar; import org.robotframework.red.swt.SwtThread; import org.robotframework.red.viewers.Selections; import com.google.common.annotations.VisibleForTesting; /** * @author mmarzec * */ public class ExecutionView { @Inject private IEvaluationService evaluationService; public static final String ID = "org.robotframework.ide.ExecutionView"; private static final String MENU_ID = "org.robotframework.ide.ExecutionView.viewer"; private final RobotTestExecutionService executionService; private RobotTestsLaunch launch; private RobotTestExecutionListener executionListener; private final ExecutionTreeElementListener storeListener = this::refreshChangedNode; private final ExecutionProgressListener progressListener = this::refreshProgress; private Composite parent; private CLabel testsCounterLabel; private CLabel passCounterLabel; private CLabel failCounterLabel; private SimpleProgressBar progressBar; private TreeViewer executionViewer; private StyledText messageText; private IActionBars actionBars; public ExecutionView() { this(RedPlugin.getTestExecutionService()); } @VisibleForTesting ExecutionView(final RobotTestExecutionService executionService) { this.executionService = executionService; } public TreeViewer getViewer() { return executionViewer; } public Optional<RobotTestsLaunch> getCurrentlyShownLaunch() { return Optional.ofNullable(launch); } @PostConstruct public void postConstruct(final Composite parent, final IViewPart part, final IMenuService menuService) { this.parent = parent; GridDataFactory.fillDefaults().grab(true, true).applyTo(parent); GridLayoutFactory.fillDefaults().applyTo(parent); createProgressLabels(parent); createProgressBar(parent); createExecutionTreeViewer(parent); createFailureMessageText(parent); createContextMenu(menuService); part.getViewSite().setSelectionProvider(executionViewer); actionBars = part.getViewSite().getActionBars(); setInput(); } private void createProgressLabels(final Composite parent) { final Composite labelsComposite = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(3).applyTo(labelsComposite); GridDataFactory.fillDefaults().grab(true, false).indent(2, 8).applyTo(labelsComposite); testsCounterLabel = new CLabel(labelsComposite, SWT.NONE); GridDataFactory.fillDefaults().hint(100, SWT.DEFAULT).applyTo(testsCounterLabel); testsCounterLabel.setText("Tests: 0/0"); passCounterLabel = new CLabel(labelsComposite, SWT.NONE); GridDataFactory.fillDefaults().hint(100, SWT.DEFAULT).applyTo(passCounterLabel); passCounterLabel.setImage(ImagesManager.getImage(RedImages.getSuccessImage())); passCounterLabel.setText("Passed: 0"); failCounterLabel = new CLabel(labelsComposite, SWT.NONE); GridDataFactory.fillDefaults().hint(100, SWT.DEFAULT).applyTo(failCounterLabel); failCounterLabel.setImage(ImagesManager.getImage(RedImages.getErrorImage())); failCounterLabel.setText("Failed: 0"); } private void createProgressBar(final Composite parent) { progressBar = new SimpleProgressBar(parent); GridDataFactory.fillDefaults().grab(true, false).span(5, 1).hint(SWT.DEFAULT, 15).applyTo(progressBar); } private void createExecutionTreeViewer(final Composite parent) { executionViewer = new TreeViewer(parent); executionViewer.getTree().setHeaderVisible(false); GridDataFactory.fillDefaults().grab(true, true).applyTo(executionViewer.getTree()); GridLayoutFactory.fillDefaults().numColumns(1).applyTo(executionViewer.getTree()); executionViewer.setContentProvider(new ExecutionViewContentProvider()); executionViewer.addSelectionChangedListener(createSelectionChangedListener()); executionViewer.addDoubleClickListener(createDoubleClickListener()); ViewerColumnsFactory.newColumn("").withWidth(300).shouldGrabAllTheSpaceLeft(true) .labelsProvidedBy(new ExecutionViewLabelProvider()).createFor(executionViewer); } private void createFailureMessageText(final Composite parent) { messageText = new StyledText(parent, SWT.H_SCROLL | SWT.V_SCROLL); messageText.setFont(JFaceResources.getTextFont()); GridDataFactory.fillDefaults().grab(true, false).indent(3, 0).hint(0, 50).applyTo(messageText); GridLayoutFactory.fillDefaults().applyTo(messageText); messageText.setEditable(false); messageText.setAlwaysShowScrollBars(false); } private void createContextMenu(final IMenuService menuService) { final MenuManager mgr = new MenuManager(); menuService.populateContributionManager(mgr, "popup:" + MENU_ID); executionViewer.getTree().setMenu(mgr.createContextMenu(executionViewer.getTree())); } private void setInput() { // synchronize on service, so that any thread which would like to start another launch // will have to wait synchronized (executionService) { executionListener = new ExecutionListener(); executionService.addExecutionListener(executionListener); final Optional<RobotTestsLaunch> lastLaunch = executionService.getLastLaunch(); if (lastLaunch.isPresent()) { launch = lastLaunch.get(); // this launch may be currently running, so we have to synchronize in order // to get proper state of messages, as other threads may change it in the meantime synchronized (launch) { final ExecutionStatusStore elementsStore = launch.getExecutionData(ExecutionStatusStore.class, ExecutionStatusStore::new); elementsStore.addTreeListener(storeListener); elementsStore.addProgressListener(progressListener); SwtThread.asyncExec(() -> { final ExecutionTreeNode root = elementsStore.getExecutionTree(); executionViewer.setInput(root == null ? null : newArrayList(root)); refreshProgress(elementsStore.getCurrentTest(), elementsStore.getPassedTests(), elementsStore.getFailedTests(), elementsStore.getTotalTests()); if (root != null) { expandAllFailedOrRunning(root); } }); } } } } private void expandAllFailedOrRunning(final ExecutionTreeNode root) { final List<TreePath> elementsToExpand = new ArrayList<>(); collectElementsToExpand(elementsToExpand, root); executionViewer.setExpandedTreePaths(elementsToExpand.toArray(new TreePath[0])); } private void collectElementsToExpand(final List<TreePath> elementsToExpand, final ExecutionTreeNode node) { final Status status = node.getStatus().orElse(null); if (status == Status.FAIL || status == Status.RUNNING) { elementsToExpand.add(new TreePath(getPath(node).toArray())); for (final ExecutionTreeNode child : node.getChildren()) { collectElementsToExpand(elementsToExpand, child); } } } private List<ExecutionTreeNode> getPath(final ExecutionTreeNode node) { final List<ExecutionTreeNode> path = new ArrayList<>(); ExecutionTreeNode current = node; while (current != null) { path.add(0, current); current = current.getParent(); } return path; } @Focus public void onFocus() { executionViewer.getControl().setFocus(); } public void clearView() { executionViewer.setInput(null); messageText.setText(""); final ExecutionViewContentProvider provider = (ExecutionViewContentProvider) executionViewer .getContentProvider(); provider.setFailedFilter(false); ShowFailedOnlyHandler.setCommandState(false); testsCounterLabel.setText("Tests: 0/0"); passCounterLabel.setText("Passed: 0"); failCounterLabel.setText("Failed: 0"); progressBar.reset(); executionViewer.refresh(); evaluationService .requestEvaluation(ExecutionViewPropertyTester.PROPERTY_CURRENT_LAUNCH_EXEC_STORE_IS_DISPOSED); } @PreDestroy public void dispose() { synchronized (executionService) { executionService.removeExecutionListener(executionListener); executionService.forEachLaunch(launch -> launch.getExecutionData(ExecutionStatusStore.class) .ifPresent(store -> store.removeStoreListener(storeListener, progressListener))); } } private void refreshChangedNode(final ExecutionStatusStore store, final ExecutionTreeNode node) { SwtThread.asyncExec(() -> { // it could have been queued earlier in main thread... if (parent == null || parent.isDisposed()) { return; } if (executionViewer.getInput() == null) { executionViewer.setInput(newArrayList(store.getExecutionTree())); } executionViewer.refresh(node); final List<ExecutionTreeNode> p = new ArrayList<>(); ExecutionTreeNode e = node; while (e != null) { p.add(0, e); e = e.getParent(); } final TreePath path = new TreePath(p.toArray()); final Status status = node.getStatus().orElse(null); if (status == Status.RUNNING || status == Status.FAIL) { executionViewer.expandToLevel(path, 0); } else { executionViewer.collapseToLevel(path, p.size()); } }); } private void refreshProgress(final int currentTest, final int passedSoFar, final int failedSoFar, final int totalTests) { SwtThread.asyncExec(() -> { // it could have been queued earlier in main thread... if (parent == null || parent.isDisposed()) { return; } testsCounterLabel.setText(String.format("Tests: %d/%d", currentTest, totalTests)); passCounterLabel.setText("Passed: " + passedSoFar); failCounterLabel.setText("Failed: " + failedSoFar); final Color progressBarColor = failedSoFar > 0 ? ColorsManager.getColor(180, 0, 0) : ColorsManager.getColor(0, 180, 0); progressBar.setBarColor(progressBarColor); progressBar.setProgress(passedSoFar + failedSoFar, totalTests); }); } private ISelectionChangedListener createSelectionChangedListener() { return new ISelectionChangedListener() { @Override public void selectionChanged(final SelectionChangedEvent event) { final IStructuredSelection selection = (IStructuredSelection) event.getSelection(); final String message = Selections.getOptionalFirstElement(selection, ExecutionTreeNode.class) .map(ExecutionTreeNode::getMessage).orElse(""); messageText.setText(message); } }; } private IDoubleClickListener createDoubleClickListener() { return event -> Selections .getOptionalFirstElement((IStructuredSelection) event.getSelection(), ExecutionTreeNode.class) .ifPresent(node -> E4GoToFileHandler.openExecutionNodeSourceFile(node)); } private class ExecutionListener implements RobotTestExecutionListener { @Override public void executionStarting(final RobotTestsLaunch launch) { ExecutionView.this.launch = launch; SwtThread.asyncExec(() -> { evaluationService .requestEvaluation(ExecutionViewPropertyTester.PROPERTY_CURRENT_LAUNCH_IS_TERMINATED); actionBars.updateActionBars(); clearView(); }); synchronized (ExecutionView.this.launch) { final ExecutionStatusStore elementsStore = launch.getExecutionData(ExecutionStatusStore.class, ExecutionStatusStore::new); elementsStore.addTreeListener(storeListener); elementsStore.addProgressListener(progressListener); SwtThread.asyncExec(() -> { refreshProgress(elementsStore.getCurrentTest(), elementsStore.getPassedTests(), elementsStore.getFailedTests(), elementsStore.getTotalTests()); }); } } @Override public void executionEnded(final RobotTestsLaunch launch) { // nothing to do SwtThread.asyncExec(() -> { evaluationService .requestEvaluation(ExecutionViewPropertyTester.PROPERTY_CURRENT_LAUNCH_IS_TERMINATED); actionBars.updateActionBars(); }); } } }