1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * 
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * The contents of this file are subject to the terms of either the Universal Permissive License
   7  * v 1.0 as shown at http://oss.oracle.com/licenses/upl
   8  *
   9  * or the following license:
  10  *
  11  * Redistribution and use in source and binary forms, with or without modification, are permitted
  12  * provided that the following conditions are met:
  13  * 
  14  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
  15  * and the following disclaimer.
  16  * 
  17  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
  18  * conditions and the following disclaimer in the documentation and/or other materials provided with
  19  * the distribution.
  20  * 
  21  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
  22  * endorse or promote products derived from this software without specific prior written permission.
  23  * 
  24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  26  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  27  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  30  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  31  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32  */
  33 package org.openjdk.jmc.flightrecorder.ui.common;
  34 
  35 import java.awt.Color;
  36 import java.util.ArrayList;
  37 import java.util.Collection;
  38 import java.util.Collections;
  39 import java.util.List;
  40 import java.util.stream.Collectors;
  41 import java.util.stream.Stream;
  42 
  43 import org.eclipse.jface.action.IAction;
  44 import org.eclipse.jface.layout.GridDataFactory;
  45 import org.eclipse.jface.layout.GridLayoutFactory;
  46 import org.eclipse.jface.viewers.ArrayContentProvider;
  47 import org.eclipse.jface.viewers.CellEditor;
  48 import org.eclipse.jface.viewers.CheckboxTableViewer;
  49 import org.eclipse.jface.viewers.ColumnLabelProvider;
  50 import org.eclipse.jface.viewers.EditingSupport;
  51 import org.eclipse.jface.viewers.IStructuredSelection;
  52 import org.eclipse.jface.viewers.StructuredSelection;
  53 import org.eclipse.jface.viewers.TableViewerColumn;
  54 import org.eclipse.jface.viewers.TextCellEditor;
  55 import org.eclipse.jface.viewers.ViewerCell;
  56 import org.eclipse.jface.window.ToolTip;
  57 import org.eclipse.jface.window.Window;
  58 import org.eclipse.jface.wizard.WizardPage;
  59 import org.eclipse.osgi.util.NLS;
  60 import org.eclipse.swt.SWT;
  61 import org.eclipse.swt.graphics.Point;
  62 import org.eclipse.swt.widgets.Composite;
  63 import org.eclipse.swt.widgets.Control;
  64 import org.eclipse.swt.widgets.Event;
  65 import org.eclipse.swt.widgets.Label;
  66 import org.eclipse.ui.IWorkbenchCommandConstants;
  67 import org.eclipse.ui.forms.widgets.FormText;
  68 
  69 import org.openjdk.jmc.common.IDescribable;
  70 import org.openjdk.jmc.common.IPredicate;
  71 import org.openjdk.jmc.common.IState;
  72 import org.openjdk.jmc.common.IStateful;
  73 import org.openjdk.jmc.common.IWritableState;
  74 import org.openjdk.jmc.common.item.IItem;
  75 import org.openjdk.jmc.common.item.IItemFilter;
  76 import org.openjdk.jmc.common.item.IType;
  77 import org.openjdk.jmc.common.item.ItemFilters;
  78 import org.openjdk.jmc.common.item.ItemFilters.Types;
  79 import org.openjdk.jmc.common.item.PersistableItemFilter;
  80 import org.openjdk.jmc.common.util.PredicateToolkit;
  81 import org.openjdk.jmc.common.util.StateToolkit;
  82 import org.openjdk.jmc.flightrecorder.ui.EventTypeFolderNode;
  83 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages;
  84 import org.openjdk.jmc.ui.UIPlugin;
  85 import org.openjdk.jmc.ui.handlers.ActionToolkit;
  86 import org.openjdk.jmc.ui.handlers.MCContextMenuManager;
  87 import org.openjdk.jmc.ui.misc.ActionUiToolkit;
  88 import org.openjdk.jmc.ui.misc.CompositeToolkit;
  89 import org.openjdk.jmc.ui.misc.DialogToolkit;
  90 import org.openjdk.jmc.ui.misc.SWTColorToolkit;
  91 import org.openjdk.jmc.ui.wizards.IPerformFinishable;
  92 import org.openjdk.jmc.ui.wizards.OnePageWizardDialog;
  93 
  94 public class LaneEditor {
  95 
  96         private static final IItemFilter TYPE_HAS_THREAD_AND_DURATION = new IItemFilter() {
  97                 @Override
  98                 public IPredicate<IItem> getPredicate(IType<IItem> type) {
  99                         if (DataPageToolkit.isTypeWithThreadAndDuration(type)) {
 100                                 return PredicateToolkit.truePredicate();
 101                         }
 102                         return PredicateToolkit.falsePredicate();
 103                 }
 104         };
 105 
 106         private static class EditLanesWizardPage extends WizardPage implements IPerformFinishable {
 107 
 108                 private final EventTypeFolderNode root;
 109                 private final List<LaneDefinition> lanes;
 110                 private LaneDefinition restLane;
 111                 private TypeFilterBuilder filterEditor;
 112                 private CheckboxTableViewer lanesViewer;
 113                 private Object selected;
 114 
 115                 private EditLanesWizardPage(EventTypeFolderNode root, Collection<LaneDefinition> lanesInput) {
 116                         super("EditFilterLanesPage"); //$NON-NLS-1$
 117                         this.root = root;
 118                         this.lanes = new ArrayList<>(lanesInput);
 119                         restLane = ensureRestLane(lanes);
 120                 }
 121 
 122                 @Override
 123                 public void createControl(Composite parent) {
 124                         // FIXME: Do we want to group under categories somehow, or just hide the filters that don't have any existing event types.
 125                         Composite container = new Composite(parent, SWT.NONE);
 126                         container.setLayout(GridLayoutFactory.swtDefaults().numColumns(2).create());
 127 
 128                         Composite laneHeaderContainer = new Composite(container, SWT.NONE);
 129                         laneHeaderContainer.setLayout(GridLayoutFactory.swtDefaults().create());
 130                         laneHeaderContainer.setLayoutData(GridDataFactory.fillDefaults().create());
 131 
 132                         // FIXME: Add a duplicate action?
 133                         IAction moveUpAction = ActionToolkit.action(() -> moveSelected(true), Messages.LANES_MOVE_UP_ACTION,
 134                                         UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_NAV_UP));
 135                         IAction moveDownAction = ActionToolkit.action(() -> moveSelected(false), Messages.LANES_MOVE_DOWN_ACTION,
 136                                         UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_NAV_DOWN));
 137                         IAction addAction = ActionToolkit.action(this::addLane, Messages.LANES_ADD_LANE_ACTION,
 138                                         UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_ADD));
 139                         IAction removeAction = ActionToolkit.commandAction(this::deleteSelected,
 140                                         IWorkbenchCommandConstants.EDIT_DELETE);
 141                         Control toolbar = ActionUiToolkit.buildToolBar(laneHeaderContainer,
 142                                         Stream.of(moveUpAction, moveDownAction, addAction, removeAction), false);
 143                         toolbar.setLayoutData(GridDataFactory.fillDefaults().create());
 144 
 145                         Label lanesTitle = new Label(laneHeaderContainer, SWT.NONE);
 146                         lanesTitle.setText(Messages.LANES_EDITOR_LABEL);
 147                         lanesTitle.setLayoutData(GridDataFactory.fillDefaults().create());
 148                         Label filterTitle = new Label(container, SWT.NONE);
 149                         filterTitle.setText(Messages.LANES_FILTER_LABEL);
 150                         filterTitle.setLayoutData(
 151                                         GridDataFactory.fillDefaults().grab(true, false).align(SWT.BEGINNING, SWT.END).create());
 152 
 153                         lanesViewer = CheckboxTableViewer.newCheckList(container, SWT.BORDER | SWT.V_SCROLL);
 154                         TableViewerColumn viewerColumn = new TableViewerColumn(lanesViewer, SWT.NONE);
 155                         viewerColumn.getColumn().setText(Messages.LANES_LANE_COLUMN);
 156                         viewerColumn.getColumn().setWidth(200);
 157                         // FIXME: Would like to enable editing by some other means than single-clicking, but seems a bit tricky.
 158                         viewerColumn.setEditingSupport(new EditingSupport(lanesViewer) {
 159 
 160                                 private String currentName;
 161 
 162                                 @Override
 163                                 protected void setValue(Object element, Object value) {
 164                                         String newName = value.toString();
 165                                         if (currentName != null && currentName.equals(newName)) {
 166                                                 return;
 167                                         }
 168                                         LaneDefinition oldLd = (LaneDefinition) element;
 169                                         LaneDefinition newLane = new LaneDefinition(value.toString(), oldLd.enabled, oldLd.filter,
 170                                                         oldLd.isRestLane);
 171                                         int elementIndex = lanes.indexOf(element);
 172                                         lanes.set(elementIndex, newLane);
 173                                         lanesViewer.replace(newLane, elementIndex);
 174                                         getViewer().update(element, null);
 175                                 }
 176 
 177                                 @Override
 178                                 protected Object getValue(Object element) {
 179                                         currentName = ((LaneDefinition) element).getName();
 180                                         return currentName;
 181                                 }
 182 
 183                                 @Override
 184                                 protected CellEditor getCellEditor(Object element) {
 185                                         return new TextCellEditor((Composite) getViewer().getControl());
 186                                 }
 187 
 188                                 @Override
 189                                 protected boolean canEdit(Object element) {
 190                                         return true;
 191 
 192                                 }
 193                         });
 194 
 195                         lanesViewer.setLabelProvider(new ColumnLabelProvider() {
 196 
 197                                 @Override
 198                                 public String getText(Object element) {
 199                                         if (element instanceof LaneDefinition) {
 200                                                 if (element == selected) {
 201                                                         return ((LaneDefinition) element).getNameOrCount(filterEditor.getCheckedTypeIds().count());
 202                                                 } else {
 203                                                         return ((LaneDefinition) element).getName();
 204                                                 }
 205                                         }
 206                                         return super.getText(element);
 207                                 };
 208 
 209                                 // FIXME: Do we want to use italics for empty lanes?
 210 //                              @Override
 211 //                              public Font getFont(Object element) {
 212 //                                      if (getTypesCount(element) > 0) {
 213 //                                              return JFaceResources.getFontRegistry().get(JFaceResources.DEFAULT_FONT);
 214 //                                      } else {
 215 //                                              return JFaceResources.getFontRegistry().getItalic(JFaceResources.DEFAULT_FONT);
 216 //                                      }
 217 //                              }
 218 //
 219 //                              private long getTypesCount(Object element) {
 220 //                                      if (element == selected) {
 221 //                                              return filterEditor.getCheckedTypeIds().count();
 222 //                                      } else if (element instanceof LaneDefinition) {
 223 //                                              return ((LaneDefinition)element).getTypesCount();
 224 //                                      }
 225 //                                      return 0;
 226 //                              }
 227                         });
 228                         lanesViewer.setContentProvider(ArrayContentProvider.getInstance());
 229                         // FIXME: Can we potentially reuse this tooltip in the legend as well?
 230                         new ToolTip(lanesViewer.getControl(), ToolTip.NO_RECREATE, false) {
 231 
 232                                 @Override
 233                                 protected ViewerCell getToolTipArea(Event event) {
 234                                         return lanesViewer.getCell(new Point(event.x, event.y));
 235                                 }
 236 
 237                                 @Override
 238                                 protected Composite createToolTipContentArea(Event event, Composite parent) {
 239                                         FormText formText = CompositeToolkit.createInfoFormText(parent);
 240                                         Object element = getToolTipArea(event).getElement();
 241                                         Stream<String> ids = Stream.empty();
 242                                         if (element == selected) {
 243                                                 ids = filterEditor.getCheckedTypeIds();
 244                                         } else if (element instanceof LaneDefinition
 245                                                         && ((LaneDefinition) element).filter instanceof Types) {
 246                                                 ids = ((Types) ((LaneDefinition) element).filter).getTypes().stream();
 247                                         }
 248                                         StringBuilder sb = new StringBuilder();
 249                                         ids.forEach(typeId -> {
 250                                                 Color color = TypeLabelProvider.getColorOrDefault(typeId);
 251                                                 formText.setImage(typeId, SWTColorToolkit.getColorThumbnail(SWTColorToolkit.asRGB(color)));
 252                                                 sb.append("<li style='image' value='" + typeId + "'>" + typeId + "</li>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 253                                         });
 254                                         if (sb.length() > 0) {
 255                                                 sb.insert(0, "<form>"); //$NON-NLS-1$
 256                                                 sb.append("</form>"); //$NON-NLS-1$
 257                                                 formText.setText(sb.toString(), true, false);
 258                                         } else {
 259                                                 formText.setText(Messages.LANES_CHECK_TO_INCLUDE, false, false);
 260                                         }
 261                                         return formText;
 262                                 }
 263                         };
 264                         lanesViewer.setInput(lanes);
 265                         lanesViewer.setCheckedElements(lanes.stream().filter(ld -> ld.isEnabled()).toArray());
 266                         MCContextMenuManager mm = MCContextMenuManager.create(lanesViewer.getControl());
 267                         mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, moveUpAction);
 268                         mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, moveDownAction);
 269                         // FIXME: Add icon
 270                         mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, addAction);
 271 
 272                         mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, removeAction);
 273 
 274                         filterEditor = new TypeFilterBuilder(container, this::onTypeFilterChange);
 275                         filterEditor.setInput(root);
 276                         filterEditor.getControl().setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
 277                         lanesViewer.getControl().setLayoutData(GridDataFactory.fillDefaults().grab(false, true).create());
 278 
 279                         lanesViewer.addSelectionChangedListener(
 280                                         e -> laneSelectionChanges(((IStructuredSelection) e.getSelection()).getFirstElement()));
 281                         LaneDefinition firstLane = lanes.get(0);
 282                         lanesViewer.setSelection(new StructuredSelection(firstLane));
 283 
 284                         setControl(container);
 285                 }
 286 
 287                 private void addLane() {
 288                         int selectIndex = Math.max(0, lanes.indexOf(selected));
 289                         IItemFilter emptyFilter = ItemFilters.type(Collections.emptySet());
 290                         LaneDefinition newEmpty = new LaneDefinition(null, false, emptyFilter, false);
 291                         lanes.add(selectIndex + 1, newEmpty);
 292                         lanesViewer.insert(newEmpty, selectIndex + 1);
 293                         lanesViewer.setSelection(new StructuredSelection(newEmpty));
 294                 }
 295 
 296                 private void onTypeFilterChange() {
 297                         if (selected instanceof LaneDefinition) {
 298                                 LaneDefinition selectedLane = (LaneDefinition) selected;
 299                                 if (selectedLane.isRestLane()) {
 300                                         DialogToolkit.showWarningDialogAsync(lanesViewer.getControl().getDisplay(),
 301                                                         Messages.LANES_EDIT_NOT_ALLOWED_WARNING,
 302                                                         NLS.bind(Messages.LANES_EDIT_NOT_ALLOWED_WARNING_DESC, selectedLane.getName()));
 303                                         // FIXME: Can we refresh the filter editor to show that nothing has changed?
 304                                 }
 305                         }
 306                         lanesViewer.update(selected, null);
 307                 }
 308 
 309                 private void deleteSelected() {
 310                         // FIXME: It's currently not possible to delete the last lane
 311                         int selectIndex = Math.max(0, lanes.indexOf(selected) - 1);
 312                         if (selected instanceof LaneDefinition && ((LaneDefinition) selected).isRestLane()) {
 313                                 lanes.remove(selected);
 314                                 lanesViewer.setSelection(new StructuredSelection(lanes.get(selectIndex)));
 315                                 lanesViewer.refresh();
 316                         } else {
 317                                 DialogToolkit.showWarningDialogAsync(lanesViewer.getControl().getDisplay(),
 318                                                 Messages.LANES_DELETE_NOT_ALLOWED_WARNING, NLS.bind(
 319                                                                 Messages.LANES_DELETE_NOT_ALLOWED_WARNING_DESC, ((LaneDefinition) selected).getName()));
 320                         }
 321                 }
 322 
 323                 private void moveSelected(boolean up) {
 324                         int fromIndex = lanes.indexOf(selected);
 325                         int toIndex = fromIndex + (up ? -1 : 1);
 326                         if (fromIndex >= 0 && toIndex >= 0 && toIndex < lanes.size()) {
 327                                 LaneDefinition removed = lanes.remove(fromIndex);
 328                                 lanes.add(toIndex, removed);
 329                                 lanesViewer.refresh();
 330                         }
 331                 }
 332 
 333                 private void laneSelectionChanges(Object newSelected) {
 334                         int selectedIndex = lanes.indexOf(newSelected);
 335                         if (this.selected != newSelected) {
 336                                 saveFilter();
 337                                 this.selected = lanes.get(selectedIndex);
 338                                 if (selected instanceof LaneDefinition) {
 339                                         Types typesFilter;
 340                                         if (((LaneDefinition) selected).getFilter() instanceof Types) {
 341                                                 typesFilter = ((Types) ((LaneDefinition) selected).getFilter());
 342                                         } else {
 343                                                 typesFilter = (Types) ItemFilters.convertToTypes(((LaneDefinition) selected).getFilter(),
 344                                                                 filterEditor.getAllTypes());
 345                                         }
 346                                         filterEditor.selectTypes(typesFilter.getTypes());
 347                                 }
 348                         }
 349                 }
 350 
 351                 private void saveFilter() {
 352                         int selectedIndex = lanes.indexOf(selected);
 353                         if (selectedIndex >= 0) {
 354                                 LaneDefinition ld = lanes.get(selectedIndex);
 355                                 if (!ld.isRestLane()) {
 356                                         IItemFilter newFilter = ItemFilters
 357                                                         .type(filterEditor.getCheckedTypeIds().collect(Collectors.toSet()));
 358                                         LaneDefinition newLd = new LaneDefinition(ld.name, lanesViewer.getChecked(ld), newFilter,
 359                                                         ld.isRestLane);
 360                                         lanes.set(selectedIndex, newLd);
 361                                         lanesViewer.replace(newLd, selectedIndex);
 362                                         if (restLane != null) {
 363                                                 LaneDefinition newRest = new LaneDefinition(restLane.name, restLane.enabled,
 364                                                                 getRestFilter(lanes), true);
 365                                                 int restIndex = lanes.indexOf(restLane);
 366                                                 lanes.set(restIndex, newRest);
 367                                                 lanesViewer.replace(newRest, restIndex);
 368                                                 restLane = newRest;
 369                                         }
 370                                         lanesViewer.refresh();
 371                                 }
 372                         }
 373                 }
 374 
 375                 @Override
 376                 public boolean performFinish() {
 377                         saveFilter();
 378                         for (int i = 0; i < lanes.size(); i++) {
 379                                 LaneDefinition ld = lanes.get(i);
 380                                 if (ld.isEnabled() != lanesViewer.getChecked(ld)) {
 381                                         lanes.set(i, new LaneDefinition(ld.name, lanesViewer.getChecked(ld), ld.filter, ld.isRestLane));
 382                                 }
 383                         }
 384                         return true;
 385                 }
 386         }
 387 
 388         public static class LaneDefinition implements IDescribable, IStateful {
 389 
 390                 private static final String FILTER = "filter"; //$NON-NLS-1$
 391                 private static final String NAME = "name"; //$NON-NLS-1$
 392                 private static final String ENABLED = "enabled"; //$NON-NLS-1$
 393                 private static final String IS_REST_LANE = "isRestLane"; //$NON-NLS-1$
 394 
 395                 private final String name;
 396                 private final IItemFilter filter;
 397                 private final boolean enabled;
 398                 private final boolean isRestLane;
 399 
 400                 public LaneDefinition(String name, boolean enabled, IItemFilter filter, boolean isRestLane) {
 401                         this.name = name;
 402                         this.enabled = enabled;
 403                         this.filter = filter;
 404                         this.isRestLane = isRestLane;
 405                 }
 406 
 407                 @Override
 408                 public String getName() {
 409                         long count = filter instanceof Types ? ((Types) filter).getTypes().size() : 0;
 410                         return getNameOrCount(count);
 411                 }
 412 
 413                 public String getNameOrCount(long count) {
 414                         return name != null ? name
 415                                         : count == 1 && ((Types) filter).getTypes().iterator().hasNext()
 416                                                         ? ((Types) filter).getTypes().iterator().next()
 417                                                         : count > 0 ? NLS.bind(Messages.LANES_DEFINITION_NAME, count) : Messages.LANES_EMPTY_LANE;
 418                 }
 419 
 420                 @Override
 421                 public String getDescription() {
 422                         return NLS.bind(Messages.LANES_DEFINITION_DESC, getName());
 423                 }
 424 
 425                 public IItemFilter getFilter() {
 426                         return filter;
 427                 }
 428 
 429                 public boolean isEnabled() {
 430                         return enabled;
 431                 }
 432 
 433                 public boolean isRestLane() {
 434                         return isRestLane;
 435                 }
 436 
 437                 @Override
 438                 public void saveTo(IWritableState writableState) {
 439                         writableState.putString(NAME, name);
 440                         StateToolkit.writeBoolean(writableState, ENABLED, enabled);
 441                         StateToolkit.writeBoolean(writableState, IS_REST_LANE, isRestLane);
 442                         if (!isRestLane && filter != null) {
 443                                 ((PersistableItemFilter) filter).saveTo(writableState.createChild(FILTER));
 444                         }
 445                 }
 446 
 447                 public static LaneDefinition readFrom(IState memento) {
 448                         String name = memento.getAttribute(NAME);
 449                         boolean enabled = StateToolkit.readBoolean(memento, ENABLED, false);
 450                         boolean isRestLane = StateToolkit.readBoolean(memento, IS_REST_LANE, false);
 451                         IState filterState = memento.getChild(FILTER);
 452                         IItemFilter filter;
 453                         if (isRestLane) {
 454                                 filter = null;
 455                         } else if (filterState != null) {
 456                                 filter = PersistableItemFilter.readFrom(filterState);
 457                         } else {
 458                                 throw new UnsupportedOperationException("Null filter not allowed for thread lane: " + name); //$NON-NLS-1$
 459                         }
 460                         // FIXME: Should probably warn if filter is not an instance of Types, and possibly handle other type filter variants as well, like TypeMatches.
 461                         return new LaneDefinition(name, enabled, filter, isRestLane);
 462                 }
 463 
 464                 @Override
 465                 public String toString() {
 466                         return getName() + "(" + enabled + ")"; //$NON-NLS-1$ //$NON-NLS-2$
 467                 }
 468         }
 469 
 470         public static List<LaneDefinition> openDialog(
 471                 EventTypeFolderNode root, List<LaneDefinition> lanes, String title, String description) {
 472                 EditLanesWizardPage page = new EditLanesWizardPage(root, lanes);
 473                 page.setTitle(title);
 474                 page.setDescription(description);
 475                 if (OnePageWizardDialog.open(page, 500, 600) == Window.OK) {
 476                         return page.lanes.stream().filter(LaneEditor::laneIncludesTypes).collect(Collectors.toList());
 477                 }
 478                 return lanes;
 479         }
 480 
 481         private static boolean laneIncludesTypes(LaneDefinition ld) {
 482                 return ld.isRestLane() || ld.getFilter() instanceof Types && ((Types) ld.getFilter()).getTypes().size() > 0;
 483         }
 484 
 485         private static IItemFilter getRestFilter(List<LaneDefinition> lanesInput) {
 486                 List<IItemFilter> laneFilters = lanesInput.stream().filter(ld -> !ld.isRestLane).map(ld -> ld.getFilter())
 487                                 .collect(Collectors.toList());
 488                 IItemFilter laneFilter = ItemFilters.or(laneFilters.toArray(new IItemFilter[laneFilters.size()]));
 489                 return ItemFilters.and(ItemFilters.not(laneFilter), TYPE_HAS_THREAD_AND_DURATION);
 490         }
 491 
 492         public static LaneDefinition ensureRestLane(List<LaneDefinition> lanesInput) {
 493                 // FIXME: Should we react if there are several rest lanes specified, or just ignore the other ones?
 494                 LaneDefinition oldRestLane = lanesInput.stream().filter(ld -> ld.isRestLane).findAny().orElse(null);
 495                 LaneDefinition newRestLane;
 496                 IItemFilter restFilter = getRestFilter(lanesInput);
 497                 if (oldRestLane == null) {
 498                         newRestLane = new LaneDefinition(Messages.LANES_OTHER_TYPES, false, restFilter, true);
 499                         lanesInput.add(newRestLane);
 500                 } else {
 501                         newRestLane = new LaneDefinition(oldRestLane.name, oldRestLane.enabled, restFilter, true);
 502                         lanesInput.set(lanesInput.indexOf(oldRestLane), newRestLane);
 503                 }
 504                 return newRestLane;
 505         }
 506 }