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