From a8cffce3402d52933d1f2cca9f9f93df2e1edfc2 Mon Sep 17 00:00:00 2001 From: Chrriis Date: Sat, 12 May 2012 12:40:53 +0200 Subject: [PATCH] [#1249] Add breakpoint capability to jOOQ Console - "break after" and edition in breakpoint context. --- ...reExecutionHit.java => BreakpointHit.java} | 20 ++- .../org/jooq/debug/BreakpointHitHandler.java | 4 +- .../java/org/jooq/debug/DebugListener.java | 53 +++--- .../main/java/org/jooq/debug/Debugger.java | 8 + .../java/org/jooq/debug/LocalDebugger.java | 151 +++++++++++++++++- .../jooq/debug/LocalStatementExecutor.java | 51 ++---- .../java/org/jooq/debug/QueryLoggingData.java | 2 +- ...Hit.java => StatementExecutorContext.java} | 27 ++-- .../debug/console/BreakpointHitEditor.java | 84 ++++++---- .../org/jooq/debug/console/DebuggerPane.java | 122 +++++++++++--- .../org/jooq/debug/console/EditorPane.java | 7 + .../org/jooq/debug/console/EditorsPane.java | 1 + .../debug/console/remote/ClientDebugger.java | 88 +++++++++- .../remote/ClientStatementExecutor.java | 7 +- .../debug/console/remote/ServerDebugger.java | 54 ++++++- .../resources/BreakpointHitAfter16.png | Bin 0 -> 3016 bytes 16 files changed, 526 insertions(+), 153 deletions(-) rename jOOQ-console/src/main/java/org/jooq/debug/{BreakpointBeforeExecutionHit.java => BreakpointHit.java} (83%) rename jOOQ-console/src/main/java/org/jooq/debug/{BreakpointAfterExecutionHit.java => StatementExecutorContext.java} (81%) create mode 100644 jOOQ-console/src/main/resources/org/jooq/debug/console/resources/BreakpointHitAfter16.png diff --git a/jOOQ-console/src/main/java/org/jooq/debug/BreakpointBeforeExecutionHit.java b/jOOQ-console/src/main/java/org/jooq/debug/BreakpointHit.java similarity index 83% rename from jOOQ-console/src/main/java/org/jooq/debug/BreakpointBeforeExecutionHit.java rename to jOOQ-console/src/main/java/org/jooq/debug/BreakpointHit.java index 0aa4b8778a..ad85bbc1b6 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/BreakpointBeforeExecutionHit.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/BreakpointHit.java @@ -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; } diff --git a/jOOQ-console/src/main/java/org/jooq/debug/BreakpointHitHandler.java b/jOOQ-console/src/main/java/org/jooq/debug/BreakpointHitHandler.java index 86895686f9..8f23b6958e 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/BreakpointHitHandler.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/BreakpointHitHandler.java @@ -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); } diff --git a/jOOQ-console/src/main/java/org/jooq/debug/DebugListener.java b/jOOQ-console/src/main/java/org/jooq/debug/DebugListener.java index f18ddba6b1..fe6fc88c3e 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/DebugListener.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/DebugListener.java @@ -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 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 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; diff --git a/jOOQ-console/src/main/java/org/jooq/debug/Debugger.java b/jOOQ-console/src/main/java/org/jooq/debug/Debugger.java index 48f93e89ba..84a1b3d623 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/Debugger.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/Debugger.java @@ -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); + } diff --git a/jOOQ-console/src/main/java/org/jooq/debug/LocalDebugger.java b/jOOQ-console/src/main/java/org/jooq/debug/LocalDebugger.java index 9503ff51f3..ad2455b9ab 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/LocalDebugger.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/LocalDebugger.java @@ -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> tableList = databaseDescriptor.getSchema().getTables(); + List tableNameList = new ArrayList(); + for(Table 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 columnNameSet = new HashSet(); + 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 threadIDToExecuteContextMap = new HashMap(); + + @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(); + } + }); } } diff --git a/jOOQ-console/src/main/java/org/jooq/debug/LocalStatementExecutor.java b/jOOQ-console/src/main/java/org/jooq/debug/LocalStatementExecutor.java index 20bcc09921..4ff5135526 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/LocalStatementExecutor.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/LocalStatementExecutor.java @@ -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> tableList = databaseDescriptor.getSchema().getTables(); - List tableNameList = new ArrayList(); - for(Table 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 columnNameSet = new HashSet(); - 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(); } } diff --git a/jOOQ-console/src/main/java/org/jooq/debug/QueryLoggingData.java b/jOOQ-console/src/main/java/org/jooq/debug/QueryLoggingData.java index ba96b899dc..0d80b742b2 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/QueryLoggingData.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/QueryLoggingData.java @@ -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; diff --git a/jOOQ-console/src/main/java/org/jooq/debug/BreakpointAfterExecutionHit.java b/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutorContext.java similarity index 81% rename from jOOQ-console/src/main/java/org/jooq/debug/BreakpointAfterExecutionHit.java rename to jOOQ-console/src/main/java/org/jooq/debug/StatementExecutorContext.java index 8e2a13781f..b1cae7e8fb 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/BreakpointAfterExecutionHit.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutorContext.java @@ -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(); } diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/BreakpointHitEditor.java b/jOOQ-console/src/main/java/org/jooq/debug/console/BreakpointHitEditor.java index 9224fc4211..d8587d9dec 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/console/BreakpointHitEditor.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/BreakpointHitEditor.java @@ -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(); + } } } diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/DebuggerPane.java b/jOOQ-console/src/main/java/org/jooq/debug/console/DebuggerPane.java index a1c008768c..a36411d684 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/console/DebuggerPane.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/DebuggerPane.java @@ -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 beforeExecutionHitNodeList = new ArrayList(); + List breakpointHitNodeList = new ArrayList(); // TODO: list of after exec breakpointTree.cancelEditing(); for(int i=0; i= 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(); diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/EditorPane.java b/jOOQ-console/src/main/java/org/jooq/debug/console/EditorPane.java index f45adadcc4..be35884df2 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/console/EditorPane.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/EditorPane.java @@ -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()); diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/EditorsPane.java b/jOOQ-console/src/main/java/org/jooq/debug/console/EditorsPane.java index 51f7658a6b..56af947ebb 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/console/EditorsPane.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/EditorsPane.java @@ -72,6 +72,7 @@ import org.jooq.debug.console.misc.InvisibleSplitPane; /** * @author Christopher Deckers */ +@SuppressWarnings("serial") public class EditorsPane extends JPanel { private StatementExecutorCreator statementExecutorCreator; diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/remote/ClientDebugger.java b/jOOQ-console/src/main/java/org/jooq/debug/console/remote/ClientDebugger.java index e511c866df..0f30d9dfc2 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/console/remote/ClientDebugger.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/remote/ClientDebugger.java @@ -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 threadIDToExecuteContextMap = new HashMap(); + + @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> threadIDToStatementExecutorList = new HashMap>(); + + @Override + public StatementExecutor createBreakpointHitStatementExecutor(long threadID) { + ClientStatementExecutor statementExecutor = new ClientStatementExecutor(this, threadID); + synchronized (threadIDToStatementExecutorList) { + List list = threadIDToStatementExecutorList.get(threadID); + if(list == null) { + list = new ArrayList(); + threadIDToStatementExecutorList.put(threadID, list); + } + list.add(statementExecutor); + } + return statementExecutor; + } + + + private void performThreadDataCleanup(long threadID) { + List list; + synchronized (threadIDToStatementExecutorList) { + list = threadIDToStatementExecutorList.remove(threadID); + } + if(list != null) { + for(StatementExecutor statementExecutor: list) { + statementExecutor.stopExecution(); + } + } + } + } diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/remote/ClientStatementExecutor.java b/jOOQ-console/src/main/java/org/jooq/debug/console/remote/ClientStatementExecutor.java index 479e134b97..53ad3cec45 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/console/remote/ClientStatementExecutor.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/remote/ClientStatementExecutor.java @@ -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 diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/remote/ServerDebugger.java b/jOOQ-console/src/main/java/org/jooq/debug/console/remote/ServerDebugger.java index 2af787bc32..60088beb17 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/console/remote/ServerDebugger.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/remote/ServerDebugger.java @@ -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 idToStatementExecutorMap = new HashMap(); - 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> threadIDToStatementExecutorList = new HashMap>(); + + @Override + public LocalStatementExecutor createBreakpointHitStatementExecutor(long threadID) { + LocalStatementExecutor statementExecutor = super.createBreakpointHitStatementExecutor(threadID); + synchronized (threadIDToStatementExecutorList) { + List list = threadIDToStatementExecutorList.get(threadID); + if(list == null) { + list = new ArrayList(); + threadIDToStatementExecutorList.put(threadID, list); + } + list.add(statementExecutor); + } + return statementExecutor; + } + + @Override + protected void performThreadDataCleanup(long threadID) { + List list; + synchronized (threadIDToStatementExecutorList) { + list = threadIDToStatementExecutorList.remove(threadID); + } + if(list != null) { + for(StatementExecutor statementExecutor: list) { + statementExecutor.stopExecution(); + } + } + } + } \ No newline at end of file diff --git a/jOOQ-console/src/main/resources/org/jooq/debug/console/resources/BreakpointHitAfter16.png b/jOOQ-console/src/main/resources/org/jooq/debug/console/resources/BreakpointHitAfter16.png new file mode 100644 index 0000000000000000000000000000000000000000..3e536c987f07da4be662084980ab50758567ac36 GIT binary patch literal 3016 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 KMNUMnLSTZu)|B`F literal 0 HcmV?d00001