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.io.FileInputStream;
  37 import java.io.InputStream;
  38 import java.util.ArrayList;
  39 import java.util.Arrays;
  40 import java.util.Collection;
  41 import java.util.Collections;
  42 import java.util.Comparator;
  43 import java.util.HashMap;
  44 import java.util.HashSet;
  45 import java.util.Iterator;
  46 import java.util.List;
  47 import java.util.Map;
  48 import java.util.Objects;
  49 import java.util.Optional;
  50 import java.util.Set;
  51 import java.util.function.Consumer;
  52 import java.util.function.Predicate;
  53 import java.util.function.Supplier;
  54 import java.util.regex.Matcher;
  55 import java.util.regex.Pattern;
  56 import java.util.stream.Collectors;
  57 import java.util.stream.Stream;
  58 
  59 import org.eclipse.core.runtime.IAdaptable;
  60 import org.eclipse.jface.action.Action;
  61 import org.eclipse.jface.action.IAction;
  62 import org.eclipse.jface.action.IMenuListener;
  63 import org.eclipse.jface.action.IMenuManager;
  64 import org.eclipse.jface.action.MenuManager;
  65 import org.eclipse.jface.action.Separator;
  66 import org.eclipse.jface.dialogs.InputDialog;
  67 import org.eclipse.jface.resource.ImageDescriptor;
  68 import org.eclipse.jface.resource.JFaceResources;
  69 import org.eclipse.jface.viewers.ColumnViewer;
  70 import org.eclipse.jface.viewers.IBaseLabelProvider;
  71 import org.eclipse.jface.viewers.StyledCellLabelProvider;
  72 import org.eclipse.jface.viewers.ViewerCell;
  73 import org.eclipse.jface.window.ToolTip;
  74 import org.eclipse.jface.window.Window;
  75 import org.eclipse.jface.wizard.Wizard;
  76 import org.eclipse.jface.wizard.WizardDialog;
  77 import org.eclipse.jface.wizard.WizardPage;
  78 import org.eclipse.swt.SWT;
  79 import org.eclipse.swt.custom.CTabFolder;
  80 import org.eclipse.swt.custom.CTabItem;
  81 import org.eclipse.swt.events.SelectionAdapter;
  82 import org.eclipse.swt.events.SelectionEvent;
  83 import org.eclipse.swt.graphics.GC;
  84 import org.eclipse.swt.graphics.Image;
  85 import org.eclipse.swt.graphics.ImageData;
  86 import org.eclipse.swt.layout.FillLayout;
  87 import org.eclipse.swt.layout.GridData;
  88 import org.eclipse.swt.layout.GridLayout;
  89 import org.eclipse.swt.widgets.Button;
  90 import org.eclipse.swt.widgets.Composite;
  91 import org.eclipse.swt.widgets.Control;
  92 import org.eclipse.swt.widgets.Display;
  93 import org.eclipse.swt.widgets.Event;
  94 import org.eclipse.swt.widgets.FileDialog;
  95 import org.eclipse.swt.widgets.Label;
  96 import org.eclipse.ui.forms.widgets.Form;
  97 import org.eclipse.ui.forms.widgets.FormText;
  98 import org.eclipse.ui.forms.widgets.FormToolkit;
  99 import org.openjdk.jmc.common.item.IAggregator;
 100 import org.openjdk.jmc.common.item.IAttribute;
 101 import org.openjdk.jmc.common.item.ICanonicalAccessorFactory;
 102 import org.openjdk.jmc.common.item.IItem;
 103 import org.openjdk.jmc.common.item.IItemCollection;
 104 import org.openjdk.jmc.common.item.IItemFilter;
 105 import org.openjdk.jmc.common.item.IItemIterable;
 106 import org.openjdk.jmc.common.item.IItemQuery;
 107 import org.openjdk.jmc.common.item.IMemberAccessor;
 108 import org.openjdk.jmc.common.item.IType;
 109 import org.openjdk.jmc.common.item.ItemFilters;
 110 import org.openjdk.jmc.common.item.ItemToolkit;
 111 import org.openjdk.jmc.common.unit.IQuantity;
 112 import org.openjdk.jmc.common.unit.IRange;
 113 import org.openjdk.jmc.common.unit.KindOfQuantity;
 114 import org.openjdk.jmc.common.unit.QuantitiesToolkit;
 115 import org.openjdk.jmc.common.unit.QuantityRange;
 116 import org.openjdk.jmc.common.unit.RangeContentType;
 117 import org.openjdk.jmc.common.unit.UnitLookup;
 118 import org.openjdk.jmc.common.util.ColorToolkit;
 119 import org.openjdk.jmc.common.util.CompositeKey;
 120 import org.openjdk.jmc.flightrecorder.JfrAttributes;
 121 import org.openjdk.jmc.flightrecorder.jdk.JdkAggregators;
 122 import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes;
 123 import org.openjdk.jmc.flightrecorder.jdk.JdkFilters;
 124 import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs;
 125 import org.openjdk.jmc.flightrecorder.rules.Result;
 126 import org.openjdk.jmc.flightrecorder.rules.Severity;
 127 import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI;
 128 import org.openjdk.jmc.flightrecorder.ui.IPageContainer;
 129 import org.openjdk.jmc.flightrecorder.ui.ItemCollectionToolkit;
 130 import org.openjdk.jmc.flightrecorder.ui.ItemIterableToolkit;
 131 import org.openjdk.jmc.flightrecorder.ui.PageManager;
 132 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram.CompositeKeyHistogramBuilder;
 133 import org.openjdk.jmc.flightrecorder.ui.common.ItemList.ItemListBuilder;
 134 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages;
 135 import org.openjdk.jmc.flightrecorder.ui.overview.ResultOverview;
 136 import org.openjdk.jmc.flightrecorder.ui.pages.itemhandler.ItemHandlerPage;
 137 import org.openjdk.jmc.flightrecorder.ui.pages.itemhandler.ItemHandlerPage.ItemHandlerUiStandIn;
 138 import org.openjdk.jmc.flightrecorder.ui.selection.IFilterFlavor;
 139 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStore.SelectionStoreEntry;
 140 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStoreActionToolkit;
 141 import org.openjdk.jmc.ui.charts.AWTChartToolkit.IColorProvider;
 142 import org.openjdk.jmc.ui.charts.IQuantitySeries;
 143 import org.openjdk.jmc.ui.charts.ISpanSeries;
 144 import org.openjdk.jmc.ui.charts.IXDataRenderer;
 145 import org.openjdk.jmc.ui.charts.QuantitySeries;
 146 import org.openjdk.jmc.ui.charts.RendererToolkit;
 147 import org.openjdk.jmc.ui.charts.SpanRenderer;
 148 import org.openjdk.jmc.ui.charts.SubdividedQuantityRange;
 149 import org.openjdk.jmc.ui.charts.XYChart;
 150 import org.openjdk.jmc.ui.charts.XYDataRenderer;
 151 import org.openjdk.jmc.ui.charts.XYQuantities;
 152 import org.openjdk.jmc.ui.column.ColumnMenusFactory;
 153 import org.openjdk.jmc.ui.column.TableSettings;
 154 import org.openjdk.jmc.ui.column.TableSettings.ColumnSettings;
 155 import org.openjdk.jmc.ui.handlers.ActionToolkit;
 156 import org.openjdk.jmc.ui.handlers.MCContextMenuManager;
 157 import org.openjdk.jmc.ui.misc.ChartCanvas;
 158 import org.openjdk.jmc.ui.misc.CompositeToolkit;
 159 import org.openjdk.jmc.ui.misc.DisplayToolkit;
 160 import org.openjdk.jmc.ui.misc.FilterEditor;
 161 import org.openjdk.jmc.ui.misc.FilterEditor.AttributeValueProvider;
 162 import org.openjdk.jmc.ui.misc.OverlayImageDescriptor;
 163 import org.openjdk.jmc.ui.misc.SWTColorToolkit;
 164 
 165 public class DataPageToolkit {
 166 
 167         public static final IColorProvider<IItem> ITEM_COLOR = item -> TypeLabelProvider
 168                         .getColorOrDefault(item.getType().getIdentifier());
 169 
 170         public static final IColorProvider<IItem> getAttributeValueColor(final IAttribute<?> attribute) {
 171                 return new IColorProvider<IItem>() {
 172 
 173                         @Override
 174                         public Color getColor(IItem item) {
 175                                 IMemberAccessor<?, IItem> accessor = attribute.getAccessor(ItemToolkit.getItemType(item));
 176                                 Object attributeValue = accessor != null ? accessor.getMember(item) : null;
 177                                 // FIXME: Should we include the type or not?
 178                                 return attributeValue != null
 179                                                 ? TypeLabelProvider.getColorOrDefault(attribute.getIdentifier() + "=" + attributeValue + "(" //$NON-NLS-1$ //$NON-NLS-2$
 180                                                                 + item.getType().getIdentifier() + ")") //$NON-NLS-1$
 181                                                 : ITEM_COLOR.getColor(item);
 182                         }
 183                 };
 184         }
 185 
 186         private static final Map<String, Color> FIELD_COLOR_MAP = new HashMap<>();
 187         private static final Map<String, Integer> DEFAULT_COLUMNS_ORDER;
 188 
 189         static {
 190 
 191                 // FIXME: Create FieldAppearance class, similar to TypeAppearence?
 192                 FIELD_COLOR_MAP.put(JdkAttributes.MACHINE_TOTAL.getIdentifier(), new Color(255, 128, 0));
 193                 FIELD_COLOR_MAP.put(JdkAttributes.JVM_SYSTEM.getIdentifier(), new Color(128, 128, 128));
 194                 FIELD_COLOR_MAP.put(JdkAttributes.JVM_USER.getIdentifier(), new Color(0, 0, 255));
 195                 FIELD_COLOR_MAP.put(JdkAttributes.JVM_TOTAL.getIdentifier(), new Color(64, 64, 191));
 196 
 197                 // FIXME: Handle ColorProvider and combined events
 198                 Map<String, Integer> columnsOrderMap = new HashMap<>();
 199                 columnsOrderMap.put(createColumnId(JfrAttributes.START_TIME), 1);
 200                 columnsOrderMap.put(createColumnId(JfrAttributes.DURATION), 2);
 201                 columnsOrderMap.put(createColumnId(JfrAttributes.END_TIME), 3);
 202                 columnsOrderMap.put(createColumnId(JfrAttributes.EVENT_THREAD), 4);
 203                 DEFAULT_COLUMNS_ORDER = Collections.unmodifiableMap(columnsOrderMap);
 204         }
 205 
 206         public static final Color ALLOCATION_COLOR = new Color(64, 144, 230);
 207 
 208         public static final String FORM_TOOLBAR_PAGE_RESULTS = "pageResults"; //$NON-NLS-1$
 209         public static final String FORM_TOOLBAR_PAGE_SETUP = "pageSetup"; //$NON-NLS-1$
 210         public static final String FORM_TOOLBAR_PAGE_NAV = "pageNav"; //$NON-NLS-1$
 211 
 212         public static final String RESULT_ACTION_ID = "resultAction"; //$NON-NLS-1$
 213 
 214         public static Color getFieldColor(String fieldId) {
 215                 return FIELD_COLOR_MAP.getOrDefault(fieldId, ColorToolkit.getDistinguishableColor(fieldId));
 216         }
 217 
 218         public static Color getFieldColor(IAttribute<?> attribute) {
 219                 return getFieldColor(attribute.getIdentifier());
 220         }
 221 
 222         public static TableSettings createTableSettingsByOrderByAndColumnsWithDefaultOrdering(
 223                 final String orderBy, final Collection<ColumnSettings> columns) {
 224                 final Stream<ColumnSettings> defaultOrderColumns = columns.stream()
 225                                 .filter(c -> DEFAULT_COLUMNS_ORDER.containsKey(c.getId())).filter(c -> !c.isHidden())
 226                                 .sorted((c1, c2) -> Integer.compare(DEFAULT_COLUMNS_ORDER.get(c1.getId()),
 227                                                 DEFAULT_COLUMNS_ORDER.get(c2.getId())));
 228                 final Stream<ColumnSettings> naturalOrderColumns = columns.stream()
 229                                 .filter(c -> !DEFAULT_COLUMNS_ORDER.containsKey(c.getId()))
 230                                 .sorted((c1, c2) -> String.CASE_INSENSITIVE_ORDER.compare(c1.getId(), c2.getId()));
 231                 final List<ColumnSettings> resultColumns = Stream.concat(defaultOrderColumns, naturalOrderColumns)
 232                                 .collect(Collectors.toList());
 233                 return new TableSettings(orderBy, resultColumns);
 234         }
 235 
 236         public static TableSettings createTableSettingsByAllAndVisibleColumns(
 237                 final Collection<String> allColumns, final Collection<String> visibleColumns) {
 238                 final List<ColumnSettings> defaultListCols = new ArrayList<>();
 239                 for (String columnId : allColumns) {
 240                         defaultListCols.add(new ColumnSettings(columnId, !visibleColumns.contains(columnId), null, null));
 241                 }
 242                 return createTableSettingsByOrderByAndColumnsWithDefaultOrdering(null, defaultListCols);
 243         }
 244 
 245         private static String createColumnId(IAttribute<?> attr) {
 246                 return new StringBuilder().append(attr.getIdentifier()).append(":") //$NON-NLS-1$
 247                                 .append(attr.getContentType().getIdentifier()).toString();
 248         }
 249 
 250         public static IAction createAttributeCheckAction(IAttribute<?> attribute, Consumer<Boolean> onChange) {
 251                 return createCheckAction(attribute.getName(), attribute.getDescription(), attribute.getIdentifier(),
 252                                 getFieldColor(attribute), onChange);
 253         }
 254 
 255         public static IAction createTypeCheckAction(
 256                 String actionId, String typeId, String name, String description, Consumer<Boolean> onChange) {
 257                 return createCheckAction(name, description, actionId, TypeLabelProvider.getColorOrDefault(typeId), onChange);
 258         }
 259 
 260         public static IAction createAggregatorCheckAction(
 261                 IAggregator<?, ?> aggregator, String id, Color color, Consumer<Boolean> onChange) {
 262                 return createCheckAction(aggregator.getName(), aggregator.getDescription(), id, color, onChange);
 263         }
 264 
 265         public static IAction createCheckAction(
 266                 String name, String description, String id, Color color, Consumer<Boolean> onChange) {
 267                 return createCheckAction(name, description, id,
 268                                 SWTColorToolkit.getColorThumbnailDescriptor(SWTColorToolkit.asRGB(color)), onChange);
 269         }
 270 
 271         public static IAction createCheckAction(
 272                 String name, String description, String id, ImageDescriptor icon, Consumer<Boolean> onChange) {
 273                 return ActionToolkit.checkAction(onChange, name, description, icon, id);
 274         }
 275 
 276         public static Optional<IXDataRenderer> buildLinesRow(
 277                 String title, String description, IItemCollection items, boolean fill, IItemQuery query,
 278                 Predicate<IAttribute<IQuantity>> attributeFilter, IQuantity includeLow, IQuantity includeHigh) {
 279                 XYDataRenderer renderer = includeHigh != null
 280                                 ? new XYDataRenderer(includeLow, includeHigh, true, title, description)
 281                                 : new XYDataRenderer(includeLow, title, description);
 282                 IItemCollection filteredItemsSupplier = items.apply(query.getFilter());
 283                 Stream<IAttribute<IQuantity>> attributes = getQuantityAttributes(query);
 284                 if (attributeFilter != null) {
 285                         attributes = attributes.filter(attributeFilter);
 286                 }
 287                 if (DataPageToolkit.addEndTimeLines(renderer, filteredItemsSupplier, fill, attributes)) {
 288                         return Optional.of(new ItemRow(title, description, renderer, filteredItemsSupplier));
 289                 }
 290                 return Optional.empty();
 291         }
 292 
 293         /**
 294          * @param q
 295          *            A query containing only {@code IAttribute<IQuantity>} attributes. Queries
 296          *            containing non-quantity attributes are not supported and may cause
 297          *            ClassCastExceptions later when the attributes are used.
 298          * @return a stream of the query attributes
 299          */
 300         /*
 301          * FIXME: JMC-5125 - This cast chain is scary and should be reworked.
 302          * 
 303          * If the query contains any non-quantity attributes then there will be a ClassCastException
 304          * later when the attributes are used to extract values.
 305          */
 306         @SuppressWarnings({"unchecked", "rawtypes"})
 307         public static Stream<IAttribute<IQuantity>> getQuantityAttributes(IItemQuery q) {
 308                 return (Stream) q.getAttributes().stream();
 309         }
 310 
 311         public static void setChart(ChartCanvas canvas, XYChart chart, Consumer<IItemCollection> selectionListener) {
 312                 setChart(canvas, chart, selectionListener, null);
 313         }
 314 
 315         public static void setChart(
 316                 ChartCanvas canvas, XYChart chart, Consumer<IItemCollection> selectionListener,
 317                 Consumer<IRange<IQuantity>> selectRangeConsumer) {
 318                 IMenuManager contextMenu = canvas.getContextMenu();
 319                 contextMenu.removeAll();
 320                 canvas.getContextMenu().add(new Action(Messages.CHART_ZOOM_TO_SELECTED_RANGE) {
 321                         @Override
 322                         public void run() {
 323                                 IQuantity selectionStart = chart.getSelectionStart();
 324                                 IQuantity selectionEnd = chart.getSelectionEnd();
 325                                 if (selectionStart == null || selectionEnd == null) {
 326                                         chart.clearVisibleRange();
 327                                 } else {
 328                                         chart.setVisibleRange(selectionStart, selectionEnd);
 329                                 }
 330                                 canvas.redrawChart();
 331                         }
 332                 });
 333 
 334                 canvas.setSelectionListener(() -> {
 335                         selectionListener.accept(ItemRow.getRangeSelection(chart, JfrAttributes.LIFETIME));
 336                         IQuantity start = chart.getSelectionStart();
 337                         IQuantity end = chart.getSelectionEnd();
 338                         if (selectRangeConsumer != null) {
 339                                 selectRangeConsumer
 340                                                 .accept(start != null && end != null ? QuantityRange.createWithEnd(start, end) : null);
 341                         }
 342                 });
 343                 canvas.setChart(chart);
 344         }
 345 
 346         public static void setChart(
 347                 ChartCanvas canvas, XYChart chart, IAttribute<IQuantity> selectionAttribute,
 348                 Consumer<IItemCollection> selectionListener) {
 349                 IMenuManager contextMenu = canvas.getContextMenu();
 350                 contextMenu.removeAll();
 351                 canvas.setSelectionListener(() -> selectionListener.accept(ItemRow.getSelection(chart, selectionAttribute)));
 352                 canvas.setChart(chart);
 353         }
 354 
 355         /**
 356          * Only works for items that are either fully overlapping, or disjunct. Must be ensured by
 357          * client code.
 358          */
 359         private static class RangePayload implements IAdaptable {
 360                 IItem item;
 361                 IQuantity start;
 362                 IQuantity end;
 363                 double rangeInPixels;
 364 
 365                 RangePayload(IItem item, IQuantity start, IQuantity end, double rangeInPixels) {
 366                         this.item = item;
 367                         this.start = start;
 368                         this.end = end;
 369                         this.rangeInPixels = rangeInPixels;
 370                 }
 371 
 372                 void combineWith(IItem item, IQuantity start, IQuantity end, double rangeInPixels) {
 373                         if (this.start.compareTo(start) < 0) {
 374                                 // Will choose the item that starts last
 375                                 this.start = start;
 376                                 this.end = end;
 377                                 this.item = item;
 378                                 extendRangeInPixels(this.end.compareTo(start) > 0, rangeInPixels);
 379                         } else {
 380                                 extendRangeInPixels(end.compareTo(this.start) > 0, rangeInPixels);
 381                         }
 382                 }
 383 
 384                 void extendRangeInPixels(boolean overlapping, double rangeInPixels) {
 385                         this.rangeInPixels = overlapping ? Math.max(this.rangeInPixels, rangeInPixels)
 386                                         : this.rangeInPixels + rangeInPixels;
 387                 }
 388 
 389                 @Override
 390                 public <T> T getAdapter(Class<T> adapter) {
 391                         return IItem.class.isAssignableFrom(adapter) ? adapter.cast(item) : null;
 392                 }
 393         }
 394 
 395         private static ISpanSeries<RangePayload> rangeSeries(
 396                 IItemCollection events, IAttribute<IQuantity> startAttribute, IAttribute<IQuantity> endAttribute) {
 397                 return new ISpanSeries<RangePayload>() {
 398                         @Override
 399                         public XYQuantities<RangePayload[]> getQuantities(SubdividedQuantityRange xBucketRange) {
 400                                 SubdividedQuantityRange xRange = xBucketRange.copyWithPixelSubdividers();
 401                                 List<RangePayload> spanningPixels = new ArrayList<>();
 402                                 RangePayload[] pixelBuckets = new RangePayload[xRange.getNumSubdividers()];
 403                                 events.forEach(is -> {
 404                                         IMemberAccessor<IQuantity, IItem> startAccessor = startAttribute.getAccessor(is.getType());
 405                                         IMemberAccessor<IQuantity, IItem> endAccessor = endAttribute.getAccessor(is.getType());
 406                                         is.forEach(item -> {
 407                                                 IQuantity start = startAccessor.getMember(item);
 408                                                 IQuantity end = endAccessor.getMember(item);
 409                                                 int xPos = xRange.getFloorSubdivider(start);
 410                                                 int endPos = xRange.getFloorSubdivider(end);
 411                                                 if (xPos < pixelBuckets.length && endPos >= 0) {
 412                                                         // FIXME: If we have very short events (nanosecond scale) we can sometimes get a negative range.
 413                                                         double rangeInPixels = xRange.getPixel(end) - xRange.getPixel(start);
 414                                                         if (xPos != endPos) {
 415                                                                 spanningPixels.add(new RangePayload(item, start, end, rangeInPixels));
 416                                                         } else if (pixelBuckets[xPos] == null) {
 417                                                                 pixelBuckets[xPos] = new RangePayload(item, start, end, rangeInPixels);
 418                                                         } else {
 419                                                                 pixelBuckets[xPos].combineWith(item, start, end, rangeInPixels);
 420                                                         }
 421                                                 }
 422                                         });
 423                                 });
 424                                 RangePayload[] sorted = Stream
 425                                                 .concat(Stream.of(pixelBuckets).filter(Objects::nonNull), spanningPixels.stream())
 426                                                 .sorted(Comparator.comparing(r -> r.start)).toArray(RangePayload[]::new);
 427                                 // FIXME: Should make it possible to use the RangePayload[] directly instead
 428                                 List<IQuantity> starts = Stream.of(sorted).map(r -> r.start).collect(Collectors.toList());
 429                                 List<IQuantity> ends = Stream.of(sorted).map(r -> r.end).collect(Collectors.toList());
 430                                 return XYQuantities.create(sorted, starts, ends, xRange);
 431                         }
 432 
 433                         @Override
 434                         public IQuantity getStartX(RangePayload payload) {
 435                                 return payload.start;
 436                         }
 437                 };
 438         }
 439 
 440         public final static Color GC_BASE_COLOR = TypeLabelProvider.getColor(JdkTypeIDs.GC_PAUSE);
 441         private final static Color VM_OPERATIONS_BASE_COLOR = TypeLabelProvider.getColor(JdkTypeIDs.VM_OPERATIONS);
 442         private final static IColorProvider<RangePayload> GC_COLOR = payload -> adjustAlpha(GC_BASE_COLOR,
 443                         payload.rangeInPixels);
 444         private final static IColorProvider<RangePayload> APPLICATION_PAUSE_COLOR = payload -> adjustAlpha(
 445                         payload.item.getType().getIdentifier().equals(JdkTypeIDs.GC_PAUSE) ? GC_BASE_COLOR
 446                                         : VM_OPERATIONS_BASE_COLOR,
 447                         payload.rangeInPixels);
 448         public final static ImageDescriptor GC_LEGEND_ICON = new OverlayImageDescriptor(
 449                         SWTColorToolkit.getColorThumbnailDescriptor(SWTColorToolkit.asRGB(GC_BASE_COLOR)), false,
 450                         FlightRecorderUI.getDefault().getMCImageDescriptor("trash_overlay.png")); //$NON-NLS-1$
 451 
 452         /**
 453          * Return a color with alpha calculated from a fraction.
 454          *
 455          * @param color
 456          *            a base color
 457          * @param fraction
 458          *            A value where 0 gives the lowest alpha value and 1 gives the highest. Fractions
 459          *            above 1 are accepted and treated as 1. Negative fractions should not be used.
 460          * @return a color with RGB from the base color and an alpha value depending on the fraction
 461          */
 462         private static Color adjustAlpha(Color color, double fraction) {
 463                 return ColorToolkit.withAlpha(color, Math.min(200, (int) ((Math.max(0, fraction) + 0.15) * 255)));
 464         }
 465 
 466         public static ItemRow buildGcPauseRow(IItemCollection items) {
 467                 IItemCollection pauseEvents = items.apply(JdkFilters.GC_PAUSE);
 468                 ISpanSeries<RangePayload> gcBackdrop = rangeSeries(pauseEvents, JfrAttributes.START_TIME,
 469                                 JfrAttributes.END_TIME);
 470                 return new ItemRow(SpanRenderer.build(gcBackdrop, GC_COLOR), pauseEvents);
 471         }
 472 
 473         public static ItemRow buildApplicationPauseRow(IItemCollection items) {
 474                 IItemFilter vmOperationPauseFilter = ItemFilters.and(JdkFilters.VM_OPERATIONS,
 475                                 ItemFilters.equals(JdkAttributes.SAFEPOINT, true));
 476                 IItemCollection applicationPauses = items
 477                                 .apply(ItemFilters.or(JdkFilters.GC_PAUSE, JdkFilters.SAFE_POINTS, vmOperationPauseFilter));
 478                 ISpanSeries<RangePayload> pausesSeries = rangeSeries(applicationPauses, JfrAttributes.START_TIME,
 479                                 JfrAttributes.END_TIME);
 480                 return new ItemRow(SpanRenderer.build(pausesSeries, APPLICATION_PAUSE_COLOR), applicationPauses);
 481         }
 482 
 483         public static IXDataRenderer buildTimestampHistogramRenderer(
 484                 IItemCollection items, IAggregator<IQuantity, ?> aggregator, IAttribute<IQuantity> timestampAttribute,
 485                 Color color) {
 486                 IQuantitySeries<IQuantity[]> aggregatorSeries = BucketBuilder.aggregatorSeries(items, aggregator,
 487                                 timestampAttribute);
 488                 XYDataRenderer renderer = new XYDataRenderer(getKindOfQuantity(aggregator).getDefaultUnit().quantity(0),
 489                                 aggregator.getName(), aggregator.getDescription());
 490                 renderer.addBarChart(aggregator.getName(), aggregatorSeries, color);
 491                 return renderer;
 492         }
 493 
 494         public static IXDataRenderer buildTimestampHistogramRenderer(
 495                 IItemCollection items, IAggregator<IQuantity, ?> aggregator, Color color) {
 496                 return buildTimestampHistogramRenderer(items, aggregator, JfrAttributes.CENTER_TIME, color);
 497         }
 498 
 499         public static ItemRow buildTimestampHistogram(
 500                 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> aggregator,
 501                 IAttribute<IQuantity> timestampAttribute, Color color) {
 502                 return new ItemRow(title, description,
 503                                 buildTimestampHistogramRenderer(items, aggregator, timestampAttribute, color), items);
 504         }
 505 
 506         public static ItemRow buildTimestampHistogram(
 507                 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> aggregator, Color color) {
 508                 return new ItemRow(title, description, buildTimestampHistogramRenderer(items, aggregator, color), items);
 509         }
 510 
 511         public static ItemHistogram createDistinctItemsTable(
 512                 Composite parent, IItemCollection items, IItemQuery query, TableSettings settings) {
 513                 CompositeKeyHistogramBuilder histogramBuilder = new CompositeKeyHistogramBuilder();
 514                 for (IAttribute<?> attribute : query.getAttributes()) {
 515                         histogramBuilder.addKeyColumn(attribute);
 516                 }
 517                 ItemHistogram table = histogramBuilder.buildWithoutBorder(parent, settings);
 518                 return table;
 519         }
 520 
 521         public static IBaseLabelProvider createTableHighlightProvider(Pattern highlightPattern, boolean isWarning) {
 522                 return new StyledCellLabelProvider() {
 523                         @Override
 524                         public void update(ViewerCell cell) {
 525                                 org.eclipse.swt.graphics.Color color = isWarning
 526                                                 ? new org.eclipse.swt.graphics.Color(Display.getCurrent(), 240, 120, 140)
 527                                                 : new org.eclipse.swt.graphics.Color(Display.getCurrent(), 255, 144, 4);
 528                                 String text = getText(cell.getElement(), cell.getColumnIndex());
 529                                 Matcher matcher = highlightPattern.matcher(text);
 530                                 if (matcher.find()) {
 531                                         cell.getViewerRow().setBackground(0, color);
 532                                         cell.getViewerRow().setBackground(1, color);
 533                                 }
 534                                 cell.setText(text);
 535                                 super.update(cell);
 536                         }
 537 
 538                         private String getText(Object element, int index) {
 539                                 Object key = AggregationGrid.getKey(element);
 540                                 Object[] keyElements = ((CompositeKey) key).getKeyElements();
 541                                 return keyElements[index].toString();
 542                         }
 543                 };
 544         }
 545 
 546         public static void addContextMenus(
 547                 IPageContainer pc, ItemHistogram h, String selectionName, IAction ... extraActions) {
 548                 MCContextMenuManager mm = MCContextMenuManager.create(h.getManager().getViewer().getControl());
 549                 ColumnMenusFactory.addDefaultMenus(h.getManager(), mm);
 550                 SelectionStoreActionToolkit.addSelectionStoreActions(pc.getSelectionStore(), h, selectionName, mm);
 551                 for (IAction action : extraActions) {
 552                         mm.add(action);
 553                 }
 554         }
 555 
 556         public static IXDataRenderer buildSizeRow(
 557                 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> a, Color color,
 558                 IColorProvider<IItem> cp) {
 559                 return RendererToolkit.layers(buildSpanRenderer(items, cp),
 560                                 buildTimestampHistogram(title, description, items, a, color));
 561         }
 562 
 563         public static ItemRow buildDurationHistogram(
 564                 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> a, Color color) {
 565                 IQuantitySeries<IQuantity[]> allocationSeries = BucketBuilder.aggregatorSeries(items, a,
 566                                 JfrAttributes.DURATION);
 567                 XYDataRenderer renderer = new XYDataRenderer(getKindOfQuantity(a).getDefaultUnit().quantity(0), title,
 568                                 description);
 569                 renderer.addBarChart(a.getName(), allocationSeries, color);
 570                 return new ItemRow(title, description, renderer, items);
 571         }
 572         
 573         public static ItemRow buildSizeHistogram(
 574                 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> a, Color color, IAttribute<IQuantity> attribute) {
 575                 
 576                 IQuantitySeries<IQuantity[]> allocationSeries = BucketBuilder.aggregatorSeries(items, a,
 577                                 JdkAttributes.IO_SIZE);
 578                 XYDataRenderer renderer = new XYDataRenderer(getKindOfQuantity(a).getDefaultUnit().quantity(0), title,
 579                                 description);
 580                 renderer.addBarChart(a.getName(), allocationSeries, color);
 581                 return new ItemRow(title, description, renderer, items);
 582         }
 583         
 584         public static IRange<IQuantity> buildSizeRange(IItemCollection items, boolean isSocket){
 585                 IQuantity end = null;
 586                 if(isSocket) {
 587                         end = QuantitiesToolkit.maxPresent(items.getAggregate(JdkAggregators.SOCKET_READ_LARGEST),
 588                                         items.getAggregate(JdkAggregators.SOCKET_WRITE_LARGEST)); 
 589                                                 
 590                 } else {
 591                         end = QuantitiesToolkit.maxPresent(items.getAggregate(JdkAggregators.FILE_READ_LARGEST),
 592                                         items.getAggregate(JdkAggregators.FILE_WRITE_LARGEST));
 593                 }
 594                 end = end == null ? UnitLookup.BYTE.quantity(1024) : end;
 595                 return QuantityRange.createWithEnd(UnitLookup.BYTE.quantity(0), end);
 596         }
 597 
 598         // FIXME: Make something that can use something other than time as x-axis?
 599         public static IXDataRenderer buildSpanRenderer(IItemCollection pathItems, IColorProvider<IItem> cp) {
 600                 ISpanSeries<IItem> dataSeries = QuantitySeries.max(pathItems, JfrAttributes.START_TIME, JfrAttributes.END_TIME);
 601                 return SpanRenderer.withBoundaries(dataSeries, cp);
 602         }
 603 
 604         public static boolean addEndTimeLines(
 605                 XYDataRenderer renderer, IItemCollection items, boolean fill, Stream<IAttribute<IQuantity>> yAttributes) {
 606                 // FIXME: JMC-4520 - Handle multiple item iterables
 607                 Iterator<IItemIterable> ii = items.iterator();
 608                 if (ii.hasNext()) {
 609                         IItemIterable itemStream = ii.next();
 610                         IType<IItem> type = itemStream.getType();
 611                         // FIXME: A better way to ensure sorting by endTime
 612                         return yAttributes.peek(a -> addEndTimeLine(renderer, itemStream.iterator(), type, a, fill))
 613                                         .mapToLong(a -> 1L).sum() > 0;
 614                 }
 615                 return false;
 616         }
 617 
 618         public static void addEndTimeLine(
 619                 XYDataRenderer renderer, Iterator<? extends IItem> items, IType<IItem> type, IAttribute<IQuantity> yAttribute,
 620                 boolean fill) {
 621                 IQuantitySeries<?> qs = buildQuantitySeries(items, type, JfrAttributes.END_TIME, yAttribute);
 622                 renderer.addLineChart(yAttribute.getName(), qs, getFieldColor(yAttribute), fill);
 623         }
 624 
 625         public static IQuantitySeries<?> buildQuantitySeries(
 626                 Iterator<? extends IItem> items, IType<IItem> type, IAttribute<IQuantity> xAttribute,
 627                 IAttribute<IQuantity> yAttribute) {
 628                 IMemberAccessor<IQuantity, IItem> yAccessor = yAttribute.getAccessor(type);
 629                 if (yAccessor == null) {
 630                         throw new RuntimeException(yAttribute.getIdentifier() + " is not an attribute for " + type.getIdentifier()); //$NON-NLS-1$
 631                 }
 632                 return buildQuantitySeries(items, type, xAttribute, yAccessor);
 633         }
 634 
 635         public static IQuantitySeries<?> buildQuantitySeries(
 636                 Iterator<? extends IItem> items, IType<IItem> type, IAttribute<IQuantity> xAttribute,
 637                 IMemberAccessor<? extends IQuantity, IItem> yAccessor) {
 638                 IMemberAccessor<IQuantity, IItem> xAccessor = xAttribute.getAccessor(type);
 639                 return QuantitySeries.all(items, xAccessor, yAccessor);
 640         }
 641 
 642         public static void createChartTooltip(ChartCanvas chart) {
 643                 createChartTooltip(chart, ChartToolTipProvider::new);
 644         }
 645 
 646         public static void createChartTimestampTooltip(ChartCanvas chart) {
 647                 createChartTooltip(chart, JfrAttributes.START_TIME, JfrAttributes.END_TIME, JfrAttributes.DURATION,
 648                                 JfrAttributes.EVENT_TYPE, JfrAttributes.EVENT_STACKTRACE);
 649         }
 650 
 651         public static void createChartTooltip(ChartCanvas chart, IAttribute<?> ... excludedAttributes) {
 652                 createChartTooltip(chart, new HashSet<>(Arrays.asList(excludedAttributes)));
 653         }
 654 
 655         public static void createChartTooltip(ChartCanvas chart, Set<IAttribute<?>> excludedAttributes) {
 656                 createChartTooltip(chart, () -> new ChartToolTipProvider() {
 657                         @Override
 658                         protected Stream<IAttribute<?>> getAttributeStream(IType<IItem> type) {
 659                                 return type.getAttributes().stream().filter(a -> !excludedAttributes.contains(a));
 660                         }
 661                 });
 662         }
 663 
 664         public static void createChartTooltip(ChartCanvas chart, Supplier<ChartToolTipProvider> toolTipProviderSupplier) {
 665                 new ToolTip(chart) {
 666                         String html;
 667                         Map<String, Image> images;
 668 
 669                         @Override
 670                         protected boolean shouldCreateToolTip(Event event) {
 671                                 ChartToolTipProvider provider = toolTipProviderSupplier.get();
 672                                 chart.infoAt(provider, event.x, event.y);
 673                                 html = provider.getHTML();
 674                                 images = provider.getImages();
 675                                 return html != null;
 676                         }
 677 
 678                         @Override
 679                         protected Composite createToolTipContentArea(Event event, Composite parent) {
 680                                 FormText formText = CompositeToolkit.createInfoFormText(parent);
 681                                 for (Map.Entry<String, Image> imgEntry : images.entrySet()) {
 682                                         formText.setImage(imgEntry.getKey(), imgEntry.getValue());
 683                                 }
 684                                 formText.setText(html, true, false);
 685                                 return formText;
 686                         }
 687 
 688                 };
 689 
 690         }
 691 
 692         private static KindOfQuantity<?> getKindOfQuantity(IAggregator<IQuantity, ?> a) {
 693                 IType<? super IQuantity> ct = a.getValueType();
 694                 // FIXME: Refactor to avoid this cast
 695                 return ((KindOfQuantity<?>) ct);
 696         }
 697 
 698         public static Form createForm(Composite parent, FormToolkit toolkit, String title, Image img) {
 699                 Form form = toolkit.createForm(parent);
 700                 form.setText(title.replaceAll("&", "&&")); //$NON-NLS-1$ //$NON-NLS-2$
 701                 form.setImage(img);
 702                 toolkit.decorateFormHeading(form);
 703                 FillLayout fillLayout = new FillLayout();
 704                 fillLayout.marginHeight = 15;
 705                 fillLayout.marginWidth = 8;
 706                 form.getBody().setLayout(fillLayout);
 707                 form.getToolBarManager().add(new Separator(FORM_TOOLBAR_PAGE_RESULTS));
 708                 form.getToolBarManager().add(new Separator(FORM_TOOLBAR_PAGE_SETUP));
 709                 form.getToolBarManager().add(new Separator(FORM_TOOLBAR_PAGE_NAV));
 710                 return form;
 711         }
 712 
 713         public static class ShowResultAction extends Action {
 714 
 715                 private String[] topics;
 716                 private final IPageContainer pageContainer;
 717                 private volatile Severity maxSeverity;
 718                 private final List<Consumer<Result>> listeners = new ArrayList<>();
 719 
 720                 ShowResultAction(String title, int style, ImageDescriptor icon, Supplier<String> tooltip,
 721                                 IPageContainer pageContainer, String ... topics) {
 722                         super(title, style);
 723                         setImageDescriptor(icon);
 724                         setToolTipText(tooltip.get());
 725                         this.topics = topics;
 726                         this.pageContainer = pageContainer;
 727                         maxSeverity = pageContainer.getRuleManager().getMaxSeverity(topics);
 728                         for (String topic : topics) {
 729                                 Consumer<Result> listener = result -> {
 730                                         Severity severity = Severity.get(result.getScore());
 731                                         if (severity.compareTo(maxSeverity) > 0) {
 732                                                 maxSeverity = severity;
 733                                                 setImageDescriptor(getResultIcon(maxSeverity));
 734                                         } else if (severity.compareTo(maxSeverity) < 0) { // severity could be less than previous max
 735                                                 maxSeverity = pageContainer.getRuleManager().getMaxSeverity(topics);
 736                                         }
 737                                         setToolTipText(tooltip.get());
 738                                 };
 739                                 listeners.add(listener);
 740                                 pageContainer.getRuleManager().addResultListener(topic, listener);
 741                         }
 742                 }
 743 
 744                 private void removeListeners() {
 745                         listeners.forEach(l -> pageContainer.getRuleManager().removeResultListener(l));
 746                 }
 747 
 748                 @Override
 749                 public void run() {
 750                         pageContainer.showResults(topics);
 751                 }
 752         }
 753 
 754         public static void addRuleResultAction(
 755                 Form form, IPageContainer pageContainer, Supplier<String> tooltip, String[] topics) {
 756                 if (topics == null || topics.length == 0 || !FlightRecorderUI.getDefault().isAnalysisEnabled()) {
 757                         return;
 758                 }
 759                 ImageDescriptor icon = getResultIcon(pageContainer.getRuleManager().getMaxSeverity(topics));
 760                 ShowResultAction resultAction = new ShowResultAction(Messages.RULES_SHOW_RESULTS_ACTION, IAction.AS_PUSH_BUTTON,
 761                                 icon, tooltip, pageContainer, topics);
 762                 resultAction.setId(RESULT_ACTION_ID);
 763                 form.getToolBarManager().appendToGroup(DataPageToolkit.FORM_TOOLBAR_PAGE_RESULTS, resultAction);
 764                 form.getToolBarManager().update(true);
 765                 form.addDisposeListener(e -> resultAction.removeListeners());
 766         }
 767 
 768         private static ImageDescriptor getResultIcon(Severity severity) {
 769                 switch (severity) {
 770                 case OK:
 771                         return ResultOverview.ICON_OK;
 772                 case INFO:
 773                         return ResultOverview.ICON_INFO;
 774                 case WARNING:
 775                         return ResultOverview.ICON_WARNING;
 776                 case NA:
 777                         return ResultOverview.ICON_NA;
 778                 }
 779                 return null;
 780         }
 781 
 782         /**
 783          * Return a disabled Action.
 784          *
 785          * @param text
 786          *            text to be displayed by the MenuItem, and represent it as it's id.
 787          * @return an Action containing the desired text, which will be disabled in a UI component.
 788          */
 789         public static IAction disabledAction(String text) {
 790                 IAction disabledAction = new Action(text) {
 791                         @Override
 792                         public boolean isEnabled() {
 793                                 return false;
 794                         }
 795                 };
 796                 disabledAction.setId(text);
 797                 return disabledAction;
 798         }
 799 
 800         public static FilterEditor buildFilterSelector(
 801                 Composite parent, IItemFilter filter, IItemCollection items, Supplier<Stream<SelectionStoreEntry>> selections,
 802                 Consumer<IItemFilter> onSelect, boolean hasBorder) {
 803                 Supplier<Collection<IAttribute<?>>> attributeSupplier = () -> getPersistableAttributes(
 804                                 getAttributes(filter != null ? items.apply(filter) : items)).collect(Collectors.toList());
 805 
 806                 AttributeValueProvider valueSupplier = new AttributeValueProvider() {
 807                         @Override
 808                         public <V> V defaultValue(ICanonicalAccessorFactory<V> attribute) {
 809                                 return findValueForFilter(items, attribute);
 810                         }
 811                 };
 812 
 813                 FilterEditor editor = new FilterEditor(parent, onSelect, filter, attributeSupplier, valueSupplier,
 814                                 TypeLabelProvider::getColorOrDefault, hasBorder ? SWT.BORDER : SWT.NONE);
 815 
 816                 MenuManager addFromSelectionPredicate = new MenuManager(Messages.FILTER_ADD_FROM_SELECTION);
 817                 editor.getContextMenu().prependToGroup(MCContextMenuManager.GROUP_NEW, addFromSelectionPredicate);
 818                 addFromSelectionPredicate.setRemoveAllWhenShown(true);
 819                 addFromSelectionPredicate.addMenuListener(new IMenuListener() {
 820 
 821                         @Override
 822                         public void menuAboutToShow(IMenuManager manager) {
 823                                 selections.get().forEach(entry -> {
 824                                         MenuManager selectionFlavors = new MenuManager(entry.getName());
 825                                         entry.getSelection().getFlavors(editor.getFilter(), items, null)
 826                                                         .filter(f -> f instanceof IFilterFlavor).forEach(flavor -> {
 827                                                                 selectionFlavors.add(new Action(flavor.getName()) {
 828                                                                         @Override
 829                                                                         public void run() {
 830                                                                                 editor.addRoot(((IFilterFlavor) flavor).getFilter());
 831                                                                         }
 832                                                                 });
 833                                                         });
 834                                         if (!selectionFlavors.isEmpty()) {
 835                                                 if (manager.find(Messages.FILTER_NO_SELECTION_AVAILABLE) != null) {
 836                                                         manager.remove(Messages.FILTER_NO_SELECTION_AVAILABLE);
 837                                                 }
 838                                                 manager.add(selectionFlavors);
 839                                         } else {
 840                                                 manager.add(disabledAction(Messages.FILTER_NO_SELECTION_AVAILABLE));
 841                                         }
 842                                 });
 843                         }
 844                 });
 845 
 846                 // FIXME: This could potentially move into the FilterEditor class
 847                 MenuManager addAttributeValuePredicate = new MenuManager(Messages.FILTER_ADD_FROM_ATTRIBUTE);
 848                 editor.getContextMenu().prependToGroup(MCContextMenuManager.GROUP_NEW, addAttributeValuePredicate);
 849                 addAttributeValuePredicate.setRemoveAllWhenShown(true);
 850                 addAttributeValuePredicate.addMenuListener(new IMenuListener() {
 851                         Collection<IAttribute<?>> attributes;
 852 
 853                         @Override
 854                         public void menuAboutToShow(IMenuManager manager) {
 855                                 if (attributes == null) {
 856                                         attributes = attributeSupplier.get();
 857                                 }
 858                                 if (!attributes.isEmpty()) {
 859                                         if (manager.find(Messages.FILTER_NO_ATTRIBUTE_AVAILABLE) != null) {
 860                                                 manager.remove(Messages.FILTER_NO_ATTRIBUTE_AVAILABLE);
 861                                         }
 862                                         attributes.stream().distinct().sorted((a1, a2) -> a1.getName().compareTo(a2.getName()))
 863                                                 .forEach(attr -> {
 864                                                         addAttributeValuePredicate.add(new Action(attr.getName()) {
 865                                                                 @Override
 866                                                                 public void run() {
 867                                                                         IItemFilter filter = createDefaultFilter(items, attr);
 868                                                                         editor.addRoot(filter);
 869                                                                 }
 870                                                         });
 871                                                 });
 872                                 } else {
 873                                         manager.add(disabledAction(Messages.FILTER_NO_ATTRIBUTE_AVAILABLE));
 874                                 }
 875 
 876                         }
 877                 });
 878                 return editor;
 879         }
 880 
 881         // FIXME: Move to some AttributeToolkit?
 882         private static Stream<IAttribute<?>> getAttributes(IItemCollection items) {
 883                 return ItemCollectionToolkit.stream(items).filter(IItemIterable::hasItems)
 884                                 .flatMap(is -> is.getType().getAttributes().stream());
 885         }
 886 
 887         public static Stream<IAttribute<?>> getPersistableAttributes(Stream<IAttribute<?>> attributes) {
 888                 // FIXME: Would like to always be able to persist a string representation of the attribute, because this is usable by filters.
 889 
 890                 // FIXME: Should we always include event type? Does it make any sense, except on the custom pages?
 891 
 892                 // FIXME: Transform both START_TIME and END_TIME to LIFETIME?
 893                 // FIXME: Add derived attributes, like a conversion of any THREAD or CLASS attribute? Thread group?
 894                 /*
 895                  * Make sure to do the conversions in the right order, so for example a stack trace can be
 896                  * converted to a top method, which then is converted to a method string.
 897                  */
 898                 return attributes.map(a -> a.equals(JfrAttributes.EVENT_THREAD) ? JdkAttributes.EVENT_THREAD_NAME : a)
 899                                 .flatMap(a -> a.equals(JfrAttributes.EVENT_STACKTRACE) ? Stream.of(JdkAttributes.STACK_TRACE_STRING,
 900                                                 JdkAttributes.STACK_TRACE_TOP_METHOD_STRING, JdkAttributes.STACK_TRACE_TOP_CLASS_STRING,
 901                                                 JdkAttributes.STACK_TRACE_TOP_PACKAGE, JdkAttributes.STACK_TRACE_BOTTOM_METHOD_STRING)
 902                                                 : Stream.of(a))
 903                                 .map(a -> a.equals(JdkAttributes.COMPILER_METHOD) ? JdkAttributes.COMPILER_METHOD_STRING : a)
 904                                 // FIXME: String or id?
 905                                 .map(a -> a.equals(JdkAttributes.REC_SETTING_FOR) ? JdkAttributes.REC_SETTING_FOR_NAME : a)
 906                                 .map(a -> a.equals(JdkAttributes.CLASS_DEFINING_CLASSLOADER)
 907                                                 ? JdkAttributes.CLASS_DEFINING_CLASSLOADER_STRING : a)
 908                                 .map(a -> a.equals(JdkAttributes.CLASS_INITIATING_CLASSLOADER)
 909                                                 ? JdkAttributes.CLASS_INITIATING_CLASSLOADER_STRING : a)
 910                                 .map(a -> a.equals(JdkAttributes.PARENT_CLASSLOADER)
 911                                                 ? JdkAttributes.PARENT_CLASSLOADER_STRING : a)
 912                                 .map(a -> a.equals(JdkAttributes.CLASSLOADER)
 913                                                 ? JdkAttributes.CLASSLOADER_STRING : a)
 914                                 .filter(a -> a.equals(JfrAttributes.EVENT_TYPE) || (a.getContentType() instanceof RangeContentType)
 915                                                 || (a.getContentType().getPersister() != null))
 916                                 .distinct();
 917         }
 918 
 919         /**
 920          * Returns a value for attribute, firstly by trying to find one in the items, secondly by
 921          * creating a default value for some known content types. Returns null if the first two cases
 922          * fail.
 923          *
 924          * @param items
 925          * @param attribute
 926          * @return a value of type V, or null
 927          */
 928         @SuppressWarnings("unchecked")
 929         private static <V> V findValueForFilter(IItemCollection items, ICanonicalAccessorFactory<V> attribute) {
 930                 IItem firstItem = ItemCollectionToolkit.stream(items).filter(is -> is.getType().hasAttribute(attribute))
 931                                 .flatMap(ItemIterableToolkit::stream)
 932                                 .filter(i -> ((IMemberAccessor<V, IItem>) attribute.getAccessor(i.getType())).getMember(i) != null)
 933                                 .findFirst().orElse(null);
 934                 if (firstItem != null) {
 935                         IMemberAccessor<V, IItem> accessor = (IMemberAccessor<V, IItem>) attribute.getAccessor(firstItem.getType());
 936                         return accessor.getMember(firstItem);
 937                 }
 938                 if (UnitLookup.PLAIN_TEXT.equals(attribute.getContentType())) {
 939                         return (V) ""; //$NON-NLS-1$
 940                 }
 941                 if (attribute.getContentType() instanceof KindOfQuantity<?>) {
 942                         return (V) ((KindOfQuantity<?>) attribute.getContentType()).getDefaultUnit().quantity(0);
 943                 }
 944                 return null;
 945         }
 946 
 947         /**
 948          * Returns an default filter for attribute, which might be an equals filter, hasAttribute
 949          * filter, or type filter, depending on the attribute and the contents of the items.
 950          *
 951          * @param items
 952          * @param attribute
 953          * @return a filter
 954          */
 955         // FIXME: Should move to FilterEditor, or some subclass/specialization?
 956         private static <V> IItemFilter createDefaultFilter(IItemCollection items, ICanonicalAccessorFactory<V> attribute) {
 957                 V value = findValueForFilter(items, attribute);
 958                 if (value == null) {
 959                         return ItemFilters.hasAttribute(attribute);
 960                 } else if (attribute.equals(JfrAttributes.EVENT_TYPE)) {
 961                         return ItemFilters.type(((IType<?>) value).getIdentifier());
 962                 }
 963                 return ItemFilters.equals(attribute, value);
 964         }
 965 
 966         public static void addRenameAction(Form form, IPageContainer editor) {
 967                 form.getMenuManager().add(new Action(Messages.PAGE_RENAME_MENU_ACTION) {
 968                         @Override
 969                         public void run() {
 970                                 InputDialog dialog = new InputDialog(form.getShell(), Messages.PAGE_RENAME_DIALOG_TITLE,
 971                                                 Messages.PAGE_RENAME_DIALOG_MESSAGE, form.getText(), null);
 972                                 if (dialog.open() == Window.OK) {
 973                                         form.setText(dialog.getValue());
 974                                         editor.currentPageRefresh();
 975                                 }
 976                         }
 977                 });
 978         }
 979 
 980         public static void addIconChangeAction(Form form, IPageContainer editor, Consumer<Image> newIconConsumer) {
 981                 form.getMenuManager().add(new Action(Messages.PAGE_CHANGE_ICON_MENU_ACTION) {
 982                         @Override
 983                         public void run() {
 984                                 WizardDialog dialog = new WizardDialog(form.getShell(),
 985                                                 new IconChangeWizard(form.getImage(), newIconConsumer));
 986                                 dialog.open();
 987                                 editor.currentPageRefresh();
 988                         }
 989                 });
 990         }
 991 
 992         private static class IconChangeWizard extends Wizard {
 993 
 994                 private final Image currentImage;
 995                 private final Consumer<Image> imageConsumer;
 996                 private Label imageLabel;
 997 
 998                 public IconChangeWizard(Image currentImage, Consumer<Image> imageConsumer) {
 999                         setWindowTitle(Messages.PAGE_CHANGE_ICON_WIZARD_TITLE);
1000                         this.currentImage = currentImage;
1001                         this.imageConsumer = imageConsumer;
1002                 }
1003 
1004                 @Override
1005                 public void addPages() {
1006                         addPage(new WizardPage(Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_TITLE) {
1007 
1008                                 @Override
1009                                 public String getTitle() {
1010                                         return Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_TITLE;
1011                                 }
1012 
1013                                 @Override
1014                                 public String getDescription() {
1015                                         return Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_DESC;
1016                                 }
1017 
1018                                 @Override
1019                                 public void createControl(Composite parent) {
1020                                         Composite container = new Composite(parent, SWT.NONE);
1021                                         GridLayout layout = new GridLayout(1, false);
1022                                         container.setLayout(layout);
1023 
1024                                         Button button = new Button(container, SWT.NONE);
1025                                         button.setText(Messages.PAGE_CHANGE_ICON_CHOOSE_IMAGE_FILE);
1026 
1027                                         button.addSelectionListener(new SelectionAdapter() {
1028                                                 @Override
1029                                                 public void widgetSelected(SelectionEvent e) {
1030                                                         chooseImageFileDialog();
1031                                                 }
1032                                         });
1033 
1034                                         if (currentImage != null) {
1035                                                 new Label(container, SWT.NONE).setText(Messages.PAGE_CHANGE_ICON_CURRENT_ICON);
1036                                                 new Label(container, SWT.BORDER).setImage(currentImage);
1037                                         }
1038                                         new Label(container, SWT.NONE).setText(Messages.PAGE_CHANGE_ICON_NEW_ICON_PREVIEW);
1039                                         imageLabel = new Label(container, SWT.BORDER);
1040                                         GridData gd = new GridData(16, 16);
1041                                         imageLabel.setLayoutData(gd);
1042 
1043                                         setControl(container);
1044                                 }
1045 
1046                                 private void chooseImageFileDialog() {
1047                                         FileDialog fileDialog = new FileDialog(getShell(), SWT.OPEN);
1048                                         String[] filterNames = new String[] {"Image Files", "All Files (*)"}; //$NON-NLS-1$ //$NON-NLS-2$
1049                                         String[] filterExtensions = new String[] {"*.gif;*.png;*.xpm;*.jpg;*.jpeg;*.tiff", "*"}; //$NON-NLS-1$ //$NON-NLS-2$
1050                                         fileDialog.setFilterNames(filterNames);
1051                                         fileDialog.setFilterExtensions(filterExtensions);
1052                                         String filename = fileDialog.open();
1053                                         if (filename == null) {
1054                                                 // Dialog was cancelled. Bail out early to avoid handling that case later. Premature?
1055                                                 return;
1056                                         }
1057                                         try (InputStream fis = new FileInputStream(filename)) {
1058                                                 ImageData imageData = new ImageData(fis);
1059                                                 // Validate image data
1060                                                 if (imageData.width != 16 || imageData.height != 16) {
1061                                                         imageData = resizeImage(imageData, 16, 16);
1062                                                 }
1063                                                 DisplayToolkit.dispose(imageLabel.getImage());
1064                                                 imageLabel.setImage(new Image(getShell().getDisplay(), imageData));
1065                                                 imageLabel.getParent().layout();
1066                                                 setPageComplete(isPageComplete());
1067                                         } catch (Exception e) {
1068                                                 // FIXME: Add proper logging
1069                                                 e.printStackTrace();
1070                                         }
1071                                 }
1072 
1073                                 private ImageData resizeImage(ImageData imageData, int width, int height) {
1074                                         Image original = ImageDescriptor.createFromImageData(imageData).createImage();
1075                                         Image scaled = new Image(Display.getDefault(), width, height);
1076                                         GC gc = new GC(scaled);
1077                                         gc.setAntialias(SWT.ON);
1078                                         gc.setInterpolation(SWT.HIGH);
1079                                         gc.drawImage(original, 0, 0, imageData.width, imageData.height, 0, 0, width, height);
1080                                         gc.dispose();
1081                                         original.dispose();
1082                                         ImageData scaledData = scaled.getImageData();
1083                                         scaled.dispose();
1084                                         return scaledData;
1085                                 }
1086 
1087                                 @Override
1088                                 public boolean isPageComplete() {
1089                                         return imageLabel.getImage() != null;
1090                                 }
1091 
1092                         });
1093                 }
1094 
1095                 @Override
1096                 public boolean performFinish() {
1097                         imageConsumer.accept(imageLabel.getImage());
1098                         DisplayToolkit.dispose(currentImage);
1099                         return true;
1100                 }
1101 
1102                 @Override
1103                 public boolean performCancel() {
1104                         DisplayToolkit.dispose(imageLabel.getImage());
1105                         return true;
1106                 }
1107 
1108         }
1109 
1110         public static ItemList createSimpleItemList(
1111                 Composite parent, ItemListBuilder listBuilder, IPageContainer pageContainer, TableSettings tableSettings,
1112                 String selectionName) {
1113 
1114                 ItemList list = listBuilder.build(parent, tableSettings);
1115                 ColumnViewer viewer = list.getManager().getViewer();
1116                 MCContextMenuManager mm = MCContextMenuManager.create(viewer.getControl());
1117                 ColumnMenusFactory.addDefaultMenus(list.getManager(), mm);
1118                 viewer.addSelectionChangedListener(
1119                                 e -> pageContainer.showSelection(ItemCollectionToolkit.build(list.getSelection().get())));
1120 
1121                 if (selectionName != null) {
1122                         SelectionStoreActionToolkit.addSelectionStoreActions(pageContainer.getSelectionStore(), list, selectionName,
1123                                         mm);
1124                 }
1125 
1126                 return list;
1127         }
1128 
1129         public static void addTabItem(CTabFolder tabFolder, Control section, String name) {
1130                 CTabItem tabItem = new CTabItem(tabFolder, SWT.NONE);
1131                 tabItem.setControl(section);
1132                 tabItem.setText(name);
1133         }
1134 
1135         public static TypeFilterBuilder buildEventTypeTree(
1136                 Composite parent, FormToolkit toolkit, Runnable onChange, boolean checkbox) {
1137                 // TODO: Make more accessible.
1138                 // TODO: Add support for storing the expansion state in a memento.
1139                 // TODO: Add input from selection store, output to selection store
1140                 // TODO: Add toolbar for choosing tree or checkbox tree.
1141                 Composite treeComposite = new Composite(parent, SWT.NONE);
1142                 treeComposite.setLayout(new GridLayout());
1143                 toolkit.adapt(treeComposite);
1144                 Label caption = toolkit.createLabel(treeComposite, Messages.EVENT_TYPE_TREE_TITLE);
1145                 caption.setFont(JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT));
1146                 caption.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
1147 
1148                 TypeFilterBuilder typeFilterTree = new TypeFilterBuilder(treeComposite, onChange, checkbox);
1149 
1150                 typeFilterTree.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
1151                 return typeFilterTree;
1152         }
1153 
1154         public static boolean isTypeWithThreadAndDuration(IType<?> type) {
1155                 return JfrAttributes.EVENT_THREAD.getAccessor(type) != null
1156                                 && JfrAttributes.START_TIME.getAccessor(type) != JfrAttributes.END_TIME.getAccessor(type);
1157         }
1158 
1159 }
--- EOF ---