[#1249] Add breakpoint capability to jOOQ Console - "break after" and

edition in breakpoint context.
This commit is contained in:
Chrriis 2012-05-12 12:40:53 +02:00
parent 7987886022
commit a8cffce340
16 changed files with 526 additions and 153 deletions

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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();
}
});
}
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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();
}
}
}

View File

@ -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();

View File

@ -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());

View File

@ -72,6 +72,7 @@ import org.jooq.debug.console.misc.InvisibleSplitPane;
/**
* @author Christopher Deckers
*/
@SuppressWarnings("serial")
public class EditorsPane extends JPanel {
private StatementExecutorCreator statementExecutorCreator;

View File

@ -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();
}
}
}
}

View File

@ -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

View File

@ -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