package xtc.lang.blink;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
/**
* The Blink break point manager.
*
* @author Byeongcheol Lee
*/
public final class BreakPointManager {
/** The break point state. */
public enum BreakPointState {
ENABLED,
DISABLED
}
/** invalid breakpoint identifier. */
public static final int INVALID_BREAKPOINT_ID = -1;
/**
* The debugger.
*/
private final Blink dbg;
/** The Blink user break point list. */
private final HashMap<Integer, BlinkBreakPoint> breakpoints
= new HashMap<Integer, BlinkBreakPoint>();
/** The deferred Native break point list. */
private final HashMap<Integer, NativeBreakpoint> deferredNativeBreakpoints
= new HashMap<Integer, NativeBreakpoint>();
/** The next identification sequence number.*/
private int nextUserBreakPointID = 1;
/** The saved break point state.*/
private final HashSet<BlinkBreakPoint> frozenBreakpoints =
new HashSet<BlinkBreakPoint>();
/**
* @param dbg The debugger to work with.
*/
BreakPointManager(Blink dbg) {
this.dbg = dbg;
}
/**
* Allocate a new identification number.
*
* @return A new identification number.
*/
private synchronized int getNextUserBreakPointID() {
return nextUserBreakPointID++;
}
/**
* Add a native user break point.
*
* @param sourceFile The source file name.
* @param sourceLine The source line number.
*/
NativeSourceLineBreakPoint setNativeBreakpoint(String sourceFile, int sourceLine)
{
//create a native breakpoint.
NativeSourceLineBreakPoint nbp;
boolean deferred;
if (dbg.IsNativeDebuggerAttached()) {
dbg.ensureGDBContext();
int nbpID = dbg.ndb.createBreakpoint(sourceFile, sourceLine);
nbp = new NativeSourceLineBreakPoint(nbpID, sourceFile,
sourceLine);
deferred = false;
} else {
nbp = new NativeSourceLineBreakPoint(sourceFile, sourceLine);
deferred = true;
dbg.out("the break point is delayed until the native debugger attached.\n");
}
//register
int id = getNextUserBreakPointID();
breakpoints.put(id, nbp);
if (deferred) {
deferredNativeBreakpoints.put(id, nbp);
}
return nbp;
}
NativeSymbolBreakpoint setNativeBreakpoint(String symbol) {
NativeSymbolBreakpoint nbp;
boolean deferred;
if (dbg.IsNativeDebuggerAttached()) {
dbg.ensureGDBContext();
int nbpID = dbg.ndb.createBreakpoint(symbol);
nbp = new NativeSymbolBreakpoint(nbpID, symbol);
deferred = false;
} else {
nbp = new NativeSymbolBreakpoint(symbol);
deferred = true;
}
int id = getNextUserBreakPointID();
breakpoints.put(id, nbp);
if (deferred) {
deferredNativeBreakpoints.put(id, nbp);
}
return nbp;
}
/**
* Add a Java user break point.
*
* @param classFile The class name.
* @param sourceLine The source line number.
* @return The break point.
*/
JavaSourceLineBreakPoint setJavaBreakPoint(String classFile, int sourceLine) {
int id =getNextUserBreakPointID();
JavaSourceLineBreakPoint jbp = new JavaSourceLineBreakPoint(classFile, sourceLine);
breakpoints.put(id, jbp);
if (!jbp.enable(dbg)) {
dbg.out("the break point is delayed \n");
}
return jbp;
}
/**
* Add a Java user break point.
*
* @param cname The class name.
* @param mname The method name.
* @return The breakpoint.
*/
JavaEntryBreakpoint setJavaBreakPoint(String cname, String mname) {
int id = getNextUserBreakPointID();
JavaEntryBreakpoint jbp = new JavaEntryBreakpoint(cname, mname);
breakpoints.put(id, jbp);
if (!jbp.enable(dbg)) {
dbg.out("the break point is delayed\n");
}
return jbp;
}
/**
* Implement "delete [n]" command.
*
* @param id The identifier of break point or watch point.
*/
void clearBreakpoint(int id) {
if (!breakpoints.containsKey(id)) {
dbg.err("not valid break point id -" + id);
return;
}
//check deferred break point.
if (deferredNativeBreakpoints.containsKey(id)) {
deferredNativeBreakpoints.remove(id);
}
if (breakpoints.containsKey(id)) {
BlinkBreakPoint bp = breakpoints.get(id);
bp.disable(dbg);
breakpoints.remove(id);
}
}
/**
* Find a breakpoint using the native breakpoint identifier.
* @param nativeBreakPointID The identifier.
* @return The Breakpoint identifier.
*/
int findNativeBreakpoint(int nativeBreakPointID) {
for(final int bpid: breakpoints.keySet()) {
BlinkBreakPoint bp = breakpoints.get(bpid);
if (bp instanceof NativeBreakpoint) {
NativeBreakpoint nbp = (NativeBreakpoint)bp;
if (nbp.getNativeBreakPointID()== nativeBreakPointID) {
return bpid; //found
}
}
}
return INVALID_BREAKPOINT_ID; // not found
}
int findJavaBreakpoint(String cname, String mname, int lineNumber) {
for(final int bpid: breakpoints.keySet()) {
BlinkBreakPoint bp = breakpoints.get(bpid);
if (bp instanceof JavaSourceLineBreakPoint) {
JavaSourceLineBreakPoint jbp = (JavaSourceLineBreakPoint)bp;
if (jbp.getClassName().equals(cname)
&& jbp.getLineNumber() == lineNumber) {
return bpid;
}
} else if (bp instanceof JavaEntryBreakpoint) {
JavaEntryBreakpoint jbp = (JavaEntryBreakpoint)bp;
if (jbp.matches(cname, mname)) {
return bpid;
}
}
}
return INVALID_BREAKPOINT_ID; // not found
}
/**
* Implement "info break" command.
*/
void showUserBreakPointList() {
TreeSet<Integer> breakPointIDList = new TreeSet<Integer>();
breakPointIDList.addAll(breakpoints.keySet());
StringBuilder sb = new StringBuilder();
for (final Integer id : breakPointIDList) {
BlinkBreakPoint bp = breakpoints.get(id);
sb.append(id).append(" ");
sb.append(bp.toString()).append('\n');
}
dbg.out("%s", sb.toString());
}
/**
* @return true if there is blink deferred C break point.
*/
boolean hasDeferredNativeBreakpoint() {
return deferredNativeBreakpoints.size() > 0;
}
/**
* Handle deferred native break points when the shared library is loaded into
* the debuggee JVM.
*/
void handleDeferredNativeBreakPoint() {
assert dbg.IsNativeDebuggerAttached();
dbg.ensureGDBContext();
Set<Integer> resolved = new TreeSet<Integer>();
try {
for(final Integer id : deferredNativeBreakpoints.keySet()) {
NativeBreakpoint bp = deferredNativeBreakpoints.get(id);
if (bp instanceof NativeSourceLineBreakPoint) {
NativeSourceLineBreakPoint sbp = (NativeSourceLineBreakPoint)bp;
int nbpid = dbg.ndb.createBreakpoint(sbp.getSourceFileName(),
sbp.getSourceLineNumber());
sbp.setNativeBreakPointID(nbpid);
resolved.add(id);
}
}
} finally {
for(final Integer id : resolved) {
deferredNativeBreakpoints.remove(id);
}
}
}
/**
* Freeze all the active user break points for the expression evaluation.
*/
void freezeActiveBreakPoints() {
for(BlinkBreakPoint b: breakpoints.values()) {
BreakPointState s = b.getState();
if (s == BreakPointState.ENABLED) {
if (b.disable(dbg)) {
frozenBreakpoints.add(b);
} else {
dbg.err("could not freeze : " + b + "\n");
}
}
}
}
/**
* Restore the user break point state after the expression evaluation.
*/
void unfreezeAllBreakpoints() {
HashSet<NativeBreakpoint> gdbBreakPointToWake = new HashSet<NativeBreakpoint>();
HashSet<JavaBreakpoint> jdbBreakPointToWake = new HashSet<JavaBreakpoint>();
for(BlinkBreakPoint b: frozenBreakpoints) {
if (b instanceof NativeBreakpoint) {
gdbBreakPointToWake.add((NativeBreakpoint)b);
} else if (b instanceof JavaBreakpoint ) {
jdbBreakPointToWake.add((JavaBreakpoint)b);
}
}
for(JavaBreakpoint b: jdbBreakPointToWake) {
b.enable(dbg);
}
for(NativeBreakpoint b: gdbBreakPointToWake) {
b.enable(dbg);
}
frozenBreakpoints.clear();
}
/**
* A base class for the Blink break point.
*/
static abstract class BlinkBreakPoint {
/** enable the break point. */
abstract boolean enable(Blink dbg);
/** disable the break point. */
abstract boolean disable(Blink dbg);
/** query the break point state. */
abstract BreakPointState getState();
}
/**
* A base class for the native break point.
*/
static abstract class NativeBreakpoint extends BlinkBreakPoint {
static final int DEFERRED_BREAKPOINT_ID = -1;
/** The break point state. */
BreakPointState mdbState = BreakPointState.DISABLED;
/** The native break point identifier. */
private int nativeBreakpointID;
/** Constructor. */
protected NativeBreakpoint(int nbpID) {
nativeBreakpointID = nbpID;
}
/** Getter/setter method for the nativeBreakPointID. */
public int getNativeBreakPointID() {
assert nativeBreakpointID != DEFERRED_BREAKPOINT_ID;
return nativeBreakpointID;
}
public void setNativeBreakPointID(int nativeBreakPointID) {
assert this.nativeBreakpointID == DEFERRED_BREAKPOINT_ID;
this.nativeBreakpointID = nativeBreakPointID;
}
/** Getter method for break point state. */
public BreakPointState getState() {
return mdbState;
}
/**
* Enable the break point.
* @param dbg The debugger.
*/
boolean enable(Blink dbg) {
switch(mdbState) {
case ENABLED:
return true;
case DISABLED:
if(!dbg.ensureGDBContext()) {
return false;
}
// try to set the break point
dbg.ndb.enableBreakpoint(nativeBreakpointID);
mdbState = BreakPointState.ENABLED;
return true;
default:
assert false : "not reachable.";
return false;
}
}
/**
* Disable this break point in the gdb.
*
* @param dbg The Blink debugger.
*/
boolean disable(Blink dbg) {
switch(mdbState) {
case DISABLED:
return true;
case ENABLED:
if (!dbg.ensureGDBContext()) {
dbg.err("counld not disable break point: " + this + "\n");
return false;
}
dbg.ndb.disableBreakpoint(nativeBreakpointID);
mdbState = BreakPointState.DISABLED;
return true;
}
return false;
}
}
/**
* A Source level GDB break point.
*/
static class NativeSourceLineBreakPoint extends NativeBreakpoint {
/** The source file name.*/
final String sourceFileName;
/** The line number in the source file. */
final int sourceLineNumber;
/** Constructors. */
NativeSourceLineBreakPoint(String sname, int lineno) {
this(DEFERRED_BREAKPOINT_ID, sname, lineno);
}
NativeSourceLineBreakPoint(int nbpID, String sname, int lineno) {
super(nbpID);
sourceFileName = sname;
sourceLineNumber = lineno;
}
/** Getter method for sourceFileName. */
String getSourceFileName() {
return sourceFileName;
}
/** Getter method for sourceLineNumber.*/
int getSourceLineNumber() {
return sourceLineNumber;
}
/**
* Check the equality.
* @param o The compared object.
*/
public boolean equals(Object o) {
if (o instanceof NativeSourceLineBreakPoint == false)
return false;
NativeSourceLineBreakPoint nbp = (NativeSourceLineBreakPoint) o;
return (this == nbp)
|| (this.sourceFileName.equals(nbp.sourceFileName) && this.sourceLineNumber == nbp.sourceLineNumber);
}
/**
* @return The string representation.
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("native").append(" ");
sb.append(sourceFileName).append(":").append(sourceLineNumber);
return sb.toString();
}
}
static class NativeSymbolBreakpoint extends NativeBreakpoint {
private final String symbol;
NativeSymbolBreakpoint(String symbo) {
this(DEFERRED_BREAKPOINT_ID, symbo);
}
NativeSymbolBreakpoint(int nbpID, String symbo) {
super(nbpID);
this.symbol = symbo;
}
public final String getSymbo() {
return symbol;
}
}
/**
* A base class for the JDB break point.
*/
static abstract class JavaBreakpoint extends BlinkBreakPoint {
/** The break point state. */
BreakPointState mdbState = BreakPointState.DISABLED;
/** Getter method for the state. */
BreakPointState getState() {
return mdbState;
}
}
/**
* A source level JDB break point (e.g. Main:12 ).
*/
static class JavaSourceLineBreakPoint extends JavaBreakpoint {
/** The source file name. */
private final String className;
/** The line number in the source file. */
private final int lineNumber;
/**
* @param sname The source file name.
* @param lineno The line number.
*/
JavaSourceLineBreakPoint(String sname, int lineno) {
className = sname;
lineNumber = lineno;
}
/** Getter method for class name. */
String getClassName() {return className;}
/** Getter method for line number. */
int getLineNumber() {return lineNumber;}
/**
* Check the equality.
*
* @param o The compared object.
*/
public boolean equals(Object o) {
if (o instanceof NativeSourceLineBreakPoint == false)
return false;
JavaSourceLineBreakPoint nbp = (JavaSourceLineBreakPoint) o;
return (this == nbp)
|| (this.className.equals(nbp.className) && this.lineNumber == nbp.lineNumber);
}
/**
* @return The string representation.
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(" ").append("java").append(" ");
sb.append(className).append(":").append(lineNumber);
return sb.toString();
}
/**
* Request the jdb to set my break point
*
* @param dbg The debugger.
*/
boolean enable(Blink dbg) {
switch(mdbState) {
case DISABLED:
if(!dbg.ensureJDBContext()) {
return false;
}
// try to set the break point
dbg.jdb.setBreakPoint(className, lineNumber);
mdbState = BreakPointState.ENABLED;
return true;
case ENABLED:
return true;
}
return false;
}
/**
* Delete this break.
*
* @param dbg The Blink debugger.
*/
boolean disable(Blink dbg) {
switch(mdbState) {
case DISABLED:
return true;
case ENABLED:
if (!dbg.ensureJDBContext()) {
return false;
}
dbg.jdb.clearBreakPoint(className, lineNumber);
mdbState = BreakPointState.DISABLED;
return true;
}
return false;
}
}
/**
* A JDB method name break point. (e.g. Foo.bar() ).
*/
static class JavaEntryBreakpoint extends JavaBreakpoint {
private final String cname;
private final String mname;
/**
* @param classAndmethod The class name and method.
*/
public JavaEntryBreakpoint(final String cname, final String mname) {
super();
this.cname = cname;
this.mname = mname;
}
/** Getters. */
public String getCname() {return cname;}
public String getMname() {return mname;}
public boolean matches(String cname, String mname) {
return this.cname.equals(cname) && this.mname.equals(mname);
}
/**
* Enable the break point.
* @param dbg The debugger.
*/
boolean enable(Blink dbg) {
switch(mdbState) {
case DISABLED:
if(!dbg.ensureJDBContext()) {
return false;
}
// try to set the break point
dbg.jdb.setBreakPoint(cname + "." + mname);
mdbState = BreakPointState.ENABLED;
return true;
case ENABLED:
return true;
}
return false;
}
/**
* Disable the break point.
* @param dbg The Blink debugger.
*/
boolean disable(Blink dbg) {
switch(mdbState) {
case DISABLED:
return true;
case ENABLED:
if (!dbg.ensureJDBContext()) {
return false;
}
dbg.jdb.clearBreakPoint(cname + "." + "mname");
mdbState = BreakPointState.DISABLED;
return true;
}
return false;
}
}
}