/* * 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.object; import static com.sun.max.tele.MaxProcessState.*; import java.awt.*; import java.util.*; import java.util.List; import javax.swing.*; import com.sun.max.ins.*; import com.sun.max.ins.gui.*; import com.sun.max.ins.gui.TableColumnVisibilityPreferences.TableColumnViewPreferenceListener; 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.unsafe.*; /** * A view that displays the content of a low level heap object in the VM. */ public abstract class ObjectView<View_Type extends ObjectView> extends AbstractView<View_Type> { private static final int MAX_TITLE_STRING_LENGTH = 40; private static final int TRACE_VALUE = 1; private static final ViewKind VIEW_KIND = ViewKind.OBJECT; private static ObjectViewManager viewManager; public static ObjectViewManager makeViewManager(Inspection inspection) { if (viewManager == null) { viewManager = new ObjectViewManager(inspection); } return viewManager; } private MaxObject object; /** The origin is an actual location in memory of the VM; * keep a copy for comparison, since it might change via GC. */ private Pointer currentObjectOrigin; /** * @return The actual location in VM memory where * the object resides at present; this may change via GC. */ Pointer currentOrigin() { return currentObjectOrigin; } private Color backgroundColor = null; /** * Cache of the most recent update to a textual description of the object; needed in situations where the frame * becomes unavailable. This cache does not include the object state modifier or region information. */ private String objectDescription = ""; private InspectorTable objectHeaderTable; protected final ObjectViewPreferences instanceViewPreferences; private Rectangle originalFrameGeometry = null; private InspectorMenu objectMenu; /** Actions that need to be refreshed. */ private List<InspectorAction> actions = new ArrayList<InspectorAction>(); private InspectorAction visitStaticTupleAction = null; protected ObjectView(final Inspection inspection, final MaxObject object) { super(inspection, VIEW_KIND, null); this.object = object; this.currentObjectOrigin = object().origin(); instanceViewPreferences = new ObjectViewPreferences(ObjectViewPreferences.globalPreferences(inspection)) { @Override protected void setShowHeader(boolean showHeader) { super.setShowHeader(showHeader); reconstructView(); } @Override protected void setElideNullArrayElements(boolean hideNullArrayElements) { super.setElideNullArrayElements(hideNullArrayElements); reconstructView(); } }; instanceViewPreferences.addListener(new TableColumnViewPreferenceListener() { public void tableColumnViewPreferencesChanged() { reconstructView(); } }); Trace.line(TRACE_VALUE, tracePrefix() + " creating for " + getTextForTitle()); } @Override public InspectorFrame createFrame(boolean addMenuBar) { final InspectorFrame frame = super.createFrame(addMenuBar); gui().setLocationRelativeToMouse(this, preference().geometry().newFrameDiagonalOffset()); originalFrameGeometry = getGeometry(); return frame; } @Override protected void createViewContent() { final JPanel panel = new InspectorPanel(inspection(), new BorderLayout()); if (instanceViewPreferences.showHeader()) { objectHeaderTable = new ObjectHeaderTable(inspection(), this); objectHeaderTable.setBorder(preference().style().defaultPaneBottomBorder()); // Will add without column headers panel.add(objectHeaderTable, BorderLayout.NORTH); } setContentPane(panel); // Populate menu bar final InspectorMenu defaultMenu = makeMenu(MenuKind.DEFAULT_MENU); defaultMenu.add(defaultMenuItems(MenuKind.DEFAULT_MENU)); defaultMenu.addSeparator(); defaultMenu.add(views().deactivateOtherViewsAction(ViewKind.OBJECT, this)); defaultMenu.add(views().deactivateAllViewsAction(ViewKind.OBJECT)); final InspectorMenu memoryMenu = makeMenu(MenuKind.MEMORY_MENU); memoryMenu.add(views().memory().makeViewAction(object, "View this object's memory")); if (vm().heap().providesHeapRegionInfo()) { // TODO: Need to revisit this to better integrate with the Views framework, e.g., have something like: // views().heapRegionInfo().makeViewAction(...). This requires adding a factory and other boiler plate. InspectorAction action = HeapRegionInfoView.viewManager(inspection()).makeViewAction(object, "View this object's heap region info"); memoryMenu.add(action); } if (vm().watchpointManager() != null) { memoryMenu.add(actions().setObjectWatchpoint(object, "Watch this object's memory")); } memoryMenu.add(actions().copyObjectOrigin(object, "Copy this object's origin to clipboard")); memoryMenu.add(actions().copyObjectDescription(object, "Copy this object's origin + description to clipboard")); memoryMenu.add(defaultMenuItems(MenuKind.MEMORY_MENU)); memoryMenu.add(views().activateSingletonViewAction(ViewKind.ALLOCATIONS)); // Ensure that the object menu appears in the right position, but defer its creation // to subclasses, so that more view-specific items can be prepended to the standard ones. objectMenu = makeMenu(MenuKind.OBJECT_MENU); visitStaticTupleAction = actions().viewStaticTupleForObject(object); actions.add(visitStaticTupleAction); objectMenu.add(visitStaticTupleAction); if (vm().heap().hasForwarders()) { final InspectorAction visitForwardedToAction = new VisitForwardedToAction(inspection()); objectMenu.add(visitForwardedToAction); actions.add(visitForwardedToAction); final InspectorAction visitForwardedFromAction = new VisitForwardedFromAction(inspection()); objectMenu.add(visitForwardedFromAction); actions.add(visitForwardedFromAction); } final InspectorAction visitOverwritingObjectAction = new VisitOverwritingObjectAction(inspection()); objectMenu.add(visitOverwritingObjectAction); actions.add(visitOverwritingObjectAction); makeMenu(MenuKind.CODE_MENU); if (object.getTeleClassMethodActorForObject() != null || TeleTargetMethod.class.isAssignableFrom(object.getClass())) { makeMenu(MenuKind.DEBUG_MENU); } final InspectorMenuItems defaultViewMenuItems = defaultMenuItems(MenuKind.VIEW_MENU); final InspectorMenu viewMenu = makeMenu(MenuKind.VIEW_MENU); final List<InspectorAction> extraViewMenuActions = extraViewMenuActions(); if (!extraViewMenuActions.isEmpty()) { for (InspectorAction action : extraViewMenuActions) { viewMenu.add(action); } viewMenu.addSeparator(); } viewMenu.add(defaultViewMenuItems); refreshBackgroundColor(); } @Override public Rectangle defaultGeometry() { return originalFrameGeometry; } /** * {@inheritDoc} * <p> * Constructs a full description of the object, including a possible prefix, the * title of the object, and several possible suffixes. */ @Override public final String getTextForTitle() { final StringBuilder titleText = new StringBuilder(); refreshObjectDescription(); final ObjectStatus status = object.status(); if (!status.isLive()) { // Add a prefix describing status, but omit for the normal case (LIVE). titleText.append("(").append(status.label()); if (status.isDead()) { // If DEAD and was previously a quasi object, note this final ObjectStatus priorStatus = object.reference().priorStatus(); if (priorStatus != null && priorStatus.isQuasi()) { titleText.append("-").append(priorStatus.label()); } } titleText.append(") "); } titleText.append(objectDescription); if (isElided()) { titleText.append("(ELIDED)"); } final MaxMemoryRegion memoryRegion = vm().state().findMemoryRegion(currentObjectOrigin); titleText.append(" in ").append(memoryRegion == null ? "unknown region" : memoryRegion.regionName()); return titleText.toString(); } @Override public InspectorAction getViewOptionsAction() { return new InspectorAction(inspection(), "View Options") { @Override public void procedure() { final ObjectViewPreferences globalPreferences = ObjectViewPreferences.globalPreferences(inspection()); new TableColumnVisibilityPreferences.ColumnPreferencesDialog<ObjectColumnKind>(inspection(), "View Options", instanceViewPreferences, globalPreferences); } }; } @Override public void threadFocusSet(MaxThread oldThread, MaxThread thread) { // Object view displays are sensitive to the current thread selection. forceRefresh(); } @Override public void addressFocusChanged(Address oldAddress, Address newAddress) { forceRefresh(); } @Override public void viewGetsWindowFocus() { if (object != focus().object()) { focus().setHeapObject(object); } super.viewGetsWindowFocus(); } @Override public void viewLosesWindowFocus() { if (object == focus().object()) { focus().setHeapObject(null); } super.viewLosesWindowFocus(); } @Override public void viewClosing() { if (object == focus().object()) { focus().setHeapObject(null); } super.viewClosing(); } @Override public void watchpointSetChanged() { // TODO (mlvdv) patch for concurrency issue; not completely safe if (vm().state().processState() == STOPPED) { forceRefresh(); } } @Override public void vmProcessTerminated() { dispose(); } @Override protected void refreshState(boolean force) { if (object.reference().forwardedFrom().equals(currentOrigin())) { /* * The object has just been forwarded, and this view was previously showing what is now the old copy. By * policy, we want this view to stick on the old location, so find the "forwarder" object that represents * the old copy and reset this view to display that object. */ MaxObject forwarderObject = null; try { forwarderObject = vm().objects().findQuasiObjectAt(object.reference().forwardedFrom()); } catch (MaxVMBusyException e) { } if (forwarderObject != null) { final MaxObject oldObject = object; object = forwarderObject; viewManager.resetObjectToViewMapEntry(oldObject, forwarderObject, this); reconstructView(); } } else if (!object.origin().equals(currentObjectOrigin)) { // The object has just been relocated in memory; reset this view to display the new copy of the object. currentObjectOrigin = object.origin(); reconstructView(); } if (objectHeaderTable != null) { objectHeaderTable.refresh(force); } refreshActions(force); refreshBackgroundColor(); setTitle(); } /** * @return local surrogate for the VM object being inspected in this object view */ public MaxObject object() { return object; } /** * @return the view preferences currently in effect for this object view */ public ObjectViewPreferences viewPreferences() { return instanceViewPreferences; } /** * @return a color to use for background, especially cell backgrounds, in the object view; {@code null} if default color should be used. */ public Color viewBackgroundColor() { return backgroundColor; } /** * Constructs a string that identifies the object being viewed. */ private void refreshObjectDescription() { final ObjectStatus status = object.status(); if (status.isNotDead()) { // Revise the title of the object if we still can final StringBuilder sb = new StringBuilder(); if (status.isLive()) { sb.append("Object: "); } else { sb.append("Quasi object: "); } sb.append(object.origin().toHexString()); sb.append(inspection().nameDisplay().referenceLabelText(object, MAX_TITLE_STRING_LENGTH)); objectDescription = sb.toString(); } } private void refreshActions(boolean force) { for (InspectorAction action : actions) { action.refresh(force); } } /** * Changes the background color setting for this view, depending on object status. * * @return {@code true} iff color has changed */ private boolean refreshBackgroundColor() { final Color oldBackgroundColor = backgroundColor; final ObjectStatus status = object.status(); if (status.isLive()) { backgroundColor = null; } else if (status.isQuasi()) { backgroundColor = preference().style().quasiObjectBackgroundColor(); } else { // DEAD backgroundColor = preference().style().deadObjectBackgroundColor(); } setStateColor(backgroundColor); objectHeaderTable.setBackground(backgroundColor); return backgroundColor != oldBackgroundColor; } /** * Gets any view-specific actions that should appear on the {@link AbstractView.MenuKind#VIEW_MENU}. */ protected List<InspectorAction> extraViewMenuActions() { return Collections.emptyList(); } /** * @return whether the display mode is hiding some of the members of the object */ protected boolean isElided() { return false; } private final class VisitForwardedToAction extends InspectorAction { private MaxObject forwardedToObject; public VisitForwardedToAction(Inspection inspection) { super(inspection, "View object forwarded to"); refresh(true); } @Override protected void procedure() { focus().setHeapObject(forwardedToObject); } @Override public void refresh(boolean force) { super.refresh(force); forwardedToObject = null; final Address toAddress = object.reference().forwardedTo(); if (toAddress.isNotZero()) { try { forwardedToObject = vm().objects().findObjectAt(toAddress); } catch (MaxVMBusyException e) { forwardedToObject = null; } } setEnabled(forwardedToObject != null); } } private final class VisitForwardedFromAction extends InspectorAction { private MaxObject forwardedFromObject; public VisitForwardedFromAction(Inspection inspection) { super(inspection, "View object forwarded from"); refresh(true); } @Override protected void procedure() { focus().setHeapObject(forwardedFromObject); } @Override public void refresh(boolean force) { super.refresh(force); forwardedFromObject = null; final Address fromAddress = object.reference().forwardedFrom(); if (fromAddress.isNotZero()) { try { forwardedFromObject = vm().objects().findQuasiObjectAt(fromAddress); } catch (MaxVMBusyException e) { forwardedFromObject = null; } } setEnabled(forwardedFromObject != null); } } /** * Enabled when the object being viewed is dead, but there is another object now at the same origin. */ private final class VisitOverwritingObjectAction extends InspectorAction { private MaxObject forwardedFromObject; public VisitOverwritingObjectAction(Inspection inspection) { super(inspection, "View overwriting object"); refresh(true); } @Override protected void procedure() { try { final MaxObject overwritingObject = vm().objects().findAnyObjectAt(object.origin()); if (overwritingObject != null) { focus().setHeapObject(overwritingObject); } } catch (MaxVMBusyException e) { } } @Override public void refresh(boolean force) { super.refresh(force); try { setEnabled(object.status().isDead() && vm().objects().findAnyObjectAt(object.origin()) != null); return; } catch (MaxVMBusyException e1) { setEnabled(false); } } } }