/* * 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.launch.tabs; import static com.google.common.collect.Lists.newArrayList; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTreeViewer; import org.eclipse.jface.viewers.DecorationOverlayIcon; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.ICheckStateProvider; import org.eclipse.jface.viewers.IDecoration; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.TreeSelection; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.jface.viewers.ViewersConfigurator; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.dialogs.ElementTreeSelectionDialog; import org.eclipse.ui.model.BaseWorkbenchContentProvider; import org.eclipse.ui.model.WorkbenchLabelProvider; import org.robotframework.ide.eclipse.main.plugin.RedImages; import org.robotframework.ide.eclipse.main.plugin.model.RobotCase; import org.robotframework.ide.eclipse.main.plugin.model.RobotCasesSection; import org.robotframework.ide.eclipse.main.plugin.model.RobotModelManager; import org.robotframework.ide.eclipse.main.plugin.model.RobotSuiteFile; import org.robotframework.red.graphics.ImagesManager; import org.robotframework.red.viewers.RedCommonLabelProvider; import org.robotframework.red.viewers.Selections; import org.robotframework.red.viewers.TreeContentProvider; import com.google.common.annotations.VisibleForTesting; /** * @author mmarzec */ class SuitesToRunComposite extends Composite { private String projectName; private CheckboxTreeViewer viewer; private final Map<EButton, Button> buttons = new HashMap<>(); private final List<SuiteLaunchElement> suitesToLaunch = new ArrayList<>(); private final SuitesListener listener; SuitesToRunComposite(final Composite parent, final SuitesListener listener) { super(parent, SWT.NONE); this.listener = listener; GridLayoutFactory.fillDefaults().numColumns(2).margins(2, 1).applyTo(this); createViewer(); createAllButtons(); } private void createViewer() { viewer = new CheckboxTreeViewer(this, SWT.MULTI | SWT.BORDER | SWT.CHECK); viewer.setUseHashlookup(true); ViewersConfigurator.enableDeselectionPossibility(viewer); GridDataFactory.fillDefaults().grab(true, true).span(1, 5).minSize(200, 130).applyTo(viewer.getTree()); viewer.setCheckStateProvider(new CheckStateProvider()); viewer.setLabelProvider(new DelegatingStyledCellLabelProvider(new CheckboxTreeViewerLabelProvider())); viewer.setContentProvider(new CheckboxTreeViewerContentProvider()); viewer.addCheckStateListener(new ICheckStateListener() { @Override public void checkStateChanged(final CheckStateChangedEvent event) { final Object element = event.getElement(); final boolean isElementChecked = event.getChecked(); if (element instanceof SuiteLaunchElement) { ((SuiteLaunchElement) element).updateChecked(isElementChecked); } else if (element instanceof TestCaseLaunchElement) { ((TestCaseLaunchElement) element).updateChecked(isElementChecked); } listener.suitesChanged(); } }); viewer.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(final SelectionChangedEvent event) { final Button removeButton = buttons.get(EButton.REMOVE); final IStructuredSelection selection = (IStructuredSelection) event.getSelection(); if (selection.isEmpty()) { removeButton.setEnabled(false); return; } final List<SuiteLaunchElement> suites = Selections.getElements(selection, SuiteLaunchElement.class); final List<TestCaseLaunchElement> tests = Selections.getElements(selection, TestCaseLaunchElement.class); if (!suites.isEmpty() && tests.isEmpty()) { removeButton.setEnabled(true); } else if (suites.isEmpty() && !tests.isEmpty()) { boolean allAreMissing = true; for (final TestCaseLaunchElement test : tests) { if (!test.isMissing) { allAreMissing = false; break; } } removeButton.setEnabled(allAreMissing); } else { removeButton.setEnabled(false); } } }); } private void createAllButtons() { buttons.put(EButton.BROWSE, createBrowseButton()); buttons.put(EButton.REMOVE, createRemoveButton()); buttons.put(EButton.SELECT_ALL, createSelectButton()); buttons.put(EButton.DESELECT_ALL, createDeselectButton()); } private Button createBrowseButton() { final Button browseSuitesButton = new Button(this, SWT.PUSH); browseSuitesButton.setText("Browse..."); GridDataFactory.fillDefaults().applyTo(browseSuitesButton); browseSuitesButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { final ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(), new WorkbenchLabelProvider(), new BaseWorkbenchContentProvider()); dialog.setAllowMultiple(true); dialog.setTitle("Select test suite"); dialog.setMessage("Select the test suite to execute:"); dialog.addFilter(new ViewerFilter() { @Override public boolean select(final Viewer viewer, final Object parentElement, final Object element) { return element instanceof IResource && ((IResource) element).getProject().getName().equals(projectName); } }); dialog.setInput(ResourcesPlugin.getWorkspace().getRoot()); if (dialog.open() == Window.OK) { for (final Object obj : dialog.getResult()) { final IResource chosenResource = (IResource) obj; if (chosenResource.getType() == IResource.PROJECT) { continue; } final SuiteLaunchElement suite = new SuiteLaunchElement(chosenResource); for (final RobotCase test : getCases(chosenResource)) { final TestCaseLaunchElement child = new TestCaseLaunchElement(suite, test.getName(), false, false); suite.addChild(child); } if (!suitesToLaunch.contains(suite)) { suitesToLaunch.add(suite); } } listener.suitesChanged(); } } }); return browseSuitesButton; } private Button createRemoveButton() { final Button removeSuite = new Button(this, SWT.PUSH); GridDataFactory.fillDefaults().applyTo(removeSuite); removeSuite.setText("Remove"); removeSuite.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { final IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); final List<SuiteLaunchElement> suites = Selections.getElements(selection, SuiteLaunchElement.class); boolean changed = false; if (!suites.isEmpty()) { suitesToLaunch.removeAll(suites); changed = true; } final List<TestCaseLaunchElement> tests = Selections.getElements(selection, TestCaseLaunchElement.class); for (final TestCaseLaunchElement test : tests) { test.getParent().getChildren().remove(test); changed = true; } if (changed) { listener.suitesChanged(); } } }); return removeSuite; } private Button createSelectButton() { final Button selectAll = new Button(this, SWT.PUSH); GridDataFactory.fillDefaults().indent(0, 10).applyTo(selectAll); selectAll.setText("Select All"); selectAll.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { setLaunchElementsChecked(true); listener.suitesChanged(); } }); return selectAll; } private Button createDeselectButton() { final Button deselectAll = new Button(this, SWT.PUSH); GridDataFactory.fillDefaults().applyTo(deselectAll); deselectAll.setText("Deselect All"); deselectAll.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { setLaunchElementsChecked(false); listener.suitesChanged(); } }); return deselectAll; } @Override public void dispose() { buttons.clear(); super.dispose(); } void switchTo(final String projectName) { this.projectName = projectName; for (final SuiteLaunchElement suite : suitesToLaunch) { if (!projectName.isEmpty()) { suite.updateProject(projectName); } } viewer.refresh(); } private void setLaunchElementsChecked(final boolean isChecked) { final List<TestCaseLaunchElement> testsToCheck = newArrayList(); final List<SuiteLaunchElement> suitesToCheck = newArrayList(); if (viewer.getTree().getSelectionCount() == 0) { suitesToCheck.addAll(suitesToLaunch); } else { suitesToCheck .addAll(Selections.getElements((TreeSelection) viewer.getSelection(), SuiteLaunchElement.class)); testsToCheck .addAll(Selections.getElements((TreeSelection) viewer.getSelection(), TestCaseLaunchElement.class)); } for (final TestCaseLaunchElement test : testsToCheck) { test.updateChecked(isChecked); } for (final SuiteLaunchElement suite : suitesToCheck) { suite.updateChecked(isChecked); } } void setInput(final String projectName, final Map<String, List<String>> map) { this.projectName = projectName; suitesToLaunch.clear(); viewer.setInput(suitesToLaunch); final Object[] checked = viewer.getExpandedElements(); try { viewer.getTree().setRedraw(false); if (projectName.isEmpty()) { return; } final IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); for (final Entry<String, List<String>> entry : map.entrySet()) { suitesToLaunch.add(extractSuite(project, entry)); } } finally { viewer.refresh(); viewer.setExpandedElements(checked); viewer.getTree().setRedraw(true); } } private SuiteLaunchElement extractSuite(final IProject project, final Entry<String, List<String>> entry) { final IPath path = Path.fromPortableString(entry.getKey()); final IResource resource = path.getFileExtension() == null ? project.getFolder(path) : project.getFile(path); final SuiteLaunchElement suite = new SuiteLaunchElement(resource); final List<String> allCases = newArrayList(entry.getValue()); for (final RobotCase testCase : getCases(resource)) { final String name = testCase.getName(); suite.addChild(new TestCaseLaunchElement(suite, name, entry.getValue().isEmpty() || entry.getValue().contains(name.toLowerCase()), false)); allCases.remove(name.toLowerCase()); } for (final String missingSuite : allCases) { suite.addChild(new TestCaseLaunchElement(suite, missingSuite, true, true)); } return suite; } private static List<RobotCase> getCases(final IResource resource) { final List<RobotCase> cases = new ArrayList<>(); if (resource.exists() && resource.getType() == IResource.FILE) { final RobotSuiteFile suiteModel = RobotModelManager.getInstance().createSuiteFile((IFile) resource); final Optional<RobotCasesSection> section = suiteModel.findSection(RobotCasesSection.class); if (section.isPresent()) { cases.addAll(section.get().getChildren()); } } return cases; } Map<String, List<String>> extractSuitesToRun() { final LinkedHashMap<String, List<String>> suitesToRun = new LinkedHashMap<>(); for (final SuiteLaunchElement suite : suitesToLaunch) { if (suite.isChecked()) { final List<String> tests = new ArrayList<>(); if (!suite.hasCheckedAllChildren()) { for (final TestCaseLaunchElement test : suite.getChildren()) { if (test.isChecked()) { tests.add(test.getName().toLowerCase()); } } } suitesToRun.put(suite.getPath(), tests); } } return suitesToRun; } @VisibleForTesting static class CheckboxTreeViewerContentProvider extends TreeContentProvider { @Override public Object[] getElements(final Object inputElement) { return ((List<?>) inputElement).toArray(); } @Override public Object[] getChildren(final Object parentElement) { if (parentElement instanceof SuiteLaunchElement) { final List<TestCaseLaunchElement> children = ((SuiteLaunchElement) parentElement).getChildren(); return children.toArray(new TestCaseLaunchElement[children.size()]); } return null; } @Override public Object getParent(final Object element) { if (element instanceof TestCaseLaunchElement) { return ((TestCaseLaunchElement) element).getParent(); } return null; } @Override public boolean hasChildren(final Object element) { return element instanceof SuiteLaunchElement && ((SuiteLaunchElement) element).getChildren().size() > 0; } } @VisibleForTesting static class CheckboxTreeViewerLabelProvider extends RedCommonLabelProvider { @Override public StyledString getStyledText(final Object element) { if (element instanceof SuiteLaunchElement) { return new StyledString(((SuiteLaunchElement) element).getPath()); } else { return new StyledString(((TestCaseLaunchElement) element).getName()); } } @Override public Image getImage(final Object element) { if (element instanceof SuiteLaunchElement) { return ((SuiteLaunchElement) element).getImage(); } else { return ((TestCaseLaunchElement) element).getImage(); } } } @VisibleForTesting static class CheckStateProvider implements ICheckStateProvider { @Override public boolean isChecked(final Object element) { if (element instanceof SuiteLaunchElement) { return ((SuiteLaunchElement) element).isChecked(); } else { return ((TestCaseLaunchElement) element).isChecked(); } } @Override public boolean isGrayed(final Object element) { if (element instanceof SuiteLaunchElement) { final SuiteLaunchElement suite = (SuiteLaunchElement) element; return suite.hasCheckedChild() && !suite.hasCheckedAllChildren(); } return false; } } @VisibleForTesting static final class SuiteLaunchElement { private IResource resource; private final List<TestCaseLaunchElement> children; private boolean isChecked = true; SuiteLaunchElement(final IResource resource) { this.resource = resource; this.children = new ArrayList<>(); } Image getImage() { final ImageDescriptor baseImage = resource.getType() == IResource.FILE ? RedImages.getRobotFileImage() : RedImages.getFolderImage(); if (resource.exists()) { return ImagesManager.getImage(baseImage); } else { return ImagesManager.getImage(new DecorationOverlayIcon(ImagesManager.getImage(baseImage), RedImages.getErrorImage(), IDecoration.BOTTOM_LEFT)); } } String getPath() { return resource.getProjectRelativePath().toPortableString(); } List<TestCaseLaunchElement> getChildren() { return children; } void addChild(final TestCaseLaunchElement child) { children.add(child); } boolean isChecked() { return isChecked; } void setChecked(final boolean isChecked) { this.isChecked = isChecked; } void updateChecked(final boolean isChecked) { this.isChecked = isChecked; for (final TestCaseLaunchElement testCaseElement : children) { testCaseElement.setChecked(isChecked); } } private boolean hasCheckedChild() { return children.stream().anyMatch(test -> test.isChecked()); } private boolean hasCheckedAllChildren() { return children.stream().allMatch(test -> test.isChecked()); } public void updateProject(final String projectName) { final IProject newProject = resource.getWorkspace().getRoot().getProject(projectName); final IPath path = resource.getProjectRelativePath(); this.resource = path.getFileExtension() == null ? newProject.getFolder(path) : newProject.getFile(path); } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } else if (obj.getClass() == getClass()) { final SuiteLaunchElement other = (SuiteLaunchElement) obj; return Objects.equals(resource, other.resource); } return false; } @Override public int hashCode() { return Objects.hash(resource); } } @VisibleForTesting static final class TestCaseLaunchElement { private final String name; private final SuiteLaunchElement parent; private boolean isChecked; private final boolean isMissing; TestCaseLaunchElement(final SuiteLaunchElement parent, final String name, final boolean isChecked, final boolean isMissing) { this.name = name; this.parent = parent; this.isChecked = isChecked; this.isMissing = isMissing; } Image getImage() { final ImageDescriptor baseImage = RedImages.getTestCaseImage(); if (isMissing) { return ImagesManager.getImage(new DecorationOverlayIcon(ImagesManager.getImage(baseImage), RedImages.getErrorImage(), IDecoration.BOTTOM_LEFT)); } else { return ImagesManager.getImage(baseImage); } } String getName() { return name; } SuiteLaunchElement getParent() { return parent; } boolean isChecked() { return isChecked; } void setChecked(final boolean isChecked) { this.isChecked = isChecked; } private void updateChecked(final boolean isChecked) { this.isChecked = isChecked; if (isChecked) { parent.setChecked(isChecked); } else if (!parent.hasCheckedChild()) { parent.setChecked(false); } } } private enum EButton { BROWSE, REMOVE, SELECT_ALL, DESELECT_ALL; } public interface SuitesListener { void suitesChanged(); } }