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.views.stacktrace;
  34 
  35 import java.util.Arrays;
  36 import java.util.List;
  37 import java.util.Optional;
  38 import java.util.concurrent.CompletableFuture;
  39 import java.util.logging.Level;
  40 import java.util.stream.Collectors;
  41 import java.util.stream.IntStream;
  42 import java.util.stream.Stream;
  43 
  44 import org.eclipse.core.runtime.IAdapterFactory;
  45 import org.eclipse.core.runtime.Platform;
  46 import org.eclipse.jface.action.Action;
  47 import org.eclipse.jface.action.IAction;
  48 import org.eclipse.jface.action.IMenuManager;
  49 import org.eclipse.jface.action.IToolBarManager;
  50 import org.eclipse.jface.action.MenuManager;
  51 import org.eclipse.jface.action.Separator;
  52 import org.eclipse.jface.viewers.AbstractTreeViewer;
  53 import org.eclipse.jface.viewers.ColumnLabelProvider;
  54 import org.eclipse.jface.viewers.ColumnViewer;
  55 import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
  56 import org.eclipse.jface.viewers.ISelection;
  57 import org.eclipse.jface.viewers.ISelectionChangedListener;
  58 import org.eclipse.jface.viewers.IStructuredSelection;
  59 import org.eclipse.jface.viewers.ITreeContentProvider;
  60 import org.eclipse.jface.viewers.SelectionChangedEvent;
  61 import org.eclipse.jface.viewers.StructuredSelection;
  62 import org.eclipse.jface.viewers.StructuredViewer;
  63 import org.eclipse.jface.viewers.TableViewer;
  64 import org.eclipse.jface.viewers.TableViewerColumn;
  65 import org.eclipse.jface.viewers.TreeViewer;
  66 import org.eclipse.jface.viewers.TreeViewerColumn;
  67 import org.eclipse.jface.viewers.ViewerCell;
  68 import org.eclipse.jface.viewers.ViewerColumn;
  69 import org.eclipse.jface.window.ToolTip;
  70 import org.eclipse.swt.SWT;
  71 import org.eclipse.swt.events.TraverseEvent;
  72 import org.eclipse.swt.events.TraverseListener;
  73 import org.eclipse.swt.graphics.Color;
  74 import org.eclipse.swt.graphics.Image;
  75 import org.eclipse.swt.graphics.RGB;
  76 import org.eclipse.swt.widgets.Composite;
  77 import org.eclipse.swt.widgets.Display;
  78 import org.eclipse.swt.widgets.Event;
  79 import org.eclipse.swt.widgets.Listener;
  80 import org.eclipse.swt.widgets.TableColumn;
  81 import org.eclipse.swt.widgets.TreeColumn;
  82 import org.eclipse.ui.IEditorPart;
  83 import org.eclipse.ui.IMemento;
  84 import org.eclipse.ui.ISelectionListener;
  85 import org.eclipse.ui.IViewSite;
  86 import org.eclipse.ui.IWorkbenchPart;
  87 import org.eclipse.ui.PartInitException;
  88 import org.eclipse.ui.PlatformUI;
  89 import org.eclipse.ui.forms.widgets.FormText;
  90 import org.eclipse.ui.part.ViewPart;
  91 
  92 import org.openjdk.jmc.common.IDisplayable;
  93 import org.openjdk.jmc.common.IMCFrame;
  94 import org.openjdk.jmc.common.IState;
  95 import org.openjdk.jmc.common.collection.SimpleArray;
  96 import org.openjdk.jmc.common.item.IItemCollection;
  97 import org.openjdk.jmc.common.unit.UnitLookup;
  98 import org.openjdk.jmc.common.util.StateToolkit;
  99 import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator;
 100 import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator.FrameCategorization;
 101 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceFormatToolkit;
 102 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceFrame;
 103 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceModel;
 104 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceModel.Branch;
 105 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceModel.Fork;
 106 import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI;
 107 import org.openjdk.jmc.flightrecorder.ui.IPageContainer;
 108 import org.openjdk.jmc.flightrecorder.ui.ItemCollectionToolkit;
 109 import org.openjdk.jmc.flightrecorder.ui.common.ImageConstants;
 110 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages;
 111 import org.openjdk.jmc.flightrecorder.ui.selection.IFlavoredSelection;
 112 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStore;
 113 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStoreActionToolkit;
 114 import org.openjdk.jmc.flightrecorder.ui.selection.StacktraceFrameSelection;
 115 import org.openjdk.jmc.ui.CoreImages;
 116 import org.openjdk.jmc.ui.UIPlugin;
 117 import org.openjdk.jmc.ui.accessibility.FocusTracker;
 118 import org.openjdk.jmc.ui.common.util.AdapterUtil;
 119 import org.openjdk.jmc.ui.handlers.ActionToolkit;
 120 import org.openjdk.jmc.ui.handlers.CopySelectionAction;
 121 import org.openjdk.jmc.ui.handlers.InFocusHandlerActivator;
 122 import org.openjdk.jmc.ui.handlers.MCContextMenuManager;
 123 import org.openjdk.jmc.ui.handlers.MethodFormatter;
 124 import org.openjdk.jmc.ui.misc.AbstractStructuredContentProvider;
 125 import org.openjdk.jmc.ui.misc.CompositeToolkit;
 126 import org.openjdk.jmc.ui.misc.CopySettings;
 127 import org.openjdk.jmc.ui.misc.DisplayToolkit;
 128 import org.openjdk.jmc.ui.misc.FormatToolkit;
 129 import org.openjdk.jmc.ui.misc.MementoToolkit;
 130 import org.openjdk.jmc.ui.misc.SWTColorToolkit;
 131 
 132 public class StacktraceView extends ViewPart implements ISelectionListener {
 133 
 134         static {
 135                 // Adapt using IAdapterFactory to support object contribution for IMCMethod (e.g jump to source)
 136                 Platform.getAdapterManager().registerAdapters(new IAdapterFactory() {
 137 
 138                         @Override
 139                         public <T> T getAdapter(Object adaptableObject, Class<T> adapterType) {
 140                                 if (adaptableObject instanceof StacktraceFrame && adapterType == IMCFrame.class) {
 141                                         return adapterType.cast(((StacktraceFrame) adaptableObject).getFrame());
 142                                 }
 143                                 return null;
 144                         }
 145 
 146                         @Override
 147                         public Class<?>[] getAdapterList() {
 148                                 return new Class[] {IMCFrame.class};
 149                         }
 150                 }, StacktraceFrame.class);
 151         }
 152 
 153         private class GroupByAction extends Action {
 154 
 155                 private final boolean fromThreadRootAction;
 156 
 157                 GroupByAction(boolean fromRoot) {
 158                         super(fromRoot ? Messages.STACKTRACE_VIEW_THREAD_ROOT : Messages.STACKTRACE_VIEW_LAST_FRAME,
 159                                         IAction.AS_RADIO_BUTTON);
 160                         fromThreadRootAction = fromRoot;
 161                         setToolTipText(fromRoot ? Messages.STACKTRACE_VIEW_GROUP_TRACES_FROM_ROOT
 162                                         : Messages.STACKTRACE_VIEW_GROUP_TRACES_FROM_LAST_FRAME);
 163                         setImageDescriptor(fromRoot ? CoreImages.THREAD : CoreImages.METHOD_NON_OPTIMIZED);
 164                         setChecked(fromRoot == threadRootAtTop);
 165                 }
 166 
 167                 @Override
 168                 public void run() {
 169                         boolean newValue = isChecked() == fromThreadRootAction;
 170                         if (newValue != threadRootAtTop) {
 171                                 threadRootAtTop = newValue;
 172                                 rebuildModel();
 173                         }
 174                 }
 175         }
 176 
 177         private static final String HELP_CONTEXT_ID = FlightRecorderUI.PLUGIN_ID + ".StacktraceView"; //$NON-NLS-1$
 178         // FIXME: Define dynamic color (editable in preferences, to handle dark themes etc.)
 179         private static final Color ALTERNATE_COLOR = SWTColorToolkit.getColor(new RGB(255, 255, 240));
 180         private static final String COUNT_IMG_KEY = "countColor"; //$NON-NLS-1$
 181         private static final Color COUNT_COLOR = SWTColorToolkit.getColor(new RGB(100, 200, 100));
 182         private static final String SIBLINGS_IMG_KEY = "siblingsColor"; //$NON-NLS-1$
 183         private static final Color SIBLINGS_COUNT_COLOR = SWTColorToolkit.getColor(new RGB(170, 250, 170));
 184         private static final int[] DEFAULT_COLUMN_WIDTHS = {700, 150};
 185         private static final String THREAD_ROOT_KEY = "threadRootAtTop"; //$NON-NLS-1$
 186         private static final String FRAME_OPTIMIZATION_KEY = "distinguishFramesByOptimization"; //$NON-NLS-1$
 187         private static final String FRAME_CATEGORIZATION_KEY = "distinguishFramesCategorization"; //$NON-NLS-1$
 188         private static final String TREE_LAYOUT_KEY = "treeLayout"; //$NON-NLS-1$
 189         private static final String REDUCED_TREE_KEY = "reducedTreeLayout"; //$NON-NLS-1$
 190         private static final String METHOD_FORMAT_KEY = "metodFormat"; //$NON-NLS-1$
 191         private static final String COLUMNS_KEY = "columns"; //$NON-NLS-1$
 192         private static final String COLUMNS_SEPARATOR = " "; //$NON-NLS-1$
 193         private ColumnViewer viewer;
 194         private boolean treeLayout;
 195         private boolean reducedTree;
 196         private boolean threadRootAtTop;
 197         private IItemCollection itemsToShow;
 198         private MethodFormatter methodFormatter;
 199         private FrameSeparatorManager frameSeparatorManager;
 200         private GroupByAction[] groupByActions;
 201         private IAction[] layoutActions;
 202         private ViewerAction[] viewerActions;
 203         private int[] columnWidths;
 204 
 205         private static class StacktraceViewToolTipSupport extends ColumnViewerToolTipSupport {
 206 
 207                 StacktraceViewToolTipSupport(ColumnViewer viewer) {
 208                         super(viewer, ToolTip.NO_RECREATE, false);
 209                 }
 210 
 211                 @Override
 212                 protected Composite createViewerToolTipContentArea(Event event, ViewerCell cell, Composite parent) {
 213                         FormText formText = CompositeToolkit.createInfoFormText(parent);
 214                         formText.setImage(COUNT_IMG_KEY, SWTColorToolkit.getColorThumbnail(COUNT_COLOR.getRGB()));
 215                         formText.setImage(SIBLINGS_IMG_KEY, SWTColorToolkit.getColorThumbnail(SIBLINGS_COUNT_COLOR.getRGB()));
 216                         formText.setText(getText(event), true, false);
 217                         return formText;
 218                 }
 219 
 220         }
 221 
 222         private static class ViewerAction extends Action implements ISelectionChangedListener {
 223 
 224                 protected StructuredViewer provider = null;
 225 
 226                 public ViewerAction(String text) {
 227                         super(text);
 228                         setViewer(null);
 229                         setEnabled(false);
 230                 }
 231 
 232                 public void setViewer(StructuredViewer provider) {
 233                         this.provider = provider;
 234                         if (provider != null) {
 235                                 provider.addSelectionChangedListener(this);
 236                                 selectionChanged(getStructuredSelection());
 237                         } else {
 238                                 setEnabled(false);
 239                         }
 240                 }
 241 
 242                 @Override
 243                 public void selectionChanged(SelectionChangedEvent event) {
 244                         ISelection selection = event.getSelection();
 245                         if (selection instanceof IStructuredSelection) {
 246                                 selectionChanged((IStructuredSelection) selection);
 247                         }
 248                 }
 249 
 250                 protected void selectionChanged(IStructuredSelection selection) {
 251                 }
 252 
 253                 protected IStructuredSelection getStructuredSelection() {
 254                         if (provider != null) {
 255                                 ISelection selection = provider.getSelection();
 256                                 if (selection instanceof IStructuredSelection) {
 257                                         return (IStructuredSelection) selection;
 258                                 }
 259                         }
 260                         return new StructuredSelection();
 261                 }
 262 
 263         }
 264 
 265         static class SelectFrameGroupAction extends ViewerAction {
 266 
 267                 SelectFrameGroupAction() {
 268                         super(Messages.STACKTRACE_VIEW_FRAME_GROUP_CHOOSE);
 269                         setImageDescriptor(
 270                                         FlightRecorderUI.getDefault().getMCImageDescriptor(ImageConstants.ICON_ARROW_FORK3_STAR));
 271                         setAccelerator(SWT.CR);
 272                 }
 273 
 274                 @Override
 275                 public void setViewer(StructuredViewer provider) {
 276                         super.setViewer(provider);
 277                         if (provider != null) {
 278                                 provider.addDoubleClickListener(e -> {
 279                                         if (isEnabled()) {
 280                                                 run();
 281                                         }
 282                                 });
 283                         }
 284                 }
 285 
 286                 @Override
 287                 public void run() {
 288                         StacktraceFrame frame = (StacktraceFrame) getStructuredSelection().getFirstElement();
 289                         // FIXME: Would like to move the table cursor after changing sibling state, not just the selection.
 290                         if (isInOpenFork(frame)) {
 291                                 frame.getBranch().selectSibling(0);
 292                         } else {
 293                                 frame.getBranch().selectSibling(null);
 294                         }
 295                         provider.getControl().setRedraw(false);
 296                         try {
 297                                 provider.refresh();
 298                         } finally {
 299                                 provider.getControl().setRedraw(true);
 300                         }
 301                         provider.setSelection(new StructuredSelection(frame));
 302 
 303                 }
 304 
 305                 @Override
 306                 public void selectionChanged(IStructuredSelection selection) {
 307                         setEnabled(selection.size() == 1
 308                                         && isFirstInBranchWithSiblings((StacktraceFrame) selection.getFirstElement()));
 309                 }
 310 
 311         }
 312 
 313         static class NavigateAction extends ViewerAction implements TraverseListener {
 314 
 315                 private final int offset;
 316 
 317                 NavigateAction(boolean forward) {
 318                         super(forward ? Messages.STACKTRACE_VIEW_FRAME_GROUP_NEXT : Messages.STACKTRACE_VIEW_FRAME_GROUP_PREVIOUS);
 319                         setImageDescriptor(
 320                                         forward ? FlightRecorderUI.getDefault().getMCImageDescriptor(ImageConstants.ICON_ARROW_FORK3_RIGHT)
 321                                                         : FlightRecorderUI.getDefault().getMCImageDescriptor(ImageConstants.ICON_ARROW_FORK3_LEFT));
 322                         offset = forward ? 1 : -1;
 323                         setAccelerator(forward ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT);
 324                 }
 325 
 326                 @Override
 327                 public void setViewer(StructuredViewer provider) {
 328                         super.setViewer(provider);
 329                         if (provider != null) {
 330                                 provider.getControl().addTraverseListener(this);
 331                         }
 332                 }
 333 
 334                 @Override
 335                 public void run() {
 336                         Branch branch = ((StacktraceFrame) getStructuredSelection().getFirstElement()).getBranch();
 337                         Branch selectedSibling = branch.selectSibling(offset);
 338                         provider.refresh();
 339                         provider.setSelection(new StructuredSelection(selectedSibling.getFirstFrame()));
 340                 }
 341 
 342                 @Override
 343                 protected void selectionChanged(IStructuredSelection selection) {
 344                         setEnabled(selection.size() == 1 && isNavigationFrame((StacktraceFrame) selection.getFirstElement()));
 345                 }
 346 
 347                 @Override
 348                 public void keyTraversed(TraverseEvent e) {
 349                         if (isEnabled()) {
 350                                 if (e.keyCode == getAccelerator()) {
 351                                         run();
 352                                         e.detail = SWT.TRAVERSE_NONE;
 353                                         e.doit = true;
 354                                 }
 355                         }
 356                 }
 357 
 358         };
 359 
 360         @Override
 361         public void init(IViewSite site, IMemento memento) throws PartInitException {
 362                 super.init(site, memento);
 363                 IState state = MementoToolkit.asState(memento);
 364                 threadRootAtTop = StateToolkit.readBoolean(state, THREAD_ROOT_KEY, false);
 365                 groupByActions = new GroupByAction[] {new GroupByAction(false), new GroupByAction(true)};
 366                 treeLayout = StateToolkit.readBoolean(state, TREE_LAYOUT_KEY, false);
 367                 reducedTree = StateToolkit.readBoolean(state, REDUCED_TREE_KEY, true);
 368 
 369                 IAction reducedTreeAction = ActionToolkit.checkAction(this::setReducedTree,
 370                                 Messages.STACKTRACE_VIEW_REDUCE_TREE_DEPTH, null);
 371                 reducedTreeAction.setChecked(reducedTree);
 372                 IAction treeAction = ActionToolkit.checkAction(this::setTreeLayout, Messages.STACKTRACE_VIEW_SHOW_AS_TREE,
 373                                 CoreImages.TREE_MODE);
 374                 treeAction.setChecked(treeLayout);
 375                 layoutActions = new IAction[] {treeAction, reducedTreeAction};
 376 
 377                 NavigateAction forwardAction = new NavigateAction(true);
 378                 NavigateAction backwardAction = new NavigateAction(false);
 379                 SelectFrameGroupAction selectGroupAction = new SelectFrameGroupAction();
 380                 viewerActions = new ViewerAction[] {selectGroupAction, forwardAction, backwardAction};
 381 
 382                 try {
 383                         columnWidths = Optional.ofNullable(state)
 384                                         .map(s -> Stream.of(s.getAttribute(COLUMNS_KEY).split(COLUMNS_SEPARATOR))
 385                                                         .mapToInt(Integer::parseInt).toArray())
 386                                         .filter(widths -> widths.length == DEFAULT_COLUMN_WIDTHS.length
 387                                                         && Arrays.stream(widths).allMatch(w -> w >= 0))
 388                                         .orElse(DEFAULT_COLUMN_WIDTHS);
 389                 } catch (RuntimeException e) {
 390                         columnWidths = DEFAULT_COLUMN_WIDTHS;
 391                 }
 392 
 393                 FrameCategorization categorization = StateToolkit.readEnum(state, FRAME_CATEGORIZATION_KEY,
 394                                 FrameCategorization.METHOD, FrameCategorization.class);
 395                 boolean byOptimization = StateToolkit.readBoolean(state, FRAME_OPTIMIZATION_KEY, false);
 396                 frameSeparatorManager = new FrameSeparatorManager(this::rebuildModel,
 397                                 new FrameSeparator(categorization, byOptimization));
 398                 methodFormatter = new MethodFormatter(memento == null ? null : memento.getChild(METHOD_FORMAT_KEY),
 399                                 () -> viewer.refresh());
 400                 IMenuManager siteMenu = site.getActionBars().getMenuManager();
 401                 siteMenu.add(new Separator(MCContextMenuManager.GROUP_TOP));
 402                 siteMenu.add(new Separator(MCContextMenuManager.GROUP_VIEWER_SETUP));
 403                 addOptions(siteMenu);
 404                 IToolBarManager toolBar = site.getActionBars().getToolBarManager();
 405                 toolBar.add(selectGroupAction);
 406                 toolBar.add(backwardAction);
 407                 toolBar.add(forwardAction);
 408                 toolBar.add(new Separator());
 409                 toolBar.add(treeAction);
 410                 toolBar.add(new Separator());
 411                 Stream.of(groupByActions).forEach(toolBar::add);
 412 
 413                 getSite().getPage().addSelectionListener(this);
 414         }
 415 
 416         @Override
 417         public void dispose() {
 418                 getSite().getPage().removeSelectionListener(this);
 419                 super.dispose();
 420         }
 421 
 422         @Override
 423         public void createPartControl(Composite parent) {
 424                 buildViewer(parent);
 425         }
 426 
 427         private void setTreeLayout(boolean treeLayout) {
 428                 this.treeLayout = treeLayout;
 429                 rebuildViewer();
 430         }
 431 
 432         private void setReducedTree(boolean reducedTree) {
 433                 this.reducedTree = reducedTree;
 434                 if (viewer instanceof TreeViewer) {
 435                         viewer.setContentProvider(createTreeContentProvider());
 436                 }
 437         }
 438 
 439         private void rebuildViewer() {
 440                 boolean hasFocus = viewer.getControl().isFocusControl();
 441                 ISelection oldSelection = viewer.getSelection();
 442                 Fork oldInput = (Fork) viewer.getInput();
 443                 Composite parent = viewer.getControl().getParent();
 444                 viewer.getControl().dispose();
 445                 buildViewer(parent);
 446                 if (hasFocus) {
 447                         viewer.getControl().setFocus();
 448                 }
 449                 parent.layout();
 450                 if (viewer instanceof TreeViewer) {
 451                         // Async set input to avoid drawing issue with tree
 452                         Display.getCurrent().asyncExec(() -> {
 453                                 if (!viewer.getControl().isDisposed()) {
 454                                         setViewerInput(oldInput);
 455                                         if (reducedTree && oldInput != null) {
 456                                                 Branch selectedBranch = getLastSelectedBranch(oldInput);
 457                                                 if (selectedBranch != null) {
 458                                                         viewer.getControl().setRedraw(false);
 459                                                         ((TreeViewer) viewer).expandToLevel(selectedBranch.getLastFrame(),
 460                                                                         AbstractTreeViewer.ALL_LEVELS);
 461                                                         viewer.getControl().setRedraw(true);
 462                                                 }
 463                                         }
 464                                         viewer.setSelection(oldSelection, true);
 465                                 }
 466                         });
 467                 } else {
 468                         Branch branch = null;
 469                         for (Object o : ((IStructuredSelection) oldSelection).toList()) {
 470                                 if (branch == null) {
 471                                         branch = ((StacktraceFrame) o).getBranch();
 472                                 } else if (branch != ((StacktraceFrame) o).getBranch()) {
 473                                         branch = null;
 474                                         break;
 475                                 }
 476                         }
 477                         if (branch != null) {
 478                                 branch.selectSibling(0);
 479                         }
 480                         setViewerInput(oldInput);
 481                         viewer.setSelection(oldSelection, true);
 482                 }
 483         }
 484 
 485         private void buildViewer(Composite parent) {
 486                 if (treeLayout) {
 487                         viewer = buildTree(parent);
 488                 } else {
 489                         viewer = buildTable(parent);
 490                 }
 491                 new StacktraceViewToolTipSupport(viewer);
 492                 MCContextMenuManager mm = MCContextMenuManager.create(viewer.getControl());
 493                 CopySelectionAction copyAction = new CopySelectionAction(viewer,
 494                                 FormatToolkit.selectionFormatter(stackTraceLabelProvider, countLabelProvider));
 495                 InFocusHandlerActivator.install(viewer.getControl(), copyAction);
 496                 mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, copyAction);
 497                 mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, CopySettings.getInstance().createContributionItem());
 498                 addOptions(mm);
 499                 getSite().registerContextMenu(mm, viewer);
 500                 if (!treeLayout) {
 501                         String navigateGroupName = "NAVIGATE"; //$NON-NLS-1$
 502                         mm.insert(0, new Separator(navigateGroupName));
 503                         Stream.of(viewerActions).forEach(a -> {
 504                                 a.setViewer(viewer);
 505                                 mm.appendToGroup(navigateGroupName, a);
 506                         });
 507                 } else {
 508                         Stream.of(viewerActions).forEach(a -> a.setViewer(null));
 509                 }
 510 
 511                 viewer.getControl().addListener(SWT.EraseItem, COUNT_BACKGROUND_DRAWER);
 512                 viewer.getControl().addDisposeListener(e -> columnWidths = getColumnWidths());
 513 
 514                 buildColumn(viewer, Messages.STACKTRACE_VIEW_STACK_TRACE, SWT.NONE, columnWidths[0])
 515                                 .setLabelProvider(stackTraceLabelProvider);
 516                 buildColumn(viewer, Messages.STACKTRACE_VIEW_COUNT_COLUMN_NAME, SWT.RIGHT, columnWidths[1])
 517                                 .setLabelProvider(countLabelProvider);
 518 
 519                 PlatformUI.getWorkbench().getHelpSystem().setHelp(viewer.getControl(), HELP_CONTEXT_ID);
 520 
 521                 if (UIPlugin.getDefault().getAccessibilityMode()) {
 522                         if (treeLayout) {
 523                                 FocusTracker.enableFocusTracking(((TreeViewer) viewer).getTree());
 524                         } else {
 525                                 FocusTracker.enableFocusTracking(((TableViewer) viewer).getTable());
 526                         }
 527                 }
 528         }
 529 
 530         private static TableViewer buildTable(Composite parent) {
 531                 TableViewer tableViewer = new TableViewer(parent,
 532                                 SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
 533                 tableViewer.setContentProvider(new AbstractStructuredContentProvider() {
 534                         @Override
 535                         public Object[] getElements(Object inputElement) {
 536                                 SimpleArray<StacktraceFrame> trace = new SimpleArray<>(new StacktraceFrame[100]);
 537                                 addSelectedBranches((Fork) inputElement, trace, false);
 538                                 return trace.elements();
 539                         }
 540                 });
 541                 tableViewer.getTable().setHeaderVisible(true);
 542                 tableViewer.getTable().setLinesVisible(true);
 543                 return tableViewer;
 544         }
 545 
 546         private TreeViewer buildTree(Composite parent) {
 547                 TreeViewer treeViewer = new TreeViewer(parent,
 548                                 SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
 549                 treeViewer.setContentProvider(createTreeContentProvider());
 550                 treeViewer.getTree().setHeaderVisible(true);
 551                 treeViewer.getTree().setLinesVisible(true);
 552                 return treeViewer;
 553         }
 554 
 555         private static ViewerColumn buildColumn(ColumnViewer viewer, String text, int style, int width) {
 556                 if (viewer instanceof TableViewer) {
 557                         TableViewerColumn vc = new TableViewerColumn((TableViewer) viewer, style);
 558                         vc.getColumn().setWidth(width);
 559                         vc.getColumn().setText(text);
 560                         return vc;
 561                 } else {
 562                         TreeViewerColumn vc = new TreeViewerColumn((TreeViewer) viewer, style);
 563                         vc.getColumn().setWidth(width);
 564                         vc.getColumn().setText(text);
 565                         return vc;
 566                 }
 567         }
 568 
 569         private int[] getColumnWidths() {
 570                 if (!viewer.getControl().isDisposed()) {
 571                         if (viewer instanceof TableViewer) {
 572                                 return Stream.of(((TableViewer) viewer).getTable().getColumns()).mapToInt(TableColumn::getWidth)
 573                                                 .toArray();
 574                         } else {
 575                                 return Stream.of(((TreeViewer) viewer).getTree().getColumns()).mapToInt(TreeColumn::getWidth).toArray();
 576                         }
 577                 }
 578                 return columnWidths;
 579         }
 580 
 581         private void addOptions(IMenuManager menu) {
 582                 MenuManager groupMenu = new MenuManager(Messages.STACKTRACE_VIEW_GROUP_FROM);
 583                 Stream.of(groupByActions).forEach(groupMenu::add);
 584                 menu.appendToGroup(MCContextMenuManager.GROUP_TOP, groupMenu);
 585                 menu.appendToGroup(MCContextMenuManager.GROUP_TOP, frameSeparatorManager.createMenu());
 586                 MenuManager layoutMenu = new MenuManager(Messages.STACKTRACE_VIEW_LAYOUT_OPTIONS);
 587                 Stream.of(layoutActions).forEach(layoutMenu::add);
 588                 menu.appendToGroup(MCContextMenuManager.GROUP_VIEWER_SETUP, layoutMenu);
 589                 menu.appendToGroup(MCContextMenuManager.GROUP_VIEWER_SETUP, methodFormatter.createMenu());
 590                 SelectionStoreActionToolkit.addSelectionStoreActions(viewer, this::getSelectionStore,
 591                                 this::getFlavoredSelection, menu);
 592         }
 593 
 594         private IFlavoredSelection getFlavoredSelection() {
 595                 ISelection selection = viewer.getSelection();
 596                 if (selection instanceof IStructuredSelection && !selection.isEmpty()) {
 597                         List<?> selected = ((StructuredSelection) selection).toList();
 598                         StacktraceFrame frame = (StacktraceFrame) selected.get(0);
 599                         return new StacktraceFrameSelection(frame.getFrame(),
 600                                         ItemCollectionToolkit.build(Stream.of(frame.getItems().elements())),
 601                                         Messages.STACKTRACE_VIEW_SELECTION);
 602                 }
 603                 return null;
 604         }
 605 
 606         private SelectionStore getSelectionStore() {
 607                 IEditorPart editorPart = null;
 608                 try {
 609                         PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
 610                         editorPart = getSite().getPage().getActiveEditor();
 611                 } catch (Exception e) {
 612                         FlightRecorderUI.getDefault().getLogger().log(Level.INFO,
 613                                         "Got exception while trying to get the active editor", e); //$NON-NLS-1$
 614                 }
 615                 if (editorPart instanceof IPageContainer) {
 616                         return ((IPageContainer) editorPart).getSelectionStore();
 617                 }
 618                 return null;
 619         }
 620 
 621         @Override
 622         public void setFocus() {
 623                 viewer.getControl().setFocus();
 624         }
 625 
 626         @Override
 627         public void saveState(IMemento memento) {
 628                 memento.putString(COLUMNS_KEY, IntStream.of(getColumnWidths()).mapToObj(Integer::toString)
 629                                 .collect(Collectors.joining(COLUMNS_SEPARATOR)));
 630                 methodFormatter.saveState(memento.createChild(METHOD_FORMAT_KEY));
 631                 memento.putBoolean(THREAD_ROOT_KEY, threadRootAtTop);
 632                 memento.putBoolean(TREE_LAYOUT_KEY, treeLayout);
 633                 memento.putBoolean(REDUCED_TREE_KEY, reducedTree);
 634                 FrameSeparator frameSeparator = frameSeparatorManager.getFrameSeparator();
 635                 memento.putBoolean(FRAME_OPTIMIZATION_KEY, frameSeparator.isDistinguishFramesByOptimization());
 636                 memento.putString(FRAME_CATEGORIZATION_KEY, frameSeparator.getCategorization().name());
 637         }
 638 
 639         @Override
 640         public void selectionChanged(IWorkbenchPart part, ISelection selection) {
 641                 if (selection instanceof IStructuredSelection) {
 642                         Object first = ((IStructuredSelection) selection).getFirstElement();
 643                         IItemCollection items = AdapterUtil.getAdapter(first, IItemCollection.class);
 644                         if (items != null && !items.equals(itemsToShow)) {
 645                                 setItems(items);
 646                         }
 647                 }
 648         }
 649 
 650         private void setItems(IItemCollection items) {
 651                 itemsToShow = items;
 652                 rebuildModel();
 653         }
 654 
 655         private StacktraceModel createStacktraceModel() {
 656                 return new StacktraceModel(threadRootAtTop, frameSeparatorManager.getFrameSeparator(), itemsToShow);
 657         }
 658 
 659         private void rebuildModel() {
 660                 // Release old model before building the new
 661                 setViewerInput(null);
 662                 CompletableFuture<StacktraceModel> modelPreparer = getModelPreparer(createStacktraceModel(), !treeLayout);
 663                 modelPreparer.thenAcceptAsync(this::setModel, DisplayToolkit.inDisplayThread())
 664                                 .exceptionally(StacktraceView::handleModelBuildException);
 665         }
 666 
 667         private static CompletableFuture<StacktraceModel> getModelPreparer(
 668                 StacktraceModel model, boolean materializeSelectedBranches) {
 669                 return CompletableFuture.supplyAsync(() -> {
 670                         Fork root = model.getRootFork();
 671                         if (materializeSelectedBranches) {
 672                                 Branch selectedBranch = getLastSelectedBranch(root);
 673                                 if (selectedBranch != null) {
 674                                         selectedBranch.getEndFork();
 675                                 }
 676                         }
 677                         return model;
 678                 });
 679         }
 680 
 681         private static Void handleModelBuildException(Throwable ex) {
 682                 FlightRecorderUI.getDefault().getLogger().log(Level.SEVERE, "Failed to build stacktrace view model", ex); //$NON-NLS-1$
 683                 return null;
 684         }
 685 
 686         private void setModel(StacktraceModel model) {
 687                 // Check that the model is up to date
 688                 if (model.equals(createStacktraceModel()) && !viewer.getControl().isDisposed()) {
 689                         setViewerInput(model.getRootFork());
 690                 }
 691         }
 692 
 693         private void setViewerInput(Fork rootFork) {
 694                 // NOTE: will be slow for TreeViewer if number of roots or children of a node are more than ~1000
 695                 viewer.setInput(rootFork);
 696         }
 697 
 698         private ITreeContentProvider createTreeContentProvider() {
 699                 return reducedTree ? new StacktraceReducedTreeContentProvider() : new StacktraceTreeContentProvider();
 700         }
 701 
 702         private static final Listener COUNT_BACKGROUND_DRAWER = new Listener() {
 703                 @Override
 704                 public void handleEvent(Event event) {
 705                         StacktraceFrame frame = (StacktraceFrame) event.item.getData();
 706                         Fork rootFork = getRootFork(frame.getBranch().getParentFork());
 707                         double total;
 708                         if (event.index == 1 && (total = rootFork.getItemsInFork()) > 0) { // index == 1 => count column
 709                                 // Draw siblings
 710                                 Fork parentFork = frame.getBranch().getParentFork();
 711                                 int forkOffset = parentFork.getItemOffset();
 712                                 int siblingsStart = (int) Math.floor(event.width * forkOffset / total);
 713                                 int siblingsWidth = (int) Math.round(event.width * parentFork.getItemsInFork() / total);
 714                                 event.gc.setBackground(SIBLINGS_COUNT_COLOR);
 715                                 event.gc.fillRectangle(event.x + siblingsStart, event.y, siblingsWidth, event.height);
 716                                 // Draw group
 717                                 double offset = (forkOffset + frame.getBranch().getItemOffsetInFork()) / total;
 718                                 double fraction = frame.getItemCount() / total;
 719                                 event.gc.setBackground(COUNT_COLOR);
 720                                 int startPixel = (int) Math.floor(event.width * offset);
 721                                 int widthPixel = (int) Math.round(event.width * fraction);
 722                                 event.gc.fillRectangle(event.x + startPixel, event.y, Math.max(widthPixel, 1), event.height);
 723                                 event.detail &= ~SWT.BACKGROUND;
 724                         }
 725                 }
 726         };
 727 
 728         private final ColumnLabelProvider countLabelProvider = new ColumnLabelProvider() {
 729                 @Override
 730                 public String getText(Object element) {
 731                         return Integer.toString(((StacktraceFrame) element).getItemCount());
 732                 }
 733 
 734                 @Override
 735                 public String getToolTipText(Object element) {
 736                         StacktraceFrame frame = (StacktraceFrame) element;
 737                         Fork rootFork = getRootFork(frame.getBranch().getParentFork());
 738                         int itemCount = frame.getItemCount();
 739                         int totalCount = rootFork.getItemsInFork();
 740                         Fork parentFork = frame.getBranch().getParentFork();
 741                         int itemsInSiblings = parentFork.getItemsInFork() - frame.getBranch().getFirstFrame().getItemCount();
 742                         String frameFraction = UnitLookup.PERCENT_UNITY.quantity(itemCount / (double) totalCount)
 743                                         .displayUsing(IDisplayable.AUTO);
 744                         StringBuilder sb = new StringBuilder("<form>"); //$NON-NLS-1$
 745                         sb.append("<li style='image' value='" + COUNT_IMG_KEY + "'>"); //$NON-NLS-1$ //$NON-NLS-2$
 746                         sb.append(Messages.stackTraceMessage(itemCount, totalCount, frameFraction));
 747                         sb.append("</li>"); //$NON-NLS-1$
 748                         sb.append("<li style='image' value='" + SIBLINGS_IMG_KEY + "'>"); //$NON-NLS-1$ //$NON-NLS-2$
 749                         sb.append(Messages.siblingMessage(itemsInSiblings, parentFork.getBranchCount() - 1));
 750                         sb.append("</li>"); //$NON-NLS-1$
 751                         sb.append("</form>"); //$NON-NLS-1$
 752                         return sb.toString();
 753                 }
 754         };
 755 
 756         private final ColumnLabelProvider stackTraceLabelProvider = new ColumnLabelProvider() {
 757 
 758                 @Override
 759                 public String getText(Object element) {
 760                         IMCFrame frame = ((StacktraceFrame) element).getFrame();
 761                         FrameSeparator frameSeparator = frameSeparatorManager.getFrameSeparator();
 762                         return getText(frame, frameSeparator);
 763                 }
 764 
 765                 private String getText(IMCFrame frame, FrameSeparator frameSeparator) {
 766                         return StacktraceFormatToolkit.formatFrame(frame, frameSeparator, methodFormatter.showReturnValue(),
 767                                         methodFormatter.showReturnValuePackage(), methodFormatter.showClassName(),
 768                                         methodFormatter.showClassPackageName(), methodFormatter.showArguments(),
 769                                         methodFormatter.showArgumentsPackage());
 770                 }
 771 
 772                 @Override
 773                 public Image getImage(Object element) {
 774                         StacktraceFrame frame = (StacktraceFrame) element;
 775                         FlightRecorderUI plugin = FlightRecorderUI.getDefault();
 776                         boolean isFirstInBranch = isFirstInBranchWithSiblings(frame);
 777                         boolean firstInOpenFork = isFirstInBranch && isInOpenFork(frame);
 778                         if (firstInOpenFork || treeLayout && (!reducedTree || isFirstInBranch)) {
 779                                 return plugin.getImage(
 780                                                 threadRootAtTop ? ImageConstants.ICON_ARROW_CURVED_DOWN : ImageConstants.ICON_ARROW_CURVED_UP);
 781                         } else if (isFirstInBranchWithSiblings(frame)) {
 782                                 return plugin.getImage(
 783                                                 threadRootAtTop ? ImageConstants.ICON_ARROW_FORK3_DOWN : ImageConstants.ICON_ARROW_FORK3_UP);
 784                         } else if (isLastFrame(frame)) {
 785                                 return plugin.getImage(threadRootAtTop ? ImageConstants.ICON_ARROW_DOWN_END : ImageConstants.ICON_ARROW_UP_END);
 786                         } else {
 787                                 return plugin.getImage(threadRootAtTop ? ImageConstants.ICON_ARROW_DOWN : ImageConstants.ICON_ARROW_UP);
 788                         }
 789                 }
 790 
 791                 @Override
 792                 public Color getBackground(Object element) {
 793                         if (treeLayout) {
 794                                 return null;
 795                         } else {
 796                                 int parentCount = 0;
 797                                 Branch e = ((StacktraceFrame) element).getBranch();
 798                                 while (e != null) {
 799                                         e = e.getParentFork().getParentBranch();
 800                                         parentCount++;
 801                                 }
 802                                 return parentCount % 2 == 0 ? null : ALTERNATE_COLOR;
 803                         }
 804                 }
 805         };
 806 
 807         private static boolean isNavigationFrame(StacktraceFrame frame) {
 808                 return isFirstInBranchWithSiblings(frame) && !isInOpenFork(frame);
 809         }
 810 
 811         private static boolean isInOpenFork(StacktraceFrame frame) {
 812                 return frame.getBranch().getParentFork().getSelectedBranch() == null;
 813         }
 814 
 815         private static boolean isFirstInBranchWithSiblings(StacktraceFrame frame) {
 816                 return frame.getBranch().getFirstFrame() == frame && frame.getBranch().getParentFork().getBranchCount() > 1;
 817         }
 818         
 819         private static boolean isLastFrame(StacktraceFrame frame) {
 820                 return frame.getBranch().getLastFrame() == frame && frame.getBranch().getEndFork().getBranchCount() == 0;
 821         }
 822 
 823         /*
 824          * FIXME: 'backwards' argument was used for displaying trace groups built from thread roots with
 825          * the thread roots at the bottom. If we don't want to support that scenario then we can remove
 826          * this argument.
 827          */
 828         private static void addSelectedBranches(Fork fork, SimpleArray<StacktraceFrame> input, boolean backwards) {
 829                 Branch selectedBranch = fork.getSelectedBranch();
 830                 if (selectedBranch == null) {
 831                         Stream.of(fork.getFirstFrames()).forEach(input::add);
 832                 } else if (backwards) {
 833                         addSelectedBranches(selectedBranch.getEndFork(), input, backwards);
 834                         StacktraceFrame[] tail = selectedBranch.getTailFrames();
 835                         for (int i = tail.length; i > 0; i--) {
 836                                 input.add(tail[i - 1]);
 837                         }
 838                         input.add(selectedBranch.getFirstFrame());
 839                 } else {
 840                         input.add(selectedBranch.getFirstFrame());
 841                         input.addAll(selectedBranch.getTailFrames());
 842                         addSelectedBranches(selectedBranch.getEndFork(), input, backwards);
 843                 }
 844         }
 845 
 846         private static Branch getLastSelectedBranch(Fork fromFork) {
 847                 Branch lastSelectedBranch = null;
 848                 Branch branch = fromFork.getSelectedBranch();
 849                 while (branch != null) {
 850                         lastSelectedBranch = branch;
 851                         branch = branch.getEndFork().getSelectedBranch();
 852                 }
 853                 return lastSelectedBranch;
 854         }
 855 
 856         private static Fork getRootFork(Fork fork) {
 857                 while (fork.getParentBranch() != null) {
 858                         fork = fork.getParentBranch().getParentFork();
 859                 }
 860                 return fork;
 861         }
 862 
 863         private static class StacktraceTreeContentProvider extends AbstractStructuredContentProvider
 864                         implements ITreeContentProvider {
 865 
 866                 @Override
 867                 public StacktraceFrame[] getElements(Object inputElement) {
 868                         return ((Fork) inputElement).getFirstFrames();
 869                 }
 870 
 871                 @Override
 872                 public boolean hasChildren(Object element) {
 873                         StacktraceFrame frame = (StacktraceFrame) element;
 874                         return !isLastFrame(frame);
 875                 }
 876 
 877                 @Override
 878                 public StacktraceFrame[] getChildren(Object parentElement) {
 879                         StacktraceFrame frame = (StacktraceFrame) parentElement;
 880                         StacktraceFrame[] tailFrames = frame.getBranch().getTailFrames();
 881                         if (frame.getIndexInBranch() == tailFrames.length) {
 882                                 return frame.getBranch().getEndFork().getFirstFrames();
 883                         } else {
 884                                 return new StacktraceFrame[] {tailFrames[frame.getIndexInBranch()]};
 885                         }
 886                 }
 887 
 888                 @Override
 889                 public StacktraceFrame getParent(Object element) {
 890                         StacktraceFrame frame = (StacktraceFrame) element;
 891                         int parentIndexInBranch = frame.getIndexInBranch() - 1;
 892                         if (parentIndexInBranch > 0) {
 893                                 return frame.getBranch().getTailFrames()[parentIndexInBranch - 1];
 894                         } else if (parentIndexInBranch == 0) {
 895                                 return frame.getBranch().getFirstFrame();
 896                         } else {
 897                                 Branch parentBranch = frame.getBranch().getParentFork().getParentBranch();
 898                                 return parentBranch == null ? null : parentBranch.getLastFrame();
 899                         }
 900                 }
 901         };
 902 
 903         private static class StacktraceReducedTreeContentProvider extends AbstractStructuredContentProvider
 904                         implements ITreeContentProvider {
 905 
 906                 @Override
 907                 public StacktraceFrame[] getElements(Object inputElement) {
 908                         Fork rootFork = (Fork) inputElement;
 909                         if (rootFork.getBranchCount() == 1) {
 910                                 Branch branch = rootFork.getBranch(0);
 911                                 return Stream
 912                                                 .concat(Stream.concat(Stream.of(branch.getFirstFrame()), Stream.of(branch.getTailFrames())),
 913                                                                 Stream.of(branch.getEndFork().getFirstFrames()))
 914                                                 .toArray(StacktraceFrame[]::new);
 915                         } else {
 916                                 return rootFork.getFirstFrames();
 917                         }
 918                 }
 919 
 920                 @Override
 921                 public boolean hasChildren(Object element) {
 922                         StacktraceFrame frame = (StacktraceFrame) element;
 923                         return isFirstInBranchWithSiblings(frame) && frame.getBranch().hasTail();
 924                 }
 925 
 926                 @Override
 927                 public StacktraceFrame[] getChildren(Object parentElement) {
 928                         Stream<StacktraceFrame> children = Stream.empty();
 929                         StacktraceFrame frame = (StacktraceFrame) parentElement;
 930                         if (isFirstInBranchWithSiblings(frame)) {
 931                                 children = Stream.concat(Stream.of(frame.getBranch().getTailFrames()),
 932                                                 Stream.of(frame.getBranch().getEndFork().getFirstFrames()));
 933                         }
 934                         return children.toArray(StacktraceFrame[]::new);
 935                 }
 936 
 937                 @Override
 938                 public StacktraceFrame getParent(Object element) {
 939                         StacktraceFrame frame = (StacktraceFrame) element;
 940                         if (isFirstInBranchWithSiblings(frame) || frame.getBranch().getParentFork().getBranchCount() == 1) {
 941                                 Branch parentBranch = frame.getBranch().getParentFork().getParentBranch();
 942                                 return parentBranch == null ? null : parentBranch.getFirstFrame();
 943                         } else {
 944                                 return frame.getBranch().getFirstFrame();
 945                         }
 946                 }
 947         }
 948 
 949 }