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.pages; 34 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.Iterator; 38 import java.util.List; 39 import java.util.Objects; 40 import java.util.Set; 41 import java.util.stream.Collectors; 42 import java.util.stream.Stream; 43 44 import org.eclipse.jface.action.IAction; 45 import org.eclipse.jface.action.Separator; 46 import org.eclipse.jface.resource.ImageDescriptor; 47 import org.eclipse.jface.viewers.ISelection; 48 import org.eclipse.jface.viewers.TreePath; 49 import org.eclipse.osgi.util.NLS; 50 import org.eclipse.swt.SWT; 51 import org.eclipse.swt.custom.SashForm; 52 import org.eclipse.swt.layout.FillLayout; 53 import org.eclipse.swt.widgets.Composite; 54 import org.eclipse.swt.widgets.Control; 55 import org.eclipse.ui.forms.widgets.Form; 56 import org.eclipse.ui.forms.widgets.FormToolkit; 57 import org.openjdk.jmc.common.IState; 58 import org.openjdk.jmc.common.IWritableState; 59 import org.openjdk.jmc.common.item.IAttribute; 60 import org.openjdk.jmc.common.item.IItem; 61 import org.openjdk.jmc.common.item.IItemCollection; 62 import org.openjdk.jmc.common.item.IItemFilter; 63 import org.openjdk.jmc.common.item.IMemberAccessor; 64 import org.openjdk.jmc.common.item.IType; 65 import org.openjdk.jmc.common.item.ItemFilters; 66 import org.openjdk.jmc.common.item.ItemToolkit; 67 import org.openjdk.jmc.common.unit.ContentType; 68 import org.openjdk.jmc.common.unit.IQuantity; 69 import org.openjdk.jmc.common.unit.IRange; 70 import org.openjdk.jmc.common.unit.LinearKindOfQuantity; 71 import org.openjdk.jmc.common.util.StateToolkit; 72 import org.openjdk.jmc.flightrecorder.JfrAttributes; 73 import org.openjdk.jmc.flightrecorder.ui.EventTypeFolderNode.EventTypeNode; 74 import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI; 75 import org.openjdk.jmc.flightrecorder.ui.IDataPageFactory; 76 import org.openjdk.jmc.flightrecorder.ui.IDisplayablePage; 77 import org.openjdk.jmc.flightrecorder.ui.IPageContainer; 78 import org.openjdk.jmc.flightrecorder.ui.IPageDefinition; 79 import org.openjdk.jmc.flightrecorder.ui.IPageUI; 80 import org.openjdk.jmc.flightrecorder.ui.ItemCollectionToolkit; 81 import org.openjdk.jmc.flightrecorder.ui.PageManager; 82 import org.openjdk.jmc.flightrecorder.ui.RuleManager; 83 import org.openjdk.jmc.flightrecorder.ui.StreamModel; 84 import org.openjdk.jmc.flightrecorder.ui.common.AbstractDataPage; 85 import org.openjdk.jmc.flightrecorder.ui.common.DataPageToolkit; 86 import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector; 87 import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector.FlavorSelectorState; 88 import org.openjdk.jmc.flightrecorder.ui.common.ImageConstants; 89 import org.openjdk.jmc.flightrecorder.ui.common.ItemList; 90 import org.openjdk.jmc.flightrecorder.ui.common.ItemList.ItemListBuilder; 91 import org.openjdk.jmc.flightrecorder.ui.common.TypeFilterBuilder; 92 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; 93 import org.openjdk.jmc.flightrecorder.ui.pages.itemhandler.ItemHandlerPage; 94 import org.openjdk.jmc.flightrecorder.ui.pages.itemhandler.ItemHandlerPage.ItemHandlerUiStandIn; 95 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStoreActionToolkit; 96 import org.openjdk.jmc.ui.OrientationAction; 97 import org.openjdk.jmc.ui.column.ColumnManager.SelectionState; 98 import org.openjdk.jmc.ui.column.TableSettings; 99 import org.openjdk.jmc.ui.column.TableSettings.ColumnSettings; 100 import org.openjdk.jmc.ui.handlers.ActionToolkit; 101 import org.openjdk.jmc.ui.handlers.MCContextMenuManager; 102 import org.openjdk.jmc.ui.misc.PersistableSashForm; 103 104 public class EventBrowserPage extends AbstractDataPage { 105 private static final ImageDescriptor NEW_PAGE_ICON = FlightRecorderUI.getDefault() 106 .getMCImageDescriptor(ImageConstants.ICON_NEW_PAGE); 107 108 public static class Factory implements IDataPageFactory { 109 110 @Override 111 public String getName(IState state) { 112 return Messages.EventBrowserPage_PAGE_NAME; 113 } 114 115 @Override 116 public String getDescription(IState state) { 117 return Messages.EventBrowserPage_PAGE_DESC; 118 } 119 120 @Override 121 public String[] getTopics(IState state) { 122 // All topics 123 return new String[] {RuleManager.UNMAPPED_REMAINDER_TOPIC}; 124 } 125 126 @Override 127 public ImageDescriptor getImageDescriptor(IState state) { 128 return FlightRecorderUI.getDefault().getMCImageDescriptor(ImageConstants.ICON_EVENT_TYPE_SELECTOR); 129 } 130 131 @Override 132 public IDisplayablePage createPage(IPageDefinition dpd, StreamModel items, IPageContainer editor) { 133 return new EventBrowserPage(dpd, items, editor); 134 } 135 136 } 137 138 @Override 139 public String getName() { 140 return super.getName(); 141 } 142 143 @Override 144 public IPageUI display(Composite parent, FormToolkit toolkit, IPageContainer editor, IState state) { 145 return new EventBrowserUI(parent, toolkit, state, editor); 146 } 147 148 private SelectionState tableSelection; 149 private ISelection treeSelection; 150 public TreePath[] treeExpansion; 151 public FlavorSelectorState flavorSelectorState; 152 // public int topIndex; 153 154 public EventBrowserPage(IPageDefinition definition, StreamModel items, IPageContainer editor) { 155 super(definition, items, editor); 156 } 157 158 @Override 159 public IItemFilter getDefaultSelectionFilter() { 160 return ItemFilters.all(); 161 } 162 163 class EventBrowserUI implements IPageUI { 164 165 private static final String TREE_SASH = "treeSash"; //$NON-NLS-1$ 166 private static final String ITEM_LIST = "itemList"; //$NON-NLS-1$ 167 private static final String SHOW_TYPES_WITHOUT_EVENTS = "showTypesWithoutEvents"; //$NON-NLS-1$ 168 private ItemList list; 169 private final SashForm treeSash; 170 private final IPageContainer container; 171 private final List<ColumnSettings> listColumns = new ArrayList<>(); 172 private String listOrderBy; 173 private Set<IType<?>> selectedTypes = Collections.emptySet(); 174 private final TypeFilterBuilder typeFilterTree; 175 private IItemCollection selectionItems; 176 private FlavorSelector flavorSelector; 177 private Boolean showTypesWithoutEvents; 178 179 EventBrowserUI(Composite parent, FormToolkit toolkit, IState state, IPageContainer container) { 180 this.container = container; 181 182 Form form = DataPageToolkit.createForm(parent, toolkit, getName(), getIcon()); 183 184 treeSash = new SashForm(form.getBody(), SWT.HORIZONTAL); 185 toolkit.adapt(treeSash); 186 typeFilterTree = DataPageToolkit.buildEventTypeTree(treeSash, toolkit, this::onTypeChange, false); 187 MCContextMenuManager mm = typeFilterTree.getMenuManager(); 188 IAction addPageAction = ActionToolkit.action(this::addPage, 189 Messages.EventBrowserPage_NEW_PAGE_USING_TYPES_ACTION, NEW_PAGE_ICON); 190 mm.appendToGroup(MCContextMenuManager.GROUP_NEW, addPageAction); 191 192 IAction typesWithoutEventsAction = ActionToolkit.checkAction(this::setTypesWithoutEvents, 193 Messages.EventBrowserPage_DISPLAY_TYPES_WITHOUT_EVENTS, null); 194 showTypesWithoutEvents = StateToolkit.readBoolean(state, SHOW_TYPES_WITHOUT_EVENTS, true); 195 typesWithoutEventsAction.setChecked(showTypesWithoutEvents); 196 mm.appendToGroup(MCContextMenuManager.GROUP_OPEN, typesWithoutEventsAction); 197 198 SelectionStoreActionToolkit.addSelectionStoreActions(typeFilterTree.getViewer(), () -> selectedTypes, 199 container.getSelectionStore(), Messages.EventBrowserPage_EVENT_TYPE_TREE_SELECTION, mm); 200 201 Composite listParent = toolkit.createComposite(treeSash); 202 listParent.setLayout(new FillLayout()); 203 PersistableSashForm.loadState(treeSash, state.getChild(TREE_SASH)); 204 205 form.getToolBarManager().add(addPageAction); 206 form.getToolBarManager().add(new Separator()); 207 OrientationAction.installActions(form, treeSash); 208 209 IState itemListState = state.getChild(ITEM_LIST); 210 if (itemListState != null) { 211 TableSettings settings = new TableSettings(itemListState); 212 listColumns.addAll(settings.getColumns()); 213 listOrderBy = settings.getOrderBy(); 214 } 215 list = new ItemListBuilder().build(listParent, null); 216 217 flavorSelector = FlavorSelector.itemsWithTimerange(form, null, getDataSource().getItems(), container, 218 this::onInputSelected, flavorSelectorState); 219 220 addResultActions(form); 221 if (treeExpansion != null) { 222 typeFilterTree.getViewer().setExpandedTreePaths(treeExpansion); 223 } else { 224 typeFilterTree.getViewer().expandAll(); 225 } 226 typeFilterTree.getViewer().setSelection(treeSelection); 227 // if (topIndex >= 0) { 228 // typeFilterTree.getViewer().getTree().setTopItem(typeFilterTree.getViewer().getTree().getItem(topIndex)); 229 // } 230 list.getManager().setSelectionState(tableSelection); 231 } 232 233 private void addPage() { 234 PageManager pm = FlightRecorderUI.getDefault().getPageManager(); 235 pm.makeRoot(pm.createPage(ItemHandlerPage.Factory.class, new ItemHandlerUiStandIn(selectedTypes))); 236 } 237 238 private void setTypesWithoutEvents(boolean checked) { 239 showTypesWithoutEvents = checked; 240 refreshTree(); 241 } 242 243 private void onInputSelected(IItemCollection items, IRange<IQuantity> timeRange) { 244 this.selectionItems = (items == null) ? getDataSource().getItems() : items; 245 refreshTree(); 246 } 247 248 private void refreshTree() { 249 boolean noTypesWereSelected = selectedTypes.isEmpty(); 250 251 typeFilterTree.getViewer().getControl().setRedraw(false); 252 TreePath[] expansion = typeFilterTree.getViewer().getExpandedTreePaths(); 253 ISelection selection = typeFilterTree.getViewer().getSelection(); 254 typeFilterTree.setInput(getDataSource().getTypeTree((ItemCollectionToolkit.stream(selectionItems) 255 .filter(ii -> showTypesWithoutEvents || ii.hasItems())))); 256 typeFilterTree.getViewer().setExpandedTreePaths(expansion); 257 typeFilterTree.getViewer().setSelection(selection); 258 typeFilterTree.getViewer().getControl().setRedraw(true); 259 typeFilterTree.getViewer().getControl().redraw(); 260 261 if (noTypesWereSelected) { 262 // force re-interpretation of empty type selection 263 rebuildItemList(); 264 } 265 } 266 267 private IItemCollection getFilteredItems() { 268 if (!selectedTypes.isEmpty()) { 269 Set<String> types = selectedTypes.stream().map(t -> t.getIdentifier()).collect(Collectors.toSet()); 270 return selectionItems.apply(ItemFilters.type(types)); 271 } 272 return selectionItems; 273 } 274 275 private void onTypeChange() { 276 Set<IType<?>> oldSelectedTypes = selectedTypes; 277 selectedTypes = typeFilterTree.getSelectedTypes().map(EventTypeNode::getType).collect(Collectors.toSet()); 278 if (!Objects.equals(selectedTypes, oldSelectedTypes)) { 279 container.showSelection(getFilteredItems()); 280 rebuildItemList(); 281 } 282 } 283 284 private void rebuildItemList() { 285 mergeListSettings(); 286 287 Iterator<? extends IType<?>> types = selectedTypes.iterator(); 288 IItemCollection filteredItems = getFilteredItems(); 289 if (selectedTypes.isEmpty()) { 290 types = ItemCollectionToolkit.stream(selectionItems).map(is -> is.getType()).distinct().iterator(); 291 } 292 293 // FIXME: Possibly move to attribute toolkit/handler? 294 // FIXME: Make sure to get Event Type as the first column 295 // FIXME: Stream<IType> -> Stream<IAttribute> should be delegated to some context (e.g. the editor) 296 Stream<IAttribute<?>> commonAttributes = Stream.empty(); 297 if (types.hasNext()) { 298 List<IAttribute<?>> attributes = types.next().getAttributes(); 299 if (types.hasNext()) { 300 while (types.hasNext()) { 301 attributes = types.next().getAttributes().stream().filter(attributes::contains) 302 .collect(Collectors.toList()); 303 } 304 commonAttributes = attributes.stream(); 305 } else { 306 commonAttributes = attributes.stream().filter(a -> !a.equals(JfrAttributes.EVENT_TYPE)); 307 } 308 commonAttributes = commonAttributes.filter(a -> !a.equals(JfrAttributes.EVENT_STACKTRACE)); 309 } 310 311 String orderBy = listOrderBy; 312 Set<String> existingColumnIds = listColumns.stream().map(ColumnSettings::getId).collect(Collectors.toSet()); 313 List<ColumnSettings> newColumns = new ArrayList<>(); 314 ItemListBuilder itemListBuilder = new ItemListBuilder(); 315 commonAttributes.forEach(a -> { 316 String combinedId = ItemList.getColumnId(a); 317 ContentType<?> contentType = a.getContentType(); 318 IMemberAccessor<?, IItem> accessor = ItemToolkit.accessor(a); 319 // FIXME: This is duplicated in JfrPropertySheet, where we also create a tooltip for an attribute. 320 itemListBuilder.addColumn(combinedId, a.getName(), 321 NLS.bind(Messages.ATTRIBUTE_ID_LABEL, a.getIdentifier()) + System.getProperty("line.separator") //$NON-NLS-1$ 322 + NLS.bind(Messages.ATTRIBUTE_DESCRIPTION_LABEL, a.getDescription()), 323 contentType instanceof LinearKindOfQuantity, accessor); 324 if (combinedId.equals(listOrderBy)) { 325 // the list now has the most current order, to allow the list to clear it 326 listOrderBy = null; 327 } 328 if (!existingColumnIds.contains(combinedId)) { 329 newColumns.add(0, new ColumnSettings(combinedId, false, null, null)); 330 } 331 }); 332 listColumns.addAll(0, newColumns); 333 334 Control oldListControl = list.getManager().getViewer().getControl(); 335 Composite parent = oldListControl.getParent(); 336 oldListControl.dispose(); 337 list = DataPageToolkit.createSimpleItemList(parent, itemListBuilder, container, 338 DataPageToolkit.createTableSettingsByOrderByAndColumnsWithDefaultOrdering(orderBy, listColumns), 339 Messages.EventBrowserPage_EVENT_BROWSER_SELECTION); 340 parent.layout(); 341 list.show(filteredItems); 342 } 343 344 private void mergeListSettings() { 345 TableSettings settings = list.getManager().getSettings(); 346 Set<String> columns = settings.getColumns().stream().map(ColumnSettings::getId).collect(Collectors.toSet()); 347 List<Integer> replaceIndexs = new ArrayList<>(columns.size()); 348 for (int i = 0; i < listColumns.size(); i++) { 349 if (columns.contains(listColumns.get(i).getId())) { 350 replaceIndexs.add(i); 351 } 352 } 353 Iterator<ColumnSettings> replacements = settings.getColumns().iterator(); 354 Iterator<Integer> indexs = replaceIndexs.iterator(); 355 while (indexs.hasNext() && replacements.hasNext()) { 356 listColumns.set(indexs.next(), replacements.next()); 357 } 358 if (settings.getOrderBy() != null) { 359 listOrderBy = settings.getOrderBy(); 360 } 361 } 362 363 @Override 364 public void saveTo(IWritableState state) { 365 PersistableSashForm.saveState(treeSash, state.createChild(TREE_SASH)); 366 mergeListSettings(); 367 new TableSettings(listOrderBy, listColumns).saveState(state.createChild(ITEM_LIST)); 368 StateToolkit.writeBoolean(state, SHOW_TYPES_WITHOUT_EVENTS, showTypesWithoutEvents); 369 saveToLocal(); 370 } 371 372 private void saveToLocal() { 373 treeSelection = typeFilterTree.getViewer().getSelection(); 374 treeExpansion = typeFilterTree.getViewer().getExpandedTreePaths(); 375 // FIXME: indexOf doesn't seem to work for some reason, probably an SWT bug 376 // topIndex = typeFilterTree.getViewer().getTree().indexOf(typeFilterTree.getViewer().getTree().getTopItem()); 377 tableSelection = list.getManager().getSelectionState(); 378 flavorSelectorState = flavorSelector.getFlavorSelectorState(); 379 } 380 } 381 }