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 for (IAggregator<?, ?> aggregator : query.getAggregators()) { 518 histogramBuilder.addColumn(aggregator.getName(), aggregator); 519 } 520 ItemHistogram table = histogramBuilder.buildWithoutBorder(parent, settings); 521 return table; 522 } 523 524 public static IBaseLabelProvider createTableHighlightProvider(Pattern highlightPattern, boolean isWarning) { 525 return new StyledCellLabelProvider() { 526 @Override 527 public void update(ViewerCell cell) { 528 org.eclipse.swt.graphics.Color color = isWarning 529 ? new org.eclipse.swt.graphics.Color(Display.getCurrent(), 240, 120, 140) 530 : new org.eclipse.swt.graphics.Color(Display.getCurrent(), 255, 144, 4); 531 String text = getText(cell.getElement(), cell.getColumnIndex()); 532 Matcher matcher = highlightPattern.matcher(text); 533 if (matcher.find()) { 534 cell.getViewerRow().setBackground(0, color); 535 cell.getViewerRow().setBackground(1, color); 536 } 537 cell.setText(text); 538 super.update(cell); 539 } 540 541 private String getText(Object element, int index) { 542 Object key = AggregationGrid.getKey(element); 543 Object[] keyElements = ((CompositeKey) key).getKeyElements(); 544 return keyElements[index].toString(); 545 } 546 }; 547 } 548 549 public static void addContextMenus( 550 IPageContainer pc, ItemHistogram h, String selectionName, IAction ... extraActions) { 551 MCContextMenuManager mm = MCContextMenuManager.create(h.getManager().getViewer().getControl()); 552 ColumnMenusFactory.addDefaultMenus(h.getManager(), mm); 553 SelectionStoreActionToolkit.addSelectionStoreActions(pc.getSelectionStore(), h, selectionName, mm); 554 for (IAction action : extraActions) { 555 mm.add(action); 556 } 557 } 558 559 public static IXDataRenderer buildSizeRow( 560 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> a, Color color, 561 IColorProvider<IItem> cp) { 562 return RendererToolkit.layers(buildSpanRenderer(items, cp), 563 buildTimestampHistogram(title, description, items, a, color)); 564 } 565 566 public static ItemRow buildDurationHistogram( 567 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> a, Color color) { 568 IQuantitySeries<IQuantity[]> allocationSeries = BucketBuilder.aggregatorSeries(items, a, 569 JfrAttributes.DURATION); 570 XYDataRenderer renderer = new XYDataRenderer(getKindOfQuantity(a).getDefaultUnit().quantity(0), title, 571 description); 572 renderer.addBarChart(a.getName(), allocationSeries, color); 573 return new ItemRow(title, description, renderer, items); 574 } 575 576 public static ItemRow buildSizeHistogram( 577 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> a, Color color, IAttribute<IQuantity> attribute) { 578 IQuantitySeries<IQuantity[]> allocationSeries = BucketBuilder.aggregatorSeries(items, a, 579 JdkAttributes.IO_SIZE); 580 XYDataRenderer renderer = new XYDataRenderer(getKindOfQuantity(a).getDefaultUnit().quantity(0), title, 581 description); 582 renderer.addBarChart(a.getName(), allocationSeries, color); 583 return new ItemRow(title, description, renderer, items); 584 } 585 586 public static IRange<IQuantity> buildSizeRange(IItemCollection items, boolean isSocket){ 587 IQuantity end = null; 588 if(isSocket) { 589 end = QuantitiesToolkit.maxPresent(items.getAggregate(JdkAggregators.SOCKET_READ_LARGEST), 590 items.getAggregate(JdkAggregators.SOCKET_WRITE_LARGEST)); 591 } else { 592 end = QuantitiesToolkit.maxPresent(items.getAggregate(JdkAggregators.FILE_READ_LARGEST), 593 items.getAggregate(JdkAggregators.FILE_WRITE_LARGEST)); 594 } 595 end = end == null ? UnitLookup.BYTE.quantity(1024) : end; 596 return QuantityRange.createWithEnd(UnitLookup.BYTE.quantity(0), end); 597 } 598 599 // FIXME: Make something that can use something other than time as x-axis? 600 public static IXDataRenderer buildSpanRenderer(IItemCollection pathItems, IColorProvider<IItem> cp) { 601 ISpanSeries<IItem> dataSeries = QuantitySeries.max(pathItems, JfrAttributes.START_TIME, JfrAttributes.END_TIME); 602 return SpanRenderer.withBoundaries(dataSeries, cp); 603 } 604 605 public static boolean addEndTimeLines( 606 XYDataRenderer renderer, IItemCollection items, boolean fill, Stream<IAttribute<IQuantity>> yAttributes) { 607 // FIXME: JMC-4520 - Handle multiple item iterables 608 Iterator<IItemIterable> ii = items.iterator(); 609 if (ii.hasNext()) { 610 IItemIterable itemStream = ii.next(); 611 IType<IItem> type = itemStream.getType(); 612 // FIXME: A better way to ensure sorting by endTime 613 return yAttributes.peek(a -> addEndTimeLine(renderer, itemStream.iterator(), type, a, fill)) 614 .mapToLong(a -> 1L).sum() > 0; 615 } 616 return false; 617 } 618 619 public static void addEndTimeLine( 620 XYDataRenderer renderer, Iterator<? extends IItem> items, IType<IItem> type, IAttribute<IQuantity> yAttribute, 621 boolean fill) { 622 IQuantitySeries<?> qs = buildQuantitySeries(items, type, JfrAttributes.END_TIME, yAttribute); 623 renderer.addLineChart(yAttribute.getName(), qs, getFieldColor(yAttribute), fill); 624 } 625 626 public static IQuantitySeries<?> buildQuantitySeries( 627 Iterator<? extends IItem> items, IType<IItem> type, IAttribute<IQuantity> xAttribute, 628 IAttribute<IQuantity> yAttribute) { 629 IMemberAccessor<IQuantity, IItem> yAccessor = yAttribute.getAccessor(type); 630 if (yAccessor == null) { 631 throw new RuntimeException(yAttribute.getIdentifier() + " is not an attribute for " + type.getIdentifier()); //$NON-NLS-1$ 632 } 633 return buildQuantitySeries(items, type, xAttribute, yAccessor); 634 } 635 636 public static IQuantitySeries<?> buildQuantitySeries( 637 Iterator<? extends IItem> items, IType<IItem> type, IAttribute<IQuantity> xAttribute, 638 IMemberAccessor<? extends IQuantity, IItem> yAccessor) { 639 IMemberAccessor<IQuantity, IItem> xAccessor = xAttribute.getAccessor(type); 640 return QuantitySeries.all(items, xAccessor, yAccessor); 641 } 642 643 public static void createChartTooltip(ChartCanvas chart) { 644 createChartTooltip(chart, ChartToolTipProvider::new); 645 } 646 647 public static void createChartTimestampTooltip(ChartCanvas chart) { 648 createChartTooltip(chart, JfrAttributes.START_TIME, JfrAttributes.END_TIME, JfrAttributes.DURATION, 649 JfrAttributes.EVENT_TYPE, JfrAttributes.EVENT_STACKTRACE); 650 } 651 652 public static void createChartTooltip(ChartCanvas chart, IAttribute<?> ... excludedAttributes) { 653 createChartTooltip(chart, new HashSet<>(Arrays.asList(excludedAttributes))); 654 } 655 656 public static void createChartTooltip(ChartCanvas chart, Set<IAttribute<?>> excludedAttributes) { 657 createChartTooltip(chart, () -> new ChartToolTipProvider() { 658 @Override 659 protected Stream<IAttribute<?>> getAttributeStream(IType<IItem> type) { 660 return type.getAttributes().stream().filter(a -> !excludedAttributes.contains(a)); 661 } 662 }); 663 } 664 665 public static void createChartTooltip(ChartCanvas chart, Supplier<ChartToolTipProvider> toolTipProviderSupplier) { 666 new ToolTip(chart) { 667 String html; 668 Map<String, Image> images; 669 670 @Override 671 protected boolean shouldCreateToolTip(Event event) { 672 ChartToolTipProvider provider = toolTipProviderSupplier.get(); 673 chart.infoAt(provider, event.x, event.y); 674 html = provider.getHTML(); 675 images = provider.getImages(); 676 return html != null; 677 } 678 679 @Override 680 protected Composite createToolTipContentArea(Event event, Composite parent) { 681 FormText formText = CompositeToolkit.createInfoFormText(parent); 682 for (Map.Entry<String, Image> imgEntry : images.entrySet()) { 683 formText.setImage(imgEntry.getKey(), imgEntry.getValue()); 684 } 685 formText.setText(html, true, false); 686 return formText; 687 } 688 689 }; 690 691 } 692 693 private static KindOfQuantity<?> getKindOfQuantity(IAggregator<IQuantity, ?> a) { 694 IType<? super IQuantity> ct = a.getValueType(); 695 // FIXME: Refactor to avoid this cast 696 return ((KindOfQuantity<?>) ct); 697 } 698 699 public static Form createForm(Composite parent, FormToolkit toolkit, String title, Image img) { 700 Form form = toolkit.createForm(parent); 701 form.setText(title.replaceAll("&", "&&")); //$NON-NLS-1$ //$NON-NLS-2$ 702 form.setImage(img); 703 toolkit.decorateFormHeading(form); 704 FillLayout fillLayout = new FillLayout(); 705 fillLayout.marginHeight = 15; 706 fillLayout.marginWidth = 8; 707 form.getBody().setLayout(fillLayout); 708 form.getToolBarManager().add(new Separator(FORM_TOOLBAR_PAGE_RESULTS)); 709 form.getToolBarManager().add(new Separator(FORM_TOOLBAR_PAGE_SETUP)); 710 form.getToolBarManager().add(new Separator(FORM_TOOLBAR_PAGE_NAV)); 711 return form; 712 } 713 714 public static class ShowResultAction extends Action { 715 716 private String[] topics; 717 private final IPageContainer pageContainer; 718 private volatile Severity maxSeverity; 719 private final List<Consumer<Result>> listeners = new ArrayList<>(); 720 721 ShowResultAction(String title, int style, ImageDescriptor icon, Supplier<String> tooltip, 722 IPageContainer pageContainer, String ... topics) { 723 super(title, style); 724 setImageDescriptor(icon); 725 setToolTipText(tooltip.get()); 726 this.topics = topics; 727 this.pageContainer = pageContainer; 728 maxSeverity = pageContainer.getRuleManager().getMaxSeverity(topics); 729 for (String topic : topics) { 730 Consumer<Result> listener = result -> { 731 Severity severity = Severity.get(result.getScore()); 732 if (severity.compareTo(maxSeverity) > 0) { 733 maxSeverity = severity; 734 setImageDescriptor(getResultIcon(maxSeverity)); 735 } else if (severity.compareTo(maxSeverity) < 0) { // severity could be less than previous max 736 maxSeverity = pageContainer.getRuleManager().getMaxSeverity(topics); 737 } 738 setToolTipText(tooltip.get()); 739 }; 740 listeners.add(listener); 741 pageContainer.getRuleManager().addResultListener(topic, listener); 742 } 743 } 744 745 private void removeListeners() { 746 listeners.forEach(l -> pageContainer.getRuleManager().removeResultListener(l)); 747 } 748 749 @Override 750 public void run() { 751 pageContainer.showResults(topics); 752 } 753 } 754 755 public static void addRuleResultAction( 756 Form form, IPageContainer pageContainer, Supplier<String> tooltip, String[] topics) { 757 if (topics == null || topics.length == 0 || !FlightRecorderUI.getDefault().isAnalysisEnabled()) { 758 return; 759 } 760 ImageDescriptor icon = getResultIcon(pageContainer.getRuleManager().getMaxSeverity(topics)); 761 ShowResultAction resultAction = new ShowResultAction(Messages.RULES_SHOW_RESULTS_ACTION, IAction.AS_PUSH_BUTTON, 762 icon, tooltip, pageContainer, topics); 763 resultAction.setId(RESULT_ACTION_ID); 764 form.getToolBarManager().appendToGroup(DataPageToolkit.FORM_TOOLBAR_PAGE_RESULTS, resultAction); 765 form.getToolBarManager().update(true); 766 form.addDisposeListener(e -> resultAction.removeListeners()); 767 } 768 769 private static ImageDescriptor getResultIcon(Severity severity) { 770 switch (severity) { 771 case OK: 772 return ResultOverview.ICON_OK; 773 case INFO: 774 return ResultOverview.ICON_INFO; 775 case WARNING: 776 return ResultOverview.ICON_WARNING; 777 case NA: 778 return ResultOverview.ICON_NA; 779 } 780 return null; 781 } 782 783 /** 784 * Return a disabled Action. 785 * 786 * @param text 787 * text to be displayed by the MenuItem, and represent it as it's id. 788 * @return an Action containing the desired text, which will be disabled in a UI component. 789 */ 790 public static IAction disabledAction(String text) { 791 IAction disabledAction = new Action(text) { 792 @Override 793 public boolean isEnabled() { 794 return false; 795 } 796 }; 797 disabledAction.setId(text); 798 return disabledAction; 799 } 800 801 public static FilterEditor buildFilterSelector( 802 Composite parent, IItemFilter filter, IItemCollection items, Supplier<Stream<SelectionStoreEntry>> selections, 803 Consumer<IItemFilter> onSelect, boolean hasBorder) { 804 Supplier<Collection<IAttribute<?>>> attributeSupplier = () -> getPersistableAttributes( 805 getAttributes(filter != null ? items.apply(filter) : items)).collect(Collectors.toList()); 806 807 AttributeValueProvider valueSupplier = new AttributeValueProvider() { 808 @Override 809 public <V> V defaultValue(ICanonicalAccessorFactory<V> attribute) { 810 return findValueForFilter(items, attribute); 811 } 812 }; 813 814 FilterEditor editor = new FilterEditor(parent, onSelect, filter, attributeSupplier, valueSupplier, 815 TypeLabelProvider::getColorOrDefault, hasBorder ? SWT.BORDER : SWT.NONE); 816 817 MenuManager addFromSelectionPredicate = new MenuManager(Messages.FILTER_ADD_FROM_SELECTION); 818 editor.getContextMenu().prependToGroup(MCContextMenuManager.GROUP_NEW, addFromSelectionPredicate); 819 addFromSelectionPredicate.setRemoveAllWhenShown(true); 820 addFromSelectionPredicate.addMenuListener(new IMenuListener() { 821 822 @Override 823 public void menuAboutToShow(IMenuManager manager) { 824 selections.get().forEach(entry -> { 825 MenuManager selectionFlavors = new MenuManager(entry.getName()); 826 entry.getSelection().getFlavors(editor.getFilter(), items, null) 827 .filter(f -> f instanceof IFilterFlavor).forEach(flavor -> { 828 selectionFlavors.add(new Action(flavor.getName()) { 829 @Override 830 public void run() { 831 editor.addRoot(((IFilterFlavor) flavor).getFilter()); 832 } 833 }); 834 }); 835 if (!selectionFlavors.isEmpty()) { 836 if (manager.find(Messages.FILTER_NO_SELECTION_AVAILABLE) != null) { 837 manager.remove(Messages.FILTER_NO_SELECTION_AVAILABLE); 838 } 839 manager.add(selectionFlavors); 840 } else { 841 manager.add(disabledAction(Messages.FILTER_NO_SELECTION_AVAILABLE)); 842 } 843 }); 844 } 845 }); 846 847 // FIXME: This could potentially move into the FilterEditor class 848 MenuManager addAttributeValuePredicate = new MenuManager(Messages.FILTER_ADD_FROM_ATTRIBUTE); 849 editor.getContextMenu().prependToGroup(MCContextMenuManager.GROUP_NEW, addAttributeValuePredicate); 850 addAttributeValuePredicate.setRemoveAllWhenShown(true); 851 addAttributeValuePredicate.addMenuListener(new IMenuListener() { 852 Collection<IAttribute<?>> attributes; 853 854 @Override 855 public void menuAboutToShow(IMenuManager manager) { 856 if (attributes == null) { 857 attributes = attributeSupplier.get(); 858 } 859 if (!attributes.isEmpty()) { 860 if (manager.find(Messages.FILTER_NO_ATTRIBUTE_AVAILABLE) != null) { 861 manager.remove(Messages.FILTER_NO_ATTRIBUTE_AVAILABLE); 862 } 863 attributes.stream().distinct().sorted((a1, a2) -> a1.getName().compareTo(a2.getName())) 864 .forEach(attr -> { 865 addAttributeValuePredicate.add(new Action(attr.getName()) { 866 @Override 867 public void run() { 868 IItemFilter filter = createDefaultFilter(items, attr); 869 editor.addRoot(filter); 870 } 871 }); 872 }); 873 } else { 874 manager.add(disabledAction(Messages.FILTER_NO_ATTRIBUTE_AVAILABLE)); 875 } 876 877 } 878 }); 879 return editor; 880 } 881 882 // FIXME: Move to some AttributeToolkit? 883 private static Stream<IAttribute<?>> getAttributes(IItemCollection items) { 884 return ItemCollectionToolkit.stream(items).filter(IItemIterable::hasItems) 885 .flatMap(is -> is.getType().getAttributes().stream()); 886 } 887 888 public static Stream<IAttribute<?>> getPersistableAttributes(Stream<IAttribute<?>> attributes) { 889 // FIXME: Would like to always be able to persist a string representation of the attribute, because this is usable by filters. 890 891 // FIXME: Should we always include event type? Does it make any sense, except on the custom pages? 892 893 // FIXME: Transform both START_TIME and END_TIME to LIFETIME? 894 // FIXME: Add derived attributes, like a conversion of any THREAD or CLASS attribute? Thread group? 895 /* 896 * Make sure to do the conversions in the right order, so for example a stack trace can be 897 * converted to a top method, which then is converted to a method string. 898 */ 899 return attributes.map(a -> a.equals(JfrAttributes.EVENT_THREAD) ? JdkAttributes.EVENT_THREAD_NAME : a) 900 .flatMap(a -> a.equals(JfrAttributes.EVENT_STACKTRACE) ? Stream.of(JdkAttributes.STACK_TRACE_STRING, 901 JdkAttributes.STACK_TRACE_TOP_METHOD_STRING, JdkAttributes.STACK_TRACE_TOP_CLASS_STRING, 902 JdkAttributes.STACK_TRACE_TOP_PACKAGE, JdkAttributes.STACK_TRACE_BOTTOM_METHOD_STRING) 903 : Stream.of(a)) 904 .map(a -> a.equals(JdkAttributes.COMPILER_METHOD) ? JdkAttributes.COMPILER_METHOD_STRING : a) 905 // FIXME: String or id? 906 .map(a -> a.equals(JdkAttributes.REC_SETTING_FOR) ? JdkAttributes.REC_SETTING_FOR_NAME : a) 907 .map(a -> a.equals(JdkAttributes.CLASS_DEFINING_CLASSLOADER) 908 ? JdkAttributes.CLASS_DEFINING_CLASSLOADER_STRING : a) 909 .map(a -> a.equals(JdkAttributes.CLASS_INITIATING_CLASSLOADER) 910 ? JdkAttributes.CLASS_INITIATING_CLASSLOADER_STRING : a) 911 .map(a -> a.equals(JdkAttributes.PARENT_CLASSLOADER) 912 ? JdkAttributes.PARENT_CLASSLOADER_STRING : a) 913 .map(a -> a.equals(JdkAttributes.CLASSLOADER) 914 ? JdkAttributes.CLASSLOADER_STRING : a) 915 .filter(a -> a.equals(JfrAttributes.EVENT_TYPE) || (a.getContentType() instanceof RangeContentType) 916 || (a.getContentType().getPersister() != null)) 917 .distinct(); 918 } 919 920 /** 921 * Returns a value for attribute, firstly by trying to find one in the items, secondly by 922 * creating a default value for some known content types. Returns null if the first two cases 923 * fail. 924 * 925 * @param items 926 * @param attribute 927 * @return a value of type V, or null 928 */ 929 @SuppressWarnings("unchecked") 930 private static <V> V findValueForFilter(IItemCollection items, ICanonicalAccessorFactory<V> attribute) { 931 IItem firstItem = ItemCollectionToolkit.stream(items).filter(is -> is.getType().hasAttribute(attribute)) 932 .flatMap(ItemIterableToolkit::stream) 933 .filter(i -> ((IMemberAccessor<V, IItem>) attribute.getAccessor(i.getType())).getMember(i) != null) 934 .findFirst().orElse(null); 935 if (firstItem != null) { 936 IMemberAccessor<V, IItem> accessor = (IMemberAccessor<V, IItem>) attribute.getAccessor(firstItem.getType()); 937 return accessor.getMember(firstItem); 938 } 939 if (UnitLookup.PLAIN_TEXT.equals(attribute.getContentType())) { 940 return (V) ""; //$NON-NLS-1$ 941 } 942 if (attribute.getContentType() instanceof KindOfQuantity<?>) { 943 return (V) ((KindOfQuantity<?>) attribute.getContentType()).getDefaultUnit().quantity(0); 944 } 945 return null; 946 } 947 948 /** 949 * Returns an default filter for attribute, which might be an equals filter, hasAttribute 950 * filter, or type filter, depending on the attribute and the contents of the items. 951 * 952 * @param items 953 * @param attribute 954 * @return a filter 955 */ 956 // FIXME: Should move to FilterEditor, or some subclass/specialization? 957 private static <V> IItemFilter createDefaultFilter(IItemCollection items, ICanonicalAccessorFactory<V> attribute) { 958 V value = findValueForFilter(items, attribute); 959 if (value == null) { 960 return ItemFilters.hasAttribute(attribute); 961 } else if (attribute.equals(JfrAttributes.EVENT_TYPE)) { 962 return ItemFilters.type(((IType<?>) value).getIdentifier()); 963 } 964 return ItemFilters.equals(attribute, value); 965 } 966 967 public static void addRenameAction(Form form, IPageContainer editor) { 968 form.getMenuManager().add(new Action(Messages.PAGE_RENAME_MENU_ACTION) { 969 @Override 970 public void run() { 971 InputDialog dialog = new InputDialog(form.getShell(), Messages.PAGE_RENAME_DIALOG_TITLE, 972 Messages.PAGE_RENAME_DIALOG_MESSAGE, form.getText(), null); 973 if (dialog.open() == Window.OK) { 974 form.setText(dialog.getValue()); 975 editor.currentPageRefresh(); 976 } 977 } 978 }); 979 } 980 981 public static void addIconChangeAction(Form form, IPageContainer editor, Consumer<Image> newIconConsumer) { 982 form.getMenuManager().add(new Action(Messages.PAGE_CHANGE_ICON_MENU_ACTION) { 983 @Override 984 public void run() { 985 WizardDialog dialog = new WizardDialog(form.getShell(), 986 new IconChangeWizard(form.getImage(), newIconConsumer)); 987 dialog.open(); 988 editor.currentPageRefresh(); 989 } 990 }); 991 } 992 993 private static class IconChangeWizard extends Wizard { 994 995 private final Image currentImage; 996 private final Consumer<Image> imageConsumer; 997 private Label imageLabel; 998 999 public IconChangeWizard(Image currentImage, Consumer<Image> imageConsumer) { 1000 setWindowTitle(Messages.PAGE_CHANGE_ICON_WIZARD_TITLE); 1001 this.currentImage = currentImage; 1002 this.imageConsumer = imageConsumer; 1003 } 1004 1005 @Override 1006 public void addPages() { 1007 addPage(new WizardPage(Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_TITLE) { 1008 1009 @Override 1010 public String getTitle() { 1011 return Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_TITLE; 1012 } 1013 1014 @Override 1015 public String getDescription() { 1016 return Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_DESC; 1017 } 1018 1019 @Override 1020 public void createControl(Composite parent) { 1021 Composite container = new Composite(parent, SWT.NONE); 1022 GridLayout layout = new GridLayout(1, false); 1023 container.setLayout(layout); 1024 1025 Button button = new Button(container, SWT.NONE); 1026 button.setText(Messages.PAGE_CHANGE_ICON_CHOOSE_IMAGE_FILE); 1027 1028 button.addSelectionListener(new SelectionAdapter() { 1029 @Override 1030 public void widgetSelected(SelectionEvent e) { 1031 chooseImageFileDialog(); 1032 } 1033 }); 1034 1035 if (currentImage != null) { 1036 new Label(container, SWT.NONE).setText(Messages.PAGE_CHANGE_ICON_CURRENT_ICON); 1037 new Label(container, SWT.BORDER).setImage(currentImage); 1038 } 1039 new Label(container, SWT.NONE).setText(Messages.PAGE_CHANGE_ICON_NEW_ICON_PREVIEW); 1040 imageLabel = new Label(container, SWT.BORDER); 1041 GridData gd = new GridData(16, 16); 1042 imageLabel.setLayoutData(gd); 1043 1044 setControl(container); 1045 } 1046 1047 private void chooseImageFileDialog() { 1048 FileDialog fileDialog = new FileDialog(getShell(), SWT.OPEN); 1049 String[] filterNames = new String[] {"Image Files", "All Files (*)"}; //$NON-NLS-1$ //$NON-NLS-2$ 1050 String[] filterExtensions = new String[] {"*.gif;*.png;*.xpm;*.jpg;*.jpeg;*.tiff", "*"}; //$NON-NLS-1$ //$NON-NLS-2$ 1051 fileDialog.setFilterNames(filterNames); 1052 fileDialog.setFilterExtensions(filterExtensions); 1053 String filename = fileDialog.open(); 1054 if (filename == null) { 1055 // Dialog was cancelled. Bail out early to avoid handling that case later. Premature? 1056 return; 1057 } 1058 try (InputStream fis = new FileInputStream(filename)) { 1059 ImageData imageData = new ImageData(fis); 1060 // Validate image data 1061 if (imageData.width != 16 || imageData.height != 16) { 1062 imageData = resizeImage(imageData, 16, 16); 1063 } 1064 DisplayToolkit.dispose(imageLabel.getImage()); 1065 imageLabel.setImage(new Image(getShell().getDisplay(), imageData)); 1066 imageLabel.getParent().layout(); 1067 setPageComplete(isPageComplete()); 1068 } catch (Exception e) { 1069 // FIXME: Add proper logging 1070 e.printStackTrace(); 1071 } 1072 } 1073 1074 private ImageData resizeImage(ImageData imageData, int width, int height) { 1075 Image original = ImageDescriptor.createFromImageData(imageData).createImage(); 1076 Image scaled = new Image(Display.getDefault(), width, height); 1077 GC gc = new GC(scaled); 1078 gc.setAntialias(SWT.ON); 1079 gc.setInterpolation(SWT.HIGH); 1080 gc.drawImage(original, 0, 0, imageData.width, imageData.height, 0, 0, width, height); 1081 gc.dispose(); 1082 original.dispose(); 1083 ImageData scaledData = scaled.getImageData(); 1084 scaled.dispose(); 1085 return scaledData; 1086 } 1087 1088 @Override 1089 public boolean isPageComplete() { 1090 return imageLabel.getImage() != null; 1091 } 1092 1093 }); 1094 } 1095 1096 @Override 1097 public boolean performFinish() { 1098 imageConsumer.accept(imageLabel.getImage()); 1099 DisplayToolkit.dispose(currentImage); 1100 return true; 1101 } 1102 1103 @Override 1104 public boolean performCancel() { 1105 DisplayToolkit.dispose(imageLabel.getImage()); 1106 return true; 1107 } 1108 1109 } 1110 1111 public static ItemList createSimpleItemList( 1112 Composite parent, ItemListBuilder listBuilder, IPageContainer pageContainer, TableSettings tableSettings, 1113 String selectionName) { 1114 1115 ItemList list = listBuilder.build(parent, tableSettings); 1116 ColumnViewer viewer = list.getManager().getViewer(); 1117 MCContextMenuManager mm = MCContextMenuManager.create(viewer.getControl()); 1118 ColumnMenusFactory.addDefaultMenus(list.getManager(), mm); 1119 viewer.addSelectionChangedListener( 1120 e -> pageContainer.showSelection(ItemCollectionToolkit.build(list.getSelection().get()))); 1121 1122 if (selectionName != null) { 1123 SelectionStoreActionToolkit.addSelectionStoreActions(pageContainer.getSelectionStore(), list, selectionName, 1124 mm); 1125 } 1126 1127 return list; 1128 } 1129 1130 public static void addTabItem(CTabFolder tabFolder, Control section, String name) { 1131 CTabItem tabItem = new CTabItem(tabFolder, SWT.NONE); 1132 tabItem.setControl(section); 1133 tabItem.setText(name); 1134 } 1135 1136 public static TypeFilterBuilder buildEventTypeTree( 1137 Composite parent, FormToolkit toolkit, Runnable onChange, boolean checkbox) { 1138 // TODO: Make more accessible. 1139 // TODO: Add support for storing the expansion state in a memento. 1140 // TODO: Add input from selection store, output to selection store 1141 // TODO: Add toolbar for choosing tree or checkbox tree. 1142 Composite treeComposite = new Composite(parent, SWT.NONE); 1143 treeComposite.setLayout(new GridLayout()); 1144 toolkit.adapt(treeComposite); 1145 Label caption = toolkit.createLabel(treeComposite, Messages.EVENT_TYPE_TREE_TITLE); 1146 caption.setFont(JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT)); 1147 caption.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); 1148 1149 TypeFilterBuilder typeFilterTree = new TypeFilterBuilder(treeComposite, onChange, checkbox); 1150 1151 typeFilterTree.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); 1152 return typeFilterTree; 1153 } 1154 1155 public static boolean isTypeWithThreadAndDuration(IType<?> type) { 1156 return JfrAttributes.EVENT_THREAD.getAccessor(type) != null 1157 && JfrAttributes.START_TIME.getAccessor(type) != JfrAttributes.END_TIME.getAccessor(type); 1158 } 1159 1160 public static void addPage(Set<IType<?>> selectedTypes) { 1161 PageManager pm = FlightRecorderUI.getDefault().getPageManager(); 1162 pm.makeRoot(pm.createPage(ItemHandlerPage.Factory.class, new ItemHandlerUiStandIn(selectedTypes))); 1163 } 1164 }