From b97d23fd0fc990337f3ce773b69aad4669d8190f Mon Sep 17 00:00:00 2001 From: Chrriis Date: Wed, 9 May 2012 23:25:33 +0200 Subject: [PATCH] [#1249] Add breakpoint capability to jOOQ Console - Initial breakpoint code structure. --- .../main/java/org/jooq/debug/Breakpoint.java | 92 ++++++ .../debug/BreakpointAfterExecutionHit.java | 63 ++++ .../debug/BreakpointBeforeExecutionHit.java | 84 +++++ .../org/jooq/debug/BreakpointHitHandler.java | 48 +++ .../java/org/jooq/debug/DebugListener.java | 293 +++++++++++++---- .../main/java/org/jooq/debug/Debugger.java | 12 +- .../java/org/jooq/debug/LocalDebugger.java | 58 +++- .../jooq/debug/LocalStatementExecutor.java | 3 + .../java/org/jooq/debug/LoggingListener.java | 3 + .../java/org/jooq/debug/QueryLoggingData.java | 1 - .../org/jooq/debug/StatementExecution.java | 4 +- .../StatementExecutionMessageResult.java | 3 + .../jooq/debug/StatementExecutionResult.java | 3 + .../StatementExecutionResultSetResult.java | 3 + .../org/jooq/debug/StatementExecutor.java | 3 + .../java/org/jooq/debug/StatementInfo.java | 3 + .../java/org/jooq/debug/StatementMatcher.java | 3 + .../org/jooq/debug/StatementProcessor.java | 87 +++++ .../jooq/debug/UsageTrackingResultSet.java | 3 + .../jooq/debug/console/BreakpointEditor.java | 311 ++++++++++++++++++ .../java/org/jooq/debug/console/Console.java | 29 +- .../org/jooq/debug/console/DebuggerPane.java | 240 ++++++++++++++ .../debug/console/StatementMatcherPane.java | 5 +- .../console/StatementMatchersDialogBox.java | 3 + .../debug/console/StatementMatchersPane.java | 3 + .../debug/console/StatementProcessorPane.java | 79 +++++ .../jooq/debug/console/TextMatcherPane.java | 5 +- .../jooq/debug/console/misc/CheckBoxNode.java | 32 ++ .../console/misc/CheckBoxNodeEditor.java | 63 ++++ .../console/misc/CheckBoxNodeRenderer.java | 78 +++++ .../debug/console/misc/JSedRegExBuilder.java | 98 ++++++ .../jooq/debug/console/misc/TextMatcher.java | 3 + .../org/jooq/debug/console/misc/Utils.java | 114 +++++++ .../debug/console/remote/ClientDebugger.java | 94 +++++- .../remote/ClientDebuggerCommandMessage.java | 3 + .../DebuggerCommmunicationInterface.java | 3 + .../debug/console/remote/ServerDebugger.java | 45 +++ .../remote/ServerDebuggerCommandMessage.java | 3 + .../messaging/CommunicationInterface.java | 2 +- .../CommunicationInterfaceFactory.java | 3 + 40 files changed, 1896 insertions(+), 89 deletions(-) create mode 100644 jOOQ-console/src/main/java/org/jooq/debug/Breakpoint.java create mode 100644 jOOQ-console/src/main/java/org/jooq/debug/BreakpointAfterExecutionHit.java create mode 100644 jOOQ-console/src/main/java/org/jooq/debug/BreakpointBeforeExecutionHit.java create mode 100644 jOOQ-console/src/main/java/org/jooq/debug/BreakpointHitHandler.java create mode 100644 jOOQ-console/src/main/java/org/jooq/debug/StatementProcessor.java create mode 100644 jOOQ-console/src/main/java/org/jooq/debug/console/BreakpointEditor.java create mode 100644 jOOQ-console/src/main/java/org/jooq/debug/console/DebuggerPane.java create mode 100644 jOOQ-console/src/main/java/org/jooq/debug/console/StatementProcessorPane.java create mode 100644 jOOQ-console/src/main/java/org/jooq/debug/console/misc/CheckBoxNode.java create mode 100644 jOOQ-console/src/main/java/org/jooq/debug/console/misc/CheckBoxNodeEditor.java create mode 100644 jOOQ-console/src/main/java/org/jooq/debug/console/misc/CheckBoxNodeRenderer.java create mode 100644 jOOQ-console/src/main/java/org/jooq/debug/console/misc/JSedRegExBuilder.java diff --git a/jOOQ-console/src/main/java/org/jooq/debug/Breakpoint.java b/jOOQ-console/src/main/java/org/jooq/debug/Breakpoint.java new file mode 100644 index 0000000000..b1e6ff3284 --- /dev/null +++ b/jOOQ-console/src/main/java/org/jooq/debug/Breakpoint.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * Christopher Deckers, chrriis@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.debug; + +import java.io.Serializable; + +/** + * @author Christopher Deckers + */ +@SuppressWarnings("serial") +public class Breakpoint implements Serializable { + + private int id; + private StatementMatcher statementMatcher; +// private Integer hitCount; + private boolean isBreaking; + private StatementProcessor beforeExecutionProcessor; + private StatementProcessor replacementExecutionProcessor; + private StatementProcessor afterExecutionProcessor; + + public Breakpoint(int id, StatementMatcher statementMatcher, boolean isBreaking, StatementProcessor beforeExecutionProcessor, StatementProcessor replacementExecutionProcessor, StatementProcessor afterExecutionProcessor) { + this.id = id; + this.statementMatcher = statementMatcher; + this.isBreaking = isBreaking; + this.beforeExecutionProcessor = beforeExecutionProcessor; + this.replacementExecutionProcessor = replacementExecutionProcessor; + this.afterExecutionProcessor = afterExecutionProcessor; + } + + public int getID() { + return id; + } + + public StatementMatcher getStatementMatcher() { + return statementMatcher; + } + + public boolean matches(StatementInfo statementInfo) { + return statementMatcher != null && statementMatcher.matches(statementInfo); + } + + public boolean isBreaking() { + return isBreaking; + } + + public StatementProcessor getBeforeExecutionProcessor() { + return beforeExecutionProcessor; + } + + public StatementProcessor getReplacementExecutionProcessor() { + return replacementExecutionProcessor; + } + + public StatementProcessor getAfterExecutionProcessor() { + return afterExecutionProcessor; + } + +} diff --git a/jOOQ-console/src/main/java/org/jooq/debug/BreakpointAfterExecutionHit.java b/jOOQ-console/src/main/java/org/jooq/debug/BreakpointAfterExecutionHit.java new file mode 100644 index 0000000000..8e2a13781f --- /dev/null +++ b/jOOQ-console/src/main/java/org/jooq/debug/BreakpointAfterExecutionHit.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * Christopher Deckers, chrriis@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.debug; + +import java.io.Serializable; + +/** + * @author Christopher Deckers + */ +@SuppressWarnings("serial") +public class BreakpointAfterExecutionHit implements Serializable { + + private int breakpointID; + private String sql; + + public BreakpointAfterExecutionHit(int breakpointID, String sql) { + this.breakpointID = breakpointID; + this.sql = sql; + } + + public int getBreakpointID() { + return breakpointID; + } + + public String getSql() { + return sql; + } + +} diff --git a/jOOQ-console/src/main/java/org/jooq/debug/BreakpointBeforeExecutionHit.java b/jOOQ-console/src/main/java/org/jooq/debug/BreakpointBeforeExecutionHit.java new file mode 100644 index 0000000000..0df0f860de --- /dev/null +++ b/jOOQ-console/src/main/java/org/jooq/debug/BreakpointBeforeExecutionHit.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * Christopher Deckers, chrriis@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.debug; + +import java.io.Serializable; + +/** + * @author Christopher Deckers + */ +@SuppressWarnings("serial") +public class BreakpointBeforeExecutionHit implements Serializable { + + public static enum ExecutionType { + STEP_THROUGH, + RUN_OVER, + RUN, + } + + private Integer breakpointID; + private String sql; + + public BreakpointBeforeExecutionHit(int breakpointID, String sql) { + this.breakpointID = breakpointID; + this.sql = sql; + } + + /** + * @return null if the breakpoint was processed and contains an execution type. + */ + public Integer getBreakpointID() { + return breakpointID; + } + + public String getSql() { + return sql; + } + + private ExecutionType executionType = ExecutionType.RUN; + + public void setExecutionType(ExecutionType executionType, String sql) { + this.breakpointID = null; + this.executionType = executionType; + this.sql = sql; + } + + public ExecutionType getExecutionType() { + return executionType; + } + +} diff --git a/jOOQ-console/src/main/java/org/jooq/debug/BreakpointHitHandler.java b/jOOQ-console/src/main/java/org/jooq/debug/BreakpointHitHandler.java new file mode 100644 index 0000000000..86895686f9 --- /dev/null +++ b/jOOQ-console/src/main/java/org/jooq/debug/BreakpointHitHandler.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * Christopher Deckers, chrriis@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.debug; + +/** + * @author Christopher Deckers + */ +public interface BreakpointHitHandler { + + public void processBreakpointBeforeExecutionHit(BreakpointBeforeExecutionHit breakpointHit); + + public void processBreakpointAfterExecutionHit(BreakpointAfterExecutionHit 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 bba01d4321..52e39153f2 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/DebugListener.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/DebugListener.java @@ -39,20 +39,26 @@ package org.jooq.debug; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.Statement; import java.util.ArrayList; import java.util.List; import org.jooq.ExecuteContext; import org.jooq.ExecuteType; +import org.jooq.debug.BreakpointBeforeExecutionHit.ExecutionType; import org.jooq.impl.DefaultExecuteListener; +import org.jooq.impl.Factory; +/** + * @author Christopher Deckers + */ public class DebugListener extends DefaultExecuteListener { - private boolean isLogging; + private boolean hasDebuggers; @Override public void renderStart(ExecuteContext ctx) { - isLogging = !DebuggerRegistry.getDebuggerList().isEmpty(); + hasDebuggers = !DebuggerRegistry.getDebuggerList().isEmpty(); startPreparationTime = 0; aggregatedPreparationDuration = 0; startBindTime = 0; @@ -66,7 +72,7 @@ public class DebugListener extends DefaultExecuteListener { @Override public void prepareStart(ExecuteContext ctx) { - if(!isLogging) { + if(!hasDebuggers) { return; } startPreparationTime = System.currentTimeMillis(); @@ -74,7 +80,7 @@ public class DebugListener extends DefaultExecuteListener { @Override public void prepareEnd(ExecuteContext ctx) { - if(!isLogging) { + if(!hasDebuggers) { return; } aggregatedPreparationDuration += System.currentTimeMillis() - startPreparationTime; @@ -91,7 +97,7 @@ public class DebugListener extends DefaultExecuteListener { @Override public void bindStart(ExecuteContext ctx) { - if(!isLogging) { + if(!hasDebuggers) { return; } startBindTime = System.currentTimeMillis(); @@ -99,7 +105,7 @@ public class DebugListener extends DefaultExecuteListener { @Override public void bindEnd(ExecuteContext ctx) { - if(!isLogging) { + if(!hasDebuggers) { return; } endBindTime = System.currentTimeMillis(); @@ -107,80 +113,241 @@ public class DebugListener extends DefaultExecuteListener { private long startExecutionTime; private long endExecutionTime; + private String matchingSQL; + private String effectiveSQL; + private Breakpoint matchingBreakpoint; + private BreakpointHitHandler matchingBreakpointHitHandler; @Override public void executeStart(ExecuteContext ctx) { - if(!isLogging) { - return; - } + BreakpointHitHandler breakpointHitHandler = null; + List debuggerList = DebuggerRegistry.getDebuggerList(); + if(!debuggerList.isEmpty()) { + StatementInfo statementInfo = null; + bp: for(Debugger debugger: debuggerList) { + Breakpoint[] breakpoints = debugger.getBreakpoints(); + if(breakpoints != null) { + for(Breakpoint breakpoint: breakpoints) { + if(statementInfo == null) { + String[] sql = ctx.batchSQL(); + SqlQueryType sqlQueryType = SqlQueryType.detectType(sql[0]); + String parameterDescription = null; + if(sql.length == 1) { + matchingSQL = sql[0]; + PreparedStatement statement = ctx.statement(); + if(statement instanceof UsageTrackingPreparedStatement) { + parameterDescription = ((UsageTrackingPreparedStatement) statement).getParameterDescription(); + } + } else { + StringBuilder sb = new StringBuilder(); + for(int i=0; i 0) { + sb.append('\n'); + } + sb.append(sql[i]); + } + matchingSQL = sb.toString(); + } + statementInfo = new StatementInfo(sqlQueryType, sql, parameterDescription); + } + if(breakpoint.matches(statementInfo)) { + matchingBreakpoint = breakpoint; + if(breakpoint.isBreaking()) { + breakpointHitHandler = debugger.getBreakpointHitHandler(); + } + break bp; + } + } + } + } + } + // We consider raw SQL (not the parameters). If we want to match on parameters, this should be a separate matcher. + // For batched execution of in-lined statements, we aggregate the statements as a multiple-line one for matching purposes. + if(matchingBreakpoint != null) { + StatementProcessor beforeExecutionProcessor = matchingBreakpoint.getBeforeExecutionProcessor(); + if(beforeExecutionProcessor != null) { + String sql = beforeExecutionProcessor.processSQL(matchingSQL); + long subStartExecutionTime = System.currentTimeMillis(); + executeSQL(ctx, sql); + long subEndExecutionTime = System.currentTimeMillis(); + // Log result of pre-processing. + for(Debugger listener: debuggerList) { + LoggingListener loggingListener = listener.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(); + if(loggingStatementMatchers == null) { + loggingListener.logQueries(queryLoggingData); + } else for(StatementMatcher statementMatcher: loggingStatementMatchers) { + if(statementMatcher.matches(queryLoggingData)) { + loggingListener.logQueries(queryLoggingData); + break; + } + } + } + } + } + String mainSQL = null; + StatementProcessor replacementExecutionProcessor = matchingBreakpoint.getReplacementExecutionProcessor(); + if(replacementExecutionProcessor != null) { + mainSQL = replacementExecutionProcessor.processSQL(matchingSQL); + try { + ctx.statement().close(); + ctx.sql(mainSQL); + ctx.statement(ctx.getConnection().prepareStatement(mainSQL)); + } catch(Exception e) { + // TODO: how to process properly breakpoint errors?? + throw new RuntimeException(e); + } + } + ExecutionType executionType = BreakpointBeforeExecutionHit.ExecutionType.RUN; + if(breakpointHitHandler != null) { + 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); + executionType = breakpointBeforeExecutionHit.getExecutionType(); + } + switch(executionType) { + case STEP_THROUGH: { + matchingBreakpointHitHandler = breakpointHitHandler; + break; + } + case RUN_OVER: { + try { + ctx.statement().close(); + // Better return possibility? Based on originating query? + String sql = new Factory(ctx.getDialect()).selectZero().where("1 = 2").getSQL(); + ctx.sql(sql); + ctx.statement(ctx.getConnection().prepareStatement(sql)); + } catch(Exception e) { + // TODO: how to process properly breakpoint errors?? + throw new RuntimeException(e); + } + break; + } + } + } + if(!hasDebuggers) { + return; + } startExecutionTime = System.currentTimeMillis(); } + private void executeSQL(ExecuteContext ctx, String sql) { + Statement statement = null; + try { + statement = ctx.getConnection().createStatement(); + statement.execute(sql); + } catch(Exception e) { + // TODO: how to process properly breakpoint errors?? + throw new RuntimeException(e); + } finally { + if(statement != null) { + try { + statement.close(); + } catch(Exception e) { + // No error for closing problems. + e.printStackTrace(); + } + } + } + } + @Override public void executeEnd(ExecuteContext ctx) { - if(!isLogging) { + if(!hasDebuggers) { return; } endExecutionTime = System.currentTimeMillis(); - List debuggerList = DebuggerRegistry.getDebuggerList(); - if(debuggerList.isEmpty()) { - return; + if(matchingBreakpointHitHandler != null) { + matchingBreakpointHitHandler.processBreakpointAfterExecutionHit(new BreakpointAfterExecutionHit(matchingBreakpoint.getID(), effectiveSQL)); } - boolean hasListener = false; - for(Debugger listener: debuggerList) { - LoggingListener loggingListener = listener.getLoggingListener(); - if(loggingListener != null) { - hasListener = true; - break; - } - } - if(!hasListener) { - return; - } - ResultSet resultSet = ctx.resultSet(); - String[] sql = ctx.batchSQL(); - SqlQueryType sqlQueryType = SqlQueryType.detectType(sql[0]); - String parameterDescription = null; - if(sql.length == 1) { - PreparedStatement statement = ctx.statement(); - if(statement instanceof UsageTrackingPreparedStatement) { - parameterDescription = ((UsageTrackingPreparedStatement) statement).getParameterDescription(); + List debuggerList = DebuggerRegistry.getDebuggerList(); + if(!debuggerList.isEmpty()) { + boolean hasListener = false; + for(Debugger debugger: debuggerList) { + LoggingListener loggingListener = debugger.getLoggingListener(); + if(loggingListener != null) { + hasListener = true; + break; + } } - } - QueryLoggingData queryLoggingData = new QueryLoggingData(sqlQueryType, sql, parameterDescription, startPreparationTime == 0? null: aggregatedPreparationDuration, startBindTime == 0? null: endBindTime - startBindTime, endExecutionTime - startExecutionTime); - final List loggingListenerList = new ArrayList(debuggerList.size()); - for(Debugger listener: debuggerList) { - LoggingListener loggingListener = listener.getLoggingListener(); - if(loggingListener != null) { - StatementMatcher[] loggingStatementMatchers = listener.getLoggingStatementMatchers(); - if(loggingStatementMatchers == null) { - loggingListenerList.add(loggingListener); - loggingListener.logQueries(queryLoggingData); - } else for(StatementMatcher statementMatcher: loggingStatementMatchers) { - if(statementMatcher.matches(queryLoggingData)) { - loggingListenerList.add(loggingListener); - loggingListener.logQueries(queryLoggingData); - break; + if(hasListener) { + String[] sql = ctx.batchSQL(); + SqlQueryType sqlQueryType = SqlQueryType.detectType(sql[0]); + String parameterDescription = null; + if(sql.length == 1) { + PreparedStatement statement = ctx.statement(); + if(statement instanceof UsageTrackingPreparedStatement) { + parameterDescription = ((UsageTrackingPreparedStatement) statement).getParameterDescription(); } } + QueryLoggingData queryLoggingData = new QueryLoggingData(sqlQueryType, sql, parameterDescription, startPreparationTime == 0? null: aggregatedPreparationDuration, startBindTime == 0? null: endBindTime - startBindTime, endExecutionTime - startExecutionTime); + final List loggingListenerList = new ArrayList(debuggerList.size()); + for(Debugger listener: debuggerList) { + LoggingListener loggingListener = listener.getLoggingListener(); + if(loggingListener != null) { + StatementMatcher[] loggingStatementMatchers = listener.getLoggingStatementMatchers(); + if(loggingStatementMatchers == null) { + loggingListenerList.add(loggingListener); + loggingListener.logQueries(queryLoggingData); + } else for(StatementMatcher statementMatcher: loggingStatementMatchers) { + if(statementMatcher.matches(queryLoggingData)) { + loggingListenerList.add(loggingListener); + loggingListener.logQueries(queryLoggingData); + break; + } + } + } + } + ResultSet resultSet = ctx.resultSet(); + if(resultSet != null && !loggingListenerList.isEmpty()) { + final int queryLoggingDataID = queryLoggingData.getID(); + ResultSet newResultSet = new UsageTrackingResultSet(resultSet) { + @Override + protected void notifyData(long lifeTime, int readRows, int readCount, int writeCount) { + ResultSetLoggingData resultSetLoggingData = null; + for(LoggingListener loggingListener: loggingListenerList) { + if(resultSetLoggingData == null) { + resultSetLoggingData = new ResultSetLoggingData(lifeTime, readRows, readCount, writeCount); + } + loggingListener.logResultSet(queryLoggingDataID, resultSetLoggingData); + } + } + }; + ctx.resultSet(newResultSet); + } } } - if(resultSet != null && !loggingListenerList.isEmpty()) { - final int queryLoggingDataID = queryLoggingData.getID(); - ResultSet newResultSet = new UsageTrackingResultSet(resultSet) { - @Override - protected void notifyData(long lifeTime, int readRows, int readCount, int writeCount) { - ResultSetLoggingData resultSetLoggingData = null; - for(LoggingListener loggingListener: loggingListenerList) { - if(resultSetLoggingData == null) { - resultSetLoggingData = new ResultSetLoggingData(lifeTime, readRows, readCount, writeCount); - } - loggingListener.logResultSet(queryLoggingDataID, resultSetLoggingData); - } - } - }; - ctx.resultSet(newResultSet); - } + if(matchingBreakpoint != null) { + StatementProcessor afterExecutionProcessor = matchingBreakpoint.getAfterExecutionProcessor(); + matchingBreakpoint = null; + if(afterExecutionProcessor != null) { + String sql = afterExecutionProcessor.processSQL(matchingSQL); + long subStartExecutionTime = System.currentTimeMillis(); + executeSQL(ctx, sql); + long subEndExecutionTime = System.currentTimeMillis(); + // Log result of pre-processing. + for(Debugger listener: debuggerList) { + LoggingListener loggingListener = listener.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(); + if(loggingStatementMatchers == null) { + loggingListener.logQueries(queryLoggingData); + } else for(StatementMatcher statementMatcher: loggingStatementMatchers) { + if(statementMatcher.matches(queryLoggingData)) { + loggingListener.logQueries(queryLoggingData); + break; + } + } + } + } + } + } } // private long startFetchTime; 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 81ed73eb08..8e7dad0673 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/Debugger.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/Debugger.java @@ -37,7 +37,9 @@ package org.jooq.debug; - +/** + * @author Christopher Deckers + */ public interface Debugger { /** @@ -51,6 +53,14 @@ public interface Debugger { public StatementMatcher[] getLoggingStatementMatchers(); + public void setBreakpoints(Breakpoint[] breakpoints); + + public void setBreakpointHitHandler(BreakpointHitHandler breakpointHitHandler); + + public BreakpointHitHandler getBreakpointHitHandler(); + + public Breakpoint[] getBreakpoints(); + public boolean isExecutionSupported(); public StatementExecutor createStatementExecutor(); 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 f3c98af55a..ffc687494d 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/LocalDebugger.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/LocalDebugger.java @@ -39,6 +39,9 @@ package org.jooq.debug; import org.jooq.debug.console.DatabaseDescriptor; +/** + * @author Christopher Deckers + */ public class LocalDebugger implements Debugger { private DatabaseDescriptor databaseDescriptor; @@ -48,27 +51,74 @@ public class LocalDebugger implements Debugger { } private LoggingListener loggingListener; + private final Object LOGGING_LISTENER_LOCK = new Object(); @Override public void setLoggingListener(LoggingListener loggingListener) { - this.loggingListener = loggingListener; + synchronized (LOGGING_LISTENER_LOCK) { + this.loggingListener = loggingListener; + } } @Override public LoggingListener getLoggingListener() { - return loggingListener; + synchronized (LOGGING_LISTENER_LOCK) { + return loggingListener; + } } private StatementMatcher[] loggingStatementMatchers; + private final Object LOGGING_STATEMENT_MATCHERS_LOCK = new Object(); @Override public void setLoggingStatementMatchers(StatementMatcher[] loggingStatementMatchers) { - this.loggingStatementMatchers = loggingStatementMatchers; + synchronized (LOGGING_STATEMENT_MATCHERS_LOCK) { + this.loggingStatementMatchers = loggingStatementMatchers; + } } @Override public StatementMatcher[] getLoggingStatementMatchers() { - return loggingStatementMatchers; + synchronized (LOGGING_STATEMENT_MATCHERS_LOCK) { + return loggingStatementMatchers; + } + } + + private Breakpoint[] breakpoints; + private final Object BREAKPOINT_LOCK = new Object(); + + @Override + public void setBreakpoints(Breakpoint[] breakpoints) { + if(breakpoints != null && breakpoints.length == 0) { + breakpoints = null; + } + synchronized (BREAKPOINT_LOCK) { + this.breakpoints = breakpoints; + } + } + + @Override + public Breakpoint[] getBreakpoints() { + synchronized (BREAKPOINT_LOCK) { + return breakpoints; + } + } + + private BreakpointHitHandler breakpointHitHandler; + private final Object BREAKPOINT_HIT_HANDLER_LOCK = new Object(); + + @Override + public void setBreakpointHitHandler(BreakpointHitHandler breakpointHitHandler) { + synchronized (BREAKPOINT_HIT_HANDLER_LOCK) { + this.breakpointHitHandler = breakpointHitHandler; + } + } + + @Override + public BreakpointHitHandler getBreakpointHitHandler() { + synchronized (BREAKPOINT_HIT_HANDLER_LOCK) { + return breakpointHitHandler; + } } @Override 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 a2180ade94..24112250db 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/LocalStatementExecutor.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/LocalStatementExecutor.java @@ -68,6 +68,9 @@ import org.jooq.debug.StatementExecutionResultSetResult.TypeInfo; import org.jooq.debug.console.DatabaseDescriptor; import org.jooq.debug.console.misc.Utils; +/** + * @author Christopher Deckers + */ public class LocalStatementExecutor implements StatementExecutor { private DatabaseDescriptor databaseDescriptor; diff --git a/jOOQ-console/src/main/java/org/jooq/debug/LoggingListener.java b/jOOQ-console/src/main/java/org/jooq/debug/LoggingListener.java index 5702ef10a2..3f2e17ae65 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/LoggingListener.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/LoggingListener.java @@ -37,6 +37,9 @@ package org.jooq.debug; +/** + * @author Christopher Deckers + */ public interface LoggingListener { public void logQueries(QueryLoggingData queryLoggingData); 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 0454282e86..ba96b899dc 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/QueryLoggingData.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/QueryLoggingData.java @@ -39,7 +39,6 @@ package org.jooq.debug; import java.util.concurrent.atomic.AtomicInteger; - /** * @author Christopher Deckers */ diff --git a/jOOQ-console/src/main/java/org/jooq/debug/StatementExecution.java b/jOOQ-console/src/main/java/org/jooq/debug/StatementExecution.java index db308f0f64..e775934dcc 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/StatementExecution.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/StatementExecution.java @@ -37,7 +37,9 @@ package org.jooq.debug; - +/** + * @author Christopher Deckers + */ public class StatementExecution { private long executionDuration; diff --git a/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutionMessageResult.java b/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutionMessageResult.java index f3c4f76644..6d81625ad3 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutionMessageResult.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutionMessageResult.java @@ -39,6 +39,9 @@ package org.jooq.debug; import java.io.PrintWriter; import java.io.StringWriter; +/** + * @author Christopher Deckers + */ public class StatementExecutionMessageResult implements StatementExecutionResult { private String message; diff --git a/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutionResult.java b/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutionResult.java index 37efef589d..33a6102341 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutionResult.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutionResult.java @@ -37,6 +37,9 @@ package org.jooq.debug; +/** + * @author Christopher Deckers + */ public interface StatementExecutionResult { } diff --git a/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutionResultSetResult.java b/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutionResultSetResult.java index 9f489b83b6..318077db92 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutionResultSetResult.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutionResultSetResult.java @@ -42,6 +42,9 @@ import java.sql.SQLException; import org.jooq.debug.console.misc.Utils; +/** + * @author Christopher Deckers + */ public class StatementExecutionResultSetResult implements StatementExecutionResult { public static class TypeInfo { diff --git a/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutor.java b/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutor.java index 9f21414706..7ddf320f04 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutor.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/StatementExecutor.java @@ -37,6 +37,9 @@ package org.jooq.debug; +/** + * @author Christopher Deckers + */ public interface StatementExecutor { public StatementExecution execute(String sql, int maxRSRowsParsing, int retainParsedRSDataRowCountThreshold); diff --git a/jOOQ-console/src/main/java/org/jooq/debug/StatementInfo.java b/jOOQ-console/src/main/java/org/jooq/debug/StatementInfo.java index fc94e0ae37..a8a90ce118 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/StatementInfo.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/StatementInfo.java @@ -38,6 +38,9 @@ package org.jooq.debug; import java.io.Serializable; +/** + * @author Christopher Deckers + */ @SuppressWarnings("serial") public class StatementInfo implements Serializable { diff --git a/jOOQ-console/src/main/java/org/jooq/debug/StatementMatcher.java b/jOOQ-console/src/main/java/org/jooq/debug/StatementMatcher.java index 5a3cde522c..15469b4a98 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/StatementMatcher.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/StatementMatcher.java @@ -42,6 +42,9 @@ import java.util.Set; import org.jooq.debug.console.misc.TextMatcher; +/** + * @author Christopher Deckers + */ @SuppressWarnings("serial") public class StatementMatcher implements Serializable { diff --git a/jOOQ-console/src/main/java/org/jooq/debug/StatementProcessor.java b/jOOQ-console/src/main/java/org/jooq/debug/StatementProcessor.java new file mode 100644 index 0000000000..818520ccf4 --- /dev/null +++ b/jOOQ-console/src/main/java/org/jooq/debug/StatementProcessor.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * Christopher Deckers, chrriis@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.debug; + +import java.io.Serializable; + +import org.jooq.debug.console.misc.Utils; + +/** + * @author Christopher Deckers + */ +@SuppressWarnings("serial") +public class StatementProcessor implements Serializable { + + public static enum ProcessorExecutionType { + STATIC("Static SQL"), + SED_LIKE_REG_EXP("Sed-like Reg. Exp."), + ; + private String name; + private ProcessorExecutionType(String name) { + this.name = name; + } + @Override + public String toString() { + return name; + } + } + + private ProcessorExecutionType type; + private String text; + + public StatementProcessor(ProcessorExecutionType type, String text) { + this.type = type; + this.text = text; + } + + public ProcessorExecutionType getType() { + return type; + } + + public String getText() { + return text; + } + + public String processSQL(String sql) { + switch(type) { + case STATIC: return text; + case SED_LIKE_REG_EXP: return Utils.applySedRegularExpression(sql, text); + } + throw new IllegalStateException(); + } + +} diff --git a/jOOQ-console/src/main/java/org/jooq/debug/UsageTrackingResultSet.java b/jOOQ-console/src/main/java/org/jooq/debug/UsageTrackingResultSet.java index 5e38e7d969..727be4585e 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/UsageTrackingResultSet.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/UsageTrackingResultSet.java @@ -58,6 +58,9 @@ import java.sql.Timestamp; import java.util.Calendar; import java.util.Map; +/** + * @author Christopher Deckers + */ abstract class UsageTrackingResultSet implements ResultSet { private ResultSet resultSet; diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/BreakpointEditor.java b/jOOQ-console/src/main/java/org/jooq/debug/console/BreakpointEditor.java new file mode 100644 index 0000000000..f98b282bc2 --- /dev/null +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/BreakpointEditor.java @@ -0,0 +1,311 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * Christopher Deckers, chrriis@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.debug.console; + +import java.awt.FlowLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +import org.jooq.debug.Breakpoint; +import org.jooq.debug.SqlQueryType; +import org.jooq.debug.StatementMatcher; +import org.jooq.debug.StatementProcessor; +import org.jooq.debug.console.misc.TextMatcher; + +/** + * @author Christopher Deckers + */ +@SuppressWarnings("serial") +public class BreakpointEditor extends JPanel { + + private static final String BREAK = "Break on match"; + private static final String PROCESS = "Process on match"; + + private int id; + private JCheckBox threadNameTextMatcherCheckBox; + private TextMatcherPane threadNameTextMatcherPane; + private JCheckBox statementTextMatcherCheckBox; + private TextMatcherPane statementTextMatcherPane; + private JCheckBox statementTypeCheckBox; + private JCheckBox statementTypeSelectCheckBox; + private JCheckBox statementTypeUpdateCheckBox; + private JCheckBox statementTypeInsertCheckBox; + private JCheckBox statementTypeDeleteCheckBox; + private JCheckBox statementTypeOtherCheckBox; + private JComboBox breakpointTypeComboBox; + private JPanel processorPane; + private JCheckBox beforeExecutionCheckBox; + private StatementProcessorPane beforeExecutionProcessorPane; + private JRadioButton executeRadioButton; +// private JRadioButton doNotExecuteRadioButton; + private JRadioButton replaceExecutionRadioButton; + private StatementProcessorPane replacementExecutionProcessorPane; + private JCheckBox afterExecutionCheckBox; + private StatementProcessorPane afterExecutionProcessorPane; + + public BreakpointEditor(final DebuggerPane debuggerPane, Breakpoint breakpoint) { + super(new GridBagLayout()); + StatementMatcher statementMatcher = breakpoint.getStatementMatcher(); + id = breakpoint.getID(); + if(statementMatcher == null) { + statementMatcher = new StatementMatcher(null, null, null, true); + } + int y = 0; + TextMatcher statementTextMatcher = statementMatcher.getStatementTextMatcher(); + statementTextMatcherCheckBox = new JCheckBox("Statement", statementTextMatcher != null); + statementTextMatcherCheckBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + adjustStates(); + } + }); + add(statementTextMatcherCheckBox, new GridBagConstraints(0, y, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + statementTextMatcherPane = new TextMatcherPane(statementTextMatcher); + add(statementTextMatcherPane, new GridBagConstraints(1, y, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 0, 0), 0, 0)); + y++; + Set queryTypeSet = statementMatcher.getQueryTypeSet(); + statementTypeCheckBox = new JCheckBox("Type", queryTypeSet != null); + statementTypeCheckBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + adjustStates(); + } + }); + add(statementTypeCheckBox, new GridBagConstraints(0, y, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + JPanel typesPane = new JPanel(new GridBagLayout()); + statementTypeSelectCheckBox = new JCheckBox("SELECT", queryTypeSet != null && queryTypeSet.contains(SqlQueryType.SELECT)); + typesPane.add(statementTypeSelectCheckBox, new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + statementTypeUpdateCheckBox = new JCheckBox("UPDATE", queryTypeSet != null && queryTypeSet.contains(SqlQueryType.UPDATE)); + typesPane.add(statementTypeUpdateCheckBox, new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 2, 0, 0), 0, 0)); + statementTypeInsertCheckBox = new JCheckBox("INSERT", queryTypeSet != null && queryTypeSet.contains(SqlQueryType.INSERT)); + typesPane.add(statementTypeInsertCheckBox, new GridBagConstraints(2, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 2, 0, 0), 0, 0)); + statementTypeDeleteCheckBox = new JCheckBox("DELETE", queryTypeSet != null && queryTypeSet.contains(SqlQueryType.DELETE)); + typesPane.add(statementTypeDeleteCheckBox, new GridBagConstraints(3, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 2, 0, 0), 0, 0)); + statementTypeOtherCheckBox = new JCheckBox("OTHER", queryTypeSet != null && queryTypeSet.contains(SqlQueryType.OTHER)); + typesPane.add(statementTypeOtherCheckBox, new GridBagConstraints(4, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 2, 0, 0), 0, 0)); + add(typesPane, new GridBagConstraints(1, y, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 0), 0, 0)); + y++; + TextMatcher threadNameTextMatcher = statementMatcher.getThreadNameTextMatcher(); + threadNameTextMatcherCheckBox = new JCheckBox("Thread name", threadNameTextMatcher != null); + threadNameTextMatcherCheckBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + adjustStates(); + } + }); + add(threadNameTextMatcherCheckBox, new GridBagConstraints(0, y, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + threadNameTextMatcherPane = new TextMatcherPane(threadNameTextMatcher); + add(threadNameTextMatcherPane, new GridBagConstraints(1, y, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 0), 0, 0)); + y++; + breakpointTypeComboBox = new JComboBox(new Object[] {BREAK, PROCESS}); + breakpointTypeComboBox.setSelectedItem(breakpoint.isBreaking()? BREAK: PROCESS); + breakpointTypeComboBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + adjustStates(); + } + }); + add(breakpointTypeComboBox, new GridBagConstraints(0, y, 2, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + y++; + processorPane = new JPanel(new GridBagLayout()); + populateProcessorPane(breakpoint); + add(processorPane, new GridBagConstraints(0, y, 2, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 20, 0, 0), 0, 0)); + y++; + JPanel buttonPane = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(20, 5, 5, 5)); + JButton applyButton = new JButton("Apply changes"); + applyButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + debuggerPane.modifyBreakpoint(getBreakpoint()); + } + }); + buttonPane.add(applyButton); + add(buttonPane, new GridBagConstraints(0, y, 2, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 20, 0, 0), 0, 0)); + add(Box.createGlue(), new GridBagConstraints(0, Short.MAX_VALUE, 1, 1, 0, 1, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + adjustStates(); + } + + private void populateProcessorPane(Breakpoint breakpoint) { + int y = 0; + StatementProcessor beforeExecutionProcessor = breakpoint.getBeforeExecutionProcessor(); + beforeExecutionCheckBox = new JCheckBox("Execute before: "); + beforeExecutionCheckBox.setSelected(beforeExecutionProcessor != null); + beforeExecutionCheckBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + adjustStates(); + } + }); + processorPane.add(beforeExecutionCheckBox, new GridBagConstraints(0, y, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + beforeExecutionProcessorPane = new StatementProcessorPane(beforeExecutionProcessor); + processorPane.add(beforeExecutionProcessorPane, new GridBagConstraints(1, y, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 0, 0), 0, 0)); + y++; + ButtonGroup executionButtonGroup = new ButtonGroup(); + StatementProcessor replacementExecutionProcessor = breakpoint.getReplacementExecutionProcessor(); + executeRadioButton = new JRadioButton("Execute"); + executionButtonGroup.add(executeRadioButton); + processorPane.add(executeRadioButton, new GridBagConstraints(0, y, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + y++; +// doNotExecuteRadioButton = new JRadioButton("Do not execute"); +// executionButtonGroup.add(doNotExecuteRadioButton); +// processorPane.add(doNotExecuteRadioButton, new GridBagConstraints(0, y, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); +// y++; + replaceExecutionRadioButton = new JRadioButton("Replace with: "); + executionButtonGroup.add(replaceExecutionRadioButton); + if(replacementExecutionProcessor != null) { + replaceExecutionRadioButton.setSelected(true); + } else { + executeRadioButton.setSelected(true); + } + processorPane.add(replaceExecutionRadioButton, new GridBagConstraints(0, y, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + replacementExecutionProcessorPane = new StatementProcessorPane(breakpoint.getReplacementExecutionProcessor()); + processorPane.add(replacementExecutionProcessorPane, new GridBagConstraints(1, y, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 0, 0), 0, 0)); + executeRadioButton.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + adjustStates(); + } + }); + replaceExecutionRadioButton.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + adjustStates(); + } + }); + y++; + StatementProcessor afterExecutionProcessor = breakpoint.getAfterExecutionProcessor(); + afterExecutionCheckBox = new JCheckBox("Execute after: "); + afterExecutionCheckBox.setSelected(afterExecutionProcessor != null); + afterExecutionCheckBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + adjustStates(); + } + }); + processorPane.add(afterExecutionCheckBox, new GridBagConstraints(0, y, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + afterExecutionProcessorPane = new StatementProcessorPane(afterExecutionProcessor); + processorPane.add(afterExecutionProcessorPane, new GridBagConstraints(1, y, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 0, 0), 0, 0)); + } + + private void adjustStates() { + boolean isActive = true; + statementTextMatcherCheckBox.setEnabled(isActive); + statementTypeCheckBox.setEnabled(isActive); + threadNameTextMatcherCheckBox.setEnabled(isActive); + statementTextMatcherPane.setLocked(!isActive || !statementTextMatcherCheckBox.isSelected()); + statementTypeSelectCheckBox.setEnabled(isActive && statementTypeCheckBox.isSelected()); + statementTypeUpdateCheckBox.setEnabled(isActive && statementTypeCheckBox.isSelected()); + statementTypeInsertCheckBox.setEnabled(isActive && statementTypeCheckBox.isSelected()); + statementTypeDeleteCheckBox.setEnabled(isActive && statementTypeCheckBox.isSelected()); + statementTypeOtherCheckBox.setEnabled(isActive && statementTypeCheckBox.isSelected()); + threadNameTextMatcherPane.setLocked(!isActive || !threadNameTextMatcherCheckBox.isSelected()); + breakpointTypeComboBox.setEnabled(isActive && (statementTextMatcherCheckBox.isSelected() || statementTypeCheckBox.isSelected() || threadNameTextMatcherCheckBox.isSelected())); + processorPane.setVisible(isActive && breakpointTypeComboBox.getSelectedItem() == PROCESS); + beforeExecutionCheckBox.setEnabled(isActive); + beforeExecutionProcessorPane.setLocked(!isActive || !beforeExecutionCheckBox.isSelected()); + executeRadioButton.setEnabled(isActive); + replaceExecutionRadioButton.setEnabled(isActive); +// doNotExecuteRadioButton; + replacementExecutionProcessorPane.setLocked(!isActive || !replaceExecutionRadioButton.isSelected()); + afterExecutionCheckBox.setEnabled(isActive); + afterExecutionProcessorPane.setLocked(!isActive || !afterExecutionCheckBox.isSelected()); + } + + public StatementMatcher getStatementMatcher() { + boolean isActive = true;//activeCheckBox.isSelected(); + TextMatcher threadNameTextMatcher = threadNameTextMatcherCheckBox.isSelected()? threadNameTextMatcherPane.getTextMatcher(): null; + TextMatcher statementTextMatcher = statementTextMatcherCheckBox.isSelected()? statementTextMatcherPane.getTextMatcher(): null; + Set queryTypeSet; + if(statementTypeCheckBox.isSelected()) { + List typeList = new ArrayList(); + if(statementTypeSelectCheckBox.isSelected()) { + typeList.add(SqlQueryType.SELECT); + } + if(statementTypeUpdateCheckBox.isSelected()) { + typeList.add(SqlQueryType.UPDATE); + } + if(statementTypeInsertCheckBox.isSelected()) { + typeList.add(SqlQueryType.INSERT); + } + if(statementTypeDeleteCheckBox.isSelected()) { + typeList.add(SqlQueryType.DELETE); + } + if(statementTypeOtherCheckBox.isSelected()) { + typeList.add(SqlQueryType.OTHER); + } + queryTypeSet = EnumSet.copyOf(typeList); + } else { + queryTypeSet = null; + } + return new StatementMatcher(threadNameTextMatcher, statementTextMatcher, queryTypeSet, isActive); + } + + public Breakpoint getBreakpoint() { + StatementMatcher statementMatcher = getStatementMatcher(); +// Integer hitCount; + boolean isBreaking = breakpointTypeComboBox.getSelectedItem() == BREAK; + StatementProcessor beforeExecutionProcessor = null; + StatementProcessor replacementExecutionProcessor = null; + StatementProcessor afterExecutionProcessor = null; + if(!isBreaking) { + beforeExecutionProcessor = beforeExecutionCheckBox.isSelected()? beforeExecutionProcessorPane.getStatementProcessor(): null; + replacementExecutionProcessor = replaceExecutionRadioButton.isSelected()? replacementExecutionProcessorPane.getStatementProcessor(): null; + afterExecutionProcessor = afterExecutionCheckBox.isSelected()? afterExecutionProcessorPane.getStatementProcessor(): null; + } + return new Breakpoint(id, statementMatcher, isBreaking, beforeExecutionProcessor, replacementExecutionProcessor, afterExecutionProcessor); + } + +} diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/Console.java b/jOOQ-console/src/main/java/org/jooq/debug/console/Console.java index 42b73e6e65..da91186fea 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/console/Console.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/Console.java @@ -103,7 +103,7 @@ public class Console extends JFrame { private JTabbedPane mainTabbedPane; private JTabbedPane editorTabbedPane; - public Console(DatabaseDescriptor editorDatabaseDescriptor, boolean isShowingLoggingTab) { + public Console(DatabaseDescriptor editorDatabaseDescriptor, boolean isShowingLoggingTab, boolean isShowingDebugger) { debugger = new LocalDebugger(editorDatabaseDescriptor); // Local debugger registration is managed by the console since it hides the debugger. addWindowListener(new WindowAdapter() { @@ -116,20 +116,16 @@ public class Console extends JFrame { DebuggerRegistry.removeSqlQueryDebugger(debugger); } }); - init(isShowingLoggingTab); + init(isShowingLoggingTab, isShowingDebugger); } - public Console(final Debugger debugger, boolean isShowingLoggingTab) { + public Console(final Debugger debugger, boolean isShowingLoggingTab, boolean isShowingDebugger) { this.debugger = debugger; // Local debugger registration is handled externally if needed (e.g.: remote client must not be registered). - init(isShowingLoggingTab); + init(isShowingLoggingTab, isShowingDebugger); } - /** - * @param debugger - * @param isShowingLoggingTab - */ - private void init(boolean isShowingLoggingTab) { + private void init(boolean isShowingLoggingTab, boolean isShowingDebugger) { setDefaultCloseOperation(DISPOSE_ON_CLOSE); JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); @@ -233,6 +229,9 @@ public class Console extends JFrame { if(isShowingLoggingTab) { addLoggerTab(); } + if(isShowingDebugger) { + addDebuggerTab(); + } getContentPane().add(mainTabbedPane, BorderLayout.CENTER); setLocationByPlatform(true); setSize(800, 600); @@ -263,6 +262,14 @@ public class Console extends JFrame { } } + private DebuggerPane sqlDebuggerPane; + + private void addDebuggerTab() { + sqlDebuggerPane = new DebuggerPane(debugger); + mainTabbedPane.addTab("Debugger", sqlDebuggerPane); + + } + private LoggerPane sqlLoggerPane; private void addLoggerTab() { @@ -407,7 +414,7 @@ public class Console extends JFrame { } public static void openConsole(DatabaseDescriptor databaseDescriptor, boolean isLoggingActive) { - Console sqlConsoleFrame = new Console(databaseDescriptor, true); + Console sqlConsoleFrame = new Console(databaseDescriptor, true, true); sqlConsoleFrame.setLoggingActive(isLoggingActive); sqlConsoleFrame.setVisible(true); } @@ -506,7 +513,7 @@ public class Console extends JFrame { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - Console sqlConsoleFrame = new Console(debugger, true); + Console sqlConsoleFrame = new Console(debugger, true, true); sqlConsoleFrame.setDefaultCloseOperation(EXIT_ON_CLOSE); sqlConsoleFrame.setVisible(true); } 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 new file mode 100644 index 0000000000..338ad9e32a --- /dev/null +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/DebuggerPane.java @@ -0,0 +1,240 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * Christopher Deckers, chrriis@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.debug.console; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTextField; +import javax.swing.JTree; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; + +import org.jooq.debug.Breakpoint; +import org.jooq.debug.Debugger; +import org.jooq.debug.console.misc.CheckBoxNode; +import org.jooq.debug.console.misc.CheckBoxNodeEditor; +import org.jooq.debug.console.misc.CheckBoxNodeRenderer; + +/** + * @author Christopher Deckers + */ +@SuppressWarnings("serial") +public class DebuggerPane extends JPanel { + + private Debugger debugger; + private DefaultMutableTreeNode rootNode; + private JTree breakpointTree; + private DefaultTreeModel breakpointTreeModel; + private JPanel eastPane; + + public DebuggerPane(Debugger debugger) { + super(new BorderLayout()); + this.debugger = debugger; + JPanel westPane = new JPanel(new BorderLayout()); + JPanel breakpointAddPane = new JPanel(new GridBagLayout()); + final JTextField addBreakpointTextField = new JTextField(7); + breakpointAddPane.add(addBreakpointTextField, new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + final JButton addBreakpointButton = new JButton("Add"); + addBreakpointButton.setEnabled(false); + breakpointAddPane.add(addBreakpointButton, new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 2, 0, 0), 0, 0)); + addBreakpointTextField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + adjustStates(); + } + @Override + public void insertUpdate(DocumentEvent e) { + adjustStates(); + } + @Override + public void changedUpdate(DocumentEvent e) { + adjustStates(); + } + private void adjustStates() { + // TODO: restrict to unique names? + boolean isEnabled = addBreakpointTextField.getText().length() > 0; + addBreakpointButton.setEnabled(isEnabled); + } + }); + ActionListener addBreakpointActionListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String name = addBreakpointTextField.getText(); + addBreakpointTextField.setText(""); + addBreakpoint(name); + } + }; + addBreakpointTextField.addActionListener(addBreakpointActionListener); + addBreakpointButton.addActionListener(addBreakpointActionListener); + westPane.add(breakpointAddPane, BorderLayout.NORTH); + rootNode = new DefaultMutableTreeNode(); + breakpointTreeModel = new DefaultTreeModel(rootNode) { + @Override + public void valueForPathChanged(TreePath path, Object newValue) { + if(newValue instanceof CheckBoxNode) { + CheckBoxNode node = (CheckBoxNode)path.getLastPathComponent(); + node.setSelected(((CheckBoxNode) newValue).isSelected()); + super.valueForPathChanged(path, node.getUserObject()); + } + } + }; + breakpointTree = new JTree(breakpointTreeModel); + breakpointTree.setRootVisible(false); + breakpointTree.setCellRenderer(new CheckBoxNodeRenderer()); + breakpointTree.setCellEditor(new CheckBoxNodeEditor(breakpointTree)); + breakpointTree.setEditable(true); + westPane.add(new JScrollPane(breakpointTree), BorderLayout.CENTER); + JPanel breakpointRemovePane = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0)); + final JButton removeBreakpointButton = new JButton("Remove"); + removeBreakpointButton.setEnabled(false); + removeBreakpointButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + TreePath[] paths = breakpointTree.getSelectionPaths(); + boolean isValid = paths != null && paths.length > 0; + if(isValid) { + for(int i=0; i 0; + if(isValid) { + for(int i=0; i breakpointList = new ArrayList(); + int childCount = rootNode.getChildCount(); + for(int i=0; i queryTypeSet = statementMatcher.getQueryTypeSet(); statementTypeCheckBox = new JCheckBox("Type", queryTypeSet != null); diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/StatementMatchersDialogBox.java b/jOOQ-console/src/main/java/org/jooq/debug/console/StatementMatchersDialogBox.java index 124f188f21..5d8e7d2b14 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/console/StatementMatchersDialogBox.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/StatementMatchersDialogBox.java @@ -53,6 +53,9 @@ import javax.swing.SwingUtilities; import org.jooq.debug.Debugger; import org.jooq.debug.StatementMatcher; +/** + * @author Christopher Deckers + */ @SuppressWarnings("serial") public class StatementMatchersDialogBox extends JDialog { diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/StatementMatchersPane.java b/jOOQ-console/src/main/java/org/jooq/debug/console/StatementMatchersPane.java index 5b18e5ee20..f816f5a147 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/console/StatementMatchersPane.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/StatementMatchersPane.java @@ -51,6 +51,9 @@ import javax.swing.Scrollable; import org.jooq.debug.StatementMatcher; +/** + * @author Christopher Deckers + */ @SuppressWarnings("serial") public class StatementMatchersPane extends JPanel { diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/StatementProcessorPane.java b/jOOQ-console/src/main/java/org/jooq/debug/console/StatementProcessorPane.java new file mode 100644 index 0000000000..d549518bf7 --- /dev/null +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/StatementProcessorPane.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * Christopher Deckers, chrriis@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.debug.console; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +import javax.swing.JComboBox; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.jooq.debug.StatementProcessor; + +/** + * @author Christopher Deckers + */ +@SuppressWarnings("serial") +public class StatementProcessorPane extends JPanel { + + private JComboBox processorTypeComboBox; + private JTextField processorTextField; + + public StatementProcessorPane(StatementProcessor statementProcessor) { + super(new GridBagLayout()); + if(statementProcessor == null) { + statementProcessor = new StatementProcessor(StatementProcessor.ProcessorExecutionType.STATIC, ""); + } + processorTypeComboBox = new JComboBox(StatementProcessor.ProcessorExecutionType.values()); + processorTypeComboBox.setSelectedItem(statementProcessor.getType()); + add(processorTypeComboBox, new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + processorTextField = new JTextField(statementProcessor.getText(), 14); + add(processorTextField, new GridBagConstraints(1, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 0, 0), 0, 0)); + } + + public StatementProcessor getStatementProcessor() { + return new StatementProcessor((StatementProcessor.ProcessorExecutionType)processorTypeComboBox.getSelectedItem(), processorTextField.getText()); + } + + public void setLocked(boolean isLocked) { + processorTypeComboBox.setEnabled(!isLocked); + processorTextField.setEnabled(!isLocked); + } + +} diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/TextMatcherPane.java b/jOOQ-console/src/main/java/org/jooq/debug/console/TextMatcherPane.java index 34cfd3c18c..d617a7d535 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/console/TextMatcherPane.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/TextMatcherPane.java @@ -49,6 +49,9 @@ import org.jooq.debug.console.misc.TextMatcher; import org.jooq.debug.console.misc.TextMatcher.TextMatchingType; +/** + * @author Christopher Deckers + */ @SuppressWarnings("serial") public class TextMatcherPane extends JPanel { @@ -61,7 +64,7 @@ public class TextMatcherPane extends JPanel { matcherTypeComboBox = new JComboBox(TextMatchingType.values()); add(matcherTypeComboBox, new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); textField = new JTextField(14); - add(textField, new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 2, 0, 0), 0, 0)); + add(textField, new GridBagConstraints(1, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 2, 0, 0), 0, 0)); caseSensitiveCheckBox = new JCheckBox("Case sensitive"); add(caseSensitiveCheckBox, new GridBagConstraints(2, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 2, 0, 0), 0, 0)); if(textMatcher != null) { diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/misc/CheckBoxNode.java b/jOOQ-console/src/main/java/org/jooq/debug/console/misc/CheckBoxNode.java new file mode 100644 index 0000000000..bd5d628e6f --- /dev/null +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/misc/CheckBoxNode.java @@ -0,0 +1,32 @@ +package org.jooq.debug.console.misc; + +import javax.swing.tree.DefaultMutableTreeNode; + + +/** + * @author Christopher Deckers + */ +public class CheckBoxNode extends DefaultMutableTreeNode { + + private String text; + private boolean selected; + + public CheckBoxNode(Object o, String text, boolean selected) { + super(o); + this.text = text; + this.selected = selected; + } + + public String getText() { + return text; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean newValue) { + selected = newValue; + } + +} diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/misc/CheckBoxNodeEditor.java b/jOOQ-console/src/main/java/org/jooq/debug/console/misc/CheckBoxNodeEditor.java new file mode 100644 index 0000000000..c7178945f9 --- /dev/null +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/misc/CheckBoxNodeEditor.java @@ -0,0 +1,63 @@ +package org.jooq.debug.console.misc; + +import java.awt.Component; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.MouseEvent; +import java.util.EventObject; + +import javax.swing.AbstractCellEditor; +import javax.swing.JCheckBox; +import javax.swing.JTree; +import javax.swing.tree.TreeCellEditor; +import javax.swing.tree.TreePath; + +/** + * @author Christopher Deckers + */ +public class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor { + + private CheckBoxNodeRenderer renderer = new CheckBoxNodeRenderer(); + private JTree tree; + + public CheckBoxNodeEditor(JTree tree) { + this.tree = tree; + } + + @Override + public Object getCellEditorValue() { + JCheckBox checkbox = renderer.getCheckBoxRenderer(); + return new CheckBoxNode(null, checkbox.getText(), checkbox.isSelected()); + } + + @Override + public boolean isCellEditable(EventObject event) { + boolean returnValue = false; + if (event instanceof MouseEvent) { + MouseEvent mouseEvent = (MouseEvent) event; + TreePath path = tree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY()); + if (path != null) { + Object node = path.getLastPathComponent(); + returnValue = node instanceof CheckBoxNode; + } + } + return returnValue; + } + + @Override + public Component getTreeCellEditorComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row) { + Component editor = renderer.getTreeCellRendererComponent(tree, value, true, expanded, leaf, row, true); + // editor always selected / focused + if (editor instanceof JCheckBox) { + ((JCheckBox) editor).addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent itemEvent) { + if (stopCellEditing()) { + fireEditingStopped(); + } + } + }); + } + return editor; + } +} \ No newline at end of file diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/misc/CheckBoxNodeRenderer.java b/jOOQ-console/src/main/java/org/jooq/debug/console/misc/CheckBoxNodeRenderer.java new file mode 100644 index 0000000000..49dd847cc9 --- /dev/null +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/misc/CheckBoxNodeRenderer.java @@ -0,0 +1,78 @@ +package org.jooq.debug.console.misc; + +import java.awt.Color; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Insets; + +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JTree; +import javax.swing.UIManager; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreeCellRenderer; + +/** + * @author Christopher Deckers + */ +public class CheckBoxNodeRenderer implements TreeCellRenderer { + + private JCheckBox checkBoxRenderer; + + private DefaultTreeCellRenderer nonCheckBoxRenderer = new DefaultTreeCellRenderer(); + + Color selectionBorderColor, selectionForeground, selectionBackground, textForeground, textBackground; + + protected JCheckBox getCheckBoxRenderer() { + return checkBoxRenderer; + } + + public CheckBoxNodeRenderer() { + checkBoxRenderer = new JCheckBox(); + checkBoxRenderer.setMargin(new Insets(0, 0, 0, 0)); + Font fontValue = UIManager.getFont("Tree.font"); + if (fontValue != null) { + checkBoxRenderer.setFont(fontValue); + } + Boolean booleanValue = (Boolean) UIManager.get("Tree.drawsFocusBorderAroundIcon"); + checkBoxRenderer.setFocusPainted((booleanValue != null) && (booleanValue.booleanValue())); + selectionBorderColor = UIManager.getColor("Tree.selectionBorderColor"); + selectionForeground = UIManager.getColor("Tree.selectionForeground"); + selectionBackground = UIManager.getColor("Tree.selectionBackground"); + textForeground = UIManager.getColor("Tree.textForeground"); + textBackground = UIManager.getColor("Tree.textBackground"); + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + Object usedValue = value;// stringValue = tree.convertValueToText(value, selected, expanded, leaf, row, false); + if (value instanceof CheckBoxNode) { + usedValue = ((CheckBoxNode) value).getText(); + } + Component c = nonCheckBoxRenderer.getTreeCellRendererComponent(tree, usedValue, selected, expanded, leaf, row, hasFocus); + if (value instanceof CheckBoxNode) { +// checkBoxRenderer.setText(stringValue); + checkBoxRenderer.setSelected(false); + checkBoxRenderer.setEnabled(tree.isEnabled()); + checkBoxRenderer.setOpaque(false); +// if (selected) { +// checkBoxRenderer.setForeground(selectionForeground); +// checkBoxRenderer.setBackground(selectionBackground); +// } else { +// checkBoxRenderer.setForeground(textForeground); +// checkBoxRenderer.setBackground(textBackground); +// } + CheckBoxNode node = (CheckBoxNode) value; +// checkBoxRenderer.setText(node.getText()); + checkBoxRenderer.setSelected(node.isSelected()); + JPanel pane = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); + pane.setOpaque(false); + pane.add(checkBoxRenderer); + pane.add(c); + c = pane; + } + return c; + } + +} \ No newline at end of file diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/misc/JSedRegExBuilder.java b/jOOQ-console/src/main/java/org/jooq/debug/console/misc/JSedRegExBuilder.java new file mode 100644 index 0000000000..ecde709f3b --- /dev/null +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/misc/JSedRegExBuilder.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * Christopher Deckers, chrriis@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.debug.console.misc; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +/** + * @author Christopher Deckers + */ +@SuppressWarnings("serial") +public class JSedRegExBuilder extends JPanel { + + public JSedRegExBuilder(String patternSummary, String sampleText) { + setLayout(new GridBagLayout()); + JPanel summaryPanel = new JPanel(new GridBagLayout()); + summaryPanel.add(new JLabel("Pattern summary: "), new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + final JTextField summaryTextField = new JTextField(7); + final JTextArea sampleTextTextArea = new JTextArea(sampleText); + final JTextArea outputTextArea = new JTextArea(); + outputTextArea.setEditable(false); + DocumentListener documentListener = new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + update(); + } + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + @Override + public void changedUpdate(DocumentEvent e) { + update(); + } + private void update() { + try { + outputTextArea.setText(Utils.applySedRegularExpression(sampleTextTextArea.getText(), summaryTextField.getText())); + } catch(Exception e) { + outputTextArea.setText(e.getMessage()); + outputTextArea.setCaretPosition(0); + } + } + }; + summaryTextField.getDocument().addDocumentListener(documentListener); + summaryPanel.add(summaryTextField, new GridBagConstraints(1, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + add(summaryPanel, new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + add(new JLabel("Sample text:"), new GridBagConstraints(0, 1, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0)); + add(new JScrollPane(sampleTextTextArea), new GridBagConstraints(0, 2, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + add(new JLabel("Output:"), new GridBagConstraints(0, 3, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0)); + add(new JScrollPane(outputTextArea), new GridBagConstraints(0, 4, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + summaryTextField.setText(patternSummary); + sampleTextTextArea.getDocument().addDocumentListener(documentListener); + } + +} diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/misc/TextMatcher.java b/jOOQ-console/src/main/java/org/jooq/debug/console/misc/TextMatcher.java index 7d1f473eb4..dd45ac3a5b 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/console/misc/TextMatcher.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/misc/TextMatcher.java @@ -39,6 +39,9 @@ package org.jooq.debug.console.misc; import java.io.Serializable; import java.util.regex.Pattern; +/** + * @author Christopher Deckers + */ @SuppressWarnings("serial") public class TextMatcher implements Serializable { diff --git a/jOOQ-console/src/main/java/org/jooq/debug/console/misc/Utils.java b/jOOQ-console/src/main/java/org/jooq/debug/console/misc/Utils.java index e05359c857..1259de38e4 100644 --- a/jOOQ-console/src/main/java/org/jooq/debug/console/misc/Utils.java +++ b/jOOQ-console/src/main/java/org/jooq/debug/console/misc/Utils.java @@ -43,6 +43,8 @@ import java.util.HashSet; import java.util.Locale; import java.util.Set; import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * @author Christopher Deckers & others @@ -287,4 +289,116 @@ public class Utils { return keywordSet.contains(s.substring(0, index)); } + /** + * A "sed -e" like reg exp, of the form:
+ * - /regexp/flags: find and output the matches.
+ * - /regexp/replacement/flags: replace the matches and output the resulting string.
+ * Flags can be left empty or any combinations of the characters 'gidmsux' (g perfoms a replace all instead of just the first match. For other flags, refer to the Javadoc of Pattern). + * It is also possible to chain the output using ';' to perform multiple replacements.
+ * If the regexp contains capturing groups, a find operation would only retain those; for a replace operation, the replacement string can refer to capturing groups with a syntax like '$1'. + */ + public static String applySedRegularExpression(String text, String regex) { + String originalRegEx = regex; + if(!regex.startsWith("/")) { + throw new IllegalArgumentException("Invalid expression format: " + originalRegEx); + } + regex = regex.substring(1); + StringBuilder sb = new StringBuilder(); + char[] chars = regex.toCharArray(); + int index1 = -1; + int index2 = -1; + for(int i=0; i= chars.length || chars[i] != '/') { + throw new IllegalArgumentException("Invalid expression format: " + originalRegEx); + } + break; + case '\\': + i++; + if(i >= chars.length) { + throw new IllegalArgumentException("Invalid expression format: " + originalRegEx); + } + switch(chars[i]) { + case '/': sb.append('/'); break; + case ';': sb.append(';'); break; + default: sb.append('\\').append(chars[i]); break; + } + break; + case '/': + if(index1 == -1) { + index1 = sb.length(); + } else if(index2 == -1) { + index2 = sb.length(); + } else { + throw new IllegalArgumentException("Invalid expression format: " + originalRegEx); + } + break; + default: sb.append(c); break; + } + } + if(index1 == -1) { + throw new IllegalArgumentException("Invalid expression format: " + originalRegEx); + } + return applySedRegularExpression(sb.toString(), text, originalRegEx, index1, index2); + } + + private static String applySedRegularExpression(String s, String text, String originalRegEx, int index1, int index2) { + StringBuilder sb; + String toFind = s.substring(0, index1); + String replacement = index2 == -1? null: s.substring(index1, index2); + String modifiers = index2 == -1? s.substring(index1): s.substring(index2); + boolean isGlobal = false; + int flags = 0; + for(int i=0; i 0) { + for(int i=0; i