/*
* 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;
import java.util.*;
import com.sun.cri.ci.*;
import com.sun.max.ins.debug.*;
import com.sun.max.ins.util.*;
import com.sun.max.program.*;
import com.sun.max.tele.*;
import com.sun.max.unsafe.*;
/**
* Holds the focus of user attention, expressed by user actions
* that select something.
*
* Implements a user model policy that changes
* focus as a side effect of some other user action.
*
* Other kinds of items could also have focus in the user model.
*/
public class InspectionFocus extends AbstractInspectionHolder {
private static final int TRACE_VALUE = 2;
private boolean settingCodeLocation = false;
public InspectionFocus(Inspection inspection) {
super(inspection);
}
private Set<ViewFocusListener> listeners = CiUtil.newIdentityHashSet();
public void addListener(ViewFocusListener listener) {
Trace.line(TRACE_VALUE, tracePrefix() + "adding listener: " + listener);
listeners.add(listener);
}
public void removeListener(ViewFocusListener listener) {
Trace.line(TRACE_VALUE, tracePrefix() + " removing listener: " + listener);
listeners.remove(listener);
}
public ViewFocusListener[] copyListeners() {
return listeners.toArray(new ViewFocusListener[listeners.size()]);
}
private MaxCodeLocation codeLocation = null;
private final Object codeLocationTracer = new Object() {
@Override
public String toString() {
return tracePrefix() + "Focus (Code Location): " + inspection().nameDisplay().longName(codeLocation);
}
};
public void clearAll() {
thread = null;
stackFrame = null;
memoryRegion = null;
breakpoint = null;
object = null;
}
/**
* The current location of user interest in the code being inspected (view state).
*/
public MaxCodeLocation codeLocation() {
return codeLocation;
}
/**
* Is there a currently selected code location.
*/
public boolean hasCodeLocation() {
return codeLocation != null;
}
/**
* Selects a code location that is of immediate visual interest to the user.
* This is view state only, not necessarily related to VM execution.
*
* @param codeLocation a location in code in the VM, possibly null which means no code location
* @param interactiveForNative should user be prompted interactively if in native code without
* any meta information?
*/
public void setCodeLocation(MaxCodeLocation codeLocation, boolean interactiveForNative) {
// Terminate any loops in focus setting.
if (!settingCodeLocation) {
settingCodeLocation = true;
try {
this.codeLocation = codeLocation;
Trace.line(TRACE_VALUE, codeLocationTracer);
for (ViewFocusListener listener : copyListeners()) {
listener.codeLocationFocusSet(codeLocation, interactiveForNative);
}
// User Model Policy: when setting code location, if it happens to match a stack frame of the current thread then focus on that frame.
if (thread != null && codeLocation != null && codeLocation.hasAddress()) {
for (MaxStackFrame maxStackFrame : thread.stack().frames(StackView.DEFAULT_MAX_FRAMES_DISPLAY)) {
if (codeLocation.isSameAs(maxStackFrame.codeLocation())) {
setStackFrame(stackFrame, false);
break;
}
}
}
} finally {
settingCodeLocation = false;
}
}
}
/**
* Selects a code location that is of immediate visual interest to the user.
* This is view state only, not necessarily related to VM execution.
*
* @param codeLocation a location in code in the VM
*/
public void setCodeLocation(MaxCodeLocation codeLocation) {
setCodeLocation(codeLocation, TeleVM.promptForNativeCodeView);
}
private MaxThread thread;
private final Object threadFocusTracer = new Object() {
@Override
public String toString() {
final StringBuilder name = new StringBuilder();
name.append(tracePrefix() + "Focus (Thread): ").append(inspection().nameDisplay().longName(thread)).append(" {").append(thread).append("}");
return name.toString();
}
};
/**
* @return the {@link MaxThread} that is the current user focus (view state); non-null once set.
*/
public MaxThread thread() {
return thread;
}
/**
* Is there a currently selected thread.
*/
public boolean hasThread() {
return thread != null;
}
/**
* Shifts the focus of the Inspection to a particular thread; notify interested views.
* Sets the code location to the current InstructionPointer of the newly focused thread.
* This is a view state change that can happen when there is no change to VM state.
*/
public void setThread(MaxThread thread) {
assert thread != null;
if (!thread.equals(this.thread)) {
final MaxThread oldThread = this.thread;
this.thread = thread;
Trace.line(TRACE_VALUE, threadFocusTracer);
for (ViewFocusListener listener : copyListeners()) {
listener.threadFocusSet(oldThread, thread);
}
// User Model Policy: when thread focus changes, restore an old frame focus if possible.
// If no record of a prior choice and thread is at a breakpoint, focus there.
// Else focus on the top frame.
final MaxStackFrame previousStackFrame = frameSelections.get(thread);
MaxStackFrame newStackFrame = null;
if (previousStackFrame != null) {
for (MaxStackFrame stackFrame : this.thread.stack().frames(StackView.DEFAULT_MAX_FRAMES_DISPLAY)) {
if (stackFrame.isSameFrame(previousStackFrame)) {
newStackFrame = stackFrame;
break;
}
}
}
if (newStackFrame != null) {
// Reset frame selection to one previously selected by the user
setStackFrame(newStackFrame, false);
} else {
// No prior frame selection
final MaxBreakpoint breakpoint = this.thread.breakpoint();
if (breakpoint != null && !breakpoint.isTransient()) {
// thread is at a breakpoint; focus on the breakpoint, which should also cause focus on code and frame
setBreakpoint(breakpoint);
} else {
// default is to focus on the top frame
setStackFrame(thread.stack().top(), false);
}
}
// User Model Policy: when thread focus changes, also set the memory region focus to the thread's stack memory.
setMemoryRegion(this.thread.stack().memoryRegion());
}
}
// Remember most recent frame selection per thread, and restore this selection (if possible) when thread focus changes.
private final Map<MaxThread, MaxStackFrame> frameSelections = new HashMap<MaxThread, MaxStackFrame>();
private MaxStackFrame stackFrame;
private final Object stackFrameFocusTracer = new Object() {
@Override
public String toString() {
return tracePrefix() + "Focus (StackFrame): " + stackFrame;
}
};
/**
* @return the {@link MaxStackFrame} that is current user focus (view state).
*/
public MaxStackFrame stackFrame() {
return stackFrame;
}
/**
* Is there a currently selected stack frame.
*/
public boolean hasStackFrame() {
return stackFrame != null;
}
/**
* Shifts the focus of the Inspection to a particular stack frame in a particular thread; notify interested views.
* Sets the current thread to be the thread of the frame.
* This is a view state change that can happen when there is no change to VM state.
* @param newStackFrame the frame on which to focus.
* @param interactiveForNative whether (should a side effect be to land in a native method) the user should be consulted if unknown.
*/
public void setStackFrame(MaxStackFrame newStackFrame, boolean interactiveForNative) {
if (this.stackFrame != newStackFrame) {
final MaxStackFrame oldStackFrame = this.stackFrame;
this.stackFrame = newStackFrame;
if (newStackFrame != null) {
final MaxThread newThread = newStackFrame.stack().thread();
// For consistency, be sure we're in the right thread context before doing anything with the stack frame.
setThread(newThread);
frameSelections.put(newThread, newStackFrame);
}
Trace.line(TRACE_VALUE, stackFrameFocusTracer);
for (ViewFocusListener listener : copyListeners()) {
listener.frameFocusChanged(oldStackFrame, newStackFrame);
}
}
if (newStackFrame != null) {
// User Model Policy: When a stack frame becomes the focus, then also focus on the code at the frame's instruction pointer
// or call return location.
// Update code location, even if stack frame is the "same", where same means at the same logical position in the stack as the old one.
// Note that the old and new stack frames are not identical, and in fact may have different instruction pointers.
final MaxCodeLocation newCodeLocation = newStackFrame.codeLocation();
setCodeLocation(newCodeLocation, interactiveForNative);
}
}
// never null, zero if none set
private Address address = Address.zero();
private final Object addressFocusTracer = new Object() {
@Override
public String toString() {
final StringBuilder name = new StringBuilder();
name.append(tracePrefix()).append("Focus (Address): ").append(address.toHexString());
return name.toString();
}
};
/**
* @return the {@link Address} that is the current user focus (view state), {@link Address#zero()} if none.
*/
public Address address() {
return address;
}
/**
* Is there a currently selected {@link Address}.
*/
public boolean hasAddress() {
return address.isNotZero();
}
/**
* Shifts the focus of the Inspection to a particular {@link Address}; notify interested views.
* This is a view state change that can happen when there is no change to the VM state.
*/
public void setAddress(Address address) {
InspectorError.check(address != null, "setAddress(null) should use zero Address instead");
if ((address.isZero() && hasAddress()) || (address.isNotZero() && !address.equals(this.address))) {
final Address oldAddress = this.address;
this.address = address;
Trace.line(TRACE_VALUE, addressFocusTracer);
for (ViewFocusListener listener : copyListeners()) {
listener.addressFocusChanged(oldAddress, address);
}
// User Model Policy: select the memory region that contains the newly selected address; clears if not known.
// If
setMemoryRegion(vm().state().findMemoryRegion(address));
}
}
private MaxMemoryRegion memoryRegion;
private final Object memoryRegionFocusTracer = new Object() {
@Override
public String toString() {
final StringBuilder name = new StringBuilder();
name.append(tracePrefix()).append("Focus (MemoryRegion): ").append(memoryRegion.regionName());
return name.toString();
}
};
/**
* @return the {@linkplain MaxMemoryRegion memory region} that is the current user focus (view state).
*/
public MaxMemoryRegion memoryRegion() {
return memoryRegion;
}
/**
* Is there a currently selected {@linkplain MaxMemoryRegion memory region}.
*/
public boolean hasMemoryRegion() {
return memoryRegion != null;
}
/**
* Shifts the focus of the Inspection to a particular {@linkplain MaxMemoryRegion memory region}; notify interested views.
* If the region is a stackRegion, then set the current thread to the thread owning the stack.
* This is a view state change that can happen when there is no change to the VM state.
*/
public void setMemoryRegion(MaxMemoryRegion memoryRegion) {
// TODO (mlvdv) see about setting to null if a thread is observed to have died, or mark the region as dead?
if ((memoryRegion == null && this.memoryRegion != null) || (memoryRegion != null && !memoryRegion.sameAs(this.memoryRegion))) {
final MaxMemoryRegion oldMemoryRegion = this.memoryRegion;
this.memoryRegion = memoryRegion;
Trace.line(TRACE_VALUE, memoryRegionFocusTracer);
for (ViewFocusListener listener : copyListeners()) {
listener.memoryRegionFocusChanged(oldMemoryRegion, memoryRegion);
}
// User Model Policy: When a stack memory region gets selected for focus, also set focus to the thread owning the stack.
// if (_memoryRegion != null) {
// final MaxThread thread = vm().threadContaining(_memoryRegion.start());
// if (thread != null) {
// setThread(thread);
// }
// }
}
}
private MaxBreakpoint breakpoint;
private final Object breakpointFocusTracer = new Object() {
@Override
public String toString() {
return tracePrefix() + "Focus(Breakpoint): " + (breakpoint == null ? "null" : inspection().nameDisplay().longName(breakpoint.codeLocation()));
}
};
/**
* Currently selected breakpoint, typically controlled by the {@link BreakpointsView}.
* May be null.
*/
public MaxBreakpoint breakpoint() {
return breakpoint;
}
/**
* Is there a currently selected breakpoint in the BreakpointsInspector.
*/
public boolean hasBreakpoint() {
return breakpoint != null;
}
/**
* Selects a breakpoint that is of immediate visual interest to the user, possibly null.
* This is view state only, not necessarily related to VM execution.
*/
public void setBreakpoint(MaxBreakpoint maxBreakpoint) {
if (breakpoint != maxBreakpoint) {
final MaxBreakpoint oldMaxBreakpoint = breakpoint;
breakpoint = maxBreakpoint;
Trace.line(TRACE_VALUE, breakpointFocusTracer);
for (ViewFocusListener listener : copyListeners()) {
listener.breakpointFocusSet(oldMaxBreakpoint, maxBreakpoint);
}
}
if (maxBreakpoint != null) {
MaxThread threadAtBreakpoint = null;
for (MaxThread thread : vm().state().threads()) {
if (thread.breakpoint() == maxBreakpoint) {
threadAtBreakpoint = thread;
break;
}
}
// User Model Policy: when a breakpoint acquires focus, also set focus to the
// thread, if any, that is stopped at the breakpoint. If no thread stopped,
// then just focus on the code location.
if (threadAtBreakpoint != null) {
setStackFrame(threadAtBreakpoint.stack().top(), false);
} else {
setCodeLocation(maxBreakpoint.codeLocation());
}
}
}
private MaxWatchpoint watchpoint;
private final Object watchpointFocusTracer = new Object() {
@Override
public String toString() {
return tracePrefix() + "Focus(Watchpoint): " + (watchpoint == null ? "null" : watchpoint.toString());
}
};
/**
* Currently selected watchpoint, typically controlled by the {@link WatchpointsView}.
* May be null.
*/
public MaxWatchpoint watchpoint() {
return watchpoint;
}
/**
* Is there a currently selected watchpoint in the WatchpointsInspector.
*/
public boolean hasWatchpoint() {
return watchpoint != null;
}
/**
* Selects a watchpoint that is of immediate visual interest to the user, possibly null.
* This is view state only, not necessarily related to VM execution.
*/
public void setWatchpoint(MaxWatchpoint watchpoint) {
if (this.watchpoint != watchpoint) {
final MaxWatchpoint oldWatchpoint = this.watchpoint;
this.watchpoint = watchpoint;
Trace.line(TRACE_VALUE, watchpointFocusTracer);
for (ViewFocusListener listener : copyListeners()) {
listener.watchpointFocusSet(oldWatchpoint, watchpoint);
}
}
}
private MaxObject object;
private final Object objectFocusTracer = new Object() {
@Override
public String toString() {
return tracePrefix() + "Focus(Heap Object): " + (object == null ? "null" : object.toString());
}
};
/**
* Currently selected object in the VM heap; may be null.
*/
public MaxObject object() {
return object;
}
/**
* Whether there is a currently selected heap object.
*/
public boolean hasHeapObject() {
return object != null;
}
/**
* Shifts the focus of the Inspection to a particular heap object in the VM; notify interested views.
* This is a view state change that can happen when there is no change to VM state.
*/
public void setHeapObject(MaxObject object) {
if (this.object != object) {
final MaxObject oldTeleObject = this.object;
this.object = object;
Trace.line(TRACE_VALUE, objectFocusTracer);
for (ViewFocusListener listener : copyListeners()) {
listener.heapObjectFocusChanged(oldTeleObject, object);
}
}
}
private int markBitIndex;
private final Object markBitIndexFocusTracer = new Object() {
@Override
public String toString() {
return tracePrefix() + "Focus(Heap mark bit index): " + markBitIndex;
}
};
/**
* Currently selected bit index in the VM's heap mark bitmap, -1 if none.
*/
public int markBitIndex() {
return markBitIndex;
}
/**
* Whether there is a currently selected mark bit index.
*/
public boolean hasMarkBitIndex() {
return markBitIndex >= 0;
}
/**
* Shifts the focus of the Inspection to a particular bit in the heap's mark bitmap.
* This is a view state change that can happen when there is no change to VM state.
*/
public void setMarkBitIndex(int heapMarkBit) {
final int oldMarkBitIndex = this.markBitIndex;
this.markBitIndex = heapMarkBit;
Trace.line(TRACE_VALUE, markBitIndexFocusTracer);
for (ViewFocusListener listener : copyListeners()) {
listener.markBitIndexFocusChanged(oldMarkBitIndex, heapMarkBit);
}
}
private int cardTableIndex;
private final Object cardTableIndexFocusTracer = new Object() {
@Override
public String toString() {
return tracePrefix() + "Focus(Heap card table index): " + cardTableIndex;
}
};
/**
* Currently selected byte index in the VM's heap card table, -1 if none.
*/
public int cardTableIndex() {
return cardTableIndex;
}
/**
* Whether there is a currently selected card table byte index.
*/
public boolean hasCardTableIndex() {
return cardTableIndex >= 0;
}
/**
* Shifts the focus of the Inspection to a particular byte in the heap's card table.
* This is a view state change that can happen when there is no change to VM state.
*/
public void setCardTIndex(int cardTableByteIndex) {
final int oldCardTableByteIndex = this.markBitIndex;
this.markBitIndex = cardTableByteIndex;
Trace.line(TRACE_VALUE, cardTableIndexFocusTracer);
for (ViewFocusListener listener : copyListeners()) {
listener.cardTableIndexFocusChanged(oldCardTableByteIndex, cardTableByteIndex);
}
}
}