/* * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.max.ins.method; import static com.sun.max.ins.gui.AbstractView.MenuKind.*; import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.border.*; import com.sun.max.gui.*; import com.sun.max.ins.*; import com.sun.max.ins.InspectorNameDisplay.ReturnTypeSpecification; import com.sun.max.ins.gui.*; import com.sun.max.ins.util.*; import com.sun.max.ins.view.InspectionViews.ViewKind; import com.sun.max.program.*; import com.sun.max.tele.*; import com.sun.max.tele.object.*; import com.sun.max.vm.actor.member.*; /** * Visual view and debugger for a Java method and other routines in the VM, able to display one or more kinds of code * associated with the method: compiled code, bytecode, and source. */ public final class JavaMethodView extends MethodView<JavaMethodView> { private final int TRACE_VALUE = 1; private final MethodViewPreferences methodViewPreferences; private final MethodViewContainer container; /** * Shared check boxes to be used in all UI code view selection. */ private final JCheckBoxMenuItem[] codeViewCheckBoxes = new JCheckBoxMenuItem[MethodCodeKind.values().length]; private final TeleClassMethodActor teleClassMethodActor; /** * A particular compilation of the method, to which this view is permanently bound, and which distinguishes * this view uniquely from others that may be viewing the same method but bound to a different compilation. * Null when this view is not bound to any compilation, in which case this view is the unique (unbound) * view for the method. */ private MaxCompilation compilation; /** * The generation count for the code in the VM, the last time this classed accessed any information. */ private int vmCodeGeneration = -1; /** * The kinds of code views it is possible to create for this method. */ private Set<MethodCodeKind> enabledCodeKinds = EnumSet.noneOf(MethodCodeKind.class); /** * The code viewer kinds that should be added the next time the whole view's content is created or reconstructed. * This is established for initial view creation. If the view needs to reconstruct all its content, for * example if the underlying machine code is determined to have changed, then this is reset to the set of existing * views so that they will be restored correctly. */ private Set<MethodCodeKind> requestedCodeKinds = EnumSet.noneOf(MethodCodeKind.class); private final InspectorFrame frame; /** Map: MethodCodeKind -> CodeViewer * The viewer for the code kind, if it exists, i.e. if it is being displayed in the view. * * The map forces the corresponding view preference check boxes for the kind to agree: on if there is a viewer, off if there is not. */ private final Map<MethodCodeKind, CodeViewer> codeViewers = new EnumMap<MethodCodeKind, CodeViewer>(MethodCodeKind.class) { @Override public CodeViewer put(MethodCodeKind kind, CodeViewer value) { final CodeViewer old = super.put(kind, value); codeViewCheckBoxes[kind.ordinal()].setSelected(true); return old; } @Override public CodeViewer remove(Object key) { final MethodCodeKind kind = (MethodCodeKind) key; final CodeViewer old = super.remove(kind); codeViewCheckBoxes[kind.ordinal()].setSelected(false); return old; } }; /** * Used when two code viewers are visible; we don't yet support three. */ private JSplitPane splitPane; /** * A view for a Java Method associated with a specific compilation, and which association does not change * for the life of the view. * * @param inspection the {@link Inspection} of which this view is part * @param container the tabbed container for this view * @param compilation surrogate for the compilation in the VM * @param codeKind request for a particular code view to be displayed initially */ public JavaMethodView(Inspection inspection, MethodViewContainer container, MaxCompilation compilation, MethodCodeKind codeKind) { this(inspection, container, compilation, compilation.getTeleClassMethodActor(), codeKind); } /** * Creates a view for a Java Method without association to any compilation, and which can thus view only bytecodes or * source code. If a user, within the context of this view, requests a view of an associated compilation, then * another existing view associated with the specified compilation must be located or a new one created; in * either case, the resulting view replaces this one. * * @param inspection the {@link Inspection} of which this view is part * @param container the tabbed container for this view * @param teleClassMethodActor surrogate for the specified Java method in the VM * @param codeKind requested kind of code view: either source code or bytecodes */ public JavaMethodView(Inspection inspection, MethodViewContainer container, TeleClassMethodActor teleClassMethodActor, MethodCodeKind codeKind) { this(inspection, container, null, teleClassMethodActor, codeKind); assert codeKind != MethodCodeKind.MACHINE_CODE; } private JavaMethodView(Inspection inspection, MethodViewContainer container, MaxCompilation compilation, TeleClassMethodActor teleClassMethodActor, MethodCodeKind requestedCodeKind) { super(inspection, container); this.container = container; this.methodViewPreferences = MethodViewPreferences.globalPreferences(inspection); this.teleClassMethodActor = teleClassMethodActor; this.compilation = compilation; this.vmCodeGeneration = compilation != null ? compilation.codeVersion() : 0; // Determine which code viewers it is possible to present for this method. // This doesn't change. if (compilation != null || teleClassMethodActor.compilationCount() > 0) { enabledCodeKinds.add(MethodCodeKind.MACHINE_CODE); } if (teleClassMethodActor != null && teleClassMethodActor.hasCodeAttribute()) { enabledCodeKinds.add(MethodCodeKind.BYTECODES); } if (false) { enabledCodeKinds.add(MethodCodeKind.JAVA_SOURCE); } // Determine which code viewers to present at creation, starting with the originating request if (requestedCodeKind != null && enabledCodeKinds.contains(requestedCodeKind)) { requestedCodeKinds.add(requestedCodeKind); } // Now check for other requested views based on preference settings. for (MethodCodeKind codeKind : MethodCodeKind.values()) { if (enabledCodeKinds.contains(codeKind) && methodViewPreferences.isVisible(codeKind)) { requestedCodeKinds.add(codeKind); } } // If all else fails, revert to lowest level if (requestedCodeKinds.isEmpty()) { requestedCodeKinds.add(MethodCodeKind.MACHINE_CODE); } // Create shared check boxes that will track and help control what views are visible. // Invariant: checkbox selected iff the code kind is in {@link #codeViewers}. for (final MethodCodeKind codeKind : MethodCodeKind.values()) { // The check box settings can either be changed by user action on the check box // itself, or by other actions that add/remove code viewers. There are no code // viewers present at this point in the construction of the view. final boolean currentValue = false; final String toolTipText = "Display this kind of source for the Java method?"; final JCheckBoxMenuItem checkBox = new InspectorCheckBox(inspection(), codeKind.toString(), toolTipText, currentValue); checkBox.setEnabled(enabledCodeKinds.contains(codeKind)); checkBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent itemEvent) { // Catch check box events where the user wants to open/close a code viewer if (checkBox.isSelected()) { if (!codeViewers.containsKey(codeKind)) { addCodeViewer(codeKind); } } else if (codeViewers.containsKey(codeKind)) { closeCodeViewer(codeViewers.get(codeKind)); } } }); codeViewCheckBoxes[codeKind.ordinal()] = checkBox; } frame = createTabFrame(container); createMenus(); } private void createMenus() throws InspectorError { // The default menu operates from the perspective of the parent container. frame.makeMenu(DEFAULT_MENU).add(defaultMenuItems(DEFAULT_MENU, container)); final InspectorMenu editMenu = frame.makeMenu(EDIT_MENU); final InspectorMenu memoryMenu = frame.makeMenu(MEMORY_MENU); final InspectorMenu objectMenu = frame.makeMenu(OBJECT_MENU); final InspectorMenu codeMenu = frame.makeMenu(CODE_MENU); final InspectorMenu debugMenu = frame.makeMenu(DEBUG_MENU); frame.makeMenu(VIEW_MENU).add(defaultMenuItems(VIEW_MENU)); final InspectorMenu breakOnEntryMenu = new InspectorMenu("Break at this method entry"); final InspectorMenu breakAtLabelsMenu = new InspectorMenu("Break at this method labels"); if (compilation != null) { final InspectorAction copyAction = actions().copyCompiledCodeToClipboard(compilation, null); copyAction.setEnabled(true); memoryMenu.add(views().memory().makeViewAction(compilation.memoryRegion(), compilation.entityName(), "View memory for machine code")); editMenu.add(copyAction); objectMenu.add(views().objects().makeViewAction(compilation.representation(), "Compiled method: " + compilation.classActorForObjectType().simpleName())); } memoryMenu.add(defaultMenuItems(MEMORY_MENU)); memoryMenu.add(views().activateSingletonViewAction(ViewKind.ALLOCATIONS)); if (teleClassMethodActor != null) { final InspectorAction copyAction = actions().copyBytecodeToClipboard(teleClassMethodActor, null); copyAction.setEnabled(true); editMenu.add(copyAction); objectMenu.add(views().objects().makeViewAction(teleClassMethodActor, "Method: " + teleClassMethodActor.classActorForObjectType().simpleName())); final TeleClassActor teleClassActor = teleClassMethodActor.getTeleHolder(); objectMenu.add(views().objects().makeViewAction(teleClassActor, "Holder: " + teleClassActor.classActorForObjectType().simpleName())); objectMenu.add(actions().viewSubstitutionSourceClassActorAction(teleClassMethodActor)); objectMenu.add(actions().inspectMethodCompilationsMenu(teleClassMethodActor, "Method compilations:")); objectMenu.add(defaultMenuItems(OBJECT_MENU)); } for (final MethodCodeKind codeKind : MethodCodeKind.values()) { codeMenu.add(codeViewCheckBoxes[codeKind.ordinal()]); } if (teleClassMethodActor != null) { codeMenu.add(actions().viewMethodCompilationsMenu(teleClassMethodActor, "View method's compilations")); } codeMenu.add(defaultMenuItems(CODE_MENU)); if (compilation != null) { breakOnEntryMenu.add(actions().setMachineCodeBreakpointAtEntry(compilation, "Machine code")); } if (teleClassMethodActor != null) { breakOnEntryMenu.add(actions().setBytecodeBreakpointAtMethodEntry(teleClassMethodActor, "Bytecodes")); } debugMenu.add(breakOnEntryMenu); if (compilation != null) { breakAtLabelsMenu.add(actions().setMachineCodeLabelBreakpoints(compilation, "Add machine code breakpoints")); breakAtLabelsMenu.add(actions().removeMachineCodeLabelBreakpoints(compilation, "Remove machine code breakpoints")); } debugMenu.add(breakAtLabelsMenu); if (teleClassMethodActor != null) { debugMenu.add(actions().debugInvokeMethod(teleClassMethodActor, "Invoke this method")); } debugMenu.addSeparator(); debugMenu.add(actions().genericBreakpointMenuItems()); debugMenu.add(views().activateSingletonViewAction(ViewKind.BREAKPOINTS)); if (vm().watchpointManager() != null) { debugMenu.add(actions().genericWatchpointMenuItems()); debugMenu.add(views().activateSingletonViewAction(ViewKind.WATCHPOINTS)); } } @Override public void createViewContent() { if (frame != null) { // If the frame is already available, then this we are reconstructing the view. frame.clearMenus(); createMenus(); closeAllViews(); } // Create requested code viewers for (MethodCodeKind requestedKind : requestedCodeKinds) { if (enabledCodeKinds.contains(requestedKind)) { addCodeViewer(requestedKind); } } // If can't meet request, try anything. if (codeViewers.isEmpty()) { for (MethodCodeKind kind : MethodCodeKind.values()) { if (enabledCodeKinds.contains(kind)) { addCodeViewer(kind); } } } if (codeViewers.isEmpty()) { dispose(); } } @Override protected void refreshState(boolean force) { if (compilation != null && !compilation.isCodeLive()) { // We had a compilation, but its code has been evicted from the code cache since we last updated. // Try to reconstruct the view without a machine code viewer. if (teleClassMethodActor != null && teleClassMethodActor.hasCodeAttribute()) { compilation = null; enabledCodeKinds.remove(MethodCodeKind.MACHINE_CODE); codeViewCheckBoxes[MethodCodeKind.MACHINE_CODE.ordinal()].setEnabled(false); requestedCodeKinds.remove(MethodCodeKind.MACHINE_CODE); reconstructView(); Trace.line(TRACE_VALUE, tracePrefix() + "Machine code view removed after code eviction for method " + getToolTip()); } else { Object[] message = new Object[2]; message[0] = "Method view closed on \"" + getToolTip() + "\"."; message[1] = "The compilation was evicted from the code cache"; gui().warningMessage(message); close(); Trace.line(TRACE_VALUE, tracePrefix() + "Method view removed after code eviction for method " + getToolTip() + "no bytecode available"); } } else if (compilation != null && compilation.codeVersion() > vmCodeGeneration) { // The compiled code has been changed in some way; reconstruct the view with the same viewers. requestedCodeKinds.clear(); requestedCodeKinds.addAll(codeViewers.keySet()); reconstructView(); vmCodeGeneration = compilation.codeVersion(); Trace.line(TRACE_VALUE, tracePrefix() + "Updated after code change in method " + getToolTip()); } else if (getJComponent().isShowing() || force) { for (CodeViewer codeViewer : codeViewers.values()) { codeViewer.refresh(force); } } } @Override public String getTextForTitle() { if (teleClassMethodActor == null || teleClassMethodActor.classMethodActor() == null) { return compilation.entityName(); } final ClassMethodActor classMethodActor = teleClassMethodActor.classMethodActor(); final StringBuilder sb = new StringBuilder(50); sb.append(classMethodActor.holder().simpleName()); sb.append("."); sb.append(classMethodActor.name.toString()); sb.append(inspection().nameDisplay().shortMethodCompilationID(compilation)); sb.append(inspection().nameDisplay().methodSubstitutionShortAnnotation(teleClassMethodActor)); return sb.toString(); //return classMethodActor.holder().simpleName() + "." + classMethodActor.name().toString() + inspection().nameDisplay().methodCompilationID(_teleTargetMethod); } /** * Global code selection has been set, though possibly unchanged; update all viewers. */ @Override public void codeLocationFocusSet(MaxCodeLocation codeLocation, boolean interactiveForNative) { boolean haveSelection = false; for (CodeViewer codeViewer : codeViewers.values()) { if (codeViewer.updateCodeFocus(codeLocation)) { haveSelection = true; } } if (haveSelection && !isVisible()) { highlight(); } } /** * Global thread selection has been set, though possibly unchanged; update all viewers. */ @Override public void threadFocusSet(MaxThread oldThread, MaxThread thread) { for (CodeViewer codeViewer : codeViewers.values()) { codeViewer.updateThreadFocus(thread); } } @Override public InspectorAction getViewOptionsAction() { return new InspectorAction(inspection(), "View Options") { @Override protected void procedure() { showViewOptionsDialog(inspection()); } }; } @Override public MaxCompilation compilation() { return compilation; } @Override public TeleClassMethodActor teleClassMethodActor() { return teleClassMethodActor; } @Override public String getToolTip() { String result = ""; if (compilation != null) { result = inspection().nameDisplay().longName(compilation); } else if (teleClassMethodActor != null) { result = inspection().nameDisplay().shortName(teleClassMethodActor, ReturnTypeSpecification.AS_PREFIX); if (teleClassMethodActor.isSubstituted()) { result = result + inspection().nameDisplay().methodSubstitutionLongAnnotation(teleClassMethodActor); } } return result; } // simplified awkward model for now: there can only be 1 or 2 code viewers @Override public void closeCodeViewer(CodeViewer viewer) { if (codeViewers.size() == 1) { // only code view; nuke the whole Method viewer close(); } else if (codeViewers.size() == 2) { final Component deleteComponent = viewer; Component keepComponent = splitPane.getLeftComponent(); if (keepComponent == deleteComponent) { keepComponent = splitPane.getRightComponent(); } Container contentPane = getContentPane(); contentPane.remove(splitPane); contentPane.add(keepComponent); codeViewers.remove(viewer.codeKind()); validate(); } } private void closeAllViews() { Container contentPane = getContentPane(); if (codeViewers.size() == 2) { contentPane.remove(splitPane); } else if (codeViewers.size() == 1) { contentPane.remove(firstViewer()); } codeViewers.clear(); } @Override public void print() { final String textForTitle = getTextForTitle(); if (codeViewers.size() == 1) { firstViewer().print(textForTitle); } else { for (CodeViewer codeViewer : codeViewers.values()) { if (gui().yesNoDialog("Print " + codeViewer.codeViewerKindName() + "?")) { codeViewer.print(textForTitle); } } } } /** * Adds a specified code view to this view, if possible. */ public void viewCodeKind(MethodCodeKind kind) { if (!codeViewers.containsKey(kind) && enabledCodeKinds.contains(kind)) { addCodeViewer(kind); } } private CodeViewer codeViewerFactory(MethodCodeKind codeKind) { switch (codeKind) { case MACHINE_CODE: if (compilation == null && teleClassMethodActor != null) { try { compilation = vm().machineCode().latestCompilation(teleClassMethodActor); } catch (MaxVMBusyException e) { InspectorError.unexpected("Can't create machine code view"); } } return compilation == null ? null : new JTableMachineCodeViewer(inspection(), this, compilation); case BYTECODES: return new JTableBytecodeViewer(inspection(), this, teleClassMethodActor, compilation); case JAVA_SOURCE: InspectorError.unimplemented(); return null; default: InspectorError.unexpected("Unexpected MethodCodeKind"); } return null; } private final class ViewOptionsPanel extends InspectorPanel { public ViewOptionsPanel(Inspection inspection) { super(inspection, new BorderLayout()); final InspectorCheckBox[] checkBoxes = new InspectorCheckBox[MethodCodeKind.values().length]; final ItemListener itemListener = new ItemListener() { public void itemStateChanged(ItemEvent e) { final Object source = e.getItemSelectable(); for (MethodCodeKind codeKind : MethodCodeKind.values()) { final InspectorCheckBox checkBox = checkBoxes[codeKind.ordinal()]; if (source == checkBox) { if (checkBox.isSelected()) { if (!codeViewers.containsKey(codeKind)) { addCodeViewer(codeKind); } } else if (codeViewers.containsKey(codeKind)) { closeCodeViewer(codeViewers.get(codeKind)); } break; } } } }; final JPanel content = new InspectorPanel(inspection()); content.add(new TextLabel(inspection(), "View: ")); final String toolTipText = "Should new Method views initially display this code, when available?"; for (MethodCodeKind codeKind : MethodCodeKind.values()) { final boolean currentValue = codeViewers.containsKey(codeKind); final InspectorCheckBox checkBox = new InspectorCheckBox(inspection(), codeKind.toString(), toolTipText, currentValue); checkBox.addItemListener(itemListener); checkBoxes[codeKind.ordinal()] = checkBox; content.add(checkBox); } add(content, BorderLayout.WEST); } } private void addCodeViewer(MethodCodeKind kind) { if (kind != null && !codeViewers.containsKey(kind)) { final CodeViewer newViewer = codeViewerFactory(kind); if (newViewer != null) { // this is awkward, doesn't work if add a view that we already have assert !codeViewers.containsKey(kind); // final InspectorFrame newInspectorFrame = newInspector; // final Component newComponent = (Component) newInspectorFrame; if (codeViewers.size() == 0) { getContentPane().add(newViewer); validate(); } else if (codeViewers.size() == 1) { final CodeViewer oldViewer = firstViewer(); // final Component oldComponent = (Component) oldInspector.frame(); getContentPane().remove(oldViewer); if (oldViewer.codeKind().ordinal() < newViewer.codeKind().ordinal()) { splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, oldViewer, newViewer); } else { splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, newViewer, oldViewer); } splitPane.setOneTouchExpandable(true); splitPane.setResizeWeight(0.5); getContentPane().add(splitPane); validate(); } codeViewers.put(kind, newViewer); } } } private CodeViewer firstViewer() { final Iterator<CodeViewer> iterator = codeViewers.values().iterator(); if (iterator.hasNext()) { return iterator.next(); } return null; } private void showViewOptionsDialog(Inspection inspection) { final JPanel prefPanel = new InspectorPanel(inspection, new SpringLayout()); final Border border = BorderFactory.createLineBorder(Color.black); final JPanel thisLabelPanel = new InspectorPanel(inspection, new BorderLayout()); thisLabelPanel.setBorder(border); thisLabelPanel.add(new TextLabel(inspection, "This Method"), BorderLayout.WEST); prefPanel.add(thisLabelPanel); final JPanel thisOptionsPanel = new ViewOptionsPanel(inspection); thisOptionsPanel.setBorder(border); prefPanel.add(thisOptionsPanel); final JPanel prefslLabelPanel = new InspectorPanel(inspection, new BorderLayout()); prefslLabelPanel.setBorder(border); prefslLabelPanel.add(new TextLabel(inspection, "Preferences"), BorderLayout.WEST); prefPanel.add(prefslLabelPanel); final JPanel prefsOptionsPanel = MethodViewPreferences.globalPreferences(inspection).getPanel(); prefsOptionsPanel.setBorder(border); prefPanel.add(prefsOptionsPanel); SpringUtilities.makeCompactGrid(prefPanel, 2); new SimpleDialog(inspection, prefPanel, "Java Method View Options", true); } }