[#1249] Add breakpoint capability to jOOQ Console - "break after" and
edition in breakpoint context.
This commit is contained in:
parent
7987886022
commit
a8cffce340
@ -42,7 +42,7 @@ import java.io.Serializable;
|
||||
* @author Christopher Deckers
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class BreakpointBeforeExecutionHit implements Serializable {
|
||||
public class BreakpointHit implements Serializable {
|
||||
|
||||
public static enum ExecutionType {
|
||||
STEP_THROUGH,
|
||||
@ -50,16 +50,24 @@ public class BreakpointBeforeExecutionHit implements Serializable {
|
||||
RUN,
|
||||
}
|
||||
|
||||
private boolean isBeforeExecution;
|
||||
private Integer breakpointID;
|
||||
private String sql;
|
||||
private long threadID;
|
||||
private String threadName;
|
||||
private StackTraceElement[] callerStackTraceElements;
|
||||
|
||||
public BreakpointBeforeExecutionHit(int breakpointID, String sql) {
|
||||
public BreakpointHit(int breakpointID, String sql, long threadID, String threadName, StackTraceElement[] callerStackTraceElements, boolean isBeforeExecution) {
|
||||
this.isBeforeExecution = isBeforeExecution;
|
||||
this.breakpointID = breakpointID;
|
||||
this.sql = sql;
|
||||
this.threadName = Thread.currentThread().getName();
|
||||
this.callerStackTraceElements = new Exception().getStackTrace();
|
||||
this.threadID = threadID;
|
||||
this.threadName = threadName;
|
||||
this.callerStackTraceElements = callerStackTraceElements;
|
||||
}
|
||||
|
||||
public boolean isBeforeExecution() {
|
||||
return isBeforeExecution;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,6 +81,10 @@ public class BreakpointBeforeExecutionHit implements Serializable {
|
||||
return sql;
|
||||
}
|
||||
|
||||
public long getThreadID() {
|
||||
return threadID;
|
||||
}
|
||||
|
||||
public String getThreadName() {
|
||||
return threadName;
|
||||
}
|
||||
@ -41,8 +41,8 @@ package org.jooq.debug;
|
||||
*/
|
||||
public interface BreakpointHitHandler {
|
||||
|
||||
public void processBreakpointBeforeExecutionHit(BreakpointBeforeExecutionHit breakpointHit);
|
||||
public void processBreakpointBeforeExecutionHit(BreakpointHit breakpointHit);
|
||||
|
||||
public void processBreakpointAfterExecutionHit(BreakpointAfterExecutionHit breakpointHit);
|
||||
public void processBreakpointAfterExecutionHit(BreakpointHit breakpointHit);
|
||||
|
||||
}
|
||||
|
||||
@ -41,11 +41,12 @@ import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.jooq.ExecuteContext;
|
||||
import org.jooq.ExecuteType;
|
||||
import org.jooq.debug.BreakpointBeforeExecutionHit.ExecutionType;
|
||||
import org.jooq.debug.BreakpointHit.ExecutionType;
|
||||
import org.jooq.impl.DefaultExecuteListener;
|
||||
import org.jooq.impl.Factory;
|
||||
|
||||
@ -116,12 +117,12 @@ public class DebugListener extends DefaultExecuteListener {
|
||||
private String matchingSQL;
|
||||
private String effectiveSQL;
|
||||
private Breakpoint matchingBreakpoint;
|
||||
private BreakpointHitHandler matchingBreakpointHitHandler;
|
||||
private Debugger matchingDebugger = null;
|
||||
|
||||
@Override
|
||||
public void executeStart(ExecuteContext ctx) {
|
||||
BreakpointHitHandler breakpointHitHandler = null;
|
||||
List<Debugger> debuggerList = DebuggerRegistry.getDebuggerList();
|
||||
boolean hasBreakpointHitHandler = false;
|
||||
if(!debuggerList.isEmpty()) {
|
||||
StatementInfo statementInfo = null;
|
||||
bp: for(Debugger debugger: debuggerList) {
|
||||
@ -151,9 +152,10 @@ public class DebugListener extends DefaultExecuteListener {
|
||||
statementInfo = new StatementInfo(sqlQueryType, sql, parameterDescription);
|
||||
}
|
||||
if(breakpoint.matches(statementInfo, true)) {
|
||||
matchingDebugger = debugger;
|
||||
matchingBreakpoint = breakpoint;
|
||||
if(breakpoint.isBreaking()) {
|
||||
breakpointHitHandler = debugger.getBreakpointHitHandler();
|
||||
hasBreakpointHitHandler = debugger.getBreakpointHitHandler() != null;
|
||||
}
|
||||
break bp;
|
||||
}
|
||||
@ -171,12 +173,12 @@ public class DebugListener extends DefaultExecuteListener {
|
||||
executeSQL(ctx, sql);
|
||||
long subEndExecutionTime = System.currentTimeMillis();
|
||||
// Log result of pre-processing.
|
||||
for(Debugger listener: debuggerList) {
|
||||
LoggingListener loggingListener = listener.getLoggingListener();
|
||||
for(Debugger debugger: debuggerList) {
|
||||
LoggingListener loggingListener = debugger.getLoggingListener();
|
||||
if(loggingListener != null) {
|
||||
SqlQueryType sqlQueryType = SqlQueryType.detectType(sql);
|
||||
QueryLoggingData queryLoggingData = new QueryLoggingData(sqlQueryType, new String[] {sql}, null, null, null, subEndExecutionTime - subStartExecutionTime);
|
||||
StatementMatcher[] loggingStatementMatchers = listener.getLoggingStatementMatchers();
|
||||
StatementMatcher[] loggingStatementMatchers = debugger.getLoggingStatementMatchers();
|
||||
if(loggingStatementMatchers == null) {
|
||||
loggingListener.logQueries(queryLoggingData);
|
||||
} else for(StatementMatcher statementMatcher: loggingStatementMatchers) {
|
||||
@ -201,16 +203,21 @@ public class DebugListener extends DefaultExecuteListener {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
ExecutionType executionType = BreakpointBeforeExecutionHit.ExecutionType.RUN;
|
||||
if(breakpointHitHandler != null) {
|
||||
ExecutionType executionType = BreakpointHit.ExecutionType.RUN;
|
||||
if(hasBreakpointHitHandler) {
|
||||
effectiveSQL = mainSQL != null? mainSQL: matchingSQL;
|
||||
// TODO: find a way for the handler to replace the statement (not just step over).
|
||||
BreakpointBeforeExecutionHit breakpointBeforeExecutionHit = new BreakpointBeforeExecutionHit(matchingBreakpoint.getID(), effectiveSQL);
|
||||
breakpointHitHandler.processBreakpointBeforeExecutionHit(breakpointBeforeExecutionHit);
|
||||
Thread currentThread = Thread.currentThread();
|
||||
long threadID = currentThread.getId();
|
||||
String threadName = currentThread.getName();
|
||||
StackTraceElement[] callerStackTraceElements = currentThread.getStackTrace();
|
||||
callerStackTraceElements = Arrays.copyOfRange(callerStackTraceElements, 2, callerStackTraceElements.length);
|
||||
BreakpointHit breakpointHit = new BreakpointHit(matchingBreakpoint.getID(), effectiveSQL, threadID, threadName, callerStackTraceElements, true);
|
||||
matchingDebugger.processBreakpointBeforeExecutionHit(ctx, breakpointHit);
|
||||
// Breakpoint has an answer.
|
||||
if(breakpointBeforeExecutionHit.getBreakpointID() == null) {
|
||||
executionType = breakpointBeforeExecutionHit.getExecutionType();
|
||||
String sql = breakpointBeforeExecutionHit.getSql();
|
||||
if(breakpointHit.getBreakpointID() == null) {
|
||||
executionType = breakpointHit.getExecutionType();
|
||||
String sql = breakpointHit.getSql();
|
||||
if(sql != null) {
|
||||
effectiveSQL = sql;
|
||||
try {
|
||||
@ -224,11 +231,10 @@ public class DebugListener extends DefaultExecuteListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
if(executionType != ExecutionType.STEP_THROUGH) {
|
||||
matchingDebugger = null;
|
||||
}
|
||||
switch(executionType) {
|
||||
case STEP_THROUGH: {
|
||||
matchingBreakpointHitHandler = breakpointHitHandler;
|
||||
break;
|
||||
}
|
||||
case RUN_OVER: {
|
||||
try {
|
||||
ctx.statement().close();
|
||||
@ -276,9 +282,6 @@ public class DebugListener extends DefaultExecuteListener {
|
||||
return;
|
||||
}
|
||||
endExecutionTime = System.currentTimeMillis();
|
||||
if(matchingBreakpointHitHandler != null) {
|
||||
matchingBreakpointHitHandler.processBreakpointAfterExecutionHit(new BreakpointAfterExecutionHit(matchingBreakpoint.getID(), effectiveSQL));
|
||||
}
|
||||
List<Debugger> debuggerList = DebuggerRegistry.getDebuggerList();
|
||||
if(!debuggerList.isEmpty()) {
|
||||
boolean hasListener = false;
|
||||
@ -336,6 +339,14 @@ public class DebugListener extends DefaultExecuteListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
if(matchingDebugger != null) {
|
||||
Thread currentThread = Thread.currentThread();
|
||||
long threadID = currentThread.getId();
|
||||
String threadName = currentThread.getName();
|
||||
StackTraceElement[] callerStackTraceElements = currentThread.getStackTrace();
|
||||
callerStackTraceElements = Arrays.copyOfRange(callerStackTraceElements, 2, callerStackTraceElements.length);
|
||||
matchingDebugger.processBreakpointAfterExecutionHit(ctx, new BreakpointHit(matchingBreakpoint.getID(), effectiveSQL, threadID, threadName, callerStackTraceElements, false));
|
||||
}
|
||||
if(matchingBreakpoint != null) {
|
||||
StatementProcessor afterExecutionProcessor = matchingBreakpoint.getAfterExecutionProcessor();
|
||||
matchingBreakpoint = null;
|
||||
|
||||
@ -36,6 +36,8 @@
|
||||
*/
|
||||
package org.jooq.debug;
|
||||
|
||||
import org.jooq.ExecuteContext;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christopher Deckers
|
||||
@ -63,4 +65,10 @@ public interface Debugger extends StatementExecutorCreator {
|
||||
|
||||
public boolean isExecutionSupported();
|
||||
|
||||
public void processBreakpointBeforeExecutionHit(ExecuteContext ctx, BreakpointHit breakpointHit);
|
||||
|
||||
public void processBreakpointAfterExecutionHit(ExecuteContext ctx, BreakpointHit breakpointHit);
|
||||
|
||||
public StatementExecutor createBreakpointHitStatementExecutor(long threadID);
|
||||
|
||||
}
|
||||
|
||||
@ -36,6 +36,21 @@
|
||||
*/
|
||||
package org.jooq.debug;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jooq.ExecuteContext;
|
||||
import org.jooq.Field;
|
||||
import org.jooq.Record;
|
||||
import org.jooq.SQLDialect;
|
||||
import org.jooq.Table;
|
||||
import org.jooq.debug.console.DatabaseDescriptor;
|
||||
|
||||
|
||||
@ -128,7 +143,141 @@ public class LocalDebugger implements Debugger {
|
||||
|
||||
@Override
|
||||
public LocalStatementExecutor createStatementExecutor() {
|
||||
return new LocalStatementExecutor(databaseDescriptor);
|
||||
return new LocalStatementExecutor(new StatementExecutorContext() {
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return databaseDescriptor.isReadOnly();
|
||||
}
|
||||
@Override
|
||||
public Connection getConnection() {
|
||||
return databaseDescriptor.createConnection();
|
||||
}
|
||||
@Override
|
||||
public void releaseConnection(Connection connection) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public SQLDialect getSQLDialect() {
|
||||
return databaseDescriptor.getSQLDialect();
|
||||
}
|
||||
@Override
|
||||
public String[] getTableNames() {
|
||||
return LocalDebugger.this.getTableNames();
|
||||
}
|
||||
@Override
|
||||
public String[] getTableColumnNames() {
|
||||
return LocalDebugger.this.getTableColumnNames();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String[] getTableNames() {
|
||||
List<Table<?>> tableList = databaseDescriptor.getSchema().getTables();
|
||||
List<String> tableNameList = new ArrayList<String>();
|
||||
for(Table<? extends Record> table: tableList) {
|
||||
String tableName = table.getName();
|
||||
tableNameList.add(tableName);
|
||||
}
|
||||
Collections.sort(tableNameList, String.CASE_INSENSITIVE_ORDER);
|
||||
return tableNameList.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private String[] getTableColumnNames() {
|
||||
Set<String> columnNameSet = new HashSet<String>();
|
||||
for(Table<?> table: databaseDescriptor.getSchema().getTables()) {
|
||||
for(Field<?> field: table.getFields()) {
|
||||
String columnName = field.getName();
|
||||
columnNameSet.add(columnName);
|
||||
}
|
||||
}
|
||||
String[] columnNames = columnNameSet.toArray(new String[0]);
|
||||
Arrays.sort(columnNames, String.CASE_INSENSITIVE_ORDER);
|
||||
return columnNames;
|
||||
}
|
||||
|
||||
private Map<Long, ExecuteContext> threadIDToExecuteContextMap = new HashMap<Long, ExecuteContext>();
|
||||
|
||||
@Override
|
||||
public void processBreakpointBeforeExecutionHit(ExecuteContext ctx, BreakpointHit breakpointHit) {
|
||||
BreakpointHitHandler handler = getBreakpointHitHandler();
|
||||
if(handler == null) {
|
||||
return;
|
||||
}
|
||||
long threadID = breakpointHit.getThreadID();
|
||||
synchronized (threadIDToExecuteContextMap) {
|
||||
threadIDToExecuteContextMap.put(threadID, ctx);
|
||||
}
|
||||
try {
|
||||
handler.processBreakpointBeforeExecutionHit(breakpointHit);
|
||||
} finally {
|
||||
synchronized (threadIDToExecuteContextMap) {
|
||||
threadIDToExecuteContextMap.remove(threadID);
|
||||
}
|
||||
performThreadDataCleanup(threadID);
|
||||
}
|
||||
}
|
||||
|
||||
protected void performThreadDataCleanup(long threadID) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processBreakpointAfterExecutionHit(ExecuteContext ctx, BreakpointHit breakpointHit) {
|
||||
BreakpointHitHandler handler = getBreakpointHitHandler();
|
||||
if(handler == null) {
|
||||
return;
|
||||
}
|
||||
long threadID = breakpointHit.getThreadID();
|
||||
synchronized (threadIDToExecuteContextMap) {
|
||||
threadIDToExecuteContextMap.put(threadID, ctx);
|
||||
}
|
||||
try {
|
||||
handler.processBreakpointAfterExecutionHit(breakpointHit);
|
||||
} finally {
|
||||
synchronized (threadIDToExecuteContextMap) {
|
||||
threadIDToExecuteContextMap.remove(threadID);
|
||||
}
|
||||
performThreadDataCleanup(threadID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalStatementExecutor createBreakpointHitStatementExecutor(long threadID) {
|
||||
final ExecuteContext ctx;
|
||||
synchronized (threadIDToExecuteContextMap) {
|
||||
ctx = threadIDToExecuteContextMap.get(threadID);
|
||||
if(ctx == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return new LocalStatementExecutor(new StatementExecutorContext() {
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public Connection getConnection() {
|
||||
return ctx.getConnection();
|
||||
}
|
||||
@Override
|
||||
public void releaseConnection(Connection connection) {
|
||||
// We don't want to alter the connection.
|
||||
}
|
||||
@Override
|
||||
public SQLDialect getSQLDialect() {
|
||||
return ctx.getDialect();
|
||||
}
|
||||
@Override
|
||||
public String[] getTableNames() {
|
||||
return LocalDebugger.this.getTableNames();
|
||||
}
|
||||
@Override
|
||||
public String[] getTableColumnNames() {
|
||||
return LocalDebugger.this.getTableColumnNames();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -51,21 +51,13 @@ import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.jooq.Field;
|
||||
import org.jooq.Record;
|
||||
import org.jooq.SQLDialect;
|
||||
import org.jooq.Table;
|
||||
import org.jooq.debug.StatementExecutionResultSetResult.TypeInfo;
|
||||
import org.jooq.debug.console.DatabaseDescriptor;
|
||||
import org.jooq.debug.console.misc.Utils;
|
||||
|
||||
/**
|
||||
@ -73,10 +65,10 @@ import org.jooq.debug.console.misc.Utils;
|
||||
*/
|
||||
public class LocalStatementExecutor implements StatementExecutor {
|
||||
|
||||
private DatabaseDescriptor databaseDescriptor;
|
||||
private StatementExecutorContext executorContext;
|
||||
|
||||
public LocalStatementExecutor(DatabaseDescriptor databaseDescriptor) {
|
||||
this.databaseDescriptor = databaseDescriptor;
|
||||
public LocalStatementExecutor(StatementExecutorContext executorContext) {
|
||||
this.executorContext = executorContext;
|
||||
}
|
||||
|
||||
private volatile Connection conn;
|
||||
@ -87,7 +79,7 @@ public class LocalStatementExecutor implements StatementExecutor {
|
||||
@Override
|
||||
public StatementExecution execute(String sql, int maxRSRowsParsing, int retainParsedRSDataRowCountThreshold) {
|
||||
boolean isAllowed = true;
|
||||
if(databaseDescriptor.isReadOnly()) {
|
||||
if(executorContext.isReadOnly()) {
|
||||
String simplifiedSql = sql.replaceAll("'[^']*'", "");
|
||||
Matcher matcher = Pattern.compile("[a-zA-Z_0-9\\$]+").matcher(simplifiedSql);
|
||||
boolean isFirst = true;
|
||||
@ -122,9 +114,9 @@ public class LocalStatementExecutor implements StatementExecutor {
|
||||
evaluationThread = Thread.currentThread();
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
conn = databaseDescriptor.createConnection();
|
||||
conn = executorContext.getConnection();
|
||||
stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
|
||||
// If no error, adjust start to begining of actual execution.
|
||||
// If no error, adjust start to beginning of actual execution.
|
||||
start = System.currentTimeMillis();
|
||||
if(evaluationThread != Thread.currentThread()) {
|
||||
long executionDuration = System.currentTimeMillis() - start;
|
||||
@ -244,12 +236,12 @@ public class LocalStatementExecutor implements StatementExecutor {
|
||||
}
|
||||
}
|
||||
final long resultSetParsingDuration = System.currentTimeMillis() - rsStart;
|
||||
statementExecutionResult = new LocalStatementExecutionResultSetResult(rs, columnNames, typeInfos, columnClasses, rowDataList.toArray(new Object[0][]), rowCount, resultSetParsingDuration, retainParsedRSDataRowCountThreshold, databaseDescriptor.isReadOnly());
|
||||
statementExecutionResult = new LocalStatementExecutionResultSetResult(rs, columnNames, typeInfos, columnClasses, rowDataList.toArray(new Object[0][]), rowCount, resultSetParsingDuration, retainParsedRSDataRowCountThreshold, executorContext.isReadOnly());
|
||||
} else {
|
||||
final int updateCount = stmt.getUpdateCount();
|
||||
statementExecutionResult = new StatementExecutionMessageResult(Utils.formatDuration(executionDuration) + "> " + updateCount + " row(s) affected.", false);
|
||||
}
|
||||
if(databaseDescriptor.getSQLDialect() == SQLDialect.SQLSERVER) {
|
||||
if(executorContext.getSQLDialect() == SQLDialect.SQLSERVER) {
|
||||
try {
|
||||
executeResult = stmt.getMoreResults(Statement.KEEP_CURRENT_RESULT);
|
||||
} catch(Exception e) {
|
||||
@ -265,7 +257,7 @@ public class LocalStatementExecutor implements StatementExecutor {
|
||||
long executionDuration = System.currentTimeMillis() - start;
|
||||
return new StatementExecution(executionDuration, new StatementExecutionMessageResult(e));
|
||||
} finally {
|
||||
if(databaseDescriptor.isReadOnly()) {
|
||||
if(executorContext.isReadOnly()) {
|
||||
closeConnection();
|
||||
}
|
||||
}
|
||||
@ -284,10 +276,7 @@ public class LocalStatementExecutor implements StatementExecutor {
|
||||
}
|
||||
}
|
||||
stmt = null;
|
||||
try {
|
||||
conn.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
executorContext.releaseConnection(conn);
|
||||
conn = null;
|
||||
}
|
||||
}
|
||||
@ -302,28 +291,12 @@ public class LocalStatementExecutor implements StatementExecutor {
|
||||
|
||||
@Override
|
||||
public String[] getTableNames() {
|
||||
List<Table<?>> tableList = databaseDescriptor.getSchema().getTables();
|
||||
List<String> tableNameList = new ArrayList<String>();
|
||||
for(Table<? extends Record> table: tableList) {
|
||||
String tableName = table.getName();
|
||||
tableNameList.add(tableName);
|
||||
}
|
||||
Collections.sort(tableNameList, String.CASE_INSENSITIVE_ORDER);
|
||||
return tableNameList.toArray(new String[0]);
|
||||
return executorContext.getTableNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getTableColumnNames() {
|
||||
Set<String> columnNameSet = new HashSet<String>();
|
||||
for(Table<?> table: databaseDescriptor.getSchema().getTables()) {
|
||||
for(Field<?> field: table.getFields()) {
|
||||
String columnName = field.getName();
|
||||
columnNameSet.add(columnName);
|
||||
}
|
||||
}
|
||||
String[] columnNames = columnNameSet.toArray(new String[0]);
|
||||
Arrays.sort(columnNames, String.CASE_INSENSITIVE_ORDER);
|
||||
return columnNames;
|
||||
return executorContext.getTableColumnNames();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ public class QueryLoggingData extends StatementInfo {
|
||||
public QueryLoggingData(SqlQueryType queryType, String[] queries, String parameterDescription, Long preparationDuration, Long bindingDuration, long executionDuration) {
|
||||
super(queryType, queries, parameterDescription);
|
||||
this.id = nextID.getAndIncrement();
|
||||
this.callerStackTraceElements = new Exception().getStackTrace();
|
||||
this.callerStackTraceElements = Thread.currentThread().getStackTrace();
|
||||
this.preparationDuration = preparationDuration;
|
||||
this.bindingDuration = bindingDuration;
|
||||
this.executionDuration = executionDuration;
|
||||
|
||||
@ -36,28 +36,25 @@
|
||||
*/
|
||||
package org.jooq.debug;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.Connection;
|
||||
|
||||
import org.jooq.SQLDialect;
|
||||
|
||||
/**
|
||||
* @author Christopher Deckers
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class BreakpointAfterExecutionHit implements Serializable {
|
||||
public interface StatementExecutorContext {
|
||||
|
||||
private int breakpointID;
|
||||
private String sql;
|
||||
public boolean isReadOnly();
|
||||
|
||||
public BreakpointAfterExecutionHit(int breakpointID, String sql) {
|
||||
this.breakpointID = breakpointID;
|
||||
this.sql = sql;
|
||||
}
|
||||
public Connection getConnection();
|
||||
|
||||
public int getBreakpointID() {
|
||||
return breakpointID;
|
||||
}
|
||||
public void releaseConnection(Connection connection);
|
||||
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
public SQLDialect getSQLDialect();
|
||||
|
||||
public String[] getTableNames();
|
||||
|
||||
public String[] getTableColumnNames();
|
||||
|
||||
}
|
||||
@ -36,6 +36,7 @@
|
||||
*/
|
||||
package org.jooq.debug.console;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
@ -53,10 +54,14 @@ import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JRadioButton;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
|
||||
import org.jooq.debug.BreakpointBeforeExecutionHit;
|
||||
import org.jooq.debug.BreakpointBeforeExecutionHit.ExecutionType;
|
||||
import org.jooq.debug.console.DebuggerPane.BreakpointBeforeExecutionHitNode;
|
||||
import org.jooq.debug.BreakpointHit;
|
||||
import org.jooq.debug.BreakpointHit.ExecutionType;
|
||||
import org.jooq.debug.Debugger;
|
||||
import org.jooq.debug.StatementExecutor;
|
||||
import org.jooq.debug.StatementExecutorCreator;
|
||||
import org.jooq.debug.console.DebuggerPane.BreakpointHitNode;
|
||||
|
||||
import org.fife.ui.rtextarea.RTextScrollPane;
|
||||
|
||||
@ -66,34 +71,43 @@ import org.fife.ui.rtextarea.RTextScrollPane;
|
||||
@SuppressWarnings("serial")
|
||||
public class BreakpointHitEditor extends JPanel {
|
||||
|
||||
private BreakpointHit breakpointHit;
|
||||
private JCheckBox replaceStatementCheckBox;
|
||||
private JScrollPane replacementSQLTextAreaScrollPane;
|
||||
private SqlTextArea replacementSQLTextArea;
|
||||
|
||||
public BreakpointHitEditor(final DebuggerPane debuggerPane, final BreakpointBeforeExecutionHitNode breakpointHitNode) {
|
||||
super(new GridBagLayout());
|
||||
public BreakpointHitEditor(final Debugger debugger, final DebuggerPane debuggerPane, final BreakpointHitNode breakpointHitNode) {
|
||||
super(new BorderLayout());
|
||||
setOpaque(false);
|
||||
final BreakpointBeforeExecutionHit breakpointHit = (BreakpointBeforeExecutionHit)breakpointHitNode.getUserObject();
|
||||
JTabbedPane tabbedPane = new JTabbedPane();
|
||||
tabbedPane.setOpaque(false);
|
||||
JPanel breakpointHitExecutionPane = new JPanel(new GridBagLayout());
|
||||
breakpointHitExecutionPane.setBorder(BorderFactory.createEmptyBorder(2, 5, 5, 5));
|
||||
breakpointHitExecutionPane.setOpaque(false);
|
||||
breakpointHit = breakpointHitNode.getUserObject();
|
||||
int y = 0;
|
||||
add(new JLabel("Statement:"), new GridBagConstraints(0, y++, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
|
||||
breakpointHitExecutionPane.add(new JLabel("Statement:"), new GridBagConstraints(0, y++, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
|
||||
SqlTextArea sqlTextArea = new SqlTextArea();
|
||||
sqlTextArea.setText(breakpointHit.getSql() + "\n");
|
||||
sqlTextArea.setCaretPosition(0);
|
||||
add(new RTextScrollPane(sqlTextArea), new GridBagConstraints(0, y++, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
|
||||
replaceStatementCheckBox = new JCheckBox("Replace with statement");
|
||||
replaceStatementCheckBox.setOpaque(false);
|
||||
replaceStatementCheckBox.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
adjustStates();
|
||||
}
|
||||
});
|
||||
add(replaceStatementCheckBox, new GridBagConstraints(0, y++, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0));
|
||||
final SqlTextArea replacementSQLTextArea = new SqlTextArea();
|
||||
replacementSQLTextAreaScrollPane = new RTextScrollPane(replacementSQLTextArea);
|
||||
add(replacementSQLTextAreaScrollPane, new GridBagConstraints(0, y++, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(2, 20, 0, 0), 0, 0));
|
||||
breakpointHitExecutionPane.add(new RTextScrollPane(sqlTextArea), new GridBagConstraints(0, y++, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
|
||||
if(breakpointHit.isBeforeExecution()) {
|
||||
replaceStatementCheckBox = new JCheckBox("Replace with statement");
|
||||
replaceStatementCheckBox.setOpaque(false);
|
||||
replaceStatementCheckBox.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
adjustStates();
|
||||
}
|
||||
});
|
||||
breakpointHitExecutionPane.add(replaceStatementCheckBox, new GridBagConstraints(0, y++, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0));
|
||||
replacementSQLTextArea = new SqlTextArea();
|
||||
replacementSQLTextAreaScrollPane = new RTextScrollPane(replacementSQLTextArea);
|
||||
breakpointHitExecutionPane.add(replacementSQLTextAreaScrollPane, new GridBagConstraints(0, y++, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(2, 20, 0, 0), 0, 0));
|
||||
}
|
||||
JPanel executionTypePane = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
|
||||
// For now, this choice is not exposed.
|
||||
executionTypePane.setVisible(false);
|
||||
executionTypePane.setVisible(breakpointHit.isBeforeExecution());
|
||||
executionTypePane.setOpaque(false);
|
||||
ButtonGroup executionTypeGroup = new ButtonGroup();
|
||||
final JRadioButton executeTypeNoneRadioButton = new JRadioButton("Execute");
|
||||
@ -105,28 +119,42 @@ public class BreakpointHitEditor extends JPanel {
|
||||
executeTypeBreakRadioButton.setOpaque(false);
|
||||
executionTypeGroup.add(executeTypeBreakRadioButton);
|
||||
executionTypePane.add(executeTypeBreakRadioButton);
|
||||
add(executionTypePane, new GridBagConstraints(0, y++, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0));
|
||||
breakpointHitExecutionPane.add(executionTypePane, new GridBagConstraints(0, y++, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0));
|
||||
JPanel buttonPane = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
|
||||
buttonPane.setOpaque(false);
|
||||
buttonPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
buttonPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
|
||||
JButton applyButton = new JButton("Proceed");
|
||||
applyButton.setOpaque(false);
|
||||
applyButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
breakpointHit.setExecutionType(executeTypeNoneRadioButton.isSelected()? ExecutionType.RUN: ExecutionType.STEP_THROUGH, replaceStatementCheckBox.isSelected()? replacementSQLTextArea.getText(): null);
|
||||
if(breakpointHit.isBeforeExecution()) {
|
||||
breakpointHit.setExecutionType(executeTypeNoneRadioButton.isSelected()? ExecutionType.RUN: ExecutionType.STEP_THROUGH, replaceStatementCheckBox.isSelected()? replacementSQLTextArea.getText(): null);
|
||||
} else {
|
||||
breakpointHit.setExecutionType(ExecutionType.RUN, null);
|
||||
}
|
||||
debuggerPane.proceedBreakpointHit(breakpointHitNode);
|
||||
}
|
||||
});
|
||||
buttonPane.add(applyButton);
|
||||
add(buttonPane, new GridBagConstraints(0, y, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
|
||||
breakpointHitExecutionPane.add(buttonPane, new GridBagConstraints(0, y, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
|
||||
adjustStates();
|
||||
tabbedPane.addTab("Execution", breakpointHitExecutionPane);
|
||||
tabbedPane.addTab("Editor", new EditorsPane(new StatementExecutorCreator() {
|
||||
@Override
|
||||
public StatementExecutor createStatementExecutor() {
|
||||
return debugger.createBreakpointHitStatementExecutor(breakpointHit.getThreadID());
|
||||
}
|
||||
}, false));
|
||||
add(tabbedPane);
|
||||
}
|
||||
|
||||
private void adjustStates() {
|
||||
replacementSQLTextAreaScrollPane.setVisible(replaceStatementCheckBox.isSelected());
|
||||
revalidate();
|
||||
repaint();
|
||||
if(breakpointHit.isBeforeExecution()) {
|
||||
replacementSQLTextAreaScrollPane.setVisible(replaceStatementCheckBox.isSelected());
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -43,8 +43,15 @@ import java.awt.FlowLayout;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -53,7 +60,9 @@ import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JTextField;
|
||||
@ -70,8 +79,7 @@ import javax.swing.tree.TreeNode;
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import org.jooq.debug.Breakpoint;
|
||||
import org.jooq.debug.BreakpointAfterExecutionHit;
|
||||
import org.jooq.debug.BreakpointBeforeExecutionHit;
|
||||
import org.jooq.debug.BreakpointHit;
|
||||
import org.jooq.debug.BreakpointHitHandler;
|
||||
import org.jooq.debug.Debugger;
|
||||
import org.jooq.debug.console.misc.CheckBoxNode;
|
||||
@ -87,7 +95,8 @@ public class DebuggerPane extends JPanel {
|
||||
|
||||
private final ImageIcon BREAKPOINT_ON_ICON = new ImageIcon(getClass().getResource("/org/jooq/debug/console/resources/BreakpointOn16.png"));
|
||||
private final ImageIcon BREAKPOINT_OFF_ICON = new ImageIcon(getClass().getResource("/org/jooq/debug/console/resources/BreakpointOff16.png"));
|
||||
private final ImageIcon BREAKPOINT_HIT_ICON = new ImageIcon(getClass().getResource("/org/jooq/debug/console/resources/BreakpointHit16.png"));
|
||||
private final ImageIcon BREAKPOINT_HIT_BEFORE_ICON = new ImageIcon(getClass().getResource("/org/jooq/debug/console/resources/BreakpointHit16.png"));
|
||||
private final ImageIcon BREAKPOINT_HIT_AFTER_ICON = new ImageIcon(getClass().getResource("/org/jooq/debug/console/resources/BreakpointHitAfter16.png"));
|
||||
private final ImageIcon STACK_TRACE_ELEMENT_ICON = new ImageIcon(getClass().getResource("/org/jooq/debug/console/resources/StackTraceElement16.png"));
|
||||
|
||||
private Debugger debugger;
|
||||
@ -168,8 +177,12 @@ public class DebuggerPane extends JPanel {
|
||||
Icon icon = null;
|
||||
if(value instanceof CheckBoxNode) {
|
||||
icon = ((CheckBoxNode) value).isSelected()? BREAKPOINT_ON_ICON: BREAKPOINT_OFF_ICON;
|
||||
} else if(value instanceof BreakpointBeforeExecutionHitNode) {
|
||||
icon = BREAKPOINT_HIT_ICON;
|
||||
} else if(value instanceof BreakpointHitNode) {
|
||||
if(((BreakpointHitNode) value).getUserObject().isBeforeExecution()) {
|
||||
icon = BREAKPOINT_HIT_BEFORE_ICON;
|
||||
} else {
|
||||
icon = BREAKPOINT_HIT_AFTER_ICON;
|
||||
}
|
||||
} else if(value instanceof StackTraceElementNode) {
|
||||
icon = STACK_TRACE_ELEMENT_ICON;
|
||||
}
|
||||
@ -205,7 +218,7 @@ public class DebuggerPane extends JPanel {
|
||||
}
|
||||
}
|
||||
if(isValid) {
|
||||
List<BreakpointBeforeExecutionHitNode> beforeExecutionHitNodeList = new ArrayList<DebuggerPane.BreakpointBeforeExecutionHitNode>();
|
||||
List<BreakpointHitNode> breakpointHitNodeList = new ArrayList<DebuggerPane.BreakpointHitNode>();
|
||||
// TODO: list of after exec
|
||||
breakpointTree.cancelEditing();
|
||||
for(int i=0; i<paths.length; i++) {
|
||||
@ -213,10 +226,8 @@ public class DebuggerPane extends JPanel {
|
||||
int childCount = childNode.getChildCount();
|
||||
for(int j=0; j<childCount; j++) {
|
||||
TreeNode node = childNode.getChildAt(j);
|
||||
if(node instanceof BreakpointBeforeExecutionHitNode) {
|
||||
beforeExecutionHitNodeList.add((BreakpointBeforeExecutionHitNode)node);
|
||||
} else {
|
||||
// TODO: implement
|
||||
if(node instanceof BreakpointHitNode) {
|
||||
breakpointHitNodeList.add((BreakpointHitNode)node);
|
||||
}
|
||||
}
|
||||
int index = rootNode.getIndex(childNode);
|
||||
@ -224,7 +235,7 @@ public class DebuggerPane extends JPanel {
|
||||
breakpointTreeModel.nodesWereRemoved(rootNode, new int[] {index}, new Object[] {childNode});
|
||||
}
|
||||
commitBreakpoints();
|
||||
for(BreakpointBeforeExecutionHitNode node: beforeExecutionHitNodeList) {
|
||||
for(BreakpointHitNode node: breakpointHitNodeList) {
|
||||
synchronized (node) {
|
||||
node.proceed();
|
||||
}
|
||||
@ -249,6 +260,52 @@ public class DebuggerPane extends JPanel {
|
||||
processTreeSelection();
|
||||
}
|
||||
});
|
||||
breakpointTree.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
maybeShowPopup(e);
|
||||
}
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
maybeShowPopup(e);
|
||||
}
|
||||
private void maybeShowPopup(MouseEvent e) {
|
||||
if(e.isPopupTrigger()) {
|
||||
TreePath[] selectionPaths = breakpointTree.getSelectionPaths();
|
||||
if(selectionPaths != null && selectionPaths.length == 1) {
|
||||
Object o = selectionPaths[0].getLastPathComponent();
|
||||
if(o instanceof BreakpointHitNode) {
|
||||
final BreakpointHit breakpointHit = ((BreakpointHitNode) o).getUserObject();
|
||||
JPopupMenu popupMenu = new JPopupMenu();
|
||||
JMenuItem copyStackToClipboardMenuItem = new JMenuItem("Copy Call Stack to Clipboard");
|
||||
copyStackToClipboardMenuItem.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
StringWriter sw = new StringWriter();
|
||||
Throwable throwable = new Exception("Statement Stack trace");
|
||||
throwable.setStackTrace(breakpointHit.getCallerStackTraceElements());
|
||||
throwable.printStackTrace(new PrintWriter(sw));
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
clipboard.setContents(new StringSelection(sw.toString()), null);
|
||||
}
|
||||
});
|
||||
popupMenu.add(copyStackToClipboardMenuItem);
|
||||
JMenuItem dumpStackToConsoleMenuItem = new JMenuItem("Dump Call Stack");
|
||||
dumpStackToConsoleMenuItem.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Throwable throwable = new Exception("Statement Stack trace");
|
||||
throwable.setStackTrace(breakpointHit.getCallerStackTraceElements());
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
});
|
||||
popupMenu.add(dumpStackToConsoleMenuItem);
|
||||
popupMenu.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
breakpointRemovePane.add(removeBreakpointButton);
|
||||
westPane.add(breakpointRemovePane, BorderLayout.SOUTH);
|
||||
eastPane = new JPanel(new BorderLayout());
|
||||
@ -288,10 +345,10 @@ public class DebuggerPane extends JPanel {
|
||||
super(stackTraceElement);
|
||||
String className = stackTraceElement.getClassName();
|
||||
className = className.substring(className.lastIndexOf('.') + 1);
|
||||
name = className + '.' + stackTraceElement.getMethodName() + "()";
|
||||
if(stackTraceElement.getLineNumber() >= 0) {
|
||||
name += " line " + stackTraceElement.getLineNumber();
|
||||
}
|
||||
String fileName = stackTraceElement.getFileName();
|
||||
int lineNumber = stackTraceElement.getLineNumber();
|
||||
String fileInfo = fileName != null && lineNumber >= 0? fileName + ":" + lineNumber: "";
|
||||
name = className + '.' + stackTraceElement.getMethodName() + "(" + fileInfo + ")";
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
@ -299,14 +356,18 @@ public class DebuggerPane extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
class BreakpointBeforeExecutionHitNode extends DefaultMutableTreeNode {
|
||||
public BreakpointBeforeExecutionHitNode(BreakpointBeforeExecutionHit breakpointHit) {
|
||||
class BreakpointHitNode extends DefaultMutableTreeNode {
|
||||
public BreakpointHitNode(BreakpointHit breakpointHit) {
|
||||
super(breakpointHit);
|
||||
StackTraceElement[] callerStackTraceElements = breakpointHit.getCallerStackTraceElements();
|
||||
for(StackTraceElement stackTraceElement: callerStackTraceElements) {
|
||||
add(new StackTraceElementNode(stackTraceElement));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public BreakpointHit getUserObject() {
|
||||
return (BreakpointHit)super.getUserObject();
|
||||
}
|
||||
private boolean isLocked = true;
|
||||
public void proceed() {
|
||||
synchronized (this) {
|
||||
@ -321,11 +382,11 @@ public class DebuggerPane extends JPanel {
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Thread [" + ((BreakpointBeforeExecutionHit)getUserObject()).getThreadName() + "]";
|
||||
return "Thread [" + getUserObject().getThreadName() + "]";
|
||||
}
|
||||
}
|
||||
|
||||
void proceedBreakpointHit(BreakpointBeforeExecutionHitNode breakpointHitNode) {
|
||||
void proceedBreakpointHit(BreakpointHitNode breakpointHitNode) {
|
||||
CheckBoxNode parentNode = (CheckBoxNode)breakpointHitNode.getParent();
|
||||
int index = parentNode.getIndex(breakpointHitNode);
|
||||
parentNode.remove(index);
|
||||
@ -335,11 +396,23 @@ public class DebuggerPane extends JPanel {
|
||||
|
||||
private BreakpointHitHandler breakpointHitHandler = new BreakpointHitHandler() {
|
||||
@Override
|
||||
public void processBreakpointBeforeExecutionHit(final BreakpointBeforeExecutionHit breakpointHit) {
|
||||
public void processBreakpointBeforeExecutionHit(final BreakpointHit breakpointHit) {
|
||||
if(SwingUtilities.isEventDispatchThread()) {
|
||||
new IllegalStateException("Breakpoint triggered from UI thread: cannot break because the debugger needs a live UI thread!").printStackTrace();
|
||||
return;
|
||||
}
|
||||
final BreakpointBeforeExecutionHitNode node = new BreakpointBeforeExecutionHitNode(breakpointHit);
|
||||
handleBreakpoint(breakpointHit);
|
||||
}
|
||||
@Override
|
||||
public void processBreakpointAfterExecutionHit(BreakpointHit breakpointHit) {
|
||||
if(SwingUtilities.isEventDispatchThread()) {
|
||||
new IllegalStateException("Breakpoint triggered from UI thread: cannot break because the debugger needs a live UI thread!").printStackTrace();
|
||||
return;
|
||||
}
|
||||
handleBreakpoint(breakpointHit);
|
||||
}
|
||||
private void handleBreakpoint(final BreakpointHit breakpointHit) {
|
||||
final BreakpointHitNode node = new BreakpointHitNode(breakpointHit);
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -373,9 +446,6 @@ public class DebuggerPane extends JPanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void processBreakpointAfterExecutionHit(BreakpointAfterExecutionHit breakpointHit) {
|
||||
}
|
||||
};
|
||||
|
||||
private void commitBreakpoints() {
|
||||
@ -405,8 +475,8 @@ public class DebuggerPane extends JPanel {
|
||||
if(node instanceof CheckBoxNode) {
|
||||
Object o = node.getUserObject();
|
||||
eastPane.add(new BreakpointEditor(this, (Breakpoint)o));
|
||||
} else if(node instanceof BreakpointBeforeExecutionHitNode) {
|
||||
eastPane.add(new BreakpointHitEditor(this, (BreakpointBeforeExecutionHitNode)node));
|
||||
} else if(node instanceof BreakpointHitNode) {
|
||||
eastPane.add(new BreakpointHitEditor(debugger, this, (BreakpointHitNode)node));
|
||||
}
|
||||
}
|
||||
eastPane.revalidate();
|
||||
|
||||
@ -599,6 +599,13 @@ public class EditorPane extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify() {
|
||||
super.removeNotify();
|
||||
// TODO: if we want to recycle screens, should we do this?
|
||||
closeLastExecution();
|
||||
}
|
||||
|
||||
private void setMessage(JPanel messageContentPanel, String message, boolean isError) {
|
||||
JTextArea textArea = new JTextArea(message);
|
||||
Font editorFont = textArea.getFont().deriveFont(UIManager.getFont("TextField.font").getSize2D());
|
||||
|
||||
@ -72,6 +72,7 @@ import org.jooq.debug.console.misc.InvisibleSplitPane;
|
||||
/**
|
||||
* @author Christopher Deckers
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class EditorsPane extends JPanel {
|
||||
|
||||
private StatementExecutorCreator statementExecutorCreator;
|
||||
|
||||
@ -36,9 +36,14 @@
|
||||
*/
|
||||
package org.jooq.debug.console.remote;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jooq.ExecuteContext;
|
||||
import org.jooq.debug.Breakpoint;
|
||||
import org.jooq.debug.BreakpointAfterExecutionHit;
|
||||
import org.jooq.debug.BreakpointBeforeExecutionHit;
|
||||
import org.jooq.debug.BreakpointHit;
|
||||
import org.jooq.debug.BreakpointHitHandler;
|
||||
import org.jooq.debug.Debugger;
|
||||
import org.jooq.debug.LoggingListener;
|
||||
@ -165,7 +170,7 @@ public class ClientDebugger implements Debugger {
|
||||
|
||||
@Override
|
||||
public StatementExecutor createStatementExecutor() {
|
||||
return new ClientStatementExecutor(this);
|
||||
return new ClientStatementExecutor(this, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
@ -198,11 +203,11 @@ public class ClientDebugger implements Debugger {
|
||||
public Object run(Object[] args) {
|
||||
BreakpointHitHandler breakpointHitHandler = getDebugger().getBreakpointHitHandler();
|
||||
if(breakpointHitHandler != null) {
|
||||
BreakpointBeforeExecutionHit breakpointHit = (BreakpointBeforeExecutionHit)args[0];
|
||||
BreakpointHit breakpointHit = (BreakpointHit)args[0];
|
||||
breakpointHitHandler.processBreakpointBeforeExecutionHit(breakpointHit);
|
||||
if(breakpointHit.getBreakpointID() != null) {
|
||||
// The breakpoint was not processed, so we process it here.
|
||||
breakpointHit.setExecutionType(BreakpointBeforeExecutionHit.ExecutionType.RUN, null);
|
||||
breakpointHit.setExecutionType(BreakpointHit.ExecutionType.RUN, null);
|
||||
}
|
||||
return breakpointHit;
|
||||
}
|
||||
@ -216,11 +221,82 @@ public class ClientDebugger implements Debugger {
|
||||
public Object run(Object[] args) {
|
||||
BreakpointHitHandler breakpointHitHandler = getDebugger().getBreakpointHitHandler();
|
||||
if(breakpointHitHandler != null) {
|
||||
BreakpointAfterExecutionHit breakpointHit = (BreakpointAfterExecutionHit)args[0];
|
||||
BreakpointHit breakpointHit = (BreakpointHit)args[0];
|
||||
breakpointHitHandler.processBreakpointAfterExecutionHit(breakpointHit);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Long, ExecuteContext> threadIDToExecuteContextMap = new HashMap<Long, ExecuteContext>();
|
||||
|
||||
@Override
|
||||
public void processBreakpointBeforeExecutionHit(ExecuteContext ctx, BreakpointHit breakpointHit) {
|
||||
BreakpointHitHandler handler = getBreakpointHitHandler();
|
||||
if(handler == null) {
|
||||
return;
|
||||
}
|
||||
long threadID = breakpointHit.getThreadID();
|
||||
synchronized (threadIDToExecuteContextMap) {
|
||||
threadIDToExecuteContextMap.put(threadID, ctx);
|
||||
}
|
||||
try {
|
||||
handler.processBreakpointBeforeExecutionHit(breakpointHit);
|
||||
} finally {
|
||||
synchronized (threadIDToExecuteContextMap) {
|
||||
threadIDToExecuteContextMap.remove(threadID);
|
||||
}
|
||||
performThreadDataCleanup(threadID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processBreakpointAfterExecutionHit(ExecuteContext ctx, BreakpointHit breakpointHit) {
|
||||
BreakpointHitHandler handler = getBreakpointHitHandler();
|
||||
if(handler == null) {
|
||||
return;
|
||||
}
|
||||
long threadID = breakpointHit.getThreadID();
|
||||
synchronized (threadIDToExecuteContextMap) {
|
||||
threadIDToExecuteContextMap.put(threadID, ctx);
|
||||
}
|
||||
try {
|
||||
handler.processBreakpointAfterExecutionHit(breakpointHit);
|
||||
} finally {
|
||||
synchronized (threadIDToExecuteContextMap) {
|
||||
threadIDToExecuteContextMap.remove(threadID);
|
||||
}
|
||||
performThreadDataCleanup(threadID);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Long, List<StatementExecutor>> threadIDToStatementExecutorList = new HashMap<Long, List<StatementExecutor>>();
|
||||
|
||||
@Override
|
||||
public StatementExecutor createBreakpointHitStatementExecutor(long threadID) {
|
||||
ClientStatementExecutor statementExecutor = new ClientStatementExecutor(this, threadID);
|
||||
synchronized (threadIDToStatementExecutorList) {
|
||||
List<StatementExecutor> list = threadIDToStatementExecutorList.get(threadID);
|
||||
if(list == null) {
|
||||
list = new ArrayList<StatementExecutor>();
|
||||
threadIDToStatementExecutorList.put(threadID, list);
|
||||
}
|
||||
list.add(statementExecutor);
|
||||
}
|
||||
return statementExecutor;
|
||||
}
|
||||
|
||||
|
||||
private void performThreadDataCleanup(long threadID) {
|
||||
List<StatementExecutor> list;
|
||||
synchronized (threadIDToStatementExecutorList) {
|
||||
list = threadIDToStatementExecutorList.remove(threadID);
|
||||
}
|
||||
if(list != null) {
|
||||
for(StatementExecutor statementExecutor: list) {
|
||||
statementExecutor.stopExecution();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -51,10 +51,13 @@ public class ClientStatementExecutor implements StatementExecutor {
|
||||
private ClientDebugger debugger;
|
||||
private int id;
|
||||
|
||||
public ClientStatementExecutor(ClientDebugger debugger) {
|
||||
/**
|
||||
* @param breakpointHitThreadID null if not in a breakpoint hit.
|
||||
*/
|
||||
public ClientStatementExecutor(ClientDebugger debugger, Long breakpointHitThreadID) {
|
||||
id = nextID.incrementAndGet();
|
||||
this.debugger = debugger;
|
||||
new ServerDebugger.CMS_createServerStatementExecutor().asyncExec(debugger.getCommunicationInterface(), id);
|
||||
new ServerDebugger.CMS_createServerStatementExecutor().asyncExec(debugger.getCommunicationInterface(), id, breakpointHitThreadID);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -36,14 +36,16 @@
|
||||
*/
|
||||
package org.jooq.debug.console.remote;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jooq.debug.Breakpoint;
|
||||
import org.jooq.debug.BreakpointAfterExecutionHit;
|
||||
import org.jooq.debug.BreakpointBeforeExecutionHit;
|
||||
import org.jooq.debug.BreakpointHit;
|
||||
import org.jooq.debug.BreakpointHitHandler;
|
||||
import org.jooq.debug.LocalDebugger;
|
||||
import org.jooq.debug.LocalStatementExecutor;
|
||||
import org.jooq.debug.LoggingListener;
|
||||
import org.jooq.debug.QueryLoggingData;
|
||||
import org.jooq.debug.ResultSetLoggingData;
|
||||
@ -98,14 +100,14 @@ class ServerDebugger extends LocalDebugger {
|
||||
if(isActive) {
|
||||
setBreakpointHitHandler(new BreakpointHitHandler() {
|
||||
@Override
|
||||
public void processBreakpointBeforeExecutionHit(BreakpointBeforeExecutionHit breakpointHit) {
|
||||
BreakpointBeforeExecutionHit modifiedBreakpointHit = (BreakpointBeforeExecutionHit)new ClientDebugger.CMC_processBreakpointBeforeExecutionHit().syncExec(communicationInterface, breakpointHit);
|
||||
public void processBreakpointBeforeExecutionHit(BreakpointHit breakpointHit) {
|
||||
BreakpointHit modifiedBreakpointHit = (BreakpointHit)new ClientDebugger.CMC_processBreakpointBeforeExecutionHit().syncExec(communicationInterface, breakpointHit);
|
||||
if(modifiedBreakpointHit != null) {
|
||||
breakpointHit.setExecutionType(modifiedBreakpointHit.getExecutionType(), modifiedBreakpointHit.getSql());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void processBreakpointAfterExecutionHit(BreakpointAfterExecutionHit breakpointHit) {
|
||||
public void processBreakpointAfterExecutionHit(BreakpointHit breakpointHit) {
|
||||
new ClientDebugger.CMC_processBreakpointAfterExecutionHit().syncExec(communicationInterface, breakpointHit);
|
||||
}
|
||||
});
|
||||
@ -151,9 +153,15 @@ class ServerDebugger extends LocalDebugger {
|
||||
|
||||
private Map<Integer, StatementExecutor> idToStatementExecutorMap = new HashMap<Integer, StatementExecutor>();
|
||||
|
||||
private void createStatementExecutor(int id) {
|
||||
private void createStatementExecutor(int id, Long breakpointHitThreadID) {
|
||||
LocalStatementExecutor statementExecutor;
|
||||
if(breakpointHitThreadID == null) {
|
||||
statementExecutor = createStatementExecutor();
|
||||
} else {
|
||||
statementExecutor = createBreakpointHitStatementExecutor(breakpointHitThreadID);
|
||||
}
|
||||
synchronized (idToStatementExecutorMap) {
|
||||
idToStatementExecutorMap.put(id, createStatementExecutor());
|
||||
idToStatementExecutorMap.put(id, statementExecutor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,7 +182,8 @@ class ServerDebugger extends LocalDebugger {
|
||||
@Override
|
||||
public Object run(Object[] args) {
|
||||
int id = (Integer)args[0];
|
||||
getDebugger().createStatementExecutor(id);
|
||||
Long breakpointHitThreadID = (Long)args[1];
|
||||
getDebugger().createStatementExecutor(id, breakpointHitThreadID);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -229,4 +238,33 @@ class ServerDebugger extends LocalDebugger {
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Long, List<StatementExecutor>> threadIDToStatementExecutorList = new HashMap<Long, List<StatementExecutor>>();
|
||||
|
||||
@Override
|
||||
public LocalStatementExecutor createBreakpointHitStatementExecutor(long threadID) {
|
||||
LocalStatementExecutor statementExecutor = super.createBreakpointHitStatementExecutor(threadID);
|
||||
synchronized (threadIDToStatementExecutorList) {
|
||||
List<StatementExecutor> list = threadIDToStatementExecutorList.get(threadID);
|
||||
if(list == null) {
|
||||
list = new ArrayList<StatementExecutor>();
|
||||
threadIDToStatementExecutorList.put(threadID, list);
|
||||
}
|
||||
list.add(statementExecutor);
|
||||
}
|
||||
return statementExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performThreadDataCleanup(long threadID) {
|
||||
List<StatementExecutor> list;
|
||||
synchronized (threadIDToStatementExecutorList) {
|
||||
list = threadIDToStatementExecutorList.remove(threadID);
|
||||
}
|
||||
if(list != null) {
|
||||
for(StatementExecutor statementExecutor: list) {
|
||||
statementExecutor.stopExecution();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
Loading…
Reference in New Issue
Block a user