diff --git a/.gitignore b/.gitignore
index 84ad9299..b81a66d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,25 +1,46 @@
+# Compiled class file
*.class
-*._trace
-*.xtendbin
-*.DS_Store
-*.log
-**/target
-**/bin
-**/test-output
-**/xtend-gen
-**/.sonar
-**/.project
-**/.classpath
-**/.settings
-# Mobile Tools for Java (J2ME)
-.mtj.tmp/
+# Log file
+*.log
-# Package Files #
+# Package Files
*.jar
*.war
+*.nar
*.ear
+*.zip
+*.tar.gz
+*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
+# Xtend
+*._trace
+*.xtendbin
+**/xtend-gen
+
+# SonarQube
+**/.sonar
+**/.scannerwork
+
+# Eclipse / Visual Studio Code
+.project
+.classpath
+**/.settings
+.vscode
+
+# IntelliJ
+**/.idea
+*.iml
+
+# macOS
+*.DS_Store
+
+# Windows
+Thumbs.db
+
+# Targets
+**/target
+**/bin
diff --git a/README.md b/README.md
index e0d6e688..fa77ed47 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,106 @@
## Introduction
-utPLSQL for SQL Developer extends Oracle's SQL Developer by context menu entries and keyboard shortcuts for running utPLSQL unit tests from the Connections window or the PL/SQL editor.
+utPLSQL for SQL Developer extends Oracle's SQL Developer to
-
+* run utPLSQL unit tests
+* debug utPLSQL unit tests
+* run utPLSQL code coverage reports
+* generate utPLSQL unit tests (skeletons)
+* provide utPLSQL annotations and expectations as snippets
+
+### Run utPLSQL test
+
+In the Connections window, select a connection or one or more packages or package procedures to run utPLSQL tests. Or you can run utPLSQL tests directly from the PL/SQL editor. The test procedures are determined according to the cursor position in the PL/SQL editor.
+
+
+
+### Debug utPLSQL test
+
+Select the tests to debug, the same way you would if you were just running them, but select `Debug utPLSQL test...` in the context menu.
+
+And then the PL/SQL Debugger is called for the generated, anonymous PL/SQL block.
+
+
+
+The initialization timeout for the realtime reporter is increased to 1 hour. The overall timeout is left to the default value of 4 hours. So you have enough time to step through the PL/SQL code and analyse it. Press the `Stop` button at any time to cancel the test run.
+
+
+
+### Code coverage...
+
+In the Connections window, select a connection or one or more packages for a code coverage report.
+
+In the dialog you can set some parameters. If you run code coverage for a few chosen packages or from the PL/SQL editor the objects to be included are determined according the object dependencies in the Oracle data dictionary.
+
+
+
+Press `Run` to produce the code coverage process in the background. You may minimize or close the dialog. To abort the process, press `Cancel`.
+
+The HTML report is shown in your default browser.
+
+
+
+### Generate utPLSQL test
+
+#### Based on existing code
+
+Select a package, type, function or procedure to build a utPLSQL test package skeleton. The generated package compiles and executes, but still needs some rework.
+
+
+
+#### From scratch
+
+1. Press the button `Create code templates` in the preferences
+2. Type `ut_` in the Worksheet or a PL/SQL editor and press `Ctrl-Space` to get the list of a utPLSQL code templates
+3. Press `Return` to paste a code template at the current cursor position
+
+
+
+### Snippets
+
+Import utPLSQL snippets for annotations and expectations via the the `Import Snippets` button of the utPLSQL [preferences dialog](#preferences).
+
+Insert the snippets by drag & drop into your code.
+
+#### Annotations
+
+Annotations are used in the package specification for various test configurations.
+
+
+
+#### Expectations
+
+Expectations are used in the package body to compare actual with expected results.
+
+
+
+### Preferences
+
+Set your preferences to change the behaviour of utPLSQL within SQL Developer.
+
+
+
+### oddgen integration
+
+utPLSQL for SQL Developer implements the [OddgenGenerator2 interface](https://www.oddgen.org/java-interface/) to
+
+* run existing utPLSQL test suitepaths and test suites
+* bulk generate new utPLSQL unit tests into dedicated files
+
+The use of [oddgen for SQL Developer](https://www.oddgen.org/) is optional.
+
+#### Run utPLSQL test
+
+utPLSQL can run tests based on suitepaths and suites. The next screenshot shows the generated run command for two selected suitepaths.
+
+
+
+#### Generate utPLSQL test
+
+Use oddgen to generate utPLSQL test package skeletons. When generating to files, a file for each package specification and package body is created.
+
+
## Releases
@@ -20,7 +117,18 @@ Binary releases are published [here](https://github.com/utPLSQL/utPLSQL-SQLDevel
3. Use the ```Install From Local File``` option to install the previously downloaded ```utplsql_for_SQLDev_*.zip``` file.
+### Via Update Center
+
+1. Start SQL Developer
+
+2. Select ```Check for Updates…``` in the help menu.
+
+3. Press ```Add…``` to register the ```salvis.com``` update site ```http://update.salvis.com/```.
+
+4. Use the ```Search Update Center``` option and select the ```salvis.com``` update center to install the lastest version of ```utPLSQL for SQL Developer```.
+
## Issues
+
Please file your bug reports, enhancement requests, questions and other support requests within [Github's issue tracker](https://help.github.com/articles/about-issues/).
* [Questions](https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues?q=is%3Aissue+label%3Aquestion)
@@ -31,26 +139,27 @@ Please file your bug reports, enhancement requests, questions and other support
## How to Contribute
1. Describe your idea by [submitting an issue](https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/new)
-2. [Fork the utPLSQL-SQLDeveloper respository](https://github.com/utPLSQL/utPLSQL-SQLDeveloper/fork)
+2. [Fork the utPLSQL-SQLDeveloper repository](https://github.com/utPLSQL/utPLSQL-SQLDeveloper/fork)
3. [Create a branch](https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/), commit and publish your changes and enhancements
4. [Create a pull request](https://help.github.com/articles/creating-a-pull-request/)
## How to Build
-1. [Download](http://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/index.html) and install SQL Developer 17.4.0
-2. [Download](https://maven.apache.org/download.cgi) and install Apache Maven 3.5.2
-3. [Download](https://git-scm.com/downloads) and install a git command line client
-4. Clone the utPLSQL-SQLDeveloper repository
-5. Open a terminal window in the utPLSQL-SQLDeveloper root folder and type
+1. [Download](http://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/index.html) and install SQL Developer 24.3.1
+2. Download and install a JDK 17 or newer
+3. [Download](https://maven.apache.org/download.cgi) and install Apache Maven 3.9.16
+4. [Download](https://git-scm.com/downloads) and install a git command line client
+5. Clone the utPLSQL-SQLDeveloper repository
+6. Open a terminal window in the utPLSQL-SQLDeveloper root folder and type
- cd sqldev
+ cd sqldev
-6. Run maven build by the following command
+7. Run maven build by the following command
- mvn -Dsqldev.basedir=/Applications/SQLDeveloper17.4.0.app/Contents/Resources/sqldeveloper -DskipTests=true clean package
+ mvn -Dsqldev.basedir=/Applications/SQLDeveloper24.3.1.app/Contents/Resources/sqldeveloper -DskipTests=true clean package
- Amend the parameter sqldev.basedir to match the path of your SQL Developer installation. This folder is used to reference Oracle jar files which are not available in public Maven repositories
-7. The resulting file ```utplsql_for_SQLDev_x.x.x-SNAPSHOT.zip``` in the ```target``` directory may be installed within SQL Developer
+ Amend the parameter sqldev.basedir to match the path of your SQL Developer installation. This folder is used to reference Oracle jar files which are not available in public Maven repositories
+8. The resulting file ```utplsql_for_SQLDev_x.x.x-SNAPSHOT.zip``` in the ```target``` directory can be installed within SQL Developer
## License
diff --git a/images/code_coverage_dialog.png b/images/code_coverage_dialog.png
new file mode 100644
index 00000000..b514dddf
Binary files /dev/null and b/images/code_coverage_dialog.png differ
diff --git a/images/code_coverage_report_all_files.png b/images/code_coverage_report_all_files.png
new file mode 100644
index 00000000..bce5f4a8
Binary files /dev/null and b/images/code_coverage_report_all_files.png differ
diff --git a/images/debug_utplsql_test_anonymous_block.png b/images/debug_utplsql_test_anonymous_block.png
new file mode 100644
index 00000000..46391e85
Binary files /dev/null and b/images/debug_utplsql_test_anonymous_block.png differ
diff --git a/images/debug_utplsql_test_stop.png b/images/debug_utplsql_test_stop.png
new file mode 100644
index 00000000..fa390ef4
Binary files /dev/null and b/images/debug_utplsql_test_stop.png differ
diff --git a/images/generate_utplsql_test.png b/images/generate_utplsql_test.png
new file mode 100644
index 00000000..49f45b58
Binary files /dev/null and b/images/generate_utplsql_test.png differ
diff --git a/images/generate_utplsql_test_from_template.png b/images/generate_utplsql_test_from_template.png
new file mode 100644
index 00000000..af3ae0a5
Binary files /dev/null and b/images/generate_utplsql_test_from_template.png differ
diff --git a/images/oddgen_generate_utplsql_test.png b/images/oddgen_generate_utplsql_test.png
new file mode 100644
index 00000000..f9438d03
Binary files /dev/null and b/images/oddgen_generate_utplsql_test.png differ
diff --git a/images/oddgen_run_utplsql_test.png b/images/oddgen_run_utplsql_test.png
new file mode 100644
index 00000000..72f5a2a1
Binary files /dev/null and b/images/oddgen_run_utplsql_test.png differ
diff --git a/images/preferences.png b/images/preferences.png
new file mode 100644
index 00000000..7f2d8ef9
Binary files /dev/null and b/images/preferences.png differ
diff --git a/images/run_utplsql_test.png b/images/run_utplsql_test.png
new file mode 100644
index 00000000..44c76099
Binary files /dev/null and b/images/run_utplsql_test.png differ
diff --git a/images/runner_model.png b/images/runner_model.png
new file mode 100644
index 00000000..40491d80
Binary files /dev/null and b/images/runner_model.png differ
diff --git a/images/snippets_annotations.png b/images/snippets_annotations.png
new file mode 100644
index 00000000..6c7400f8
Binary files /dev/null and b/images/snippets_annotations.png differ
diff --git a/images/snippets_expectations.png b/images/snippets_expectations.png
new file mode 100644
index 00000000..ccbd7836
Binary files /dev/null and b/images/snippets_expectations.png differ
diff --git a/sqldev/bundle.xml b/sqldev/bundle.xml
index 65bdf437..f8f568d9 100644
--- a/sqldev/bundle.xml
+++ b/sqldev/bundle.xml
@@ -8,7 +8,6 @@
-
diff --git a/sqldev/extension.xml b/sqldev/extension.xml
index 7899e6da..88d49292 100644
--- a/sqldev/extension.xml
+++ b/sqldev/extension.xml
@@ -15,13 +15,82 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -30,12 +99,36 @@
Code-Editor
+
+
+ ${MENU_CODE_COVERAGE_LABEL}
+ res:/org/utplsql/sqldev/resources/images/coverage.png
+ Code-Editor
+
+
+
+
+ ${MENU_DEBUG_TEST_LABEL}
+ res:/org/utplsql/sqldev/resources/images/debug.png
+ Code-Editor
+
+
+
+
+ ${MENU_GENERATE_TEST_LABEL}
+ res:/org/utplsql/sqldev/resources/images/oddgen.png
+ Code-Editor
+
+
@@ -46,15 +139,29 @@
-
+
+
+
+
+
@@ -64,10 +171,17 @@
- org.utplsql.sqldev.PreferencePanel
+ org.utplsql.sqldev.ui.preference.PreferencePanel
+
+
+
+
+
+
+
diff --git a/sqldev/pom.xml b/sqldev/pom.xml
index dece830f..a2340e3d 100644
--- a/sqldev/pom.xml
+++ b/sqldev/pom.xml
@@ -1,3 +1,4 @@
+
4.0.0
@@ -5,170 +6,248 @@
org.utplsql
org.utplsql.sqldev
- 0.3.0-SNAPSHOT
+ 2.1.0-SNAPSHOT
bundle
UTF-8
- 1.8
- 1.8
- 2.12.0
-
- /Applications/SQLDeveloper17.4.0.app/Contents/Resources/sqldeveloper
+ 17
+ 24.3.1
+
+ /Applications/SQLDeveloper${sqldev.version}.app/Contents/Resources/sqldeveloper
utplsql_for_SQLDev_${project.version}
-
+
+
+
+
oracle
idert
- 12.2.0
+ ${sqldev.version}
system
${sqldev.basedir}/ide/lib/idert.jar
oracle
- javatools-nodeps
- 12.2.0
+ oracle.javatools-nodeps
+ ${sqldev.version}
system
- ${sqldev.basedir}/modules/oracle.javatools/javatools-nodeps.jar
+ ${sqldev.basedir}/external/oracle.javatools-nodeps.jar
oracle
javatools
- 12.2.0
+ ${sqldev.version}
system
${sqldev.basedir}/ide/lib/javatools.jar
oracle
oracle.ide.ceditor
- 12.2.0
+ ${sqldev.version}
system
${sqldev.basedir}/ide/extensions/oracle.ide.ceditor.jar
oracle
oracle.ide
- 12.2.0
+ ${sqldev.version}
system
${sqldev.basedir}/ide/extensions/oracle.ide.jar
oracle
uic
- 12.2.2
+ ${sqldev.version}
system
${sqldev.basedir}/ide/lib/uic.jar
-
- oracle
- oracle.sqldeveloper.utils
- 12.2.0
- system
- ${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.utils.jar
-
oracle
oracle.ide.navigator
- 12.2.0
+ ${sqldev.version}
system
${sqldev.basedir}/ide/extensions/oracle.ide.navigator.jar
oracle
javax-ide
- 12.2.0
+ ${sqldev.version}
system
${sqldev.basedir}/ide/lib/javax-ide.jar
oracle
oracle.dbtools-common
- 12.2.0
+ ${sqldev.version}
system
- ${sqldev.basedir}/sqldeveloper/lib/oracle.dbtools-common.jar
+ ${sqldev.basedir}/sqldeveloper/lib/dbtools-common.jar
+
+
+ oracle
+ oracle.dbtools-arbori
+ ${sqldev.version}
+ system
+ ${sqldev.basedir}/sqldeveloper/lib/dbtools-arbori.jar
oracle
oracle.sqldeveloper
- 12.2.0
+ ${sqldev.version}
system
${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.jar
+
+ oracle
+ oracle.sqldeveloper.utils
+ ${sqldev.version}
+ system
+ ${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.utils.jar
+
oracle
oracle.sqldeveloper.worksheet
- 12.2.0
+ ${sqldev.version}
system
${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.worksheet.jar
oracle
- ojdbc8
- 12.2.0.1.0
+ oracle.sqldeveloper.schemabrowser
+ ${sqldev.version}
+ system
+ ${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.schemabrowser.jar
+
+
+ oracle
+ ojdbc11
+ ${sqldev.version}
system
- ${sqldev.basedir}/jdbc/lib/ojdbc8.jar
+ ${sqldev.basedir}/jdbc/lib/ojdbc11.jar
oracle
dbapi
- 12.2.1
+ ${sqldev.version}
system
${sqldev.basedir}/ide/lib/dbapi.jar
oracle
oracle.ide.db
- 12.2.1
+ ${sqldev.version}
system
${sqldev.basedir}/ide/extensions/oracle.ide.db.jar
oracle
oracle.jdeveloper.db.connection
- 12.2.1
+ ${sqldev.version}
system
${sqldev.basedir}/jdev/extensions/oracle.jdeveloper.db.connection.jar
oracle
oracle.jdeveloper.java.core.jar
- 12.2.1
+ ${sqldev.version}
system
${sqldev.basedir}/jdev/extensions/oracle.jdeveloper.java.core.jar
-
-
+ oracle
+ oracle.jdeveloper.runner.jar
+ ${sqldev.version}
+ system
+ ${sqldev.basedir}/jdev/extensions/oracle.jdeveloper.runner.jar
+
+
+ oracle
+ oracle.ide.runner
+ ${sqldev.version}
+ system
+ ${sqldev.basedir}/ide/extensions/oracle.ide.runner.jar
+
+
+
+ oracle
+ javatools-nodeps
+ ${sqldev.version}
+ system
+ ${sqldev.basedir}/modules/oracle.javatools/javatools-nodeps.jar
+
+
+
+ oracle
+ jewt4.jar
+ ${sqldev.version}
+ system
+ ${sqldev.basedir}/modules/oracle.bali.jewt/jewt4.jar
+
+
+ oracle
+ share.jar
+ ${sqldev.version}
+ system
+ ${sqldev.basedir}/modules/oracle.bali.share/share.jar
+
+
+
+ com.google.code.findbugs
+ jsr305
+ 3.0.2
+ provided
+
+
+
org.osgi
org.osgi.core
- 5.0.0
+ 6.0.0
provided
-
- org.eclipse.xtend
- org.eclipse.xtend.lib
- ${xtend.version}
+
+ org.springframework
+ spring-jdbc
+ 7.0.8
+
+
+
+ org.springframework
+ spring-core
+ 7.0.8
+
org.springframework
- spring-jdbc
- 5.0.2.RELEASE
+ spring-web
+ 7.0.8
+
+
+
+ org.oddgen
+ org.oddgen.sqldev
+ 0.3.1
+ provided
junit
junit
- 4.12
+ 4.13.2
test
-
+
+ org.jetbrains
+ annotations
+ 26.1.0
+ compile
+
+
${project.basedir}/src/main/java
- ${project.basedir}/src/test/java
src/main/resources
@@ -177,72 +256,45 @@
+ ${project.basedir}/src/test/java
+
+
+ src/test/resources
+
+ **/*.*
+
+
+
-
- org.eclipse.xtend
- xtend-maven-plugin
-
- ${xtend.version}
-
-
- main
-
- compile
-
-
- ${jdk.version}
- ${project.basedir}/src/main/xtend-gen
-
-
-
- test
-
- testCompile
-
-
- ${jdk.version.test}
- ${project.basedir}/src/test/xtend-gen
-
-
-
-
org.apache.maven.plugins
- 3.6.2
+ 3.15.0
maven-compiler-plugin
- ${jdk.version}
- ${jdk.version}
-
- ${jdk.version.test}
- ${jdk.version.test}
-
- **/*.java
-
+ ${jdk.version}
-
-
-
- test-compile
- process-test-sources
-
- testCompile
-
-
- ${jdk.version.test}
- ${jdk.version.test}
-
-
-
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.5.0
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ 3.1.4
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ 3.1.4
org.apache.maven.plugins
maven-surefire-plugin
- 2.20
+ 3.5.6
-
- -noverify
- -Djava.util.logging.config.file=${project.basedir}/src/test/resources/logging.conf
+
**/*.java
@@ -270,7 +322,7 @@
org.apache.maven.plugins
maven-antrun-plugin
- 1.8
+ 3.1.0
prepare-package
@@ -291,7 +343,7 @@
org.codehaus.mojo
properties-maven-plugin
- 1.0.0
+ 1.1.0
initialize
@@ -311,7 +363,7 @@
org.codehaus.mojo
build-helper-maven-plugin
- 3.0.0
+ 3.3.0
parse-version
@@ -364,10 +416,6 @@
#EXTENSION_DESCRIPTION#
${EXTENSION_DESCRIPTION}
-
- #EXTENSION_LICENSE_URL#
- ${EXTENSION_LICENSE_URL}
-
#MIN_SQLDEV_VERSION#
${MIN_SQLDEV_VERSION}
@@ -376,9 +424,16 @@
+
+
+
+
+
+
+
org.apache.felix
maven-bundle-plugin
- 3.5.0
+ 6.0.2
true
${project.name}
@@ -395,26 +450,48 @@
META-INF/extension.xml=target/classes/META-INF/extension.xml
- org.utplsql.sqldev,
+ org.utplsql.sqldev.coverage,
+ org.utplsql.sqldev.dal,
+ org.utplsql.sqldev.exception,
org.utplsql.sqldev.menu,
+ org.utplsql.sqldev.model,
+ org.utplsql.sqldev.model.oddgen,
+ org.utplsql.sqldev.model.parser,
org.utplsql.sqldev.model.preference,
- org.utplsql.sqldev.actions,
- org.utplsql.sqldev.resources
+ org.utplsql.sqldev.model.runner,
+ org.utplsql.sqldev.model.ut,
+ org.utplsql.sqldev.oddgen,
+ org.utplsql.sqldev.parser,
+ org.utplsql.sqldev.resources,
+ org.utplsql.sqldev.runner,
+ org.utplsql.sqldev.snippet,
+ org.utplsql.sqldev.ui.common,
+ org.utplsql.sqldev.ui.coverage,
+ org.utplsql.sqldev.ui.preference,
+ org.utplsql.sqldev.ui.runner
<_exportcontents>
- org.eclipse.xtext.xbase.lib,
org.aspectj.runtime.internal,
org.aspectj.lang,
org.aspectj.runtime,
- org.aspectj.lang.reflect
+ org.aspectj.lang.reflect,
+ org.springframework.core.style,
+ org.springframework.lang,
+ org.jspecify.annotations
+ <_noee>true
+ oracle.javatools,
oracle.javatools-nodeps,
oracle.jdeveloper.db.connection,
+ oracle.jdeveloper.runner,
oracle.idert,
oracle.ide,
oracle.ide.db,
+ oracle.ide.runner,
oracle.sqldeveloper,
+ oracle.sqldeveloper.schemabrowser,
+ oracle.sqldeveloper.utils,
oracle.sqldeveloper.worksheet,
oracle.uic
@@ -427,7 +504,7 @@
maven-assembly-plugin
- 3.1.0
+ 3.8.0
${final.name}
false
@@ -446,10 +523,44 @@
+
+ net.nicoulaj.maven.plugins
+ checksum-maven-plugin
+ 1.11
+
+
+ calculate-checksums
+ package
+
+ artifacts
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.15
+
+
+
+ prepare-agent
+
+
+
+ report
+ prepare-package
+
+ report
+
+
+
+
+
org.eclipse.m2e
lifecycle-mapping
1.0.0
@@ -460,7 +571,7 @@
org.apache.maven.plugins
maven-dependency-plugin
- [3.0.1,)
+ [3.1.2,)
copy-dependencies
@@ -478,7 +589,7 @@
build-helper-maven-plugin
- [3.0.0,)
+ [3.1.0,)
parse-version
@@ -495,12 +606,21 @@
org.codehaus.mojo
buildnumber-maven-plugin
- 1.4
+ 3.3.0
-
+
+
+
+
+ org.javassist
+ javassist
+ 3.30.2-GA
+
+
+
org.utplsql.sqldev
Extension for running unit tests in SQL Developer.
diff --git a/sqldev/sqldeveloper.xml b/sqldev/sqldeveloper.xml
index fdd44a57..ff6f5d3d 100644
--- a/sqldev/sqldeveloper.xml
+++ b/sqldev/sqldeveloper.xml
@@ -1,17 +1,18 @@
-
- #EXTENSION_NAME#
- #EXTENSION_VERSION#
- #EXTENSION_OWNER#
- http://utplsql.org/
+ xmlns="http://xmlns.oracle.com/jdeveloper/updatecenter"
+ xmlns:u="http://xmlns.oracle.com/jdeveloper/update">
+
+ #EXTENSION_NAME#
+ #EXTENSION_VERSION#
+ #EXTENSION_OWNER#
+ http://utplsql.org/
-
-
+
- https://github.com/utPLSQL/utPLSQL-SQLDeveloper/releases/download/v#EXTENSION_SHORT_VERSION#/#EXTENSION_DEPLOYABLE#
-
+ https://github.com/utPLSQL/utPLSQL-SQLDeveloper/releases/download/v#EXTENSION_SHORT_VERSION#/#EXTENSION_DEPLOYABLE#
+
+
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/PreferencePanel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/PreferencePanel.xtend
deleted file mode 100644
index 08ababc2..00000000
--- a/sqldev/src/main/java/org/utplsql/sqldev/PreferencePanel.xtend
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2018 Philipp Salvisberg
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.utplsql.sqldev
-
-import javax.swing.JCheckBox
-import oracle.ide.panels.DefaultTraversablePanel
-import oracle.ide.panels.TraversableContext
-import oracle.ide.panels.TraversalException
-import oracle.javatools.ui.layout.FieldLayoutBuilder
-import org.utplsql.sqldev.model.preference.PreferenceModel
-import org.utplsql.sqldev.resources.UtplsqlResources
-
-class PreferencePanel extends DefaultTraversablePanel {
- val JCheckBox unsharedWorksheetCheckBox = new JCheckBox
- val JCheckBox resetPackageCheckBox = new JCheckBox
- val JCheckBox clearScreenCheckBox = new JCheckBox
- val JCheckBox autoExecuteCheckBox = new JCheckBox
-
- new() {
- layoutControls()
- }
-
- def private layoutControls() {
- val FieldLayoutBuilder builder = new FieldLayoutBuilder(this)
- builder.alignLabelsLeft = true
- builder.add(
- builder.field.label.withText(UtplsqlResources.getString("PREF_UNSHARED_WORKSHEET_LABEL")).component(
- unsharedWorksheetCheckBox))
- builder.add(
- builder.field.label.withText(UtplsqlResources.getString("PREF_RESET_PACKAGE_LABEL")).component(
- resetPackageCheckBox))
- builder.add(
- builder.field.label.withText(UtplsqlResources.getString("PREF_CLEAR_SCREEN_LABEL")).component(
- clearScreenCheckBox))
- builder.add(
- builder.field.label.withText(UtplsqlResources.getString("PREF_AUTO_EXECUTE_LABEL")).component(
- autoExecuteCheckBox))
- builder.addVerticalSpring
- }
-
- override onEntry(TraversableContext traversableContext) {
- var PreferenceModel info = traversableContext.userInformation
- unsharedWorksheetCheckBox.selected = info.unsharedWorksheet
- resetPackageCheckBox.selected = info.resetPackage
- clearScreenCheckBox.selected = info.clearScreen
- autoExecuteCheckBox.selected = info.autoExecute
- super.onEntry(traversableContext)
- }
-
- override onExit(TraversableContext traversableContext) throws TraversalException {
- var PreferenceModel info = traversableContext.userInformation
- info.unsharedWorksheet = unsharedWorksheetCheckBox.selected
- info.resetPackage = resetPackageCheckBox.selected
- info.clearScreen = clearScreenCheckBox.selected
- info.autoExecute = autoExecuteCheckBox.selected
- super.onExit(traversableContext)
- }
-
- def private static PreferenceModel getUserInformation(TraversableContext tc) {
- return PreferenceModel.getInstance(tc.propertyStorage)
- }
-}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/UtplsqlWorksheet.xtend b/sqldev/src/main/java/org/utplsql/sqldev/UtplsqlWorksheet.xtend
deleted file mode 100644
index fefe1c24..00000000
--- a/sqldev/src/main/java/org/utplsql/sqldev/UtplsqlWorksheet.xtend
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2018 Philipp Salvisberg
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.utplsql.sqldev
-
-import java.util.logging.Logger
-import javax.swing.JSplitPane
-import oracle.dbtools.raptor.utils.Connections
-import oracle.dbtools.worksheet.editor.OpenWorksheetWizard
-import oracle.dbtools.worksheet.editor.Worksheet
-import oracle.dbtools.worksheet.utils.WorksheetUtil
-import oracle.ide.Ide
-import oracle.ide.config.Preferences
-import oracle.ide.controller.IdeAction
-import org.utplsql.sqldev.model.preference.PreferenceModel
-import org.utplsql.sqldev.resources.UtplsqlResources
-
-class UtplsqlWorksheet {
- private static val Logger logger = Logger.getLogger(UtplsqlWorksheet.name);
-
- private var PreferenceModel preferences
- private var String connectionName
- private var String path
-
- new(String path, String connectionName) {
- this.preferences = PreferenceModel.getInstance(Preferences.preferences);
- this.path = path
- setConnection(connectionName)
- }
-
- private def setConnection(String connectionName) {
- if (connectionName !== null && preferences.unsharedWorksheet) {
- this.connectionName = Connections.instance.createPrivateConnection(connectionName)
- } else {
- this.connectionName = connectionName;
- }
- }
-
- private def getCode() '''
- «IF preferences.resetPackage»
- EXECUTE dbms_session.reset_package;
- «ENDIF»
- SET SERVEROUTPUT ON SIZE UNLIMITED
- «IF preferences.clearScreen»
- CLEAR SCREEN
- «ENDIF»
- EXECUTE ut.run('«path»');
- '''
-
- private def openWorksheet() {
- val worksheet = OpenWorksheetWizard.openNewTempWorksheet(connectionName, code.toString) as Worksheet
- if (connectionName === null) {
- worksheet.comboConnection = null
- }
- WorksheetUtil.setWorksheetTabName(worksheet.context.node.URL, UtplsqlResources.getString("WORKSHEET_TITLE"))
- worksheet.context.node.markDirty(false)
- return worksheet
- }
-
- private def resizeResultPanel(Worksheet worksheet) {
- Thread.sleep(200) // give script runner time to initiate result panel
- val splitPane = worksheet.selectedResultPanel?.GUI?.parent?.parent?.parent
- if (splitPane instanceof JSplitPane) {
- splitPane.dividerLocation = 0.15 // 15% for Worksheet, 85% for Script Output
- } else {
- logger.
- severe('''Could not adjust size of worksheet. Expected JSplitPane but got «splitPane?.class?.name».''')
- }
- }
-
- private def runScript(Worksheet worksheet) {
- if (preferences.autoExecute) {
- Thread.sleep(100) // give worksheet time to initialize
- val action = Ide.getIdeActionMap.get(Ide.findCmdID("Worksheet.RunScript")) as IdeAction
- if (action !== null) {
- action.performAction(worksheet.context)
- worksheet.resizeResultPanel
- }
- }
- }
-
- private def runTest() {
- val worksheet = openWorksheet
- worksheet.runScript
- logger.fine('''utPLSQL test called for «path» in «connectionName».''')
- }
-
- def runTestAsync() {
- val Runnable runnable = [|runTest]
- val thread = new Thread(runnable)
- thread.name = "utPLSQL run test"
- thread.start
- }
-
-
-}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java
new file mode 100644
index 00000000..79bcaa79
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.coverage;
+
+import java.awt.Desktop;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.sql.Connection;
+import java.util.*;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.utplsql.sqldev.dal.RealtimeReporterDao;
+import org.utplsql.sqldev.dal.UtplsqlDao;
+import org.utplsql.sqldev.exception.GenericDatabaseAccessException;
+import org.utplsql.sqldev.exception.GenericRuntimeException;
+import org.utplsql.sqldev.model.DatabaseTools;
+import org.utplsql.sqldev.model.FileTools;
+import org.utplsql.sqldev.model.preference.PreferenceModel;
+import org.utplsql.sqldev.runner.UtplsqlRunner;
+import org.utplsql.sqldev.ui.coverage.CodeCoverageReporterDialog;
+
+import oracle.ide.config.Preferences;
+
+public class CodeCoverageReporter {
+ private static final Logger logger = Logger.getLogger(CodeCoverageReporter.class.getName());
+ private static final String ASSETS_PATH = "coverage/assets/";
+
+ private String connectionName;
+ private Connection conn;
+ private final List pathList;
+ private final List includeObjectList;
+ private CodeCoverageReporterDialog frame;
+ private String schemas;
+ private String includeObjects;
+ private String excludeObjects;
+ private Path assetDir;
+
+ public CodeCoverageReporter(final List pathList, final List includeObjectList,
+ final String connectionName) {
+ this.pathList = pathList;
+ this.includeObjectList = includeObjectList;
+ setDefaultSchema();
+ setConnection(connectionName);
+ setAssetDir();
+ }
+
+ // constructor for testing purposes only
+ public CodeCoverageReporter(final List pathList, final List includeObjectList,
+ final Connection conn) {
+ this.pathList = pathList;
+ this.includeObjectList = includeObjectList;
+ this.conn = conn;
+ setDefaultSchema();
+ setAssetDir();
+ }
+
+ private void setConnection(final String connectionName) {
+ if (connectionName == null) {
+ final String msg = "Cannot initialize a CodeCoverageReporter without a ConnectionName";
+ logger.severe(() -> msg);
+ throw new NullPointerException();
+ } else {
+ // must be closed manually
+ this.connectionName = connectionName;
+ this.conn = DatabaseTools.getConnection(connectionName);
+ }
+ }
+
+ private void setDefaultSchema() {
+ if (includeObjectList != null && !includeObjectList.isEmpty()) {
+ // use the owner with the most hits in includeObjectList
+ HashMap owners = new HashMap<>();
+ for (String entry : includeObjectList) {
+ String[] obj = entry.toUpperCase().split("\\.");
+ if (obj.length == 2) {
+ // only if objectOwner and objectName are available
+ Integer count = owners.get(obj[0]);
+ if (count == null) {
+ count = 1;
+ } else {
+ count++;
+ }
+ owners.put(obj[0], count);
+ }
+ }
+ List sortedOwners = owners.entrySet().stream()
+ .sorted(Map.Entry.comparingByValue().reversed()).map(Map.Entry::getKey)
+ .collect(Collectors.toList());
+ schemas = String.join(", ", sortedOwners);
+ }
+ }
+
+ private void setAssetDir() {
+ try {
+ assetDir = Files.createTempDirectory("utplsql_assets_");
+ } catch (IOException e) {
+ throw new GenericRuntimeException("Cannot create temporary directory for code coverage report assets.", e);
+ }
+ populateCoverageAssets();
+ }
+
+ // public for testing purposes only
+ public URL getHtmlReportAssetPath() {
+ try {
+ return Paths.get(assetDir.toString()).toUri().toURL();
+ } catch (MalformedURLException e) {
+ throw new GenericRuntimeException("Cannot convert code coverage asset path to URL.", e);
+ }
+ }
+
+ private void copyStreamToFile(InputStream inputStream, Path file) throws IOException {
+ file.toFile().mkdirs();
+ Files.copy(inputStream, file, StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ private void populateCoverageAssets() {
+ logger.fine(() -> "Copying code coverage report assets to " + assetDir.toString() + "...");
+ try {
+ final File file = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
+ if (file.isFile()) {
+ // class loaded from a JAR file
+ final JarFile jar = new JarFile(file);
+ final List entries = jar.stream().filter(entry -> !entry.isDirectory() && entry.getName().startsWith(ASSETS_PATH)).collect(Collectors.toList());
+ for (JarEntry entry : entries) {
+ Path f = Paths.get(assetDir.toString() + File.separator + entry.getName().substring(ASSETS_PATH.length()));
+ copyStreamToFile(jar.getInputStream(entry), f);
+ }
+ jar.close();
+ } else {
+ // class loaded from file system (IDE or during test/build)
+ ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+ Resource[] resources = resolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "/" + ASSETS_PATH + "**");
+ for (Resource resource : resources) {
+ if (Objects.requireNonNull(resource.getFilename()).contains(".")) {
+ // process files but not directories, assume that directories do not contain a period
+ String path = resource.getURL().getPath();
+ Path f = Paths.get(assetDir.toString() + File.separator + path.substring(path.lastIndexOf(ASSETS_PATH) + ASSETS_PATH.length()));
+ copyStreamToFile(resource.getInputStream(), f);
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new GenericRuntimeException("Error while copying coverage report assets to temporary directory.", e);
+ }
+ }
+
+ private ArrayList toStringList(final String s) {
+ final ArrayList list = new ArrayList<>();
+ if (s != null && !s.isEmpty()) {
+ for (final String item : s.split(",")) {
+ if (!item.isEmpty()) {
+ list.add(item.trim());
+ }
+ }
+ }
+ return list;
+ }
+
+ private void run() {
+ logger.fine(() -> "Running code coverage reporter for " + pathList + "...");
+ try {
+ final RealtimeReporterDao dao = new RealtimeReporterDao(conn);
+ PreferenceModel preferences;
+ try {
+ preferences = PreferenceModel.getInstance(Preferences.getPreferences());
+ } catch (NoClassDefFoundError | ExceptionInInitializerError error) {
+ // not running in SQL Developer (in tests)
+ preferences = PreferenceModel.getInstance(null);
+ }
+ if (preferences.isUseRealtimeReporter() && dao.isSupported() && connectionName != null) {
+ runCodeCoverageWithRealtimeReporter();
+ } else {
+ runCodeCoverageStandalone();
+ }
+ } finally {
+ if (frame != null) {
+ frame.exit();
+ }
+ }
+ }
+
+ private void runCodeCoverageWithRealtimeReporter() {
+ final UtplsqlRunner runner = new UtplsqlRunner(pathList, toStringList(schemas), toStringList(includeObjects),
+ toStringList(excludeObjects), getHtmlReportAssetPath(), connectionName);
+ runner.runTestAsync();
+ }
+
+ private void runCodeCoverageStandalone() {
+ Connection coverageConn = null;
+ try {
+ coverageConn = conn != null ? conn : DatabaseTools.cloneConnection(connectionName);
+ final UtplsqlDao dao = new UtplsqlDao(coverageConn);
+ final String html = dao.htmlCodeCoverage(pathList, toStringList(schemas),
+ toStringList(includeObjects), toStringList(excludeObjects), getHtmlReportAssetPath());
+ openInBrowser(html);
+ } finally {
+ try {
+ if (coverageConn != null && conn == null) {
+ // close only if connection has been cloned
+ DatabaseTools.closeConnection(coverageConn);
+ }
+ } catch (GenericDatabaseAccessException e) {
+ // ignore
+ }
+ }
+ }
+
+ public static void openInBrowser(String html) {
+ try {
+ final File file = File.createTempFile("utplsql_", ".html");
+ logger.fine(() -> "Writing result to " + file + "...");
+ FileTools.writeFile(file.toPath(), Arrays.asList(html.split(System.lineSeparator())), StandardCharsets.UTF_8);
+ final URL url = file.toURI().toURL();
+ logger.fine(() -> "Opening " + url.toExternalForm() + " in browser...");
+ final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
+ if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
+ desktop.browse(url.toURI());
+ logger.fine(() -> url.toExternalForm() + " opened in browser.");
+ } else {
+ logger.severe(
+ () -> "Could not launch " + file + " in browser. No default browser defined on this system.");
+ }
+ } catch (Exception e) {
+ final String msg = "Error while opening code coverage HTML report in browser.";
+ logger.severe(() -> msg);
+ throw new GenericRuntimeException(msg, e);
+ }
+ }
+
+ public void setFrame(final CodeCoverageReporterDialog frame) {
+ this.frame = frame;
+ }
+
+ public CodeCoverageReporterDialog getFrame() {
+ return frame;
+ }
+
+ public Connection getConnection() {
+ return conn;
+ }
+
+ public List getPathList() {
+ return pathList;
+ }
+
+ public List getIncludeObjectList() {
+ if (includeObjectList == null) {
+ return new ArrayList<>();
+ } else {
+ return includeObjectList;
+ }
+ }
+
+ public void setSchemas(final String schemas) {
+ this.schemas = schemas;
+ }
+
+ public String getSchemas() {
+ return schemas;
+ }
+
+ public void setIncludeObjects(final String includeObjects) {
+ this.includeObjects = includeObjects;
+ }
+
+ public void setExcludeObjects(final String excludeObjects) {
+ this.excludeObjects = excludeObjects;
+ }
+
+ public Thread runAsync() {
+ final Thread thread = new Thread(this::run);
+ thread.setName("code coverage reporter");
+ thread.start();
+ return thread;
+ }
+
+ public void showParameterWindow() {
+ CodeCoverageReporterDialog.createAndShow(this);
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java
new file mode 100644
index 00000000..1c7191a5
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.dal;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.URL;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.DocumentBuilder;
+
+import org.springframework.jdbc.core.CallableStatementCallback;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.SingleConnectionDataSource;
+import org.utplsql.sqldev.model.StringTools;
+import org.utplsql.sqldev.model.XMLTools;
+import org.utplsql.sqldev.model.runner.Counter;
+import org.utplsql.sqldev.model.runner.Expectation;
+import org.utplsql.sqldev.model.runner.PostEvent;
+import org.utplsql.sqldev.model.runner.PostRunEvent;
+import org.utplsql.sqldev.model.runner.PostSuiteEvent;
+import org.utplsql.sqldev.model.runner.PostTestEvent;
+import org.utplsql.sqldev.model.runner.PreRunEvent;
+import org.utplsql.sqldev.model.runner.PreSuiteEvent;
+import org.utplsql.sqldev.model.runner.PreTestEvent;
+import org.utplsql.sqldev.model.runner.RealtimeReporterEvent;
+import org.utplsql.sqldev.model.runner.Suite;
+import org.utplsql.sqldev.model.runner.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import oracle.jdbc.OracleTypes;
+
+public class RealtimeReporterDao {
+ private static final Logger logger = Logger.getLogger(RealtimeReporterDao.class.getName());
+ private static final int FIRST_VERSION_WITH_REALTIME_REPORTER = 3001004;
+ private final XMLTools xmlTools = new XMLTools();
+ private final Connection conn;
+ private final JdbcTemplate jdbcTemplate;
+
+ public RealtimeReporterDao(final Connection conn) {
+ this.conn = conn;
+ jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true));
+ jdbcTemplate.setFetchSize(UtplsqlDao.FETCH_ROWS);
+ }
+
+ public boolean isSupported() {
+ return new UtplsqlDao(conn)
+ .normalizedUtPlsqlVersionNumber() >= RealtimeReporterDao.FIRST_VERSION_WITH_REALTIME_REPORTER;
+ }
+
+ // used for execution via PL/SQL Debugger
+ public String getProduceReportPlsql(final String reporterId, final List pathList) {
+ return getProduceReportPlsql(reporterId, pathList, false);
+ }
+
+ private String getProduceReportPlsql(final String reporterId, final List pathList, boolean useBindVariable) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("DECLARE\n");
+ sb.append(" l_reporter ut_realtime_reporter := ut_realtime_reporter();\n");
+ sb.append("BEGIN\n");
+ if (useBindVariable) {
+ sb.append(" l_reporter.set_reporter_id(?);\n");
+ } else {
+ sb.append(" l_reporter.set_reporter_id('");
+ sb.append(reporterId);
+ sb.append("');\n");
+ }
+ sb.append(" l_reporter.output_buffer.init();\n");
+ sb.append(" sys.dbms_output.enable(NULL);\n");
+ sb.append(" ut_runner.run(\n");
+ sb.append(" a_paths => ut_varchar2_list(\n");
+ sb.append(StringTools.getCSV(pathList, 24));
+ sb.append(" ),\n");
+ sb.append(" a_reporters => ut_reporters(l_reporter)\n");
+ sb.append(" );\n");
+ sb.append(" sys.dbms_output.disable;\n");
+ sb.append("END;");
+ return sb.toString();
+ }
+
+ public void produceReport(final String reporterId, final List pathList) {
+ final String plsql = getProduceReportPlsql(reporterId, pathList, true);
+ final Object[] binds = { reporterId };
+ jdbcTemplate.update(plsql, binds);
+ }
+
+ public void produceReportWithCoverage(final String realtimeReporterId, final String coverageReporterId,
+ final List pathList, final List schemaList, final List includeObjectList,
+ final List excludeObjectList, final URL htmlReportAssetPath) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("DECLARE\n");
+ sb.append(" l_rt_rep ut_realtime_reporter := ut_realtime_reporter();\n");
+ sb.append(" l_cov_rep ut_coverage_html_reporter := ut_coverage_html_reporter(a_html_report_assets_path => ?);\n");
+ sb.append("BEGIN\n");
+ sb.append(" l_rt_rep.set_reporter_id(?);\n");
+ sb.append(" l_rt_rep.output_buffer.init();\n");
+ sb.append(" l_cov_rep.set_reporter_id(?);\n");
+ sb.append(" l_cov_rep.output_buffer.init();\n");
+ sb.append(" sys.dbms_output.enable(NULL);\n");
+ sb.append(" ut_runner.run(\n");
+ sb.append(" a_paths => ut_varchar2_list(\n");
+ sb.append(StringTools.getCSV(pathList, 31));
+ sb.append(" ),\n");
+ if (schemaList != null && !schemaList.isEmpty()) {
+ sb.append(" a_coverage_schemes => ut_varchar2_list(\n");
+ sb.append(StringTools.getCSV(schemaList, 31));
+ sb.append(" ),\n");
+ }
+ if (includeObjectList != null && !includeObjectList.isEmpty()) {
+ sb.append(" a_include_objects => ut_varchar2_list(\n");
+ sb.append(StringTools.getCSV(includeObjectList, 31));
+ sb.append(" ),\n");
+ }
+ if (excludeObjectList != null && !excludeObjectList.isEmpty()) {
+ sb.append(" a_exclude_objects => ut_varchar2_list(\n");
+ sb.append(StringTools.getCSV(excludeObjectList, 31));
+ sb.append(" ),\n");
+ }
+ sb.append(" a_reporters => ut_reporters(l_rt_rep, l_cov_rep)\n");
+ sb.append(" );\n");
+ sb.append(" sys.dbms_output.disable;\n");
+ sb.append("END;");
+ final String plsql = sb.toString();
+ final Object[] binds = { htmlReportAssetPath == null ? null : htmlReportAssetPath.toExternalForm(), realtimeReporterId, coverageReporterId };
+ jdbcTemplate.update(plsql, binds);
+ }
+
+ public void consumeReport(final String reporterId, final RealtimeReporterEventConsumer consumer) {
+ consumeReport(reporterId, consumer, 60);
+ }
+
+ public void consumeReport(final String reporterId, final RealtimeReporterEventConsumer consumer, final int timeoutSeconds) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("DECLARE\n");
+ sb.append(" l_reporter ut_realtime_reporter := ut_realtime_reporter();\n");
+ sb.append("BEGIN\n");
+ sb.append(" l_reporter.set_reporter_id(?);\n");
+ sb.append(" ? := l_reporter.get_lines_cursor(a_initial_timeout => ?);\n");
+ sb.append("END;");
+ final String plsql = sb.toString();
+ jdbcTemplate.setFetchSize(1);
+ try {
+ jdbcTemplate.execute(plsql, (CallableStatementCallback) cs -> {
+ cs.setString(1, reporterId);
+ cs.setInt(3, timeoutSeconds);
+ cs.registerOutParameter(2, OracleTypes.CURSOR);
+ cs.execute();
+ final ResultSet rs = (ResultSet) cs.getObject(2);
+ while (rs.next()) {
+ final String itemType = rs.getString("item_type");
+ final Clob textClob = rs.getClob("text");
+ final String textString = textClob.getSubString(1, ((int) textClob.length()));
+ final RealtimeReporterEvent event = convert(itemType, textString);
+ if (event != null) {
+ consumer.process(event);
+ }
+ }
+ rs.close();
+ return null;
+ });
+ } finally {
+ jdbcTemplate.setFetchSize(UtplsqlDao.FETCH_ROWS);
+ }
+ }
+
+ public String getHtmlCoverage(final String reporterId) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("DECLARE\n");
+ sb.append(" l_reporter ut_coverage_html_reporter := ut_coverage_html_reporter();\n");
+ sb.append("BEGIN\n");
+ sb.append(" l_reporter.set_reporter_id(?);\n");
+ sb.append(" ? := l_reporter.get_lines_cursor();\n");
+ sb.append("END;");
+ final String plsql = sb.toString();
+ return jdbcTemplate.execute(plsql, (CallableStatementCallback) cs -> {
+ cs.setString(1, reporterId);
+ cs.registerOutParameter(2, OracleTypes.CURSOR);
+ cs.execute();
+ final StringBuilder sb1 = new StringBuilder();
+ final ResultSet rs = (ResultSet) cs.getObject(2);
+ while (rs.next()) {
+ final String text = rs.getString("text");
+ if (text != null) {
+ sb1.append(text);
+ sb1.append('\n');
+ }
+ }
+ rs.close();
+ return sb1.toString();
+ });
+ }
+
+ private RealtimeReporterEvent convert(final String itemType, final String text) {
+ logger.fine(() -> "\n---- " + itemType + " ----\n" + text);
+ try {
+ final DocumentBuilder docBuilder = xmlTools.createDocumentBuilder();
+ final Document doc = docBuilder.parse(new InputSource(new StringReader(text)));
+ RealtimeReporterEvent event = null;
+ if ("pre-run".equals(itemType)) {
+ event = convertToPreRunEvent(doc);
+ } else if ("post-run".equals(itemType)) {
+ event = convertToPostRunEvent(doc);
+ } else if ("pre-suite".equals(itemType)) {
+ event = convertToPreSuiteEvent(doc);
+ } else if ("post-suite".equals(itemType)) {
+ event = convertToPostSuiteEvent(doc);
+ } else if ("pre-test".equals(itemType)) {
+ event = convertToPreTestEvent(doc);
+ } else if ("post-test".equals(itemType)) {
+ event = convertToPostTestEvent(doc);
+ }
+ return event;
+ } catch (SAXException e) {
+ // continue processing, see https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/107
+ final String msg = "Parse error while processing " + itemType + " with content: " + text;
+ logger.severe(() -> msg);
+ return null;
+ } catch (IOException e) {
+ // continue processing, see https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/107
+ final String msg = "I/O error while processing " + itemType + " with content: " + text;
+ logger.severe(() -> msg);
+ return null;
+ }
+ }
+
+ private RealtimeReporterEvent convertToPreRunEvent(final Document doc) {
+ final PreRunEvent event = new PreRunEvent();
+ final Node totalNumberOfTestsNode = xmlTools.getNode(doc, "/event/totalNumberOfTests");
+ String totalNumberOfTestsTextContent = null;
+ if (totalNumberOfTestsNode != null) {
+ totalNumberOfTestsTextContent = totalNumberOfTestsNode.getTextContent();
+ }
+ event.setTotalNumberOfTests(Integer.valueOf(totalNumberOfTestsTextContent != null ? totalNumberOfTestsTextContent : "0"));
+ final NodeList nodes = xmlTools.getNodeList(doc, "/event/items/*");
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final Node node = nodes.item(i);
+ final String nodeName = node.getNodeName();
+ if ("suite".equals(nodeName)) {
+ final Suite suite = new Suite();
+ event.getItems().add(suite);
+ populate(suite, node);
+ } else if ("test".equals(nodeName)) {
+ final Test test = new Test();
+ event.getItems().add(test);
+ populate(test, node);
+ }
+ }
+ return event;
+ }
+
+ private RealtimeReporterEvent convertToPostRunEvent(final Document doc) {
+ final PostRunEvent event = new PostRunEvent();
+ populate(event, xmlTools.getNode(doc, "/event/run"));
+ return event;
+ }
+
+ private RealtimeReporterEvent convertToPreSuiteEvent(final Document doc) {
+ final PreSuiteEvent event = new PreSuiteEvent();
+ final Node node = xmlTools.getNode(doc, "/event/suite");
+ if (node instanceof Element) {
+ event.setId(xmlTools.getAttributeValue(node, "id"));
+ }
+ return event;
+ }
+
+ private RealtimeReporterEvent convertToPostSuiteEvent(final Document doc) {
+ final PostSuiteEvent event = new PostSuiteEvent();
+ final Node node = xmlTools.getNode(doc, "/event/suite");
+ if (node instanceof Element) {
+ event.setId(xmlTools.getAttributeValue(node, "id"));
+ populate(event, node);
+ }
+ return event;
+ }
+
+ private RealtimeReporterEvent convertToPreTestEvent(final Document doc) {
+ final PreTestEvent event = new PreTestEvent();
+ final Node node = xmlTools.getNode(doc, "/event/test");
+ if (node instanceof Element) {
+ event.setId(xmlTools.getAttributeValue(node, "id"));
+ event.setTestNumber(Integer.valueOf(xmlTools.getElementValue(node, "testNumber")));
+ event.setTotalNumberOfTests(Integer.valueOf(xmlTools.getElementValue(node, "totalNumberOfTests")));
+ }
+ return event;
+ }
+
+ private RealtimeReporterEvent convertToPostTestEvent(final Document doc) {
+ final PostTestEvent event = new PostTestEvent();
+ final Node node = xmlTools.getNode(doc, "/event/test");
+ if (node instanceof Element) {
+ event.setId(xmlTools.getAttributeValue(node, "id"));
+ event.setTestNumber(Integer.valueOf(xmlTools.getElementValue(node, "testNumber")));
+ event.setTotalNumberOfTests(Integer.valueOf(xmlTools.getElementValue(node, "totalNumberOfTests")));
+ populate(event, node);
+ final NodeList failedExpectations = xmlTools.getNodeList(node, "failedExpectations/expectation");
+ for (int i = 0; i < failedExpectations.getLength(); i++) {
+ final Node expectationNode = failedExpectations.item(i);
+ final Expectation expectation = new Expectation();
+ event.getFailedExpectations().add(expectation);
+ populate(expectation, expectationNode);
+ }
+ }
+ return event;
+ }
+
+ private void populate(final Suite suite, final Node node) {
+ if (node instanceof Element) {
+ suite.setId(xmlTools.getAttributeValue(node, "id"));
+ suite.setName(xmlTools.getElementValue(node, "name"));
+ suite.setDescription(xmlTools.getElementValue(node, "description"));
+ final NodeList nodeList = xmlTools.getNodeList(node, "items/*");
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ final Node childNode = nodeList.item(i);
+ final String nodeName = childNode.getNodeName();
+ if ("suite".equals(nodeName)) {
+ final Suite childSuite = new Suite();
+ suite.getItems().add(childSuite);
+ populate(childSuite, childNode);
+ } else if ("test".equals(nodeName)) {
+ final Test childTest = new Test();
+ suite.getItems().add(childTest);
+ populate(childTest, childNode);
+ }
+ }
+ }
+ }
+
+ private void populate(final Test test, final Node node) {
+ if (node instanceof Element) {
+ test.setId(xmlTools.getAttributeValue(node, "id"));
+ test.setExecutableType(xmlTools.getElementValue(node, "executableType"));
+ test.setOwnerName(xmlTools.getElementValue(node, "ownerName"));
+ test.setObjectName(xmlTools.getElementValue(node, "objectName"));
+ test.setProcedureName(xmlTools.getElementValue(node, "procedureName"));
+ test.setDisabled("true".equals(xmlTools.getElementValue(node, "disabled")));
+ test.setDisabledReason(xmlTools.getElementValue(node, "disabledReason"));
+ test.setName(xmlTools.getElementValue(node, "name"));
+ test.setDescription(xmlTools.getElementValue(node, "description"));
+ test.setTestNumber(Integer.valueOf(xmlTools.getElementValue(node, "testNumber")));
+ }
+ }
+
+ private void populate(final PostEvent event, final Node node) {
+ if (node instanceof Element) {
+ event.setStartTime(xmlTools.getElementValue(node, "startTime"));
+ event.setEndTime(xmlTools.getElementValue(node, "endTime"));
+ event.setExecutionTime(Double.valueOf(xmlTools.getElementValue(node, "executionTime")));
+ populate(event.getCounter(), node);
+ event.setErrorStack(xmlTools.getElementValue(node, "errorStack"));
+ event.setServerOutput(xmlTools.getElementValue(node, "serverOutput"));
+ event.setWarnings(xmlTools.getElementValue(node, "warnings"));
+ }
+ }
+
+ private void populate(final Counter counter, final Node node) {
+ if (node instanceof Element) {
+ final Node counterNode = xmlTools.getElementNode(node, "counter");
+ if (counterNode instanceof Element) {
+ counter.setDisabled(Integer.valueOf(xmlTools.getElementValue(counterNode, "disabled")));
+ counter.setSuccess(Integer.valueOf(xmlTools.getElementValue(counterNode, "success")));
+ counter.setFailure(Integer.valueOf(xmlTools.getElementValue(counterNode, "failure")));
+ counter.setError(Integer.valueOf(xmlTools.getElementValue(counterNode, "error")));
+ counter.setWarning(Integer.valueOf(xmlTools.getElementValue(counterNode, "warning")));
+ }
+ }
+ }
+
+ private void populate(final Expectation expectation, final Node node) {
+ if (node instanceof Element) {
+ expectation.setDescription(xmlTools.getElementValue(node, "description"));
+ expectation.setMessage(xmlTools.getElementValue(node, "message"));
+ expectation.setCaller(xmlTools.getElementValue(node, "caller"));
+ }
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.xtend b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterEventConsumer.java
similarity index 74%
rename from sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.xtend
rename to sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterEventConsumer.java
index 94768379..cede3ca7 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.xtend
+++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterEventConsumer.java
@@ -13,13 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.utplsql.sqldev.model.parser
+package org.utplsql.sqldev.dal;
-import org.eclipse.xtend.lib.annotations.Accessors
-import org.utplsql.sqldev.model.AbstractModel
+import org.utplsql.sqldev.model.runner.RealtimeReporterEvent;
-@Accessors
-class Unit extends AbstractModel {
- String name
- Integer position
-}
\ No newline at end of file
+public interface RealtimeReporterEventConsumer {
+ void process(final RealtimeReporterEvent event);
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java
new file mode 100644
index 00000000..605d376b
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java
@@ -0,0 +1,1063 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.dal;
+
+import java.net.URL;
+import java.sql.Connection;
+import java.sql.Types;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.oddgen.sqldev.generators.model.Node;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.CallableStatementCallback;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.SingleConnectionDataSource;
+import org.utplsql.sqldev.model.StringTools;
+import org.utplsql.sqldev.model.ut.Annotation;
+import org.utplsql.sqldev.model.ut.OutputLines;
+
+public class UtplsqlDao {
+ public static final String UTPLSQL_PACKAGE_NAME = "UT";
+ public static final int FIRST_VERSION_WITH_INTERNAL_ANNOTATION_API = 3000004;
+ public static final int FIRST_VERSION_WITH_ANNOTATION_API = 3001003;
+ public static final int FIRST_VERSION_WITHOUT_INTERNAL_API = 3001008;
+ public static final int FIRST_VERSION_WITH_HAS_SUITES_API = 3001008;
+ public static final int FETCH_ROWS = 100;
+ private final JdbcTemplate jdbcTemplate;
+ // cache fields
+ private Boolean cachedDbaViewAccessible;
+ private String cachedUtplsqlSchema;
+ private String cachedUtPlsqlVersion;
+
+ public UtplsqlDao(final Connection conn) {
+ jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true));
+ jdbcTemplate.setFetchSize(FETCH_ROWS);
+ }
+
+ /**
+ * used for testing purposes only
+ */
+ public void setUtPlsqlVersion(final String utPlsqlVersion) {
+ this.cachedUtPlsqlVersion = utPlsqlVersion;
+ }
+
+ /**
+ * returns a normalized utPLSQL version in format 9.9.9
+ */
+ public String normalizedUtPlsqlVersion() {
+ final String version = this.getUtPlsqlVersion();
+ if (version != null) {
+ final Pattern p = Pattern.compile("(\\d+\\.\\d+\\.\\d+)");
+ final Matcher m = p.matcher(version);
+ if (m.find()) {
+ return m.group(0);
+ }
+ }
+ return "0.0.0";
+ }
+
+ /**
+ * get version as number, e.g. 3001004
+ */
+ public int normalizedUtPlsqlVersionNumber() {
+ final Pattern p = Pattern.compile("(\\d+)");
+ final String version = this.normalizedUtPlsqlVersion();
+ final Matcher m = p.matcher(version);
+ m.find();
+ final String major = m.group();
+ m.find();
+ final String minor = m.group();
+ m.find();
+ final String bugfix = m.group();
+ return Integer.parseInt(major) * 1000000 + Integer.parseInt(minor) * 1000 + Integer.parseInt(bugfix);
+ }
+
+ /**
+ * gets version of installed utPLSQL
+ */
+ public String getUtPlsqlVersion() {
+ if (cachedUtPlsqlVersion == null) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("BEGIN\n");
+ sb.append(" ? := ut.version;\n");
+ sb.append("END;");
+ final String sql = sb.toString();
+ try {
+ cachedUtPlsqlVersion = jdbcTemplate.execute(sql, (CallableStatementCallback) cs -> {
+ cs.registerOutParameter(1, Types.VARCHAR);
+ cs.execute();
+ return cs.getString(1);
+ });
+ } catch (DataAccessException e) {
+ // ignore error
+ }
+ }
+ return cachedUtPlsqlVersion;
+ }
+
+ public boolean isDbaViewAccessible() {
+ if (cachedDbaViewAccessible == null) {
+ try {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("SELECT 1 AS dummy\n");
+ sb.append(" FROM dba_objects\n");
+ sb.append(" WHERE 1=2\n");
+ sb.append("UNION ALL\n");
+ sb.append("SELECT 1\n");
+ sb.append(" FROM dba_synonyms\n");
+ sb.append(" WHERE 1=2\n");
+ sb.append("UNION ALL\n");
+ sb.append("SELECT 1\n");
+ sb.append(" FROM dba_dependencies\n");
+ sb.append(" WHERE 1=2\n");
+ final String sql = sb.toString();
+ jdbcTemplate.execute(sql);
+ cachedDbaViewAccessible = true;
+ } catch (DataAccessException e) {
+ cachedDbaViewAccessible = false;
+ }
+ }
+ return cachedDbaViewAccessible;
+ }
+
+ public String getDbaView(String viewName) {
+ StringBuilder sb = new StringBuilder();
+ if (isDbaViewAccessible()) {
+ sb.append("dba");
+ } else {
+ sb.append("all");
+ }
+ sb.append("_");
+ sb.append(viewName);
+ return sb.toString();
+ }
+
+ /**
+ * Gets the schema name of the utPLSQL installation.
+ *
+ * @return utPLSQL schema or null if no utPLSQL is not installed
+ * @throws DataAccessException
+ * if there is a problem
+ */
+ public String getUtplsqlSchema() {
+ if (cachedUtplsqlSchema == null) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("SELECT table_owner\n");
+ sb.append(" FROM ");
+ sb.append(getDbaView("synonyms\n"));
+ sb.append(" WHERE owner = 'PUBLIC'\n");
+ sb.append(" AND synonym_name = '");
+ sb.append(UtplsqlDao.UTPLSQL_PACKAGE_NAME);
+ sb.append("'\n");
+ sb.append(" AND table_name = '");
+ sb.append(UtplsqlDao.UTPLSQL_PACKAGE_NAME);
+ sb.append("'");
+ final String sql = sb.toString();
+ try {
+ cachedUtplsqlSchema = jdbcTemplate.queryForObject(sql, String.class);
+ } catch (EmptyResultDataAccessException e) {
+ cachedUtplsqlSchema = null;
+ }
+ }
+ return cachedUtplsqlSchema;
+ }
+
+ /**
+ * Checks if the package ut_annotation_manager is installed. This package has
+ * been introduced with utPLSQL 3.0.4. This version is a prerequisite to
+ * identify utPLSQL unit test procedures.
+ *
+ * @return true if ut_annotation_manager package has been found
+ * @throws DataAccessException
+ * if there is a problem
+ */
+ public boolean isUtAnnotationManagerInstalled() {
+ return normalizedUtPlsqlVersionNumber() >= UtplsqlDao.FIRST_VERSION_WITH_INTERNAL_ANNOTATION_API;
+ }
+
+ /**
+ * Checks if utPLSQL tests exist
+ *
+ * @param owner
+ * schema name, mandatory, case-insensitive
+ * @param objectName
+ * name of the package or package body, optional, case-insensitive
+ * @param subobjectName
+ * name of the procedure, optional, case-insensitive
+ * @return true if at least one test has been found
+ * @throws DataAccessException
+ * if a utPLSQL version less than 3.0.4 is installed or if there are
+ * other problems
+ */
+ public boolean containsUtplsqlTest(final String owner, final String objectName, final String subobjectName) {
+ try {
+ if (normalizedUtPlsqlVersionNumber() >= UtplsqlDao.FIRST_VERSION_WITH_HAS_SUITES_API && objectName != null
+ && subobjectName != null) {
+ // use faster check function available since v3.1.3 (reliable in v3.1.8)
+ final StringBuilder sb = new StringBuilder();
+ sb.append("DECLARE\n");
+ sb.append(" l_return VARCHAR2(1) := '0';\n");
+ sb.append("BEGIN\n");
+ sb.append(" IF ut_runner.is_test(?, ?, ?) THEN\n");
+ sb.append(" l_return := '1';\n");
+ sb.append(" END IF;\n");
+ sb.append(" ? := l_return;\n");
+ sb.append("END;");
+ final String sql = sb.toString();
+ final Boolean ret = jdbcTemplate.execute(sql, (CallableStatementCallback) cs -> {
+ cs.setString(1, owner);
+ cs.setString(2, objectName);
+ cs.setString(3, subobjectName);
+ cs.registerOutParameter(4, Types.VARCHAR);
+ cs.execute();
+ final String ret1 = cs.getString(4);
+ return "1".equals(ret1);
+ });
+ return ret != null && ret;
+ } else if (normalizedUtPlsqlVersionNumber() >= FIRST_VERSION_WITH_ANNOTATION_API) {
+ // using API available since 3.1.3, can handle nulls in objectName and subobjectName
+ StringBuilder sb = new StringBuilder();
+ sb.append("SELECT count(*)\n");
+ sb.append(" FROM TABLE(ut_runner.get_suites_info(upper(?), upper(?)))\n");
+ sb.append(" WHERE item_type IN ('UT_TEST', 'UT_SUITE')\n");
+ sb.append(" AND (item_name = upper(?) or ? IS NULL)\n");
+ final String sql = sb.toString();
+ final Object[] binds = new Object[] {owner, objectName, subobjectName, subobjectName};
+ final int[] types = new int[] {Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR};
+ final Integer found = jdbcTemplate.queryForObject(sql, binds, types, Integer.class);
+ return found != null && found > 0;
+ } else {
+ // using internal API (deprecated, not accessible in latest version)
+ StringBuilder sb = new StringBuilder();
+ sb.append("SELECT count(\n");
+ sb.append(" CASE\n");
+ sb.append(" WHEN a.name = 'test'\n");
+ sb.append(" AND (upper(a.subobject_name) = upper(?) OR ? IS NULL)\n");
+ sb.append(" THEN\n");
+ sb.append(" 1\n");
+ sb.append(" ELSE\n");
+ sb.append(" NULL\n");
+ sb.append(" END\n");
+ sb.append(" )\n");
+ sb.append(" FROM TABLE(");
+ sb.append(getUtplsqlSchema());
+ sb.append(".ut_annotation_manager.get_annotated_objects(upper(?), 'PACKAGE')) o\n");
+ sb.append(" CROSS JOIN TABLE(o.annotations) a\n");
+ sb.append(" WHERE (o.object_name = upper(?) OR ? IS NULL)\n");
+ sb.append(" AND a.name IN ('test', 'suite')\n");
+ sb.append("HAVING count(\n");
+ sb.append(" CASE\n");
+ sb.append(" WHEN a.name = 'suite' THEN\n");
+ sb.append(" 1\n");
+ sb.append(" ELSE\n");
+ sb.append(" NULL\n");
+ sb.append(" END\n");
+ sb.append(" ) > 0");
+ final String sql = sb.toString();
+ final Object[] binds = new Object[] {subobjectName, subobjectName, owner, objectName, objectName};
+ final Integer found = jdbcTemplate.queryForObject(sql, Integer.class, binds);
+ return found != null && found > 0;
+ }
+ } catch (EmptyResultDataAccessException e) {
+ return false;
+ }
+ }
+
+ public boolean containsUtplsqlTest(final String owner) {
+ if (normalizedUtPlsqlVersionNumber() >= org.utplsql.sqldev.dal.UtplsqlDao.FIRST_VERSION_WITH_HAS_SUITES_API) {
+ // use faster check function available since v3.1.3 (reliable in v3.1.8)
+ StringBuilder sb = new StringBuilder();
+ sb.append("DECLARE\n");
+ sb.append(" l_return VARCHAR2(1) := '0';\n");
+ sb.append("BEGIN\n");
+ sb.append(" IF ut_runner.has_suites(?) THEN\n");
+ sb.append(" l_return := '1';\n");
+ sb.append(" END IF;\n");
+ sb.append(" ? := l_return;\n");
+ sb.append("END;");
+ final String sql = sb.toString();
+ final Boolean ret = jdbcTemplate.execute(sql, (CallableStatementCallback) cs -> {
+ cs.setString(1, owner);
+ cs.registerOutParameter(2, Types.VARCHAR);
+ cs.execute();
+ final String ret1 = cs.getString(2);
+ return "1".equals(ret1);
+ });
+ return ret != null && ret;
+ } else {
+ return containsUtplsqlTest(owner, null, null);
+ }
+ }
+
+ public boolean containsUtplsqlTest(final String owner, final String objectName) {
+ if (normalizedUtPlsqlVersionNumber() >= org.utplsql.sqldev.dal.UtplsqlDao.FIRST_VERSION_WITH_HAS_SUITES_API) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("DECLARE\n");
+ sb.append(" l_return VARCHAR2(1) := '0';\n");
+ sb.append("BEGIN\n");
+ sb.append(" IF ut_runner.is_suite(?, ?) THEN\n");
+ sb.append(" l_return := '1';\n");
+ sb.append(" END IF;\n");
+ sb.append(" ? := l_return;\n");
+ sb.append("END;");
+ final String sql = sb.toString();
+ Boolean ret = jdbcTemplate.execute(sql, (CallableStatementCallback) cs -> {
+ cs.setString(1, owner);
+ cs.setString(2, objectName);
+ cs.registerOutParameter(3, Types.VARCHAR);
+ cs.execute();
+ final String ret1 = cs.getString(3);
+ return "1".equals(ret1);
+ });
+ return ret != null && ret;
+ } else {
+ return containsUtplsqlTest(owner, objectName, null);
+ }
+ }
+
+ /**
+ * Gets a list of utPLSQL annotations for a given PL/SQL package specification
+ *
+ * @param owner
+ * schema name, mandatory, case-insensitive
+ * @param objectName
+ * name of the package or package body, optional, case-insensitive
+ * @return list of Annotation with name 'suite' or 'test'
+ * @throws DataAccessException
+ * if a utPLSQL version less than 3.0.4 is installed or if there are
+ * other problems
+ */
+ public List annotations(final String owner, final String objectName) {
+ StringBuilder sb = new StringBuilder();
+ if (normalizedUtPlsqlVersionNumber() >= FIRST_VERSION_WITH_ANNOTATION_API) {
+ sb.append("SELECT object_owner,\n");
+ sb.append(" object_name,\n");
+ sb.append(" lower(substr(item_type, 4)) AS name,\n");
+ sb.append(" item_name as subobject_name\n");
+ sb.append(" FROM TABLE(ut_runner.get_suites_info(upper(?), upper(?)))");
+ } else {
+ sb.append("SELECT o.object_owner,\n");
+ sb.append(" o.object_name,\n");
+ sb.append(" a.name,\n");
+ sb.append(" a.text,\n");
+ sb.append(" coalesce(upper(a.subobject_name), o.object_name) AS subobject_name\n");
+ sb.append(" FROM TABLE(");
+ sb.append(getUtplsqlSchema());
+ sb.append(".ut_annotation_manager.get_annotated_objects(upper(?), 'PACKAGE')) o\n");
+ sb.append(" CROSS JOIN TABLE(o.annotations) a\n");
+ sb.append(" WHERE o.object_name = upper(?)");
+ }
+ final String sql = sb.toString();
+ final BeanPropertyRowMapper rowMapper = new BeanPropertyRowMapper<>(Annotation.class);
+ final Object[] binds = new Object[] {owner, objectName};
+ return jdbcTemplate.query(sql, rowMapper, binds);
+ }
+
+ /**
+ * Gets a list of public units in the object type
+ *
+ * @param objectType
+ * expected object types are PACKAGE, TYPE, FUNCTION, PROCEDURE
+ * @param objectName
+ * name of the object
+ * @return list of the public units in the object type
+ * @throws DataAccessException
+ * if there is a problem
+ */
+ public List units(final String objectType, final String objectName) {
+ if ("PACKAGE".equals(objectType) || "TYPE".equals(objectType)) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("SELECT procedure_name\n");
+ sb.append(" FROM user_procedures\n");
+ sb.append(" WHERE object_type = ?\n");
+ sb.append(" AND object_name = ?\n");
+ sb.append(" AND procedure_name IS NOT NULL\n");
+ sb.append(" GROUP BY procedure_name\n");
+ sb.append(" ORDER BY min(subprogram_id)");
+ final String sql = sb.toString();
+ final Object[] binds = new Object[] {objectType, objectName};
+ return jdbcTemplate.queryForList(sql, String.class, binds);
+ } else {
+ return Collections.singletonList(objectName);
+ }
+ }
+
+ /**
+ * Gets a list of oddgen's nodes as candidates to create utPLSQL test packages.
+ * Candidates are packages, types, functions and procedures in the current user.
+ *
+ * This functions must be called from an oddgen generator only, since the Node
+ * is not defined in the utPLSQL extension.
+ *
+ * @param objectType
+ * expected object types are PACKAGE, TYPE, FUNCTION, PROCEDURE
+ * @return list of the oddgen nodes for the requested object type
+ * @throws DataAccessException
+ * if there is a problem
+ */
+ public List testables(final String objectType) {
+ StringBuilder sb = new StringBuilder();
+ if ("PACKAGE".equals(objectType)) {
+ if (normalizedUtPlsqlVersionNumber() >= FIRST_VERSION_WITH_ANNOTATION_API) {
+ // using API available since 3.1.3
+ sb.append("SELECT DISTINCT\n");
+ sb.append(" object_type || '.' || object_name AS id,\n");
+ sb.append(" object_type AS parent_id,\n");
+ sb.append(" 1 AS leaf,\n");
+ sb.append(" 1 AS generatable,\n");
+ sb.append(" 1 AS multiselectable\n");
+ sb.append(" FROM user_procedures\n");
+ sb.append(" WHERE object_type = ?\n");
+ sb.append(" AND procedure_name IS NOT NULL\n");
+ sb.append(" AND object_name NOT IN (\n");
+ sb.append(" SELECT object_name\n");
+ sb.append(" FROM TABLE(ut_runner.get_suites_info(USER))\n");
+ sb.append(" WHERE item_type = 'UT_SUITE'\n");
+ sb.append(" )");
+ } else {
+ // using internal API (deprecated, not accessible in latest version)
+ sb.append("SELECT DISTINCT\n");
+ sb.append(" object_type || '.' || object_name AS id,\n");
+ sb.append(" object_type AS parent_id,\n");
+ sb.append(" 1 AS leaf,\n");
+ sb.append(" 1 AS generatable,\n");
+ sb.append(" 1 AS multiselectable\n");
+ sb.append(" FROM user_procedures\n");
+ sb.append(" WHERE object_type = ?\n");
+ sb.append(" AND procedure_name IS NOT NULL\n");
+ sb.append(" AND object_name NOT IN (\n");
+ sb.append(" SELECT object_name\n");
+ sb.append(" FROM TABLE(\n");
+ sb.append(getUtplsqlSchema());
+ sb.append(".ut_annotation_manager.get_annotated_objects(USER, 'PACKAGE'))\n");
+ sb.append(" )");
+ }
+ } else if ("TYPE".equals(objectType)) {
+ sb.append("SELECT DISTINCT\n");
+ sb.append(" object_type || '.' || object_name AS id,\n");
+ sb.append(" object_type AS parent_id,\n");
+ sb.append(" 1 AS leaf,\n");
+ sb.append(" 1 AS generatable,\n");
+ sb.append(" 1 AS multiselectable\n");
+ sb.append(" FROM user_procedures\n");
+ sb.append(" WHERE object_type = ?\n");
+ sb.append(" AND procedure_name IS NOT NULL");
+ } else {
+ sb.append("SELECT object_type || '.' || object_name AS id,\n");
+ sb.append(" object_type AS parent_id,\n");
+ sb.append(" 1 AS leaf,\n");
+ sb.append(" 1 AS generatable,\n");
+ sb.append(" 1 AS multiselectable\n");
+ sb.append(" FROM user_objects\n");
+ sb.append(" WHERE object_type = ?\n");
+ sb.append(" AND generated = 'N'");
+ }
+ final String sql = sb.toString();
+ final Object[] binds = new Object[] {objectType};
+ BeanPropertyRowMapper rowMapper = new BeanPropertyRowMapper<>(Node.class);
+ return jdbcTemplate.query(sql, rowMapper, binds);
+ }
+
+ /**
+ * Gets a list of oddgen's nodes as candidates to run utPLSQL tests.
+ *
+ * This functions must be called from an oddgen generator only, since the Node
+ * is not defined in the utPLSQL extension.
+ *
+ * @return list of oddgen nodes (complete hierarchy loaded eagerly)
+ * @throws DataAccessException
+ * if there is a problem
+ */
+ public List runnables() {
+ StringBuilder sb = new StringBuilder();
+ if (normalizedUtPlsqlVersionNumber() >= FIRST_VERSION_WITH_ANNOTATION_API) {
+ // using API available since 3.1.3
+ sb.append("WITH\n");
+ sb.append(" test AS (\n");
+ sb.append(" SELECT object_owner,\n");
+ sb.append(" object_name,\n");
+ sb.append(" path AS suitepath,\n");
+ sb.append(" count(\n");
+ sb.append(" CASE\n");
+ sb.append(" WHEN item_type = 'UT_TEST' THEN\n");
+ sb.append(" 1\n");
+ sb.append(" ELSE\n");
+ sb.append(" NULL\n");
+ sb.append(" END\n");
+ sb.append(" ) over (partition by object_owner, object_name) AS test_count,\n");
+ sb.append(" item_type,\n");
+ sb.append(" item_name,\n");
+ sb.append(" item_description\n");
+ sb.append(" FROM TABLE(ut_runner.get_suites_info(user))\n");
+ sb.append(" ),\n");
+ sb.append(" suite_tree AS (\n");
+ sb.append(" SELECT null AS parent_id,\n");
+ sb.append(" 'SUITE' AS id,\n");
+ sb.append(" 'All Suites' AS name,\n");
+ sb.append(" 'All utPLSQL test suites' AS description,\n");
+ sb.append(" 'PACKAGE_FOLDER_ICON' AS iconName,\n");
+ sb.append(" 'No' AS leaf,\n");
+ sb.append(" 'Yes' AS generatable,\n");
+ sb.append(" 'Yes' AS multiselectable,\n");
+ sb.append(" 'Yes' AS relevant\n");
+ sb.append(" FROM dual\n");
+ sb.append(" UNION ALL\n");
+ sb.append(" SELECT DISTINCT\n");
+ sb.append(" 'SUITE' AS parent_id,\n");
+ sb.append(" object_owner || '.' || object_name AS id,\n");
+ sb.append(" object_name AS name,\n");
+ sb.append(" null AS description,\n");
+ sb.append(" 'PACKAGE_ICON' AS iconName,\n");
+ sb.append(" 'No' AS leaf,\n");
+ sb.append(" 'Yes' AS generatable,\n");
+ sb.append(" 'Yes' AS multiselectable,\n");
+ sb.append(" 'Yes' AS relevant\n");
+ sb.append(" FROM test\n");
+ sb.append(" WHERE item_type IN ('UT_TEST', 'UT_SUITE')\n");
+ sb.append(" UNION ALL\n");
+ sb.append(" SELECT object_owner || '.' || object_name AS parent_id,\n");
+ sb.append(" object_owner || '.' || object_name || '.' || item_name AS id,\n");
+ sb.append(" item_name AS name,\n");
+ sb.append(" item_description AS description,\n");
+ sb.append(" 'PROCEDURE_ICON' AS iconName,\n");
+ sb.append(" 'Yes' AS leaf,\n");
+ sb.append(" 'Yes' AS generatable,\n");
+ sb.append(" 'Yes' AS multiselectable,\n");
+ sb.append(" 'Yes' AS relevant\n");
+ sb.append(" FROM test\n");
+ sb.append(" WHERE item_type = 'UT_TEST'\n");
+ sb.append(" ),\n");
+ sb.append(" suitepath_tree AS (\n");
+ sb.append(" SELECT NULL AS parent_id,\n");
+ sb.append(" 'SUITEPATH' AS id,\n");
+ sb.append(" 'All Suitepaths' AS name\n,");
+ sb.append(" 'All utPLSQL test suitepathes' AS description,\n");
+ sb.append(" 'FOLDER_ICON' AS iconName,\n");
+ sb.append(" 'No' AS leaf,\n");
+ sb.append(" 'Yes' AS generatable,\n");
+ sb.append(" 'Yes' AS multiselectable,\n");
+ sb.append(" 'Yes' AS relevant\n");
+ sb.append(" FROM dual\n");
+ sb.append(" UNION ALL\n");
+ sb.append(" SELECT CASE\n");
+ sb.append(" WHEN regexp_replace(suitepath,'\\.?\\w+$','') IS NULL THEN\n");
+ sb.append(" 'SUITEPATH'\n");
+ sb.append(" ELSE\n");
+ sb.append(" object_owner || ':' || regexp_replace(suitepath,'\\.?\\w+$','')\n");
+ sb.append(" END AS parent_id,\n");
+ sb.append(" object_owner || ':' || suitepath AS id,\n");
+ sb.append(" item_name AS name,\n");
+ sb.append(" item_description AS description,\n");
+ sb.append(" CASE\n");
+ sb.append(" WHEN item_type = 'UT_SUITE' AND test_count > 0 THEN\n");
+ sb.append(" 'PACKAGE_ICON'\n");
+ sb.append(" WHEN item_type = 'UT_TEST' THEN\n");
+ sb.append(" 'PROCEDURE_ICON'\n");
+ sb.append(" ELSE\n");
+ sb.append(" 'FOLDER_ICON'\n");
+ sb.append(" END AS iconName,\n");
+ sb.append(" CASE item_type\n");
+ sb.append(" WHEN 'UT_TEST' THEN\n");
+ sb.append(" 'Yes'\n");
+ sb.append(" ELSE\n");
+ sb.append(" 'No'\n");
+ sb.append(" END AS leaf,\n");
+ sb.append(" 'Yes' AS generatable,\n");
+ sb.append(" 'Yes' AS multiselectable,\n");
+ sb.append(" 'Yes' AS relevant\n");
+ sb.append(" FROM test\n");
+ sb.append(" ),\n");
+ sb.append(" tree AS (\n");
+ sb.append(" SELECT parent_id, id, name, description, iconName, leaf, generatable, multiselectable, relevant\n");
+ sb.append(" FROM suite_tree\n");
+ sb.append(" UNION ALL\n");
+ sb.append(" SELECT parent_id, id, name, description, iconName, leaf, generatable, multiselectable, relevant\n");
+ sb.append(" FROM suitepath_tree\n");
+ sb.append(" )\n");
+ sb.append("SELECT parent_id, id, initcap(name) AS name, description, iconName, leaf, generatable, multiselectable, relevant\n");
+ sb.append(" FROM tree");
+ } else {
+ // using internal API (deprecated, not accessible in latest version)
+ sb.append("WITH\n");
+ sb.append(" base AS (\n");
+ sb.append(" SELECT rownum AS an_id,\n");
+ sb.append(" o.object_owner,\n");
+ sb.append(" o.object_type,\n");
+ sb.append(" o.object_name,\n");
+ sb.append(" lower(a.name) AS name,\n");
+ sb.append(" a.text,\n");
+ sb.append(" a.subobject_name\n");
+ sb.append(" FROM table(");
+ sb.append(getUtplsqlSchema());
+ sb.append(".ut_annotation_manager.get_annotated_objects(user, 'PACKAGE')) o\n");
+ sb.append(" CROSS JOIN table(o.annotations) a\n");
+ sb.append(" WHERE lower(a.name) in ('suite', 'suitepath', 'endcontext', 'test')\n");
+ sb.append(" OR lower(a.name) = 'context' AND regexp_like(text, '(\\w+)(\\.\\w+)*')\n");
+ sb.append(" ),\n");
+ sb.append(" suite AS (\n");
+ sb.append(" SELECT object_owner, object_type, object_name, text AS suite_description\n");
+ sb.append(" FROM base\n");
+ sb.append(" WHERE name = 'suite'\n");
+ sb.append(" ),\n");
+ sb.append(" suitepath as (\n");
+ sb.append(" SELECT object_owner, object_type, object_name, lower(text) AS suitepath\n");
+ sb.append(" FROM base\n");
+ sb.append(" WHERE name = 'suitepath'");
+ sb.append(" ),\n");
+ sb.append(" context_base AS (\n");
+ sb.append(" SELECT an_id,\n");
+ sb.append(" lead(an_id) over (partition by object_owner, object_type, object_name order by an_id) AS an_id_end,\n");
+ sb.append(" object_owner,\n");
+ sb.append(" object_type,\n");
+ sb.append(" object_name,\n");
+ sb.append(" name,\n");
+ sb.append(" lead(name) over (partition by object_owner, object_type, object_name order by an_id) AS name_end,\n");
+ sb.append(" text AS context\n");
+ sb.append(" FROM base\n");
+ sb.append(" WHERE name IN ('context', 'endcontext')\n");
+ sb.append(" ),\n");
+ sb.append(" context AS (\n");
+ sb.append(" SELECT an_id, an_id_end, object_owner, object_type, object_name, context\n");
+ sb.append(" FROM context_base\n");
+ sb.append(" WHERE name = 'context'\n");
+ sb.append(" AND name_end = 'endcontext'\n");
+ sb.append(" ),\n");
+ sb.append(" test AS (\n");
+ sb.append(" SELECT b.an_id,\n");
+ sb.append(" b.object_owner,\n");
+ sb.append(" b.object_type,\n");
+ sb.append(" b.object_name,\n");
+ sb.append(" p.suitepath,\n");
+ sb.append(" c.context,\n");
+ sb.append(" b.subobject_name,\n");
+ sb.append(" b.text AS test_description\n");
+ sb.append(" FROM base b\n");
+ sb.append(" LEFT JOIN suitepath p\n");
+ sb.append(" ON p.object_owner = b.object_owner\n");
+ sb.append(" AND p.object_type = b.object_type\n");
+ sb.append(" AND p.object_name = b.object_name\n");
+ sb.append(" LEFT JOIN context c\n");
+ sb.append(" ON c.object_owner = b.object_owner\n");
+ sb.append(" AND c.object_type = b.object_type\n");
+ sb.append(" AND c.object_name = b.object_name\n");
+ sb.append(" AND b.an_id BETWEEN c.an_id AND c.an_id_end\n");
+ sb.append(" WHERE name = 'test'\n");
+ sb.append(" AND (b.object_owner, b.object_type, b.object_name) IN (\n");
+ sb.append(" SELECT object_owner, object_type, object_name\n");
+ sb.append(" FROM suite\n");
+ sb.append(" )\n");
+ sb.append(" ),\n");
+ sb.append(" suite_tree AS (\n");
+ sb.append(" SELECT null AS parent_id,\n");
+ sb.append(" 'SUITE' AS id,\n");
+ sb.append(" 'All Suites' AS name,\n");
+ sb.append(" 'All utPLSQL test suites' AS description,\n");
+ sb.append(" 'PACKAGE_FOLDER_ICON' AS iconName,\n");
+ sb.append(" 'No' AS leaf,\n");
+ sb.append(" 'Yes' AS generatable,\n");
+ sb.append(" 'Yes' AS multiselectable,\n");
+ sb.append(" 'Yes' AS relevant\n");
+ sb.append(" FROM dual\n");
+ sb.append(" UNION ALL\n");
+ sb.append(" SELECT DISTINCT\n");
+ sb.append(" 'SUITE' AS parent_id,\n");
+ sb.append(" 'object_owner || '.' || object_name AS id,\n");
+ sb.append(" object_name AS name,\n");
+ sb.append(" NULL AS description,\n");
+ sb.append(" 'PACKAGE_ICON' AS iconName,\n");
+ sb.append(" 'No' AS leaf,\n");
+ sb.append(" 'Yes' AS generatable,\n");
+ sb.append(" 'Yes' AS multiselectable,\n");
+ sb.append(" 'Yes' AS relevant\n");
+ sb.append(" FROM test\n");
+ sb.append(" UNION ALL\n");
+ sb.append(" SELECT object_owner || '.' || object_name AS parent_id,\n");
+ sb.append(" object_owner || '.' || object_name || '.' || upper(subobject_name) AS id,\n");
+ sb.append(" subobject_name AS name,\n");
+ sb.append(" NULL AS description,\n");
+ sb.append(" 'PROCEDURE_ICON' AS iconName,\n");
+ sb.append(" 'Yes' AS leaf,\n");
+ sb.append(" 'Yes' AS generatable,\n");
+ sb.append(" 'Yes' AS multiselectable,\n");
+ sb.append(" 'Yes' AS relevant\n");
+ sb.append(" FROM test\n");
+ sb.append(" ),\n");
+ sb.append(" suitepath_base AS (\n");
+ sb.append(" SELECT DISTINCT\n");
+ sb.append(" suitepath\n");
+ sb.append(" FROM suitepath\n");
+ sb.append(" ),\n");
+ sb.append(" gen AS (\n");
+ sb.append(" SELECT rownum AS pos\n");
+ sb.append(" FROM xmltable('1 to 100')\n");
+ sb.append(" ),\n");
+ sb.append(" suitepath_part AS (\n");
+ sb.append(" SELECT DISTINCT\n");
+ sb.append(" lower(substr(suitepath, 1, instr(suitepath || '.', '.', 1, g.pos) -1)) AS suitepath\n");
+ sb.append(" FROM suitepath_base b\n");
+ sb.append(" JOIN gen g\n");
+ sb.append(" ON g.pos <= regexp_count(suitepath, '\\w+')\n");
+ sb.append(" ),\n");
+ sb.append(" suitepath_tree AS (\n");
+ sb.append(" SELECT NULL AS parent_id,\n");
+ sb.append(" 'SUITEPATH' AS id,\n");
+ sb.append(" 'All Suitepaths' AS name,\n");
+ sb.append(" 'All utPLSQL test suitepathes' AS description,\n");
+ sb.append(" 'FOLDER_ICON' AS iconName,\n");
+ sb.append(" 'No' AS leaf,\n");
+ sb.append(" 'Yes' AS generatable,\n");
+ sb.append(" 'Yes' AS multiselectable,\n");
+ sb.append(" 'Yes' AS relevant\n");
+ sb.append(" FROM dual\n");
+ sb.append(" UNION ALL\n");
+ sb.append(" SELECT CASE\n");
+ sb.append(" WHEN regexp_replace(suitepath,'\\.?\\w+$','') IS NULL THEN\n");
+ sb.append(" 'SUITEPATH'\n");
+ sb.append(" ELSE\n");
+ sb.append(" USER || ':' || regexp_replace(suitepath,'\\.?\\w+$','')");
+ sb.append(" END AS parent_id,\n");
+ sb.append(" USER || ':' || suitepath AS id,\n");
+ sb.append(" regexp_substr(suitepath, '\\.?(\\w+$)', 1, 1, NULL, 1) AS name,\n");
+ sb.append(" NULL AS description,\n");
+ sb.append(" 'FOLDER_ICON' AS iconName,\n");
+ sb.append(" 'No' AS leaf,\n");
+ sb.append(" 'Yes' AS generatable,\n");
+ sb.append(" 'Yes' AS multiselectable,\n");
+ sb.append(" 'Yes' AS relevant\n");
+ sb.append(" FROM suitepath_part\n");
+ sb.append(" UNION ALL\n");
+ sb.append(" SELECT DISTINCT\n");
+ sb.append(" object_owner || ':' || suitepath AS parent_id,\n");
+ sb.append(" object_owner || ':' || suitepath || '.' || lower(object_name) AS id,\n");
+ sb.append(" object_name AS name,\n");
+ sb.append(" NULL AS description,\n");
+ sb.append(" 'PACKAGE_ICON' AS iconName,\n");
+ sb.append(" 'No' AS leaf,\n");
+ sb.append(" 'Yes' AS generatable,\n");
+ sb.append(" 'Yes' AS multiselectable,\n");
+ sb.append(" 'Yes' AS relevant\n");
+ sb.append(" FROM test\n");
+ sb.append(" WHERE suitepath IS NOT NULL\n");
+ sb.append(" UNION ALL\n");
+ sb.append(" SELECT DISTINCT\n");
+ sb.append(" object_owner || ':' || suitepath || '.' || lower(object_name) AS parent_id,\n");
+ sb.append(" object_owner || ':' || suitepath || '.' || lower(object_name) || '.' || context AS id,\n");
+ sb.append(" context AS name,\n");
+ sb.append(" NULL AS description,\n");
+ sb.append(" 'FOLDER_ICON' AS iconName,\n");
+ sb.append(" 'No' AS leaf,\n");
+ sb.append(" 'Yes' AS generatable,\n");
+ sb.append(" 'Yes' AS multiselectable,\n");
+ sb.append(" 'Yes' AS relevant\n");
+ sb.append(" FROM test\n");
+ sb.append(" WHERE suitepath IS NOT NULL\n");
+ sb.append(" AND context IS NOT NULL\n");
+ sb.append(" UNION ALL\n");
+ sb.append(" SELECT object_owner || ':' || suitepath || '.' || lower(object_name) || CASE WHEN context IS NOT NULL THEN '.' || context END AS parent_id,\n");
+ sb.append(" object_owner || ':' || suitepath || '.' || lower(object_name) || CASE WHEN context IS NOT NULL THEN '.' || context END || '.' || lower(subobject_name) AS id,\n");
+ sb.append(" subobject_name AS name,\n");
+ sb.append(" NULL AS description,\n");
+ sb.append(" 'PROCEDURE_ICON' AS iconName,\n");
+ sb.append(" 'Yes' AS leaf,\n");
+ sb.append(" 'Yes' AS generatable,\n");
+ sb.append(" 'Yes' AS multiselectable\n,");
+ sb.append(" 'Yes' AS relevant\n");
+ sb.append(" FROM test\n");
+ sb.append(" WHERE suitepath IS NOT NULL\n");
+ sb.append(" ),\n");
+ sb.append(" tree AS (\n");
+ sb.append(" SELECT parent_id, id, name, description, iconName, leaf, generatable, multiselectable, relevant\n");
+ sb.append(" FROM suite_tree\n");
+ sb.append(" UNION ALL\n");
+ sb.append(" SELECT parent_id, id, name, description, iconName, leaf, generatable, multiselectable, relevant\n");
+ sb.append(" FROM suitepath_tree\n");
+ sb.append(" )\n");
+ sb.append("SELECT parent_id, id, initcap(name) AS name, description, iconName, leaf, generatable, multiselectable, relevant\n");
+ sb.append(" FROM tree");
+ }
+ BeanPropertyRowMapper rowMapper = new BeanPropertyRowMapper<>(Node.class);
+ final String sql = sb.toString();
+ return jdbcTemplate.query(sql, rowMapper);
+ }
+
+ /**
+ * enable DBMS_OUTPUT
+ *
+ * @throws DataAccessException
+ * if there is a problem
+ */
+ public void enableDbmsOutput() {
+ // equivalent to "set serveroutput on size unlimited"
+ StringBuilder sb = new StringBuilder();
+ sb.append("BEGIN");
+ sb.append(" sys.dbms_output.enable(NULL);\n");
+ sb.append("END;");
+ jdbcTemplate.update(sb.toString());
+ }
+
+ /**
+ * disable DBMS_OUTPUT
+ *
+ * @throws DataAccessException
+ * if there is a problem
+ */
+ public void disableDbmsOutput() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("BEGIN\n");
+ sb.append(" sys.dbms_output.disable;\n");
+ sb.append("END;");
+ jdbcTemplate.update(sb.toString());
+ }
+
+ /**
+ * return the content of DBMS_OUTPUT as String
+ *
+ * @throws DataAccessException
+ * if there is a problem
+ */
+ public String getDbmsOutput() {
+ return getDbmsOutput(1000);
+ }
+
+ /**
+ * return the content of DBMS_OUTPUT as String
+ *
+ * @param bufferSize
+ * maximum number of rows to be read from the DBMS_OUTPUT buffer in
+ * one network round trip
+ * @return content of DBMS_OUTPUT as String
+ * @throws DataAccessException
+ * if there is a problem
+ */
+ public String getDbmsOutput(final int bufferSize) {
+ final StringBuilder resultSb = new StringBuilder();
+ StringBuilder sb = new StringBuilder();
+ sb.append("BEGIN");
+ sb.append(" sys.dbms_output.get_lines(?, ?);\n");
+ sb.append("END;");
+ final String sql = sb.toString();
+ OutputLines ret;
+ do {
+ ret = jdbcTemplate.execute(sql, (CallableStatementCallback) cs -> {
+ cs.registerOutParameter(1, Types.ARRAY, "DBMSOUTPUT_LINESARRAY");
+ cs.registerOutParameter(2, Types.INTEGER);
+ cs.setInt(2, bufferSize);
+ cs.execute();
+ final OutputLines out = new OutputLines();
+ try {
+ Object array = cs.getArray(1).getArray();
+ out.setLines((String[]) array);
+ out.setNumlines(cs.getInt(2));
+ } catch (NullPointerException e) {
+ out.setLines(null);
+ out.setNumlines(0);
+ }
+ return out;
+ });
+ if (ret != null && ret.getNumlines() != null) {
+ for (int i = 0; i < ret.getNumlines(); i++) {
+ final String line = ret.getLines()[i];
+ if (line != null) {
+ resultSb.append(ret.getLines()[i]);
+ }
+ resultSb.append(System.lineSeparator());
+ }
+ }
+ } while (ret != null && ret.getNumlines() != null && ret.getNumlines() > 0);
+ return resultSb.toString();
+ }
+
+ /**
+ * gets the HTML code coverage report as String
+ *
+ * @param pathList
+ * utPLSQL path list
+ * @param schemaList
+ * list of schemas under tests. Current schema, if empty
+ * @param includeObjectList
+ * list of objects to be included for coverage analysis. All, if
+ * empty
+ * @param excludeObjectList
+ * list of objects to be excluded from coverage analysis. None, if
+ * empty
+ * @param htmlReportAssetPath
+ * path of the assets for the coverage report. Default, if null
+ * @return HTML code coverage report in HTML format
+ * @throws DataAccessException
+ * if there is a problem
+ */
+ public String htmlCodeCoverage(final List pathList, final List schemaList,
+ final List includeObjectList, final List excludeObjectList, final URL htmlReportAssetPath) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("SELECT column_value\n");
+ sb.append(" FROM table(\n");
+ sb.append(" ut.run(\n");
+ sb.append(" a_paths => ut_varchar2_list(\n");
+ sb.append(StringTools.getCSV(pathList, 16));
+ sb.append(" ),\n");
+ if (schemaList != null && !schemaList.isEmpty()) {
+ sb.append(" a_coverage_schemes => ut_varchar2_list(\n");
+ sb.append(StringTools.getCSV(schemaList, 16));
+ sb.append(" ),\n");
+ }
+ if (includeObjectList != null && !includeObjectList.isEmpty()) {
+ sb.append(" a_include_objects => ut_varchar2_list(\n");
+ sb.append(StringTools.getCSV(includeObjectList, 16));
+ sb.append(" ),\n");
+ }
+ if (excludeObjectList != null && excludeObjectList.isEmpty()) {
+ sb.append(" a_exclude_objects => ut_varchar2_list(\n");
+ sb.append(StringTools.getCSV(excludeObjectList, 16));
+ sb.append(" ),\n");
+ }
+ sb.append(" a_reporter => ut_coverage_html_reporter(\n");
+ sb.append(" a_html_report_assets_path => '");
+ if (htmlReportAssetPath != null) {
+ // empty string is handled as NULL in Oracle Database
+ sb.append(htmlReportAssetPath.toExternalForm());
+ }
+ sb.append("'\n");
+ sb.append(" )\n");
+ sb.append(" )\n");
+ sb.append(" )");
+ final String sql = sb.toString();
+ final List lines = jdbcTemplate.queryForList(sql, String.class);
+ final StringBuilder resultSb = new StringBuilder();
+ for (String line : lines) {
+ if (line != null) {
+ resultSb.append(line);
+ resultSb.append("\n");
+ }
+ }
+ return resultSb.toString();
+ }
+
+ /**
+ * gets dependencies of a given object.
+ *
+ * The result can be used as input for the includeObjectList in htmlCodeCoverage
+ * The scope is reduced to non-oracle maintained schemas.
+ *
+ * Oracle introduced the column ORACLE_MAINTAINED in 12.1. To simplify the query
+ * and compatibility the result of the following query is included
+ *
+ * SELECT '''' || listagg(username, ''', ''') || '''' AS oracle_maintained_users
+ * FROM dba_users WHERE oracle_maintained = 'Y' ORDER BY username;
+ *
+ * The result may include test packages
+ *
+ * @param name
+ * test package name
+ * @return list of dependencies in the current schema
+ */
+ public List includes(final String owner, final String name) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("SELECT referenced_owner || '.' || referenced_name AS dep_name\n");
+ sb.append(" FROM ");
+ sb.append(getDbaView("dependencies\n"));
+ sb.append(" WHERE owner = upper(?)\n");
+ sb.append(" AND name = upper(?)\n");
+ sb.append(" AND referenced_owner NOT IN (\n");
+ sb.append(" 'SYS', 'SYSTEM', 'XS$NULL', 'OJVMSYS', 'LBACSYS', 'OUTLN', 'SYS$UMF',\n");
+ sb.append(" 'DBSNMP', 'APPQOSSYS', 'DBSFWUSER', 'GGSYS', 'ANONYMOUS', 'CTXSYS',\n");
+ sb.append(" 'SI_INFORMTN_SCHEMA', 'DVF', 'DVSYS', 'GSMADMIN_INTERNAL', 'ORDPLUGINS',\n");
+ sb.append(" 'MDSYS', 'OLAPSYS', 'ORDDATA', 'XDB', 'WMSYS', 'ORDSYS', 'GSMCATUSER',\n");
+ sb.append(" 'MDDATA', 'REMOTE_SCHEDULER_AGENT', 'SYSBACKUP', 'GSMUSER', 'APEX_PUBLIC_USER',\n");
+ sb.append(" 'SYSRAC', 'AUDSYS', 'DIP', 'SYSKM', 'ORACLE_OCM', 'APEX_INSTANCE_ADMIN_USER',\n");
+ sb.append(" 'SYSDG', 'FLOWS_FILES', 'ORDS_METADATA', 'ORDS_PUBLIC_USER'\n");
+ sb.append(" )\n");
+ sb.append(" AND referenced_owner NOT LIKE 'APEX\\_______'");
+ sb.append(" AND referenced_type IN ('PACKAGE', 'TYPE', 'PROCEDURE', 'FUNCTION', 'TRIGGER')");
+ final String sql = sb.toString();
+ final Object[] binds = new Object[] {owner, name};
+ return jdbcTemplate.queryForList(sql, String.class, binds);
+ }
+
+ /**
+ * gets source of an object from the database via DBMS_METADATA
+ *
+ * @param owner
+ * owner of the object (schema)
+ * @param objectType
+ * expected object types are PACKAGE, PACKAGE BODY
+ * @param objectName
+ * name of the object
+ * @return the source code of the object
+ * @throws DataAccessException
+ * if there is a problem
+ */
+ public String getSource(final String owner, final String objectType, final String objectName) {
+ String fixedObjectType;
+ if ("PACKAGE".equals(objectType)) {
+ fixedObjectType = "PACKAGE_SPEC";
+ } else if ("PACKAGE BODY".equals(objectType)) {
+ fixedObjectType = "PACKAGE_BODY";
+ } else {
+ fixedObjectType = objectType;
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append("BEGIN\n");
+ sb.append(" ? := sys.dbms_metadata.get_ddl(\n");
+ sb.append(" schema => ?,\n");
+ sb.append(" object_type => ?,\n");
+ sb.append(" name => ?\n");
+ sb.append(" );\n");
+ sb.append("END;");
+ final String sql = sb.toString();
+ return jdbcTemplate.execute(sql, (CallableStatementCallback) cs -> {
+ cs.registerOutParameter(1, Types.CLOB);
+ cs.setString(2, owner);
+ cs.setString(3, fixedObjectType);
+ cs.setString(4, objectName);
+ cs.execute();
+ return cs.getString(1);
+ });
+ }
+
+ /**
+ * gets the object type of a database object
+ *
+ * The object types "PACKAGE BODY", "TYPE BODY" have higher priority. "PACKAGE"
+ * OR "TYPE" will be returned only when no body exists.
+ *
+ * @param owner
+ * owner of the object (schema)
+ * @param objectName
+ * name of the object
+ * @return the object type, e.g. PACKAGE BODY, TYPE BODY, PROCEDURE, FUNCTION
+ */
+ public String getObjectType(final String owner, final String objectName) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("SELECT object_type\n");
+ sb.append(" FROM (\n");
+ sb.append(" SELECT object_type\n");
+ sb.append(" FROM ");
+ sb.append(getDbaView("objects\n"));
+ sb.append(" WHERE owner = ?\n");
+ sb.append(" AND object_name = ?\n");
+ sb.append(" ORDER BY decode(object_type, 'PACKAGE', 10, 'TYPE', 10, 'SYNONYM', 20, 1)\n");
+ sb.append(" )\n");
+ sb.append(" WHERE rownum = 1");
+ final String sql = sb.toString();
+ final Object[] binds = new Object[] {owner, objectName};
+ return jdbcTemplate.queryForObject(sql, String.class, binds);
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.xtend b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.xtend
deleted file mode 100644
index 5a6cf8d5..00000000
--- a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.xtend
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2018 Philipp Salvisberg
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.utplsql.sqldev.dal
-
-import java.sql.Connection
-import java.util.List
-import org.springframework.dao.DataAccessException
-import org.springframework.dao.EmptyResultDataAccessException
-import org.springframework.jdbc.core.BeanPropertyRowMapper
-import org.springframework.jdbc.core.JdbcTemplate
-import org.springframework.jdbc.datasource.SingleConnectionDataSource
-import org.utplsql.sqldev.model.ut.Annotation
-
-class UtplsqlDao {
- public static val UTPLSQL_PACKAGE_NAME = "UT"
- private var Connection conn
- private var JdbcTemplate jdbcTemplate
-
- new(Connection conn) {
- this.conn = conn
- this.jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true))
- }
-
- def boolean isDbaViewAccessible() {
- try {
- val sql = '''
- SELECT 1
- FROM dba_objects
- WHERE 1=2
- '''
- jdbcTemplate.execute(sql)
- return true
- } catch (DataAccessException e) {
- return false
- }
- }
-
- /**
- * Gets the schema name of the utPLSQL installation.
- *
- * @return utPLSQL schema or null if no utPLSQL is not installed
- * @throws DataAccessException if there is a problem
- */
- def String getUtplsqlSchema() {
- val sql = '''
- SELECT table_owner
- FROM «IF dbaViewAccessible»dba«ELSE»all«ENDIF»_synonyms
- WHERE owner = 'PUBLIC'
- AND synonym_name = '«UTPLSQL_PACKAGE_NAME»'
- AND table_name = '«UTPLSQL_PACKAGE_NAME»'
- '''
- try {
- val schema = jdbcTemplate.queryForObject(sql, String)
- return schema
- } catch (EmptyResultDataAccessException e) {
- return null
- }
- }
-
- /**
- * Checks if the package ut_annotation_manager is installed.
- * This package has been introduced with utPLSQL 3.0.4.
- * This version is a prerequisite to identify
- * utPLSQL unit test procedures.
- *
- * @return true if ut_annotation_manager package has been found
- * @throws DataAccessException if there is a problem
- */
- def boolean isUtAnnotationManagerInstalled() {
- if (utplsqlSchema !== null) {
- val sql = '''
- SELECT count(*)
- FROM all_objects
- WHERE owner = '«utplsqlSchema»'
- AND object_type = 'PACKAGE'
- AND object_name = 'UT_ANNOTATION_MANAGER'
- '''
- val found = jdbcTemplate.queryForObject(sql, Integer)
- return found == 1
- }
- return false
- }
-
- /**
- * Checks if utPLSQL tests exist
- *
- * @param owner schema name, mandatory, case-insensitive
- * @param objectName name of the package or package body, optional, case-insensitive
- * @param subobjectName name of the procedure, optional, case-insensitive
- * @return true if at least one test has been found
- * @throws DataAccessException if a utPLSQL version less than 3.0.4 is installed or if there are other problems
- */
- def boolean containsUtplsqlTest(String owner, String objectName, String subobjectName) {
- try {
- val sql = '''
- SELECT count(
- CASE
- WHEN a.name = 'test'
- AND (upper(a.subobject_name) = upper(?) OR ? IS NULL)
- THEN
- 1
- ELSE
- NULL
- END
- )
- FROM TABLE(«utplsqlSchema».ut_annotation_manager.get_annotated_objects(upper(?), 'PACKAGE')) o
- CROSS JOIN TABLE(o.annotations) a
- WHERE (o.object_name = upper(?) OR ? IS NULL)
- AND a.name IN ('test', 'suite')
- HAVING count(
- CASE
- WHEN a.name = 'suite' THEN
- 1
- ELSE
- NULL
- END
- ) > 0
- '''
- val found = jdbcTemplate.queryForObject(sql, Integer, #[subobjectName, subobjectName, owner, objectName, objectName])
- return found > 0
- } catch (EmptyResultDataAccessException e) {
- return false
- }
- }
-
- def boolean containsUtplsqlTest(String owner) {
- return containsUtplsqlTest(owner, null, null)
- }
-
- def boolean containsUtplsqlTest(String owner, String objectType) {
- return containsUtplsqlTest(owner, objectType, null)
- }
-
- /**
- * Gets a list of utPLSQL annotations for a given PL/SQL package specification
- *
- * @param owner schema name, mandatory, case-insensitive
- * @param objectName name of the package or package body, optional, case-insensitive
- * @return list of Annotation with name 'suite' or 'test'
- * @throws DataAccessException if a utPLSQL version less than 3.0.4 is installed or if there are other problems
- */
- def List annotations(String owner, String objectName) {
- val sql = '''
- SELECT o.object_owner, o.object_type, o.object_name, a.name, a.text, a.subobject_name
- FROM TABLE(«utplsqlSchema».ut_annotation_manager.get_annotated_objects(upper(?), 'PACKAGE')) o
- CROSS JOIN TABLE(o.annotations) a
- WHERE o.object_name = upper(?)
- '''
- val result = jdbcTemplate.query(sql, new BeanPropertyRowMapper(Annotation), #[owner, objectName])
- return result
- }
-
-}
\ No newline at end of file
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/exception/GenericDatabaseAccessException.java b/sqldev/src/main/java/org/utplsql/sqldev/exception/GenericDatabaseAccessException.java
new file mode 100644
index 00000000..a343592b
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/exception/GenericDatabaseAccessException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2020 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.exception;
+
+public class GenericDatabaseAccessException extends RuntimeException {
+ private static final long serialVersionUID = -5489500390596695295L;
+
+ public GenericDatabaseAccessException(String message) {
+ super(message);
+ }
+
+ public GenericDatabaseAccessException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public GenericDatabaseAccessException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/exception/GenericRuntimeException.java b/sqldev/src/main/java/org/utplsql/sqldev/exception/GenericRuntimeException.java
new file mode 100644
index 00000000..5a89ad13
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/exception/GenericRuntimeException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2020 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.exception;
+
+public class GenericRuntimeException extends RuntimeException {
+ private static final long serialVersionUID = -6258053040039956647L;
+
+ public GenericRuntimeException(String message) {
+ super(message);
+ }
+
+ public GenericRuntimeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public GenericRuntimeException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.java b/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.java
new file mode 100644
index 00000000..505e53ca
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.java
@@ -0,0 +1,545 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.menu;
+
+import java.awt.Component;
+import java.net.URL;
+import java.sql.Connection;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import javax.swing.JEditorPane;
+
+import org.utplsql.sqldev.coverage.CodeCoverageReporter;
+import org.utplsql.sqldev.dal.RealtimeReporterDao;
+import org.utplsql.sqldev.dal.UtplsqlDao;
+import org.utplsql.sqldev.exception.GenericRuntimeException;
+import org.utplsql.sqldev.model.DatabaseTools;
+import org.utplsql.sqldev.model.StringTools;
+import org.utplsql.sqldev.model.URLTools;
+import org.utplsql.sqldev.model.oddgen.GenContext;
+import org.utplsql.sqldev.model.parser.PlsqlObject;
+import org.utplsql.sqldev.model.preference.PreferenceModel;
+import org.utplsql.sqldev.oddgen.TestTemplate;
+import org.utplsql.sqldev.parser.UtplsqlParser;
+import org.utplsql.sqldev.runner.UtplsqlRunner;
+import org.utplsql.sqldev.runner.UtplsqlWorksheetRunner;
+
+import oracle.dbtools.raptor.navigator.db.DBNavigatorWindow;
+import oracle.dbtools.raptor.navigator.db.DatabaseConnection;
+import oracle.dbtools.raptor.navigator.impl.ChildObjectElement;
+import oracle.dbtools.raptor.navigator.impl.DatabaseSourceNode;
+import oracle.dbtools.raptor.navigator.impl.ObjectFolder;
+import oracle.dbtools.raptor.navigator.impl.SchemaFolder;
+import oracle.dbtools.raptor.navigator.plsql.PlSqlNode;
+import oracle.dbtools.raptor.schemabrowser.view.SBWindow;
+import oracle.dbtools.raptor.utils.Connections;
+import oracle.dbtools.worksheet.editor.Worksheet;
+import oracle.ide.Context;
+import oracle.ide.Ide;
+import oracle.ide.config.Preferences;
+import oracle.ide.controller.Controller;
+import oracle.ide.controller.IdeAction;
+import oracle.ide.editor.Editor;
+import oracle.ide.model.Node;
+import oracle.ide.view.View;
+
+@SuppressWarnings("all")
+public class UtplsqlController implements Controller {
+ private static final Logger logger = Logger.getLogger(UtplsqlController.class.getName());
+
+ public static int UTPLSQL_TEST_CMD_ID = (Ide.findCmdID("utplsql.test")).intValue();
+ public static int UTPLSQL_COVERAGE_CMD_ID = (Ide.findCmdID("utplsql.coverage")).intValue();
+ public static int UTPLSQL_DEBUG_CMD_ID = (Ide.findCmdID("utplsql.debug")).intValue();
+ public static int UTPLSQL_GENERATE_CMD_ID = (Ide.findCmdID("utplsql.generate")).intValue();
+ public static final IdeAction UTPLSQL_TEST_ACTION = IdeAction.get(UTPLSQL_TEST_CMD_ID);
+ public static final IdeAction UTPLSQL_COVERAGE_ACTION = IdeAction.get(UTPLSQL_COVERAGE_CMD_ID);
+ public static final IdeAction UTPLSQL_DEBUG_ACTION = IdeAction.get(UTPLSQL_DEBUG_CMD_ID);
+ public static final IdeAction UTPLSQL_GENERATE_ACTION = IdeAction.get(UTPLSQL_GENERATE_CMD_ID);
+
+ @Override
+ public boolean handleEvent(final IdeAction action, final Context context) {
+ try {
+ if (action.getCommandId() == UTPLSQL_TEST_CMD_ID) {
+ logger.finer(() -> "handle utplsql.test");
+ runTest(context, false);
+ return true;
+ } else if (action.getCommandId() == UTPLSQL_COVERAGE_CMD_ID) {
+ logger.finer(() -> "handle utplsql.coverage");
+ codeCoverage(context);
+ return true;
+ } else if (action.getCommandId() == UTPLSQL_DEBUG_CMD_ID) {
+ logger.finer(() -> "handle utplsql.debug");
+ runTest(context, true);
+ return true;
+ } else if (action.getCommandId() == UTPLSQL_GENERATE_CMD_ID) {
+ logger.finer(() -> "handle utplsql.generate");
+ generateTest(context);
+ return true;
+ }
+ } catch (Exception e) {
+ final String msg = "Failed to handle event for action " + action.toString() + ".";
+ logger.severe(() -> msg);
+ throw new GenericRuntimeException(msg, e);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean update(final IdeAction action, final Context context) {
+ if (action.getCommandId() == UTPLSQL_TEST_CMD_ID || action.getCommandId() == UTPLSQL_COVERAGE_CMD_ID ||
+ action.getCommandId() == UTPLSQL_DEBUG_CMD_ID) {
+ final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences());
+ action.setEnabled(false);
+ final View view = context.getView();
+ if (view instanceof Editor) {
+ final Component component = ((Editor) view).getDefaultFocusComponent();
+ if (component instanceof JEditorPane) {
+ if (preferences.isCheckRunUtplsqlTest()) {
+ final Node node = context.getNode();
+ String connectionName = null;
+ String owner = null;
+ if (node instanceof DatabaseSourceNode) {
+ connectionName = ((DatabaseSourceNode) node).getConnectionName();
+ owner = ((DatabaseSourceNode) node).getOwner();
+ } else {
+ if (view instanceof Worksheet) {
+ connectionName = ((Worksheet) view).getConnectionName();
+ }
+ }
+ logger.fine("connectionName: " + connectionName);
+ final String text = ((JEditorPane) component).getText();
+ final Connection conn = DatabaseTools.getConnection(connectionName);
+ final UtplsqlParser parser = new UtplsqlParser(text, conn, owner);
+ if (!parser.getPathAt(((JEditorPane) component).getCaretPosition()).isEmpty()) {
+ action.setEnabled(true);
+ }
+ } else {
+ action.setEnabled(true);
+ }
+ }
+ } else if (view instanceof DBNavigatorWindow || view instanceof SBWindow) {
+ action.setEnabled(true);
+ // disable action if a node in the selection is not runnable
+ for (int i = 0; i < context.getSelection().length; i++) {
+ logger.fine("section " + i + " is " + context.getSelection()[i].toString() + " of class "
+ + context.getSelection()[i].getClass().getName());
+ if (action.isEnabled()) {
+ final Object element = context.getSelection()[i];
+ final String connectionName = URLTools.getConnectionName(getURL(context));
+ if (Connections.getInstance().isConnectionOpen(connectionName)) {
+ Connection conn = DatabaseTools.getConnection(connectionName);
+ final UtplsqlDao dao = new UtplsqlDao(conn);
+ if (preferences.isCheckRunUtplsqlTest() && dao.isUtAnnotationManagerInstalled()) {
+ if (element instanceof DatabaseConnection) {
+ final String schema = DatabaseTools.getSchema((DatabaseConnection) element);
+ action.setEnabled(dao.containsUtplsqlTest(schema));
+ } else if (element instanceof SchemaFolder) {
+ final String schema = ((SchemaFolder) element).getSchemaName();
+ action.setEnabled(dao.containsUtplsqlTest(schema));
+ } else if (element instanceof ObjectFolder) {
+ final String schema = URLTools.getSchema(((ObjectFolder) element).getURL());
+ action.setEnabled(dao.containsUtplsqlTest(schema));
+ } else if (element instanceof PlSqlNode) {
+ final String schema = ((PlSqlNode) element).getOwner();
+ final String objectName = ((PlSqlNode) element).getObjectName();
+ action.setEnabled(dao.containsUtplsqlTest(schema, objectName));
+ } else if (element instanceof ChildObjectElement) {
+ final String schema = URLTools.getSchema(((ChildObjectElement) element).getURL());
+ final String objectName = URLTools.getMemberObject(((ChildObjectElement) element).getURL());
+ final String subObjectName = ((ChildObjectElement) element).getShortLabel();
+ action.setEnabled(dao.containsUtplsqlTest(schema, objectName, subObjectName));
+ }
+ }
+ } else {
+ action.setEnabled(false);
+ }
+ }
+ }
+ }
+ return true;
+ } else if (action.getCommandId() == UTPLSQL_GENERATE_CMD_ID) {
+ action.setEnabled(false);
+ // enable if generation is possible
+ final View view = context.getView();
+ if (view instanceof Editor) {
+ final Component component = ((Editor) view).getDefaultFocusComponent();
+ if (component instanceof JEditorPane) {
+ final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences());
+ if (preferences.isCheckGenerateUtplsqlTest()) {
+ final String text = ((JEditorPane) component).getText();
+ final UtplsqlParser parser = new UtplsqlParser(text);
+ PlsqlObject plsqlObject = parser.getObjectAt(((JEditorPane) component).getCaretPosition());
+ action.setEnabled(plsqlObject != null);
+ } else {
+ action.setEnabled(true);
+ }
+ }
+ } else if (view instanceof DBNavigatorWindow || view instanceof SBWindow) {
+ // multiselection is not supported, use oddgen to generte tests for multiple objects
+ if (context.getSelection().length == 1) {
+ final Object element = context.getSelection()[0];
+ if (element instanceof PlSqlNode) {
+ final String ot = ((PlSqlNode) element).getObjectType();
+ if (ot.startsWith("PACKAGE") || ot.startsWith("TYPE") || "FUNCTION".equals(ot) || "PROCEDURE".equals(ot)) {
+ action.setEnabled(true);
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private String getPath(final Object element) {
+ String path = null;
+ if (element instanceof DatabaseConnection) {
+ path = DatabaseTools.getSchema((DatabaseConnection) element);
+ } else if (element instanceof SchemaFolder) {
+ path = ((SchemaFolder) element).getSchemaName();
+ } else if (element instanceof ObjectFolder) {
+ path = URLTools.getSchema(((ObjectFolder) element).getURL());
+ } else if (element instanceof PlSqlNode) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(((PlSqlNode) element).getOwner());
+ sb.append(".");
+ sb.append(((PlSqlNode) element).getObjectName());
+ path = sb.toString();
+ } else if (element instanceof ChildObjectElement) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(URLTools.getSchema(((ChildObjectElement) element).getURL()));
+ sb.append(".");
+ sb.append(URLTools.getMemberObject(((ChildObjectElement) element).getURL()));
+ sb.append(".");
+ sb.append( ((ChildObjectElement) element).getShortLabel());
+ path = sb.toString();
+ } else {
+ path = "";
+ }
+ logger.fine("path: " + path);
+ return path;
+ }
+
+ private ArrayList getPathList(final Context context) {
+ final ArrayList pathList = new ArrayList<>();
+ for (int i = 0; i < context.getSelection().length; i++) {
+ final Object element = context.getSelection()[i];
+ pathList.add(getPath(element));
+ }
+ return pathList;
+ }
+
+ private ArrayList getPathList(final String path) {
+ final ArrayList pathList = new ArrayList<>();
+ pathList.add(path);
+ return pathList;
+ }
+
+ private ArrayList dedupPathList(final List pathList) {
+ final HashSet set = new HashSet<>();
+ for (final String path : pathList) {
+ set.add(path);
+ }
+ final ArrayList ret = new ArrayList<>();
+ final Pattern p = Pattern.compile("(((([^\\.]+)\\.)?[^\\.]+)\\.)?[^\\.]+");
+ for (final String path : set) {
+ final Matcher m = p.matcher(path);
+ if (m.matches()) {
+ final String parent1 = m.group(4); // user
+ final String parent2 = m.group(2); // user.package
+ if (parent1 == null || !set.contains(parent1)) {
+ if (parent2 == null || !set.contains(parent2)) {
+ ret.add(path);
+ }
+ }
+ } else {
+ logger.severe("path: " + path + " did not match " + p.toString() + ", this is unexected!");
+ }
+ }
+ return ret;
+ }
+
+ private URL getURL(final Context context) {
+ URL url = null;
+ final Object element = context.getSelection()[0];
+ if (element instanceof DatabaseConnection) {
+ url = ((DatabaseConnection) element).getURL();
+ } else if (element instanceof SchemaFolder) {
+ url = ((SchemaFolder) element).getURL();
+ } else if (element instanceof ObjectFolder) {
+ url = ((ObjectFolder) element).getURL();
+ } else if (element instanceof PlSqlNode) {
+ url = ((PlSqlNode) element).getURL();
+ } else if (element instanceof ChildObjectElement) {
+ url = ((ChildObjectElement) element).getURL();
+ }
+ logger.fine("url: " + url);
+ return url;
+ }
+
+ private void populateGenContext(final GenContext genContext, final PreferenceModel preferences) {
+ genContext.setTestPackagePrefix(preferences.getTestPackagePrefix().toLowerCase());
+ genContext.setTestPackageSuffix(preferences.getTestPackageSuffix().toLowerCase());
+ genContext.setTestUnitPrefix(preferences.getTestUnitPrefix().toLowerCase());
+ genContext.setTestUnitSuffix(preferences.getTestUnitSuffix().toLowerCase());
+ genContext.setNumberOfTestsPerUnit(preferences.getNumberOfTestsPerUnit());
+ genContext.setGenerateComments(preferences.isGenerateComments());
+ genContext.setDisableTests(preferences.isDisableTests());
+ genContext.setSuitePath(preferences.getSuitePath().toLowerCase());
+ genContext.setIndentSpaces(preferences.getIndentSpaces());
+ }
+
+ private GenContext getGenContext(final Context context) {
+ final String connectionName = URLTools.getConnectionName(getURL(context));
+ final GenContext genContext = new GenContext();
+ if (Connections.getInstance().isConnectionOpen(connectionName)) {
+ genContext.setConn(DatabaseTools.getConnection(connectionName));
+ final Object element = context.getSelection()[0];
+ if (element instanceof PlSqlNode) {
+ genContext.setObjectType(((PlSqlNode) element).getObjectType().replace(" BODY", ""));
+ genContext.setObjectName(((PlSqlNode) element).getObjectName());
+ final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences());
+ populateGenContext(genContext, preferences);
+ }
+ }
+ return genContext;
+ }
+
+ public void runTest(final Context context, boolean withDebug) {
+ final View view = context.getView();
+ final Node node = context.getNode();
+ final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences());
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Run utPLSQL from view ");
+ sb.append(view != null ? view.getClass().getName() : "???");
+ sb.append(" and node ");
+ sb.append(node != null ? node.getClass().getName() : "???");
+ sb.append(".");
+ logger.finer(() -> sb.toString());
+ if (view instanceof Editor) {
+ final Component component = ((Editor) view).getDefaultFocusComponent();
+ if (component instanceof JEditorPane) {
+ String connectionName = null;
+ String owner = null;
+ if (node instanceof DatabaseSourceNode) {
+ connectionName = ((DatabaseSourceNode) node).getConnectionName();
+ owner = ((DatabaseSourceNode) node).getOwner();
+ } else {
+ if (view instanceof Worksheet) {
+ connectionName = ((Worksheet) view).getConnectionName();
+ }
+ }
+ logger.fine("connectionName: " + connectionName);
+ final Connection conn = DatabaseTools.getConnection(connectionName);
+ String text = ((JEditorPane) component).getText();
+ final UtplsqlParser parser = new UtplsqlParser(text, conn, owner);
+ final int position = ((JEditorPane) component).getCaretPosition();
+ final String path = parser.getPathAt(position);
+ final RealtimeReporterDao rrDao = new RealtimeReporterDao(conn);
+ if (preferences.isUseRealtimeReporter() && rrDao.isSupported()) {
+ final UtplsqlRunner runner = new UtplsqlRunner(getPathList(path), connectionName);
+ if (withDebug) {
+ runner.enableDebugging();
+ }
+ runner.runTestAsync();
+ } else {
+ final UtplsqlWorksheetRunner worksheet = new UtplsqlWorksheetRunner(getPathList(path), connectionName);
+ if (withDebug) {
+ worksheet.enableDebugging();
+ }
+ worksheet.runTestAsync();
+ }
+ }
+ } else if (view instanceof DBNavigatorWindow || view instanceof SBWindow) {
+ final URL url = getURL(context);
+ if (url != null) {
+ final String connectionName = URLTools.getConnectionName(url);
+ logger.fine("connectionName: " + connectionName);
+ final Connection conn = DatabaseTools.getConnection(connectionName);
+ final RealtimeReporterDao rrDao = new RealtimeReporterDao(conn);
+ final ArrayList pathList = dedupPathList(getPathList(context));
+ if (preferences.isUseRealtimeReporter() && rrDao.isSupported()) {
+ final UtplsqlRunner runner = new UtplsqlRunner(pathList, connectionName);
+ if (withDebug) {
+ runner.enableDebugging();
+ }
+ runner.runTestAsync();
+ } else {
+ final UtplsqlWorksheetRunner worksheet = new UtplsqlWorksheetRunner(pathList, connectionName);
+ if (withDebug) {
+ worksheet.enableDebugging();
+ }
+ worksheet.runTestAsync();
+ }
+ }
+ }
+ }
+
+ public List dependencies(final String name, final String connectionName) {
+ List ret = null;
+ if (connectionName != null) {
+ final String owner = DatabaseTools.getSchema(connectionName);
+ ret = dependencies(owner, name, connectionName);
+ }
+ return ret;
+ }
+
+ public List dependencies(final String owner, final String name, final String connectionName) {
+ List ret = null;
+ if (connectionName != null) {
+ Connection conn = DatabaseTools.getConnection(connectionName);
+ final UtplsqlDao dao = new UtplsqlDao(conn);
+ ret = dao.includes(owner, name);
+ }
+ return ret;
+ }
+
+ public List dependencies(final Context context, final String connectionName) {
+ final HashSet ret = new HashSet();
+ for (int i = 0; i < context.getSelection().length; i++) {
+ final Object element = context.getSelection()[i];
+ if (element instanceof PlSqlNode) {
+ final String owner = ((PlSqlNode) element).getOwner();
+ final String objectName = ((PlSqlNode) element).getObjectName();
+ final List dep = dependencies(owner, objectName, connectionName);
+ ret.addAll(dep);
+ } else {
+ if (element instanceof ChildObjectElement) {
+ final String owner = URLTools.getSchema(((ChildObjectElement) element).getURL());
+ final String objectName = URLTools.getMemberObject(((ChildObjectElement) element).getURL());
+ final List dep = dependencies(owner, objectName, connectionName);
+ ret.addAll(dep);
+ }
+ }
+ }
+ return ret.stream().sorted().collect(Collectors.toList());
+ }
+
+ public void codeCoverage(final Context context) {
+ final View view = context.getView();
+ final Node node = context.getNode();
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Code coverage from view ");
+ sb.append(view != null ? view.getClass().getName() : "???");
+ sb.append(" and node ");
+ sb.append(node != null ? node.getClass().getName() : "???");
+ sb.append(".");
+ logger.finer(() -> sb.toString());
+ if (view instanceof Editor) {
+ final Component component = ((Editor) view).getDefaultFocusComponent();
+ if (component instanceof JEditorPane) {
+ String connectionName = null;
+ String owner = null;
+ if (node instanceof DatabaseSourceNode) {
+ connectionName = ((DatabaseSourceNode) node).getConnectionName();
+ } else if (view instanceof Worksheet) {
+ connectionName = ((Worksheet) view).getConnectionName();
+ }
+ logger.fine("connectionName: " + connectionName);
+ final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences());
+ String text = ((JEditorPane) component).getText();
+ Connection conn = null;
+ if (preferences.isCheckRunUtplsqlTest()) {
+ conn = DatabaseTools.getConnection(connectionName);
+ } else {
+ conn = null;
+ }
+ final UtplsqlParser parser = new UtplsqlParser(text, conn, owner);
+ final int position = ((JEditorPane) component).getCaretPosition();
+ final String path = parser.getPathAt(position);
+ final PlsqlObject object = parser.getObjectAt(position);
+ final List includeObjectList = dependencies(object.getName(), connectionName);
+ final CodeCoverageReporter reporter = new CodeCoverageReporter(getPathList(path), includeObjectList, connectionName);
+ reporter.showParameterWindow();
+ }
+ } else if (view instanceof DBNavigatorWindow || view instanceof SBWindow) {
+ logger.finer("Code coverage from DB navigator");
+ final URL url = getURL(context);
+ if (url != null) {
+ final String connectionName = URLTools.getConnectionName(url);
+ logger.fine(() -> "connectionName: " + connectionName);
+ final ArrayList pathList = dedupPathList(getPathList(context));
+ logger.fine(() -> "pathlist: " + StringTools.getSimpleCSV(pathList));
+ final List includeObjectList = dependencies(context, connectionName);
+ logger.finer(() -> "includeObjectList: " + StringTools.getSimpleCSV(includeObjectList));
+ final CodeCoverageReporter reporter = new CodeCoverageReporter(pathList, includeObjectList, connectionName);
+ logger.finer(() -> "showing code coverage dialog");
+ reporter.showParameterWindow();
+ logger.finer(() -> "code coverage dialog shown");
+ } else {
+ logger.warning("url is null");
+ }
+ }
+ }
+
+ public void generateTest(final Context context) {
+ final View view = context.getView();
+ final Node node = context.getNode();
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Generate utPLSQL test from view ");
+ sb.append(view != null ? view.getClass().getName() : "???");
+ sb.append(" and node ");
+ sb.append(node != null ? node.getClass().getName() : "???");
+ sb.append(".");
+ logger.finer(() -> sb.toString());
+ if (view instanceof Editor) {
+ final Component component = ((Editor) view).getDefaultFocusComponent();
+ if (component instanceof JEditorPane) {
+ String connectionName = null;
+ if (node instanceof DatabaseSourceNode) {
+ connectionName = ((DatabaseSourceNode) node).getConnectionName();
+ } else if (view instanceof Worksheet) {
+ connectionName = ((Worksheet) view).getConnectionName();
+ }
+ if (connectionName != null) {
+ if (Connections.getInstance().isConnectionOpen(connectionName)) {
+ final GenContext genContext = new GenContext();
+ genContext.setConn(DatabaseTools.getConnection(connectionName));
+ String text = ((JEditorPane) component).getText();
+ final UtplsqlParser parser = new UtplsqlParser(text);
+ final int position = ((JEditorPane) component).getCaretPosition();
+ final PlsqlObject obj = parser.getObjectAt(position);
+ if (obj != null) {
+ genContext.setObjectType(obj.getType().toUpperCase());
+ genContext.setObjectName(obj.getName().toUpperCase());
+ final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences());
+ populateGenContext(genContext, preferences);
+ final TestTemplate testTemplate = new TestTemplate(genContext);
+ final String code = testTemplate.generate().toString();
+ UtplsqlWorksheetRunner.openWithCode(code, connectionName);
+ }
+ }
+ }
+ }
+ } else {
+ if (view instanceof DBNavigatorWindow || view instanceof SBWindow) {
+ final URL url = getURL(context);
+ if (url != null) {
+ final String connectionName = URLTools.getConnectionName(url);
+ GenContext genContext = getGenContext(context);
+ final TestTemplate testTemplate = new TestTemplate(genContext);
+ final String code = testTemplate.generate().toString();
+ UtplsqlWorksheetRunner.openWithCode(code, connectionName);
+ }
+ }
+ }
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.xtend b/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.xtend
deleted file mode 100644
index 14eea6c5..00000000
--- a/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.xtend
+++ /dev/null
@@ -1,164 +0,0 @@
-/* Copyright 2018 Philipp Salvisberg
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.utplsql.sqldev.menu
-
-import java.net.URL
-import java.util.logging.Logger
-import javax.swing.JEditorPane
-import oracle.dbtools.raptor.navigator.db.DBNavigatorWindow
-import oracle.dbtools.raptor.navigator.db.DatabaseConnection
-import oracle.dbtools.raptor.navigator.impl.ChildObjectElement
-import oracle.dbtools.raptor.navigator.impl.DatabaseSourceNode
-import oracle.dbtools.raptor.navigator.impl.ObjectFolder
-import oracle.dbtools.raptor.navigator.plsql.PlSqlNode
-import oracle.dbtools.raptor.utils.Connections
-import oracle.dbtools.worksheet.editor.Worksheet
-import oracle.ide.Context
-import oracle.ide.Ide
-import oracle.ide.controller.Controller
-import oracle.ide.controller.IdeAction
-import oracle.ide.editor.Editor
-import org.utplsql.sqldev.UtplsqlWorksheet
-import org.utplsql.sqldev.dal.UtplsqlDao
-import org.utplsql.sqldev.model.URLTools
-import org.utplsql.sqldev.parser.UtplsqlParser
-
-class UtplsqlController implements Controller {
- private static final Logger logger = Logger.getLogger(UtplsqlController.name);
- private val extension URLTools urlTools = new URLTools
-
- public static int UTLPLSQL_TEST_CMD_ID = Ide.findCmdID("utplsql.test")
- public static final IdeAction UTLPLSQL_TEST_ACTION = IdeAction.get(UtplsqlController.UTLPLSQL_TEST_CMD_ID)
-
- override handleEvent(IdeAction action, Context context) {
- if (action.commandId === UtplsqlController.UTLPLSQL_TEST_CMD_ID) {
- runTest(context)
- return true
- }
- return false
- }
-
- override update(IdeAction action, Context context) {
- if (action.commandId === UTLPLSQL_TEST_CMD_ID) {
- action.enabled = false
- val view = context.view
- if (view instanceof Editor) {
- val component = view.defaultFocusComponent
- if (component instanceof JEditorPane) {
- val node = context.node
- var String connectionName = null;
- if (node instanceof DatabaseSourceNode) {
- connectionName = node.connectionName
- } else if (view instanceof Worksheet) {
- connectionName = view.connectionName
- }
- logger.fine('''connectionName: «connectionName»''')
- val parser = new UtplsqlParser(component.text, Connections.instance.getConnection(connectionName))
- if (!parser.getPathAt(component.caretPosition).empty) {
- action.enabled = true
- }
- }
- } else if (view instanceof DBNavigatorWindow) {
- if (context.selection.length == 1) {
- val element = context.selection.get(0)
- if (Connections.instance.isConnectionOpen(context.URL.connectionName)) {
- val dao = new UtplsqlDao(Connections.instance.getConnection(context.URL.connectionName))
- if (dao.utAnnotationManagerInstalled) {
- if (element instanceof DatabaseConnection) {
- action.enabled = dao.containsUtplsqlTest(element.connection.schema)
- } else if (element instanceof ObjectFolder) {
- action.enabled = dao.containsUtplsqlTest(element.URL.schema)
- } else if (element instanceof PlSqlNode) {
- action.enabled = dao.containsUtplsqlTest(element.owner, element.objectName)
- } else if (element instanceof ChildObjectElement) {
- action.enabled = dao.containsUtplsqlTest(element.URL.schema, element.URL.memberObject, element.shortLabel)
- }
- } else {
- action.enabled = true
- }
- }
- }
- }
- return true
- }
- return false
- }
-
- private def getPath(Context context) {
- var String path
- val element = context.selection.get(0)
- if (element instanceof DatabaseConnection) {
- path = element.connection.schema
- } else if (element instanceof ObjectFolder) {
- path = element.URL.schema
- } else if (element instanceof PlSqlNode) {
- path = '''«element.owner».«element.objectName»'''
- } else if (element instanceof ChildObjectElement) {
- path = '''«element.URL.schema».«element.URL.memberObject».«element.shortLabel»'''
- } else {
- path = ""
- }
- logger.fine('''path: «path»''')
- return path
- }
-
- private def getURL(Context context) {
- var URL url
- val element = context.selection.get(0)
- if (element instanceof DatabaseConnection) {
- url = element.URL
- } else if (element instanceof ObjectFolder) {
- url = element.URL
- } else if (element instanceof PlSqlNode) {
- url = element.URL
- } else if (element instanceof ChildObjectElement) {
- url = element.URL
- }
- logger.fine('''url: «url»''')
- return url
- }
-
- def runTest(Context context) {
- val view = context.view
- val node = context.node
- logger.finer('''Run utPLSQL from view «view?.class?.name» and node «node?.class?.name».''')
- if (view instanceof Editor) {
- val component = view.defaultFocusComponent
- if (component instanceof JEditorPane) {
- var String connectionName = null;
- if (node instanceof DatabaseSourceNode) {
- connectionName = node.connectionName
- } else if (view instanceof Worksheet) {
- connectionName = view.connectionName
- }
- logger.fine('''connectionName: «connectionName»''')
- val parser = new UtplsqlParser(component.text, Connections.instance.getConnection(connectionName))
- val position = component.caretPosition
- val path = parser.getPathAt(position)
- val utPlsqlWorksheet = new UtplsqlWorksheet(path, connectionName)
- utPlsqlWorksheet.runTestAsync
- }
- } else if (view instanceof DBNavigatorWindow) {
- val url=context.URL
- if (url !== null) {
- val connectionName = url.connectionName
- logger.fine('''connectionName: «connectionName»''')
- val path=context.path
- val utPlsqlWorksheet = new UtplsqlWorksheet(path, connectionName)
- utPlsqlWorksheet.runTestAsync
- }
- }
- }
-}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlNavigatorRule.xtend b/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlNavigatorRule.xtend
deleted file mode 100644
index 6fb99209..00000000
--- a/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlNavigatorRule.xtend
+++ /dev/null
@@ -1,55 +0,0 @@
-/* Copyright 2018 Philipp Salvisberg
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.utplsql.sqldev.menu
-
-import java.util.Map
-import java.util.logging.Logger
-import oracle.dbtools.raptor.navigator.db.DatabaseConnection
-import oracle.dbtools.raptor.navigator.impl.ChildObjectElement
-import oracle.dbtools.raptor.navigator.impl.ObjectFolder
-import oracle.dbtools.raptor.navigator.plsql.PlSqlNode
-import oracle.ide.^extension.rules.RuleEvaluationContext
-import oracle.ide.^extension.rules.RuleEvaluationException
-import oracle.ide.^extension.rules.RuleFunction
-import oracle.ide.^extension.rules.RuleFunctionParameter
-import org.utplsql.sqldev.model.URLTools
-
-class UtplsqlNavigatorRule extends RuleFunction {
- private static final Logger logger = Logger.getLogger(UtplsqlNavigatorRule.name);
- private val extension URLTools urlTools = new URLTools
-
- override evaluate(RuleEvaluationContext ruleContext,
- Map parameters) throws RuleEvaluationException {
- val element = ruleContext.ideContext.selection.get(0)
- var boolean enable = false
- logger.fine('''selected object is of type «element.class.name»''')
- if (element instanceof DatabaseConnection) {
- enable = true
- } else if (element instanceof ObjectFolder) {
- if (element.objectType == "PACKAGE") {
- enable = true
- }
- } else if (element instanceof PlSqlNode) {
- if (element.objectType == "PACKAGE" || element.objectType == "PACKAGE BODY") {
- enable = true
- }
- } else if (element instanceof ChildObjectElement) {
- if (element.URL.objectType == "PACKAGE") {
- enable = true
- }
- }
- return enable
- }
-}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/DatabaseTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/DatabaseTools.java
new file mode 100644
index 00000000..df12153b
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/DatabaseTools.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2020 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import javax.sql.DataSource;
+
+import org.springframework.core.task.SimpleAsyncTaskExecutor;
+import org.utplsql.sqldev.exception.GenericDatabaseAccessException;
+import org.utplsql.sqldev.exception.GenericRuntimeException;
+
+import oracle.dbtools.raptor.navigator.db.DatabaseConnection;
+import oracle.dbtools.raptor.utils.Connections;
+import oracle.javatools.db.DBException;
+import oracle.jdeveloper.db.ConnectionException;
+
+public class DatabaseTools {
+ // do not instantiate this class
+ private DatabaseTools() {
+ super();
+ }
+
+ public static Connection getConnection(DataSource dataSource) {
+ try {
+ return dataSource.getConnection();
+ } catch (SQLException e) {
+ throw new GenericDatabaseAccessException("Error getting connection.", e);
+ }
+ }
+
+ public static Connection getConnection(DatabaseConnection conn) {
+ try {
+ return conn.getConnection();
+ } catch (IOException e) {
+ final String msg = "Error getting connection for " + conn.getConnectionName() + ".";
+ throw new GenericDatabaseAccessException(msg, e);
+ }
+ }
+
+ public static Connection getConnection(String connectionName) {
+ try {
+ return Connections.getInstance().getConnection(connectionName);
+ } catch (DBException e) {
+ final String msg = "Error getting connection for " + connectionName + ".";
+ throw new GenericDatabaseAccessException(msg, e);
+ }
+ }
+
+ public static Connection cloneConnection(String connectionName) {
+ final Connection conn = getConnection(connectionName);
+ try {
+ return Connections.getInstance().cloneConnection(conn);
+ } catch (ConnectionException e) {
+ final String msg = "Error cloning connection " + connectionName + ".";
+ throw new GenericDatabaseAccessException(msg, e);
+ }
+ }
+
+ private static String createTemporaryConnection(String connectionName) {
+ try {
+ return Connections.getInstance().createTemporaryConnection(connectionName);
+ } catch (Throwable e) {
+ final String msg = "Error creating temporary connection based on " + connectionName + ".";
+ throw new GenericDatabaseAccessException(msg, e);
+ }
+ }
+
+ private static String createPrivateConnection(String connectionName) {
+ try {
+ return Connections.getInstance().createPrivateConnection(connectionName);
+ } catch (Throwable e) {
+ final String msg = "Error creating private connection based on " + connectionName + ".";
+ throw new GenericDatabaseAccessException(msg, e);
+ }
+ }
+
+ public static String createTemporaryOrPrivateConnection(String connectionName) {
+ // Private connections are closed in SQL Developer < 17.4.0 when the worksheet
+ // is closed, but in SQL Developer > 17.4.0 private connections are not closed.
+ // Temporary connections have been introduced in SQL Developer 17.4.0. They will
+ // be always closed, when a worksheet is closed.
+ // Hence we try to use temporary connections whenever possible. See also
+ // https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/47 .
+ try {
+ return createTemporaryConnection(connectionName);
+ } catch (GenericDatabaseAccessException e) {
+ return createPrivateConnection(connectionName);
+ }
+ }
+
+ public static void closeConnection(Connection conn) {
+ abortConnection(conn);
+ }
+
+ public static void abortConnection(Connection conn) {
+ final SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
+ try {
+ conn.abort(taskExecutor);
+ } catch (SQLException e) {
+ throw new GenericDatabaseAccessException("Could not abort connection.");
+ }
+ }
+
+ public static String getSchema(Connection conn) {
+ try {
+ return conn.getSchema();
+ } catch (SQLException e) {
+ throw new GenericRuntimeException("Error getting schema name of connection.", e);
+ }
+ }
+
+ public static String getUser(Connection conn) {
+ try {
+ return conn.getMetaData().getUserName();
+ } catch (SQLException e) {
+ throw new GenericRuntimeException("Error getting user name of connection.", e);
+ }
+ }
+
+ public static String getSchema(DatabaseConnection conn) {
+ return getSchema(getConnection(conn));
+ }
+
+
+ public static String getSchema(String connectionName) {
+ return getSchema(getConnection(connectionName));
+ }
+
+ public static boolean isSupported(final Connection conn) {
+ try {
+ boolean ret = false;
+ if (conn != null && conn.getMetaData().getDatabaseProductName().startsWith("Oracle")
+ && (conn.getMetaData().getDatabaseMajorVersion() == 11
+ && conn.getMetaData().getDatabaseMinorVersion() >= 2
+ || conn.getMetaData().getDatabaseMajorVersion() > 11)) {
+ ret = true;
+ }
+ return ret;
+ } catch (SQLException e) {
+ throw new GenericDatabaseAccessException("Error while getting product version of connection.", e);
+ }
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/FileTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/FileTools.java
new file mode 100644
index 00000000..02b32e5f
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/FileTools.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2020 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.utplsql.sqldev.exception.GenericRuntimeException;
+
+public class FileTools {
+ // do not instantiate this class
+ private FileTools() {
+ super();
+ }
+
+ public static byte[] readFile(Path path) {
+ try {
+ return Files.readAllBytes(path);
+ } catch (IOException e) {
+ final String msg = "Cannot read file " + path.toString() + ".";
+ throw new GenericRuntimeException(msg, e);
+ }
+ }
+
+ public static void writeFile(Path path, byte[] bytes) {
+ try {
+ Files.write(path, bytes);
+ } catch (IOException e) {
+ final String msg = "Cannot write file " + path.toString() + ".";
+ throw new GenericRuntimeException(msg, e);
+ }
+ }
+
+ public static void writeFile(Path path, Iterable extends CharSequence> lines, Charset cs) {
+ try {
+ Files.write(path, lines, cs);
+ } catch (IOException e) {
+ final String msg = "Cannot write file " + path.toString() + ".";
+ throw new GenericRuntimeException(msg, e);
+ }
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/JsonToStringStyler.java b/sqldev/src/main/java/org/utplsql/sqldev/model/JsonToStringStyler.java
new file mode 100644
index 00000000..7b4d429e
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/JsonToStringStyler.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2020 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model;
+
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+
+import javax.annotation.Nullable;
+
+import org.jetbrains.annotations.NotNull;
+import org.springframework.core.style.ToStringStyler;
+import org.springframework.core.style.ValueStyler;
+
+public class JsonToStringStyler implements ToStringStyler, ValueStyler{
+ public static final String INDENT_SPACES = " ";
+ private int indent = 0;
+
+ private static final ThreadLocal threadLocal = ThreadLocal.withInitial(JsonToStringStyler::new);
+
+ private void newLine(StringBuilder buffer) {
+ buffer.append('\n');
+ buffer.append(getIndentSpaces(0));
+ }
+
+ private String getIndentSpaces(int indentOffset) {
+ StringBuilder sb = new StringBuilder();
+ for (int i=0; i list) {
+ if (list.isEmpty()) {
+ return "[]";
+ }
+
+ StringJoiner result = new StringJoiner(",\n" + getIndentSpaces(1), "[\n" + getIndentSpaces(1) , "\n" + getIndentSpaces(0) + "]");
+ indent++;
+ for (Object o : list) {
+ result.add(style(o));
+ }
+ indent--;
+ return result.toString();
+ }
+
+ private String getMapStyle(Map, ?> map) {
+ if (map.isEmpty()) {
+ return "[]";
+ }
+
+ StringJoiner result = new StringJoiner(",\n" + getIndentSpaces(1), "[\n" + getIndentSpaces(1) , "\n" + getIndentSpaces(0) + "]");
+ indent++;
+ for (Object o : map.values()) {
+ result.add(style(o));
+ }
+ indent--;
+ return result.toString();
+ }
+
+ private String getDefaultStyle(Object value) {
+ return String.valueOf(value);
+ }
+
+ public static ToStringStyler getInstance() {
+ return threadLocal.get();
+ }
+
+ @Override
+ public void styleStart(@NotNull StringBuilder buffer, Object obj) {
+ indent++;
+ if (!obj.getClass().isArray()) {
+ buffer.append("{");
+ newLine(buffer);
+ buffer.append("\"className\": ");
+ buffer.append('"');
+ buffer.append(obj.getClass().getSimpleName());
+ buffer.append('"');
+ buffer.append(',');
+ } else {
+ buffer.append('[');
+ styleValue(buffer, obj);
+ }
+ }
+
+ @Override
+ public void styleEnd(@NotNull StringBuilder buffer, Object obj) {
+ indent--;
+ newLine(buffer);
+ if (!obj.getClass().isArray()) {
+ buffer.append('}');
+ } else {
+ buffer.append(']');
+ }
+ }
+
+ @Override
+ public void styleField(@NotNull StringBuilder buffer, @NotNull String fieldName, @Nullable Object value) {
+ newLine(buffer);
+ buffer.append('"');
+ buffer.append(fieldName);
+ buffer.append('"');
+ buffer.append(": ");
+ styleValue(buffer, value);
+ }
+
+ @Override
+ public void styleValue(StringBuilder buffer, @Nullable Object value) {
+ buffer.append(style(value));
+ }
+
+ @Override
+ public void styleFieldSeparator(StringBuilder buffer) {
+ buffer.append(",");
+ }
+
+ @NotNull
+ @Override
+ public String style(Object value) {
+ if (value == null) {
+ return "null";
+ } else if (value instanceof String) {
+ return getStringStyle((String) value);
+ } else if (value instanceof Object[]) {
+ return getArrayStyle((Object[]) value);
+ } else if (value instanceof List>) {
+ return getListStyle((List>) value);
+ } else if (value instanceof Map) {
+ return getMapStyle((Map, ?>) value);
+ } else {
+ return getDefaultStyle(value.toString());
+ }
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.java b/sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.java
new file mode 100644
index 00000000..550e6d72
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class LimitedLinkedHashMap extends LinkedHashMap {
+ private static final long serialVersionUID = -4184317926729190411L;
+ private final int maxEntries;
+
+ public LimitedLinkedHashMap(final int maxEntries) {
+ super((maxEntries + 1), 1.0f, false);
+ this.maxEntries = maxEntries;
+ }
+
+ @Override
+ public boolean removeEldestEntry(final Map.Entry eldest) {
+ return (size() > maxEntries);
+ }
+
+ public int getMaxEntries() {
+ return maxEntries;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/PrefixTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/PrefixTools.java
new file mode 100644
index 00000000..e17ce69d
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/PrefixTools.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2019 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model;
+
+import java.util.List;
+
+//converted to Xtend based on Java code on https://www.geeksforgeeks.org/longest-common-prefix-using-binary-search/
+//converted back to Java with some amendments
+public class PrefixTools {
+
+ // do not instantiate this class
+ private PrefixTools() {
+ super();
+ }
+
+ public static int findMinLength(final String[] arr, final int n) {
+ int min = Integer.MAX_VALUE;
+ for (int i=0; i < n; i++) {
+ if (arr[i].length() < min) {
+ min = arr[i].length();
+ }
+ }
+ return min;
+ }
+
+ public static boolean allContainsPrefix(final String[] arr, final int n, final String str, final int start, final int end) {
+ for (int i=0; i < n; i++) {
+ String item = arr[i];
+ for (int j = start; j <= end; j++) {
+ if (item.charAt(j) != str.charAt(j)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public static String commonPrefix(final String[] arr, final int n) {
+ int index = findMinLength(arr, n);
+ StringBuilder prefix = new StringBuilder();
+ int low = 0;
+ int high = index; // index-1 is wrong
+ while (low <= high) {
+ int mid = low + (high - low) / 2;
+ if (allContainsPrefix(arr, n, arr[0], low, mid)) {
+ prefix.append(arr[0].substring(low, mid + 1));
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ }
+ return prefix.toString();
+ }
+
+ public static String commonPrefix(final List list) {
+ try {
+ if (list.isEmpty()) {
+ return "";
+ } else if (list.size() == 1) {
+ final int pos = list.get(0).lastIndexOf('.');
+ if (pos > 0) {
+ return list.get(0).substring(0, pos + 1);
+ } else {
+ return "";
+ }
+ } else {
+ final String[] testArray = new String[list.size()];
+ return commonPrefix(list.toArray(testArray), list.size());
+ }
+
+ } catch (Exception e) {
+ return "";
+ }
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/StringTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/StringTools.java
new file mode 100644
index 00000000..23323043
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/StringTools.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2020 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import org.utplsql.sqldev.exception.GenericRuntimeException;
+
+public class StringTools {
+ // do not instantiate this class
+ private StringTools() {
+ super();
+ }
+
+ public static String getCSV(List list, String indent) {
+ final StringBuilder sb = new StringBuilder();
+ for (final String item : list) {
+ if (sb.length() > 0) {
+ sb.append(",\n");
+ }
+ sb.append(indent);
+ sb.append("'");
+ sb.append(item);
+ sb.append("'");
+ }
+ sb.append("\n");
+ return sb.toString();
+ }
+
+ public static String getCSV(List list, int indentSpaces) {
+ return getCSV(list, repeat(" ", indentSpaces));
+ }
+
+ public static String getSimpleCSV(List list) {
+ final StringBuilder sb = new StringBuilder();
+ for (final String item : list) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(item);
+ }
+ return sb.toString();
+ }
+
+ public static String repeat(String s, int times) {
+ return String.join("", Collections.nCopies(times, s));
+ }
+
+ public static String replaceTabsWithSpaces(final CharSequence input, int indentSpaces) {
+ final String spaces = StringTools.repeat(" ", indentSpaces);
+ return input.toString().replace("\t", spaces);
+ }
+
+ public static String formatDateTime(final String dateTime) {
+ if (dateTime == null) {
+ return null;
+ } else {
+ if (dateTime.length() == 26) {
+ return dateTime.replace("T", " ").substring(0, 23);
+ } else {
+ return dateTime;
+ }
+ }
+ }
+
+ public static String millisToDateTimeString(long millis) {
+ final Date dateTime = new Date(millis);
+ final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'000'");
+ return df.format(dateTime);
+ }
+
+ public static String getSysdate() {
+ return millisToDateTimeString(System.currentTimeMillis());
+ }
+
+ public static long dateTimeStringToMillis(final String dateTime) {
+ // handle milliseconds separately since they get lost (rounded) when converted to date
+ final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+ Date date;
+ try {
+ date = df.parse(dateTime.substring(0, 20));
+ } catch (ParseException e) {
+ throw new GenericRuntimeException("cannot parse datetime string " + dateTime + ".", e);
+ }
+ long millis = Long.parseLong(dateTime.substring(20, 23));
+ return date.getTime() + millis;
+ }
+
+ public static double elapsedTime(String startDateTime, String endDateTime) {
+ double start = (double) dateTimeStringToMillis(startDateTime);
+ double end = (double) dateTimeStringToMillis(endDateTime);
+ return (end - start) / 1000;
+ }
+
+ public static boolean isNotBlank(String value) {
+ return value != null && !value.trim().isEmpty();
+ }
+
+ public static String trim(String value) {
+ if (value == null) {
+ return null;
+ }
+ return value.trim();
+ }
+
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/SystemTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/SystemTools.java
new file mode 100644
index 00000000..30a75172
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/SystemTools.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model;
+
+public class SystemTools {
+ // do not instantiate this class
+ private SystemTools() {
+ super();
+ }
+
+ public static void sleep(int millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ public static void waitForThread(Thread thread, int maxTimeInMillis) {
+ try {
+ thread.join(maxTimeInMillis);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.java
new file mode 100644
index 00000000..26190df3
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model;
+
+import java.net.URL;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class URLTools {
+
+ // do not instantiate this class
+ private URLTools() {
+ super();
+ }
+
+ public static String replaceHexChars(final String input) {
+ String output = input;
+ final Pattern p = Pattern.compile("%([0-9A-F]{2})");
+ final Matcher m = p.matcher(input);
+ while (m.find()) {
+ final String what = m.group(0);
+ final int decimal = Integer.parseInt(m.group(1), 16);
+ final String with = String.valueOf((char) decimal);
+ output = output.replace(what, with);
+ }
+ return output;
+ }
+
+ public static String getConnectionName(final URL url) {
+ final Pattern p = Pattern.compile("(sqldev.nav:)([^/]+)(//)?");
+ final Matcher m = p.matcher(url.toString());
+ if (m.find()) {
+ return replaceHexChars(m.group(2)
+ .replace("IdeConnections%2523", "IdeConnections%23")) // remove connection prefix
+ .replace("+", " "); // spaces are encoded als plus signs, fix that, see #118
+ } else {
+ return "";
+ }
+ }
+
+ public static String getSchema(final URL url) {
+ final Pattern p = Pattern.compile("(//)([^/]+)");
+ final Matcher m = p.matcher(url.toString());
+ if (m.find()) {
+ return m.group(2);
+ } else {
+ return "";
+ }
+ }
+
+ public static String getObjectType(final URL url) {
+ final Pattern p = Pattern.compile("(//)([^/]+)(/)([^/]+)");
+ final Matcher m = p.matcher(url.toString());
+ if (m.find()) {
+ return m.group(4);
+ } else {
+ return "";
+ }
+ }
+
+ public static String getMemberObject(final URL url) {
+ final Pattern p = Pattern.compile("(/)([^/]+)(#MEMBER)");
+ final Matcher m = p.matcher(url.toString());
+ if (m.find()) {
+ return m.group(2);
+ } else {
+ return "";
+ }
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.xtend
deleted file mode 100644
index c3850874..00000000
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.xtend
+++ /dev/null
@@ -1,61 +0,0 @@
-/* Copyright 2018 Philipp Salvisberg
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.utplsql.sqldev.model
-
-import java.net.URL
-import java.util.regex.Pattern
-
-class URLTools {
- def getConnectionName(URL url) {
- val p = Pattern.compile("(sqldev.nav:)([^/]+)(//)?")
- val m = p.matcher(url.toString)
- if (m.find) {
- return m.group(2).replace("IdeConnections%2523", "IdeConnections%23")
- } else {
- return ""
- }
- }
-
- def getSchema(URL url) {
- val p = Pattern.compile("(//)([^/]+)")
- val m = p.matcher(url.toString)
- if (m.find) {
- return m.group(2)
- } else {
- return ""
- }
- }
-
- def getObjectType(URL url) {
- val p = Pattern.compile("(//)([^/]+)(/)([^/]+)")
- val m = p.matcher(url.toString)
- if (m.find) {
- return m.group(4)
- } else {
- return ""
- }
- }
-
- def getMemberObject(URL url) {
- val p = Pattern.compile("(/)([^/]+)(#MEMBER)")
- val m = p.matcher(url.toString)
-
- if (m.find) {
- return m.group(2)
- } else {
- return ""
- }
- }
-}
\ No newline at end of file
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.java
new file mode 100644
index 00000000..090cf8d6
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.logging.Logger;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.utplsql.sqldev.exception.GenericRuntimeException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class XMLTools {
+ private static final Logger logger = Logger.getLogger(XMLTools.class.getName());
+ private final XPathFactory xpathFactory = XPathFactory.newInstance();
+ private final XPath xpath = xpathFactory.newXPath();
+
+ public NodeList getNodeList(final Node doc, final String xpathString) {
+ try {
+ final XPathExpression expr = xpath.compile(xpathString);
+ return ((NodeList) expr.evaluate(doc, XPathConstants.NODESET));
+ } catch (XPathExpressionException e) {
+ final String msg = "XPathExpressionException for " + xpathString + ".";
+ logger.severe(() -> msg);
+ throw new GenericRuntimeException(msg, e);
+ }
+ }
+
+ public Node getNode(final Node doc, final String xpathString) {
+ try {
+ final XPathExpression expr = xpath.compile(xpathString);
+ return ((Node) expr.evaluate(doc, XPathConstants.NODE));
+ } catch (XPathExpressionException e) {
+ final String msg = "XPathExpressionException for " + xpathString + ".";
+ logger.severe(() -> msg);
+ throw new GenericRuntimeException(msg, e);
+ }
+ }
+
+ public void trimWhitespace(final Node node) {
+ final NodeList children = node.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++) {
+ final Node child = children.item(i);
+ if (child.getNodeType() == Node.TEXT_NODE) {
+ child.setTextContent(child.getTextContent().trim());
+ }
+ trimWhitespace(child);
+ }
+ }
+
+ public String nodeToString(final Node node, final String cdataSectionElements) {
+ try {
+ trimWhitespace(node);
+ final StringWriter writer = new StringWriter();
+ TransformerFactory factory = TransformerFactory.newInstance();
+ factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ final Transformer transformer = factory.newTransformer();
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3");
+ transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, cdataSectionElements);
+ transformer.transform( new DOMSource(node), new StreamResult(writer));
+ final String result = writer.toString();
+ return result.replaceAll("", "");
+ } catch (TransformerException e) {
+ final String msg = "TransformerException for " + cdataSectionElements + ".";
+ logger.severe(() -> msg);
+ throw new GenericRuntimeException(msg, e);
+ }
+ }
+
+ public String getAttributeValue(final Node node, final String namedItem) {
+ String value = null;
+ if (node instanceof Element) {
+ final NamedNodeMap attributes = node.getAttributes();
+ if (attributes != null) {
+ final Node item = attributes.getNamedItem(namedItem);
+ if (item != null) {
+ value = item.getNodeValue();
+ }
+ }
+ }
+ return value;
+ }
+
+ public String getElementValue(final Node node, final String tagName) {
+ String value = null;
+ final Node item = getElementNode(node, tagName);
+ if (item != null) {
+ value = item.getTextContent();
+ }
+ return value;
+ }
+
+ public Node getElementNode(final Node node, final String tagName) {
+ Node resultNode = null;
+ if (node instanceof Element) {
+ NodeList list = ((Element) node).getElementsByTagName(tagName);
+ if (list != null && list.getLength() > 0 && list.item(0).getParentNode() == node) {
+ resultNode = list.item(0);
+ }
+ }
+ return resultNode;
+ }
+
+ public DocumentBuilder createDocumentBuilder() {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ try {
+ factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
+ return factory.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ final String msg = "Could not create no document builder.";
+ logger.severe(() -> msg);
+ throw new GenericRuntimeException(msg, e);
+ }
+ }
+
+ public Document parse(final DocumentBuilder builder, final InputSource inputSource) {
+ try {
+ return builder.parse(inputSource);
+ } catch (SAXException | IOException e) {
+ final String msg = "Could not parse XML input.";
+ logger.severe(() -> msg);
+ throw new GenericRuntimeException(msg, e);
+ }
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/oddgen/GenContext.java b/sqldev/src/main/java/org/utplsql/sqldev/model/oddgen/GenContext.java
new file mode 100644
index 00000000..41fc456e
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/oddgen/GenContext.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.oddgen;
+
+import java.sql.Connection;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public class GenContext {
+ private Connection conn;
+ private String objectType;
+ private String objectName;
+ private String testPackagePrefix;
+ private String testPackageSuffix;
+ private String testUnitPrefix;
+ private String testUnitSuffix;
+ private int numberOfTestsPerUnit;
+ private boolean generateComments;
+ private boolean disableTests;
+ private String suitePath;
+ private int indentSpaces;
+
+ public Connection getConn() {
+ return conn;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append("conn", conn)
+ .append("objectType", objectType)
+ .append("objectName", objectName)
+ .append("testPackagePrefix", testPackagePrefix)
+ .append("testPackageSuffix", testPackageSuffix)
+ .append("testUnitPrefix", testUnitPrefix)
+ .append("testUnitSuffix", testUnitSuffix)
+ .append("numberOfTestsPerUnit", numberOfTestsPerUnit)
+ .append("generateComments", generateComments)
+ .append("disableTests", disableTests)
+ .append("suitePath", suitePath)
+ .append("indentSpaces", indentSpaces)
+ .toString();
+ }
+
+ public void setConn(final Connection conn) {
+ this.conn = conn;
+ }
+
+ public String getObjectType() {
+ return objectType;
+ }
+
+ public void setObjectType(final String objectType) {
+ this.objectType = objectType;
+ }
+
+ public String getObjectName() {
+ return objectName;
+ }
+
+ public void setObjectName(final String objectName) {
+ this.objectName = objectName;
+ }
+
+ public String getTestPackagePrefix() {
+ return testPackagePrefix;
+ }
+
+ public void setTestPackagePrefix(final String testPackagePrefix) {
+ this.testPackagePrefix = testPackagePrefix;
+ }
+
+ public String getTestPackageSuffix() {
+ return testPackageSuffix;
+ }
+
+ public void setTestPackageSuffix(final String testPackageSuffix) {
+ this.testPackageSuffix = testPackageSuffix;
+ }
+
+ public String getTestUnitPrefix() {
+ return testUnitPrefix;
+ }
+
+ public void setTestUnitPrefix(final String testUnitPrefix) {
+ this.testUnitPrefix = testUnitPrefix;
+ }
+
+ public String getTestUnitSuffix() {
+ return testUnitSuffix;
+ }
+
+ public void setTestUnitSuffix(final String testUnitSuffix) {
+ this.testUnitSuffix = testUnitSuffix;
+ }
+
+ public int getNumberOfTestsPerUnit() {
+ return numberOfTestsPerUnit;
+ }
+
+ public void setNumberOfTestsPerUnit(final int numberOfTestsPerUnit) {
+ this.numberOfTestsPerUnit = numberOfTestsPerUnit;
+ }
+
+ public boolean isGenerateComments() {
+ return generateComments;
+ }
+
+ public void setGenerateComments(final boolean generateComments) {
+ this.generateComments = generateComments;
+ }
+
+ public boolean isDisableTests() {
+ return disableTests;
+ }
+
+ public void setDisableTests(final boolean disableTests) {
+ this.disableTests = disableTests;
+ }
+
+ public String getSuitePath() {
+ return suitePath;
+ }
+
+ public void setSuitePath(final String suitePath) {
+ this.suitePath = suitePath;
+ }
+
+ public int getIndentSpaces() {
+ return indentSpaces;
+ }
+
+ public void setIndentSpaces(final int indentSpaces) {
+ this.indentSpaces = indentSpaces;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.java b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.java
new file mode 100644
index 00000000..51806d53
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.parser;
+
+import java.util.List;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+import org.utplsql.sqldev.model.ut.Annotation;
+
+public class PlsqlObject {
+ private String name;
+ private String type;
+ private Integer position;
+ private List annotations;
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append("name", name)
+ .append("type", type)
+ .append("position", position)
+ .append("annotations", annotations)
+ .toString();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(final String type) {
+ this.type = type;
+ }
+
+ public Integer getPosition() {
+ return position;
+ }
+
+ public void setPosition(final Integer position) {
+ this.position = position;
+ }
+
+ public List getAnnotations() {
+ return annotations;
+ }
+
+ public void setAnnotations(final List annotations) {
+ this.annotations = annotations;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.java b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.java
new file mode 100644
index 00000000..c6bdc5f8
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.parser;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public class Unit {
+ private String name;
+ private Integer position;
+ private Integer positionOfName;
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append("name", name)
+ .append("position", position)
+ .append("positionOfName", positionOfName)
+ .toString();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public Integer getPosition() {
+ return position;
+ }
+
+ public void setPosition(final Integer position) {
+ this.position = position;
+ }
+
+ public Integer getPositionOfName() {
+ return positionOfName;
+ }
+
+ public void setPositionOfName(final Integer positionOfName) {
+ this.positionOfName = positionOfName;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.java b/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.java
new file mode 100644
index 00000000..13bd9794
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.preference;
+
+import java.io.File;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+import oracle.javatools.data.HashStructure;
+import oracle.javatools.data.HashStructureAdapter;
+import oracle.javatools.data.PropertyStorage;
+
+public class PreferenceModel extends HashStructureAdapter {
+ public static final String DEFAULT_OUTPUT_DIRECTORY = System.getProperty("user.home") + File.separator + "utplsql" + File.separator + "generated";
+ private static final String DATA_KEY = "utplsql";
+
+ private PreferenceModel(final HashStructure hash) {
+ super(hash);
+ }
+
+ public static PreferenceModel getInstance(final PropertyStorage prefs) {
+ return new PreferenceModel(findOrCreate(prefs, DATA_KEY));
+ }
+
+ private static final String KEY_USE_REALTIME_REPORTER = "useRealtimeRorter";
+ private static final String KEY_UNSHARED_WORKSHEET = "unsharedWorksheet";
+ private static final String KEY_RESET_PACKAGE = "resetPackage";
+ private static final String KEY_CLEAR_SCREEN = "clearScreen";
+ private static final String KEY_AUTO_EXECUTE = "autoExecute";
+ private static final String KEY_CHECK_RUN_UTPLSQL_TEST = "checkRunUtplsqlTest";
+ private static final String KEY_USE_SMART_TIMES = "useSmartTimes";
+ private static final String KEY_NUMBER_OF_RUNS_IN_HISTORY = "numberOfRunsInHistory";
+ private static final String KEY_SHOW_DISABLED_COUNTER = "showDisabledCounter";
+ private static final String KEY_SHOW_WARNINGS_COUNTER = "showWarningsCounter";
+ private static final String KEY_SHOW_INFO_COUNTER = "showInfoCounter";
+ private static final String KEY_SHOW_WARNING_INDICATOR = "showWarningIndicator";
+ private static final String KEY_SHOW_INFO_INDICATOR = "showInfoIndicator";
+ private static final String KEY_SHOW_SUCCESSFUL_TESTS = "showSuccessfulTests";
+ private static final String KEY_SHOW_DISABLED_TESTS = "showDisabledTests";
+ private static final String KEY_SHOW_TEST_DESCRIPTION = "showTestDescription";
+ private static final String KEY_SYNC_DETAIL_TAB = "syncDetailTab";
+ private static final String KEY_SHOW_SUITES = "showSuites";
+ private static final String KEY_TEST_PACKAGE_PREFIX = "testPackagePrefix";
+ private static final String KEY_TEST_PACKAGE_SUFFIX = "testPackageSuffix";
+ private static final String KEY_TEST_UNIT_PREFIX = "testUnitPrefix";
+ private static final String KEY_TEST_UNIT_SUFFIX = "testUnitSuffix";
+ private static final String KEY_NUMBER_OF_TESTS_PER_UNIT = "numberOfTestsPerUnit";
+ private static final String KEY_CHECK_GENERATE_UTPLSQL_TEST = "checkGenerateUtplsqlTest";
+ private static final String KEY_GENERATE_COMMENTS = "generateComments";
+ private static final String KEY_DISABLE_TESTS = "disableTests";
+ private static final String KEY_SUITE_PATH = "suitePath";
+ private static final String KEY_INDENT_SPACES = "indentSpaces";
+ private static final String KEY_GENERATE_FILES = "generateFiles";
+ private static final String KEY_OUTPUT_DIRECTORY = "outputDirectory";
+ private static final String KEY_DELETE_EXISTING_FILES = "deleteExistingFiles";
+ private static final String KEY_ROOT_FOLDER_IN_ODDGEN_VIEW = "rootFolderInOddgenView";
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append(KEY_USE_REALTIME_REPORTER, isUseRealtimeReporter())
+ .append(KEY_UNSHARED_WORKSHEET, isUnsharedWorksheet())
+ .append(KEY_RESET_PACKAGE, isResetPackage())
+ .append(KEY_CLEAR_SCREEN, isClearScreen())
+ .append(KEY_AUTO_EXECUTE, isAutoExecute())
+ .append(KEY_CHECK_RUN_UTPLSQL_TEST, isCheckRunUtplsqlTest())
+ .append(KEY_USE_SMART_TIMES, isUseSmartTimes())
+ .append(KEY_NUMBER_OF_RUNS_IN_HISTORY, getNumberOfRunsInHistory())
+ .append(KEY_SHOW_DISABLED_COUNTER, isShowDisabledCounter())
+ .append(KEY_SHOW_WARNINGS_COUNTER, isShowWarningsCounter())
+ .append(KEY_SHOW_INFO_COUNTER, isShowInfoCounter())
+ .append(KEY_SHOW_WARNING_INDICATOR, isShowWarningIndicator())
+ .append(KEY_SHOW_INFO_INDICATOR, isShowInfoIndicator())
+ .append(KEY_SHOW_SUCCESSFUL_TESTS, isShowSuccessfulTests())
+ .append(KEY_SHOW_DISABLED_TESTS, isShowDisabledTests())
+ .append(KEY_SHOW_TEST_DESCRIPTION, isShowTestDescription())
+ .append(KEY_SYNC_DETAIL_TAB, isSyncDetailTab())
+ .append(KEY_SHOW_SUITES, isShowSuites())
+ .append(KEY_TEST_PACKAGE_PREFIX, getTestPackagePrefix())
+ .append(KEY_TEST_PACKAGE_SUFFIX, getTestPackageSuffix())
+ .append(KEY_TEST_UNIT_PREFIX, getTestUnitPrefix())
+ .append(KEY_TEST_UNIT_SUFFIX, getTestUnitSuffix())
+ .append(KEY_NUMBER_OF_TESTS_PER_UNIT, getNumberOfTestsPerUnit())
+ .append(KEY_CHECK_GENERATE_UTPLSQL_TEST, isCheckGenerateUtplsqlTest())
+ .append(KEY_GENERATE_COMMENTS, isGenerateComments())
+ .append(KEY_DISABLE_TESTS, isDisableTests())
+ .append(KEY_SUITE_PATH, getSuitePath())
+ .append(KEY_INDENT_SPACES, getIndentSpaces())
+ .append(KEY_GENERATE_FILES, isGenerateFiles())
+ .append(KEY_OUTPUT_DIRECTORY, getOutputDirectory())
+ .append(KEY_DELETE_EXISTING_FILES, isDeleteExistingFiles())
+ .append(KEY_ROOT_FOLDER_IN_ODDGEN_VIEW, getRootFolderInOddgenView())
+ .toString();
+ }
+
+ public boolean isUseRealtimeReporter() {
+ return getHashStructure().getBoolean(KEY_USE_REALTIME_REPORTER, true);
+ }
+
+ public void setUseRealtimeReporter(final boolean useRealtimeReporter) {
+ getHashStructure().putBoolean(KEY_USE_REALTIME_REPORTER, useRealtimeReporter);
+ }
+
+ public boolean isUnsharedWorksheet() {
+ return getHashStructure().getBoolean(KEY_UNSHARED_WORKSHEET, true);
+ }
+
+ public void setUnsharedWorksheet(final boolean unsharedWorksheet) {
+ getHashStructure().putBoolean(KEY_UNSHARED_WORKSHEET, unsharedWorksheet);
+ }
+
+ public boolean isResetPackage() {
+ return getHashStructure().getBoolean(KEY_RESET_PACKAGE, false);
+ }
+
+ public void setResetPackage(final boolean resetPackage) {
+ getHashStructure().putBoolean(KEY_RESET_PACKAGE, resetPackage);
+ }
+
+ public boolean isClearScreen() {
+ return getHashStructure().getBoolean(KEY_CLEAR_SCREEN, false);
+ }
+
+ public void setClearScreen(final boolean clearScreen) {
+ getHashStructure().putBoolean(KEY_CLEAR_SCREEN, clearScreen);
+ }
+
+ public boolean isAutoExecute() {
+ return getHashStructure().getBoolean(KEY_AUTO_EXECUTE, true);
+ }
+
+ public void setAutoExecute(final boolean autoExecute) {
+ getHashStructure().putBoolean(KEY_AUTO_EXECUTE, autoExecute);
+ }
+
+ public boolean isCheckRunUtplsqlTest() {
+ return getHashStructure().getBoolean(KEY_CHECK_RUN_UTPLSQL_TEST, false);
+ }
+
+ public void setCheckRunUtplsqlTest(final boolean checkRunUtplsqlTest) {
+ getHashStructure().putBoolean(KEY_CHECK_RUN_UTPLSQL_TEST, checkRunUtplsqlTest);
+ }
+
+ public boolean isUseSmartTimes() {
+ return getHashStructure().getBoolean(KEY_USE_SMART_TIMES, false);
+ }
+
+ public void setUseSmartTimes(final boolean useSmartTimes) {
+ getHashStructure().putBoolean(KEY_USE_SMART_TIMES, useSmartTimes);
+ }
+
+ public int getNumberOfRunsInHistory() {
+ return getHashStructure().getInt(KEY_NUMBER_OF_RUNS_IN_HISTORY, 10);
+ }
+
+ public void setNumberOfRunsInHistory(final int runs) {
+ getHashStructure().putInt(KEY_NUMBER_OF_RUNS_IN_HISTORY, runs);
+ }
+
+ public boolean isShowDisabledCounter() {
+ return getHashStructure().getBoolean(KEY_SHOW_DISABLED_COUNTER, true);
+ }
+
+ public void setShowDisabledCounter(final boolean showDisabledCounter) {
+ getHashStructure().putBoolean(KEY_SHOW_DISABLED_COUNTER, showDisabledCounter);
+ }
+
+ public boolean isShowWarningsCounter() {
+ return getHashStructure().getBoolean(KEY_SHOW_WARNINGS_COUNTER, true);
+ }
+
+ public void setShowWarningsCounter(final boolean showWarningCounter) {
+ getHashStructure().putBoolean(KEY_SHOW_WARNINGS_COUNTER, showWarningCounter);
+ }
+
+ public boolean isShowInfoCounter() {
+ return getHashStructure().getBoolean(KEY_SHOW_INFO_COUNTER, true);
+ }
+
+ public void setShowInfoCounter(final boolean showInfoCounter) {
+ getHashStructure().putBoolean(KEY_SHOW_INFO_COUNTER, showInfoCounter);
+ }
+
+ public boolean isShowWarningIndicator() {
+ return getHashStructure().getBoolean(KEY_SHOW_WARNING_INDICATOR, true);
+ }
+
+ public void setShowWarningIndicator(final boolean showWarningIndicator) {
+ getHashStructure().putBoolean(KEY_SHOW_WARNING_INDICATOR, showWarningIndicator);
+ }
+
+ public boolean isShowInfoIndicator() {
+ return getHashStructure().getBoolean(KEY_SHOW_INFO_INDICATOR, true);
+ }
+
+ public void setShowInfoIndicator(final boolean showInfoIndicator) {
+ getHashStructure().putBoolean(KEY_SHOW_INFO_INDICATOR, showInfoIndicator);
+ }
+
+ public boolean isShowSuccessfulTests() {
+ return getHashStructure().getBoolean(KEY_SHOW_SUCCESSFUL_TESTS, true);
+ }
+
+ public void setShowSuccessfulTests(final boolean showSuccessfulTests) {
+ getHashStructure().putBoolean(KEY_SHOW_SUCCESSFUL_TESTS, showSuccessfulTests);
+ }
+
+ public boolean isShowDisabledTests() {
+ return getHashStructure().getBoolean(KEY_SHOW_DISABLED_TESTS, true);
+ }
+
+ public void setShowDisabledTests(final boolean showDisabledTests) {
+ getHashStructure().putBoolean(KEY_SHOW_DISABLED_TESTS, showDisabledTests);
+ }
+
+ public boolean isShowTestDescription() {
+ return getHashStructure().getBoolean(KEY_SHOW_TEST_DESCRIPTION, true);
+ }
+
+ public void setShowTestDescription(final boolean showTestDescription) {
+ getHashStructure().putBoolean(KEY_SHOW_TEST_DESCRIPTION, showTestDescription);
+ }
+
+ public boolean isSyncDetailTab() {
+ return getHashStructure().getBoolean(KEY_SYNC_DETAIL_TAB, true);
+ }
+
+ public void setSyncDetailTab(final boolean syncDetailTab) {
+ getHashStructure().putBoolean(KEY_SYNC_DETAIL_TAB, syncDetailTab);
+ }
+
+ public boolean isShowSuites() {
+ return getHashStructure().getBoolean(KEY_SHOW_SUITES, true);
+ }
+
+ public void setShowSuites(final boolean showSuites) {
+ getHashStructure().putBoolean(KEY_SHOW_SUITES, showSuites);
+ }
+
+ public String getTestPackagePrefix() {
+ return getHashStructure().getString(KEY_TEST_PACKAGE_PREFIX, "test_");
+ }
+
+ public void setTestPackagePrefix(final String testPackagePrefix) {
+ getHashStructure().putString(KEY_TEST_PACKAGE_PREFIX, testPackagePrefix);
+ }
+
+ public String getTestPackageSuffix() {
+ return getHashStructure().getString(KEY_TEST_PACKAGE_SUFFIX, "");
+ }
+
+ public void setTestPackageSuffix(final String testPackageSuffix) {
+ getHashStructure().putString(KEY_TEST_PACKAGE_SUFFIX, testPackageSuffix);
+ }
+
+ public String getTestUnitPrefix() {
+ return getHashStructure().getString(KEY_TEST_UNIT_PREFIX, "");
+ }
+
+ public void setTestUnitPrefix(final String testUnitPrefix) {
+ getHashStructure().putString(KEY_TEST_UNIT_PREFIX, testUnitPrefix);
+ }
+
+ public String getTestUnitSuffix() {
+ return getHashStructure().getString(KEY_TEST_UNIT_SUFFIX, "");
+ }
+
+ public void setTestUnitSuffix(final String testUnitSuffix) {
+ getHashStructure().putString(KEY_TEST_UNIT_SUFFIX, testUnitSuffix);
+ }
+
+ public int getNumberOfTestsPerUnit() {
+ return getHashStructure().getInt(KEY_NUMBER_OF_TESTS_PER_UNIT, 1);
+ }
+
+ public void setNumberOfTestsPerUnit(final int numberOfTestsPerUnit) {
+ getHashStructure().putInt(KEY_NUMBER_OF_TESTS_PER_UNIT, numberOfTestsPerUnit);
+ }
+
+ public boolean isCheckGenerateUtplsqlTest() {
+ return getHashStructure().getBoolean(KEY_CHECK_GENERATE_UTPLSQL_TEST, false);
+ }
+
+ public void setCheckGenerateUtplsqlTest(final boolean checkGenerateUtplsqlTest) {
+ getHashStructure().putBoolean(KEY_CHECK_GENERATE_UTPLSQL_TEST, checkGenerateUtplsqlTest);
+ }
+
+ public boolean isGenerateComments() {
+ return getHashStructure().getBoolean(KEY_GENERATE_COMMENTS, true);
+ }
+
+ public void setGenerateComments(final boolean generateComments) {
+ getHashStructure().putBoolean(KEY_GENERATE_COMMENTS, generateComments);
+ }
+
+ public boolean isDisableTests() {
+ return getHashStructure().getBoolean(KEY_DISABLE_TESTS, false);
+ }
+
+ public void setDisableTests(final boolean disableTests) {
+ getHashStructure().putBoolean(KEY_DISABLE_TESTS, disableTests);
+ }
+
+ public String getSuitePath() {
+ return getHashStructure().getString(KEY_SUITE_PATH, "alltests");
+ }
+
+ public void setSuitePath(final String suitePath) {
+ getHashStructure().putString(KEY_SUITE_PATH, suitePath);
+ }
+
+ public int getIndentSpaces() {
+ return getHashStructure().getInt(KEY_INDENT_SPACES, 3);
+ }
+
+ public void setIndentSpaces(final int indentSpaces) {
+ getHashStructure().putInt(KEY_INDENT_SPACES, indentSpaces);
+ }
+
+ public boolean isGenerateFiles() {
+ return getHashStructure().getBoolean(KEY_GENERATE_FILES, true);
+ }
+
+ public void setGenerateFiles(final boolean generateFiles) {
+ getHashStructure().putBoolean(KEY_GENERATE_FILES, generateFiles);
+ }
+
+ public String getOutputDirectory() {
+ return getHashStructure().getString(KEY_OUTPUT_DIRECTORY, DEFAULT_OUTPUT_DIRECTORY);
+ }
+
+ public void setOutputDirectory(final String outputDirectory) {
+ final String dir = outputDirectory.isEmpty() ? DEFAULT_OUTPUT_DIRECTORY : outputDirectory;
+ getHashStructure().putString(KEY_OUTPUT_DIRECTORY, dir);
+ }
+
+ public boolean isDeleteExistingFiles() {
+ return getHashStructure().getBoolean(KEY_DELETE_EXISTING_FILES, false);
+ }
+
+ public void setDeleteExistingFiles(final boolean deleteExistingFiles) {
+ getHashStructure().putBoolean(KEY_DELETE_EXISTING_FILES, deleteExistingFiles);
+ }
+
+ public String getRootFolderInOddgenView() {
+ return getHashStructure().getString(KEY_ROOT_FOLDER_IN_ODDGEN_VIEW, "utPLSQL");
+ }
+
+ public void setRootFolderInOddgenView(final String rootFolder) {
+ final String folder = rootFolder.isEmpty() ? "utPLSQL" : rootFolder;
+ getHashStructure().putString(KEY_ROOT_FOLDER_IN_ODDGEN_VIEW, folder);
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.xtend
deleted file mode 100644
index a237b917..00000000
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.xtend
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2018 Philipp Salvisberg
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.utplsql.sqldev.model.preference
-
-import oracle.javatools.data.HashStructure
-import oracle.javatools.data.HashStructureAdapter
-import oracle.javatools.data.PropertyStorage
-import org.eclipse.xtext.xbase.lib.util.ToStringBuilder
-
-class PreferenceModel extends HashStructureAdapter {
- static final String DATA_KEY = "utplsql"
-
- private new(HashStructure hash) {
- super(hash)
- }
-
- def static getInstance(PropertyStorage prefs) {
- return new PreferenceModel(findOrCreate(prefs, DATA_KEY))
- }
-
- static final String KEY_UNSHARED_WORKSHEET = "unsharedWorksheet"
- static final String KEY_RESET_PACKAGE = "resetPackage"
- static final String KEY_CLEAR_SCREEN = "clearScreen"
- static final String KEY_AUTO_EXECUTE = "autoExecute"
-
- def isUnsharedWorksheet() {
- return getHashStructure.getBoolean(PreferenceModel.KEY_UNSHARED_WORKSHEET, true)
- }
-
- def setUnsharedWorksheet(boolean unsharedWorksheet) {
- getHashStructure.putBoolean(PreferenceModel.KEY_UNSHARED_WORKSHEET, unsharedWorksheet)
- }
-
- def isResetPackage() {
- return getHashStructure.getBoolean(PreferenceModel.KEY_RESET_PACKAGE, false)
- }
-
- def setResetPackage(boolean resetPackage) {
- getHashStructure.putBoolean(PreferenceModel.KEY_RESET_PACKAGE, resetPackage)
- }
-
- def isClearScreen() {
- return getHashStructure.getBoolean(PreferenceModel.KEY_CLEAR_SCREEN, false)
- }
-
- def setClearScreen(boolean clearScreen) {
- getHashStructure.putBoolean(PreferenceModel.KEY_CLEAR_SCREEN, clearScreen)
- }
-
- def isAutoExecute() {
- return getHashStructure.getBoolean(PreferenceModel.KEY_AUTO_EXECUTE, true)
- }
-
- def setAutoExecute(boolean autoExecute) {
- getHashStructure.putBoolean(PreferenceModel.KEY_AUTO_EXECUTE, autoExecute)
- }
-
- override toString() {
- new ToStringBuilder(this).addAllFields.toString
- }
-}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.java
new file mode 100644
index 00000000..255a52cc
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.runner;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public class Counter {
+ private Integer disabled;
+ private Integer success;
+ private Integer failure;
+ private Integer error;
+ private Integer warning;
+
+ public Counter() {
+ disabled = 0;
+ success = 0;
+ failure = 0;
+ error = 0;
+ warning = 0;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append("disabled", disabled)
+ .append("success", success)
+ .append("failure", failure)
+ .append("error", error)
+ .append("warning", warning)
+ .toString();
+ }
+
+ public Integer getDisabled() {
+ return disabled;
+ }
+
+ public void setDisabled(final Integer disabled) {
+ this.disabled = disabled;
+ }
+
+ public Integer getSuccess() {
+ return success;
+ }
+
+ public void setSuccess(final Integer success) {
+ this.success = success;
+ }
+
+ public Integer getFailure() {
+ return failure;
+ }
+
+ public void setFailure(final Integer failure) {
+ this.failure = failure;
+ }
+
+ public Integer getError() {
+ return error;
+ }
+
+ public void setError(final Integer error) {
+ this.error = error;
+ }
+
+ public Integer getWarning() {
+ return warning;
+ }
+
+ public void setWarning(final Integer warning) {
+ this.warning = warning;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.java
new file mode 100644
index 00000000..2ca29452
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.runner;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public class Expectation {
+ private String description;
+ private String message;
+ private String caller;
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append("description", description)
+ .append("message", message)
+ .append("caller", caller)
+ .append("failureText", getFailureText())
+ .append("shortFailureText", getShortFailureText())
+ .append("callerLine", getCallerLine())
+ .toString();
+ }
+
+ public String getFailureText() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(message.trim());
+ if (caller != null) {
+ sb.append('\n');
+ sb.append(caller.trim());
+ }
+ return sb.toString();
+ }
+
+ public String getShortFailureText() {
+ final StringBuilder sb = new StringBuilder();
+ if (description != null) {
+ sb.append(description);
+ sb.append(" (line ");
+ sb.append(getCallerLine());
+ sb.append(")");
+ } else {
+ sb.append("Line ");
+ sb.append(getCallerLine());
+ }
+ return sb.toString();
+ }
+
+ public Integer getCallerLine() {
+ Integer line = null;
+ if (caller != null) {
+ final Pattern p = Pattern.compile("(?i)\"[^\\\"]+\",\\s+line\\s*([0-9]+)");
+ final Matcher m = p.matcher(caller);
+ if (m.find()) {
+ line = Integer.valueOf(m.group(1));
+ }
+ }
+ return line;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(final String message) {
+ this.message = message;
+ }
+
+ public String getCaller() {
+ return caller;
+ }
+
+ public void setCaller(final String caller) {
+ this.caller = caller;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.java
new file mode 100644
index 00000000..76b859c2
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.runner;
+
+import javax.swing.Icon;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+import org.utplsql.sqldev.resources.UtplsqlResources;
+
+public abstract class Item {
+ private String id;
+ private String name;
+ private String description;
+ private String startTime;
+ private String endTime;
+ private Double executionTime;
+ private Counter counter;
+ private String errorStack;
+ private String serverOutput;
+ private String warnings;
+
+ public Item() {
+ counter = new Counter();
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append("id", id)
+ .append("name", name)
+ .append("description", description)
+ .append("startTime", startTime)
+ .append("endTime", endTime)
+ .append("executionTime", executionTime)
+ .append("counter", counter)
+ .append("errorStack", errorStack)
+ .append("serverOutput", serverOutput)
+ .append("warnings", warnings)
+ .append("parentId", getParentId())
+ .append("statusIcon", getStatusIcon())
+ .append("warningIcon", getWarningIcon())
+ .append("infoIcon", getInfoIcon())
+ .toString();
+ }
+
+ public String getParentId() {
+ // Works only if id (suitepath) is build based on names delimited with a period
+ // that's expected for real utPLSQL runs, but may fail for artificial runs.
+ // Returning null is valid, it means this item has no parent and as a
+ // consequence it will be shown on the top level in the runner.
+ // A key is required to identify an item since suites can be delivered
+ // multiple times, e.g. when running a chosen list of tests. This way
+ // the tests will shown at the right position in the tree, regardless of the call
+ // parameters.
+ if (name != null && id != null && name.length() < id.length() && id.endsWith(name)) {
+ return id.substring(0, id.length() - name.length() - 1);
+ }
+ return null;
+ }
+
+ public Icon getStatusIcon() {
+ Icon icon = null;
+ if (getStartTime() != null && getEndTime() == null) {
+ icon = UtplsqlResources.getIcon("PROGRESS_ICON");
+ } else {
+ if (getCounter() != null) {
+ // Escalation logic as for the color of the progress bar.
+ // A suite with errors or failed tests cannot be considered successful,
+ // even if some tests completed successfully.
+ if (getCounter().getError() > 0) {
+ icon = UtplsqlResources.getIcon("ERROR_ICON");
+ } else if (getCounter().getFailure() > 0) {
+ icon = UtplsqlResources.getIcon("FAILURE_ICON");
+ } else if (getCounter().getSuccess() > 0) {
+ icon = UtplsqlResources.getIcon("SUCCESS_ICON");
+ } else if (getCounter().getDisabled() > 0) {
+ icon = UtplsqlResources.getIcon("DISABLED_ICON");
+ }
+ }
+ }
+ return icon;
+ }
+
+ public Icon getWarningIcon() {
+ Icon icon = null;
+ if (getCounter() != null && getCounter().getWarning() > 0) {
+ icon = UtplsqlResources.getIcon("WARNING_ICON");
+ }
+ return icon;
+ }
+
+ public Icon getInfoIcon() {
+ Icon icon = null;
+ if (getServerOutput() != null && getServerOutput().length() > 0) {
+ icon = UtplsqlResources.getIcon("INFO_ICON");
+ }
+ return icon;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(final String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ public String getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(final String startTime) {
+ this.startTime = startTime;
+ }
+
+ public String getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(final String endTime) {
+ this.endTime = endTime;
+ }
+
+ public Double getExecutionTime() {
+ return executionTime;
+ }
+
+ public void setExecutionTime(final Double executionTime) {
+ this.executionTime = executionTime;
+ }
+
+ public Counter getCounter() {
+ return counter;
+ }
+
+ public void setCounter(final Counter counter) {
+ this.counter = counter;
+ }
+
+ public String getErrorStack() {
+ return errorStack;
+ }
+
+ public void setErrorStack(final String errorStack) {
+ this.errorStack = errorStack;
+ }
+
+ public String getServerOutput() {
+ return serverOutput;
+ }
+
+ public void setServerOutput(final String serverOutput) {
+ this.serverOutput = serverOutput;
+ }
+
+ public String getWarnings() {
+ return warnings;
+ }
+
+ public void setWarnings(final String warnings) {
+ this.warnings = warnings;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/ItemNode.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/ItemNode.java
new file mode 100644
index 00000000..591e1b3f
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/ItemNode.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2021 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.runner;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.Icon;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import org.utplsql.sqldev.resources.UtplsqlResources;
+
+public class ItemNode extends DefaultMutableTreeNode implements Comparable {
+
+ private static final long serialVersionUID = -4053143673822661743L;
+
+ public ItemNode(Item userObject) {
+ super(userObject, userObject instanceof Suite);
+ }
+
+ @Override
+ public int compareTo(ItemNode other) {
+ return getId().compareTo(other.getId());
+ }
+
+ public String getId() {
+ return ((Item) getUserObject()).getId();
+ }
+
+ public String getName() {
+ return ((Item) getUserObject()).getName();
+ }
+
+ public String getDescription() {
+ return ((Item) getUserObject()).getDescription();
+ }
+
+ public Double getExecutionTime() {
+ return ((Item) getUserObject()).getExecutionTime();
+ }
+
+ public Set getTestPackages() {
+ HashSet testPackages = new HashSet<>();
+ Enumeration> orderedNodes = preorderEnumeration();
+ while (orderedNodes.hasMoreElements()) {
+ ItemNode node = (ItemNode) orderedNodes.nextElement();
+ if (node.getUserObject() instanceof Test) {
+ Test test = (Test) node.getUserObject();
+ testPackages.add(test.getOwnerName() + "." + test.getObjectName());
+ }
+ }
+ return testPackages;
+ }
+
+ public Set getOwners() {
+ HashSet owners = new HashSet<>();
+ Enumeration> children = children();
+ while (children.hasMoreElements()) {
+ ItemNode child = (ItemNode) children.nextElement();
+ owners.add(child.getOwnerName());
+ }
+ return owners;
+ }
+
+ public String getOwnerName() {
+ String ownerName = null;
+ Enumeration> orderedNodes = preorderEnumeration();
+ while (orderedNodes.hasMoreElements()) {
+ ItemNode node = (ItemNode) orderedNodes.nextElement();
+ if (node.getUserObject() instanceof Test) {
+ Test test = (Test) node.getUserObject();
+ if (ownerName == null) {
+ ownerName = test.getOwnerName();
+ } else if (!ownerName.equals(test.getOwnerName())) {
+ ownerName = "***";
+ break;
+ }
+ }
+ }
+ return ownerName;
+ }
+
+ public String getPackageName() {
+ String packageName = null;
+ Enumeration> orderedNodes = preorderEnumeration();
+ while (orderedNodes.hasMoreElements()) {
+ ItemNode node = (ItemNode) orderedNodes.nextElement();
+ if (node.getUserObject() instanceof Test) {
+ Test test = (Test) node.getUserObject();
+ if (packageName == null) {
+ packageName = test.getObjectName();
+ } else if (!packageName.equals(test.getObjectName())) {
+ packageName = "***";
+ break;
+ }
+ }
+ }
+ return packageName;
+ }
+
+ public String getProcedureName() {
+ String procedureName = null;
+ Enumeration> orderedNodes = preorderEnumeration();
+ while (orderedNodes.hasMoreElements()) {
+ ItemNode node = (ItemNode) orderedNodes.nextElement();
+ if (node.getUserObject() instanceof Test) {
+ Test test = (Test) node.getUserObject();
+ if (procedureName == null) {
+ procedureName = test.getProcedureName();
+ } else if (!procedureName.equals(test.getProcedureName())) {
+ procedureName = "***";
+ break;
+ }
+ }
+ }
+ return procedureName;
+ }
+
+ public Icon getStatusIcon() {
+ Item item = (Item) getUserObject();
+ Icon icon = item.getStatusIcon();
+ if (icon == null) {
+ if (item.getId() != null) {
+ if (item instanceof Test) {
+ icon = UtplsqlResources.getIcon("PROCEDURE_ICON");
+ } else if (item.getId().contains("context_#")) {
+ icon = UtplsqlResources.getIcon("PROCEDURE_FOLDER_ICON");
+ } else {
+ if (item.getName().equals(getPackageName())) {
+ icon = UtplsqlResources.getIcon("PACKAGE_ICON");
+ } else {
+ icon = UtplsqlResources.getIcon("PACKAGE_FOLDER_ICON");
+ }
+ }
+ }
+ }
+ return icon;
+ }
+
+ public Icon getWarningIcon() {
+ return ((Item) getUserObject()).getWarningIcon();
+ }
+
+ public Icon getInfoIcon() {
+ return ((Item) getUserObject()).getInfoIcon();
+ }
+
+ /**
+ * Calculates non-overlapping items.
+ *
+ * This can be used to build a list of suites to be started by utPLSQL while ensuring that
+ *
+ * - all requested tests are executed, but not more than once
+ * - the test execution is efficient by ensuring that the list is as short as possible
+ *
+ * This means if all tests of a suite shall be executed that the suit should be
+ * part of the result list and not all of its tests.
+ *
+ * In other words, top-level nodes are preferred to produce an optimal result.
+ *
+ * @param selectedNodes all selected nodes must be part of the same tree
+ * @return non-overlapping set of nodes
+ */
+ public static Set createNonOverlappingSet(List selectedNodes) {
+ HashSet result = new HashSet<>();
+ if (selectedNodes != null && selectedNodes.size() > 0) {
+ HashSet expandedResult = new HashSet<>();
+ List sortedNodes = new ArrayList<>(selectedNodes);
+ Collections.sort(sortedNodes);
+ for (ItemNode sortedNode : sortedNodes) {
+ if (!expandedResult.contains(sortedNode)) {
+ result.add(sortedNode);
+ Enumeration> expandedNodes = sortedNode.preorderEnumeration();
+ while (expandedNodes.hasMoreElements()) {
+ ItemNode expandedNode = (ItemNode) expandedNodes.nextElement();
+ expandedResult.add(expandedNode);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.java
new file mode 100644
index 00000000..73b4599a
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.runner;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public abstract class PostEvent extends RealtimeReporterEvent {
+ private String startTime;
+ private String endTime;
+ private Double executionTime;
+ private Counter counter;
+ private String errorStack;
+ private String serverOutput;
+ private String warnings;
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append("startTime", startTime)
+ .append("endTime", endTime)
+ .append("executionTime", executionTime)
+ .append("counter", counter)
+ .append("errorStack", errorStack)
+ .append("serverOutput", serverOutput)
+ .append("warnings", warnings)
+ .toString();
+ }
+
+ public PostEvent() {
+ counter = new Counter();
+ }
+
+ public String getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(final String startTime) {
+ this.startTime = startTime;
+ }
+
+ public String getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(final String endTime) {
+ this.endTime = endTime;
+ }
+
+ public Double getExecutionTime() {
+ return executionTime;
+ }
+
+ public void setExecutionTime(final Double executionTime) {
+ this.executionTime = executionTime;
+ }
+
+ public Counter getCounter() {
+ return counter;
+ }
+
+ public void setCounter(final Counter counter) {
+ this.counter = counter;
+ }
+
+ public String getErrorStack() {
+ return errorStack;
+ }
+
+ public void setErrorStack(final String errorStack) {
+ this.errorStack = errorStack;
+ }
+
+ public String getServerOutput() {
+ return serverOutput;
+ }
+
+ public void setServerOutput(final String serverOutput) {
+ this.serverOutput = serverOutput;
+ }
+
+ public String getWarnings() {
+ return warnings;
+ }
+
+ public void setWarnings(final String warnings) {
+ this.warnings = warnings;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.java
new file mode 100644
index 00000000..0a167432
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.runner;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public class PostRunEvent extends PostEvent {
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ // ancestor
+ .append("startTime", getStartTime())
+ .append("endTime", getEndTime())
+ .append("executionTime", getExecutionTime())
+ .append("counter", getCounter())
+ .append("errorStack", getErrorStack())
+ .append("serverOutput", getServerOutput())
+ .append("warnings", getWarnings())
+ .toString();
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.java
new file mode 100644
index 00000000..94671c33
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.runner;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public class PostSuiteEvent extends PostEvent {
+ private String id;
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ // ancestor
+ .append("startTime", getStartTime())
+ .append("endTime", getEndTime())
+ .append("executionTime", getExecutionTime())
+ .append("counter", getCounter())
+ .append("errorStack", getErrorStack())
+ .append("serverOutput", getServerOutput())
+ .append("warnings", getWarnings())
+ // local
+ .append("id", id)
+ .toString();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(final String id) {
+ this.id = id;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.java
new file mode 100644
index 00000000..36b99abd
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.runner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public class PostTestEvent extends PostEvent {
+ private String id;
+ private Integer testNumber;
+ private Integer totalNumberOfTests;
+ private List failedExpectations;
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ // ancestor
+ .append("startTime", getStartTime())
+ .append("endTime", getEndTime())
+ .append("executionTime", getExecutionTime())
+ .append("counter", getCounter())
+ .append("errorStack", getErrorStack())
+ .append("serverOutput", getServerOutput())
+ .append("warnings", getWarnings())
+ // local
+ .append("id", id)
+ .append("testNumber", testNumber)
+ .append("totalNumberOfTests", totalNumberOfTests)
+ .append("failedExpectations", failedExpectations)
+ .toString();
+ }
+
+ public PostTestEvent() {
+ failedExpectations = new ArrayList<>();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(final String id) {
+ this.id = id;
+ }
+
+ public Integer getTestNumber() {
+ return testNumber;
+ }
+
+ public void setTestNumber(final Integer testNumber) {
+ this.testNumber = testNumber;
+ }
+
+ public Integer getTotalNumberOfTests() {
+ return totalNumberOfTests;
+ }
+
+ public void setTotalNumberOfTests(final Integer totalNumberOfTests) {
+ this.totalNumberOfTests = totalNumberOfTests;
+ }
+
+ public List getFailedExpectations() {
+ return failedExpectations;
+ }
+
+ public void setFailedExpectations(final List failedExpectations) {
+ this.failedExpectations = failedExpectations;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.java
new file mode 100644
index 00000000..3e150fec
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.runner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public class PreRunEvent extends RealtimeReporterEvent {
+ private List- items;
+ private Integer totalNumberOfTests;
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append("items", items)
+ .append("totalNumberOfTests", totalNumberOfTests)
+ .toString();
+ }
+
+ public PreRunEvent() {
+ items = new ArrayList<>();
+ }
+
+ public List
- getItems() {
+ return items;
+ }
+
+ public void setItems(final List
- items) {
+ this.items = items;
+ }
+
+ public Integer getTotalNumberOfTests() {
+ return totalNumberOfTests;
+ }
+
+ public void setTotalNumberOfTests(final Integer totalNumberOfTests) {
+ this.totalNumberOfTests = totalNumberOfTests;
+ }
+}
diff --git a/sqldev/src/test/java/org/utplsql/sqldev/tests/PreferenceModelTest.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.java
similarity index 54%
rename from sqldev/src/test/java/org/utplsql/sqldev/tests/PreferenceModelTest.xtend
rename to sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.java
index 2683a644..7026952a 100644
--- a/sqldev/src/test/java/org/utplsql/sqldev/tests/PreferenceModelTest.xtend
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.java
@@ -13,20 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.utplsql.sqldev.tests
+package org.utplsql.sqldev.model.runner;
-import org.junit.Assert
-import org.junit.Test
-import org.utplsql.sqldev.model.preference.PreferenceModel
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
-class PreferenceModelTest {
+public class PreSuiteEvent extends RealtimeReporterEvent {
+ private String id;
- @Test
- def testDefaultValues() {
- val PreferenceModel model = PreferenceModel.getInstance(null)
- Assert.assertTrue(model.unsharedWorksheet)
- Assert.assertFalse(model.resetPackage)
- Assert.assertFalse(model.clearScreen)
- Assert.assertTrue(model.autoExecute)
- }
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append("id", id)
+ .toString();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(final String id) {
+ this.id = id;
+ }
}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.java
new file mode 100644
index 00000000..30019335
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.runner;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public class PreTestEvent extends RealtimeReporterEvent {
+ private String id;
+ private Integer testNumber;
+ private Integer totalNumberOfTests;
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append("id", id)
+ .append("testNumber", testNumber)
+ .append("totalNumberOfTests", totalNumberOfTests)
+ .toString();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(final String id) {
+ this.id = id;
+ }
+
+ public Integer getTestNumber() {
+ return testNumber;
+ }
+
+ public void setTestNumber(final Integer testNumber) {
+ this.testNumber = testNumber;
+ }
+
+ public Integer getTotalNumberOfTests() {
+ return totalNumberOfTests;
+ }
+
+ public void setTotalNumberOfTests(final Integer totalNumberOfTests) {
+ this.totalNumberOfTests = totalNumberOfTests;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/AbstractModel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RealtimeReporterEvent.java
similarity index 77%
rename from sqldev/src/main/java/org/utplsql/sqldev/model/AbstractModel.xtend
rename to sqldev/src/main/java/org/utplsql/sqldev/model/runner/RealtimeReporterEvent.java
index 6a921ff1..3f7d7d1d 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/AbstractModel.xtend
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RealtimeReporterEvent.java
@@ -13,12 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.utplsql.sqldev.model
+package org.utplsql.sqldev.model.runner;
-import org.eclipse.xtext.xbase.lib.util.ToStringBuilder
-
-class AbstractModel {
- override toString() {
- new ToStringBuilder(this).addAllFields.toString
- }
+public abstract class RealtimeReporterEvent {
}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.java
new file mode 100644
index 00000000..09d38e2f
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2019 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.runner;
+
+import java.sql.Connection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+import org.utplsql.sqldev.model.URLTools;
+
+public class Run {
+ private String reporterId;
+ private String connectionName;
+ private List pathList;
+ private Integer currentTestNumber;
+ private Test currentTest;
+ private Integer totalNumberOfTests;
+ private String startTime;
+ private String endTime;
+ private Double executionTime;
+ private Counter counter;
+ private Integer infoCount;
+ private String errorStack;
+ private String serverOutput;
+ private final Set
- items;
+ private Map tests;
+ private Map itemNodes;
+ private String status;
+ private Long start;
+ // to abort connections, producerConn is handled by UtplsqlRunner
+ private Connection consumerConn;
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append("reporterId", reporterId)
+ .append("connectionName", connectionName)
+ .append("pathList", pathList)
+ .append("currentTestNumber", currentTestNumber)
+ .append("currentTest", currentTest)
+ .append("totalNumberOfTests", totalNumberOfTests)
+ .append("startTime", startTime)
+ .append("endTime", endTime)
+ .append("executionTime", executionTime)
+ .append("counter", counter)
+ .append("infoCount", infoCount)
+ .append("errorStack", errorStack)
+ .append("serverOutput", serverOutput)
+ .append("tests", tests)
+ .append("rootNode", itemNodes.get(reporterId))
+ .append("status", status)
+ .append("start", start)
+ .append("endTime", endTime)
+ .append("totalNumberOfCompletedTests", getTotalNumberOfCompletedTests())
+ .toString();
+ }
+
+ public Run(final String reporterId, final String connectionName, final List pathList) {
+ this.reporterId = reporterId;
+ this.connectionName = connectionName;
+ this.pathList = pathList;
+ counter = new Counter();
+ items = new LinkedHashSet<>();
+ tests = new LinkedHashMap<>();
+ itemNodes = new LinkedHashMap<>();
+ createRootNode();
+ }
+
+ public void setStartTime(final String startTime) {
+ this.startTime = startTime;
+ start = System.currentTimeMillis();
+ }
+
+ public String getName() {
+ final String time = startTime.substring(11, 19);
+ final String conn = connectionName != null ? URLTools.replaceHexChars(connectionName.substring(15)) : "n/a";
+ return time + " (" + conn + ")";
+ }
+
+ /**
+ * Is called after consuming the pre-run event to populate all items of a run.
+ * It's expected to be called only once.
+ *
+ * @param items items of a run, to be shown in the runner right after starting a run.
+ */
+ public void put(final List
- items) {
+ populateItems(items);
+ populateItemNodes();
+ populateItemNodeChildren();
+ }
+
+ private void createRootNode() {
+ // Create pseudo root node as suite.
+ // The TreeTableModel requires a single root node, but it will not be displayed.
+ final Suite rootSuite = new Suite();
+ rootSuite.setId(getReporterId());
+ rootSuite.setName(getReporterId());
+ ItemNode rootNode = new ItemNode(rootSuite);
+ itemNodes.put(rootSuite.getId(), rootNode);
+ }
+
+ private void populateItems(List
- items) {
+ for (final Item item : items) {
+ this.items.add(item);
+ if (item instanceof Suite) {
+ populateItems(((Suite) item).getItems());
+ } else if (item instanceof Test) {
+ this.tests.put(item.getId(), (Test) item);
+ }
+ }
+ }
+
+ private void populateItemNodes() {
+ for (final Item item : items) {
+ itemNodes.put(item.getId(), new ItemNode(item));
+ }
+ }
+
+ private void populateItemNodeChildren() {
+ for (Item item : items) {
+ String parentId = item.getParentId();
+ ItemNode node = itemNodes.get(item.getId());
+ ItemNode parent = itemNodes.get(parentId == null ? reporterId : parentId);
+ parent.add(node);
+ }
+ }
+
+ public Test getTest(final String id) {
+ return tests.get(id);
+ }
+
+ public int getTotalNumberOfCompletedTests() {
+ if (counter.getDisabled() == null || counter.getSuccess() == null || counter.getFailure() == null
+ || counter.getError() == null) {
+ return -1;
+ }
+ int total = counter.getDisabled() + counter.getSuccess() + counter.getFailure() + counter.getError();
+ if (totalNumberOfTests != null && total > totalNumberOfTests) {
+ // can happen when run is cancelled and two processes are updating the run in parallel
+ // not worth to ensure consistency for this case, using synchronized will not be enough
+ total = totalNumberOfTests;
+ }
+ return total;
+ }
+
+ public String getReporterId() {
+ return reporterId;
+ }
+
+ public void setReporterId(final String reporterId) {
+ this.reporterId = reporterId;
+ }
+
+ public String getConnectionName() {
+ return connectionName;
+ }
+
+ public void setConnectionName(final String connectionName) {
+ this.connectionName = connectionName;
+ }
+
+ public List getPathList() {
+ return pathList;
+ }
+
+ public void setPathList(final List pathList) {
+ this.pathList = pathList;
+ }
+
+ public Integer getCurrentTestNumber() {
+ return currentTestNumber;
+ }
+
+ public void setCurrentTestNumber(final Integer currentTestNumber) {
+ this.currentTestNumber = currentTestNumber;
+ }
+
+ public Test getCurrentTest() {
+ return currentTest;
+ }
+
+ public void setCurrentTest(final Test currentTest) {
+ this.currentTest = currentTest;
+ }
+
+ public Integer getTotalNumberOfTests() {
+ return totalNumberOfTests;
+ }
+
+ public void setTotalNumberOfTests(final Integer totalNumberOfTests) {
+ this.totalNumberOfTests = totalNumberOfTests;
+ }
+
+ public String getStartTime() {
+ return startTime;
+ }
+
+ public String getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(final String endTime) {
+ this.endTime = endTime;
+ }
+
+ public Double getExecutionTime() {
+ return executionTime;
+ }
+
+ public void setExecutionTime(final Double executionTime) {
+ this.executionTime = executionTime;
+ }
+
+ public Counter getCounter() {
+ return counter;
+ }
+
+ public void setCounter(final Counter counter) {
+ this.counter = counter;
+ }
+
+ public Integer getInfoCount() {
+ return infoCount;
+ }
+
+ public void setInfoCount(final Integer infoCount) {
+ this.infoCount = infoCount;
+ }
+
+ public String getErrorStack() {
+ return errorStack;
+ }
+
+ public void setErrorStack(final String errorStack) {
+ this.errorStack = errorStack;
+ }
+
+ public String getServerOutput() {
+ return serverOutput;
+ }
+
+ public void setServerOutput(final String serverOutput) {
+ this.serverOutput = serverOutput;
+ }
+
+ public LinkedHashMap getTests() {
+ return (LinkedHashMap) tests;
+ }
+
+ public void setTests(final LinkedHashMap tests) {
+ this.tests = tests;
+ }
+
+ public LinkedHashMap getItemNodes() {
+ return (LinkedHashMap) itemNodes;
+ }
+
+ public void setItemNodes(LinkedHashMap itemNodes) {
+ this.itemNodes = itemNodes;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(final String status) {
+ this.status = status;
+ }
+
+ public Long getStart() {
+ return start;
+ }
+
+ public void setStart(final Long start) {
+ this.start = start;
+ }
+
+ public Connection getConsumerConn() {
+ return consumerConn;
+ }
+
+ public void setConsumerConn(Connection consumerConn) {
+ this.consumerConn = consumerConn;
+ }
+
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RunnerModel.mgc b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RunnerModel.mgc
new file mode 100644
index 00000000..e4ec1ec8
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RunnerModel.mgc
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.java
new file mode 100644
index 00000000..f9f036db
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.runner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public class Suite extends Item {
+ private List
- items;
+
+ public Suite() {
+ items = new ArrayList<>();
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ // ancestor
+ .append("id", getId())
+ .append("name", getName())
+ .append("description", getDescription())
+ .append("startTime", getStartTime())
+ .append("endTime", getEndTime())
+ .append("executionTime", getExecutionTime())
+ .append("counter", getCounter())
+ .append("errorStack", getErrorStack())
+ .append("serverOutput", getServerOutput())
+ .append("warnings", getWarnings())
+ .append("parentId", getParentId())
+ .append("statusIcon", getStatusIcon())
+ .append("warningIcon", getWarningIcon())
+ .append("infoIcon", getInfoIcon())
+ // local
+ .append("items", items)
+ .toString();
+ }
+
+ public List
- getItems() {
+ return items;
+ }
+
+ public void setItems(final List
- items) {
+ this.items = items;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.java
new file mode 100644
index 00000000..a0e893f8
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.runner;
+
+import java.util.List;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public class Test extends Item {
+ private String executableType;
+ private String ownerName;
+ private String objectName;
+ private String procedureName;
+ private Boolean disabled;
+ private String disabledReason;
+ private Integer testNumber;
+ private List failedExpectations;
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ // ancestor
+ .append("id", getId())
+ .append("name", getName())
+ .append("description", getDescription())
+ .append("startTime", getStartTime())
+ .append("endTime", getEndTime())
+ .append("executionTime", getExecutionTime())
+ .append("counter", getCounter())
+ .append("errorStack", getErrorStack())
+ .append("serverOutput", getServerOutput())
+ .append("warnings", getWarnings())
+ .append("parentId", getParentId())
+ .append("statusIcon", getStatusIcon())
+ .append("warningIcon", getWarningIcon())
+ .append("infoIcon", getInfoIcon())
+ // local
+ .append("executableType", executableType)
+ .append("ownerName", ownerName)
+ .append("objectName", objectName)
+ .append("procedureName", procedureName)
+ .append("disabled", disabled)
+ .append("disabledReason", disabledReason)
+ .append("testNumber", testNumber)
+ .append("failedExpectations", failedExpectations)
+ .toString();
+ }
+
+ public String getExecutableType() {
+ return executableType;
+ }
+
+ public void setExecutableType(final String executableType) {
+ this.executableType = executableType;
+ }
+
+ public String getOwnerName() {
+ return ownerName;
+ }
+
+ public void setOwnerName(final String ownerName) {
+ this.ownerName = ownerName;
+ }
+
+ public String getObjectName() {
+ return objectName;
+ }
+
+ public void setObjectName(final String objectName) {
+ this.objectName = objectName;
+ }
+
+ public String getProcedureName() {
+ return procedureName;
+ }
+
+ public void setProcedureName(final String procedureName) {
+ this.procedureName = procedureName;
+ }
+
+ public Boolean isDisabled() {
+ return disabled;
+ }
+
+ public void setDisabled(final Boolean disabled) {
+ this.disabled = disabled;
+ }
+
+ public String getDisabledReason() {
+ return disabledReason;
+ }
+
+ public void setDisabledReason(final String disabledReason) {
+ this.disabledReason = disabledReason;
+ }
+
+ public Integer getTestNumber() {
+ return testNumber;
+ }
+
+ public void setTestNumber(final Integer testNumber) {
+ this.testNumber = testNumber;
+ }
+
+ public List getFailedExpectations() {
+ return failedExpectations;
+ }
+
+ public void setFailedExpectations(final List failedExpectations) {
+ this.failedExpectations = failedExpectations;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/ut/Annotation.java b/sqldev/src/main/java/org/utplsql/sqldev/model/ut/Annotation.java
new file mode 100644
index 00000000..47e792c7
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/ut/Annotation.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.ut;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public class Annotation {
+ private String objectOwner;
+ private String objectName;
+ private String name;
+ private String text;
+ private String subobjectName;
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append("objectOwner", objectOwner)
+ .append("objectName", objectName)
+ .append("name", name)
+ .append("text", text)
+ .append("subobjectName", subobjectName)
+ .toString();
+ }
+
+ public String getObjectOwner() {
+ return objectOwner;
+ }
+
+ public void setObjectOwner(final String objectOwner) {
+ this.objectOwner = objectOwner;
+ }
+
+ public String getObjectName() {
+ return objectName;
+ }
+
+ public void setObjectName(final String objectName) {
+ this.objectName = objectName;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(final String text) {
+ this.text = text;
+ }
+
+ public String getSubobjectName() {
+ return subobjectName;
+ }
+
+ public void setSubobjectName(final String subobjectName) {
+ this.subobjectName = subobjectName;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/ut/OutputLines.java b/sqldev/src/main/java/org/utplsql/sqldev/model/ut/OutputLines.java
new file mode 100644
index 00000000..75343d6b
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/ut/OutputLines.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.model.ut;
+
+import org.springframework.core.style.ToStringCreator;
+import org.utplsql.sqldev.model.JsonToStringStyler;
+
+public class OutputLines {
+ private String[] lines;
+ private Integer numlines;
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
+ .append("lines", lines)
+ .append("numlines", numlines)
+ .toString();
+ }
+
+ public String[] getLines() {
+ return lines;
+ }
+
+ public void setLines(final String[] lines) {
+ this.lines = lines;
+ }
+
+ public Integer getNumlines() {
+ return numlines;
+ }
+
+ public void setNumlines(final Integer numlines) {
+ this.numlines = numlines;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/RunGenerator.java b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/RunGenerator.java
new file mode 100644
index 00000000..f88331ba
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/RunGenerator.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.oddgen;
+
+import java.sql.Connection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.oddgen.sqldev.generators.OddgenGenerator2;
+import org.oddgen.sqldev.generators.model.Node;
+import org.utplsql.sqldev.dal.UtplsqlDao;
+import org.utplsql.sqldev.model.DatabaseTools;
+import org.utplsql.sqldev.model.StringTools;
+import org.utplsql.sqldev.model.preference.PreferenceModel;
+import org.utplsql.sqldev.resources.UtplsqlResources;
+
+import oracle.ide.config.Preferences;
+
+public class RunGenerator implements OddgenGenerator2 {
+ public static final String YES = "Yes";
+ public static final String NO = "No";
+ public static final String RESET_PACKAGE = UtplsqlResources.getString("PREF_RESET_PACKAGE_LABEL");
+ public static final String CLEAR_SCREEN = UtplsqlResources.getString("PREF_CLEAR_SCREEN_LABEL");
+ public static final String INDENT_SPACES = UtplsqlResources.getString("PREF_INDENT_SPACES_LABEL");
+
+ // oddgen node cache
+ private List runnables = null;
+
+ @Override
+ public boolean isSupported(final Connection conn) {
+ return DatabaseTools.isSupported(conn);
+ }
+
+ @Override
+ public String getName(final Connection conn) {
+ return "Run test";
+ }
+
+ @Override
+ public String getDescription(final Connection conn) {
+ return "Runs utPLSQL test packages in the current user.";
+ }
+
+ @Override
+ public List getFolders(final Connection conn) {
+ final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences());
+ final ArrayList folders = new ArrayList<>();
+ for (String f : preferences.getRootFolderInOddgenView().split(",")) {
+ if (f != null) {
+ folders.add(f.trim());
+ }
+ }
+ return folders;
+ }
+
+ @Override
+ public String getHelp(final Connection conn) {
+ return "
not yet available
";
+ }
+
+ @Override
+ public List getNodes(final Connection conn, final String parentNodeId) {
+ // oddgen asks for children for each parent node, regardless of load strategy (eager/lazy)
+ // oddgen does not know about the load strategy, hence caching is the responsibility of the generator
+ if (runnables == null) {
+ final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences());
+ final LinkedHashMap params = new LinkedHashMap<>();
+ params.put(RESET_PACKAGE, preferences.isResetPackage() ? YES : NO);
+ params.put(CLEAR_SCREEN, preferences.isClearScreen() ? YES : NO);
+ params.put(INDENT_SPACES, String.valueOf(preferences.getIndentSpaces()));
+ final UtplsqlDao dao = new UtplsqlDao(conn);
+ // load node tree eagerly (all nodes in one go)
+ runnables = dao.runnables();
+ for (final Node node : runnables) {
+ node.setParams(params);
+ }
+ }
+ return runnables;
+ }
+
+ @Override
+ public HashMap> getLov(final Connection conn, final LinkedHashMap params, final List nodes) {
+ final HashMap> lov = new HashMap<>();
+ lov.put(RESET_PACKAGE, Arrays.asList(YES, NO));
+ lov.put(CLEAR_SCREEN, Arrays.asList(YES, NO));
+ lov.put(INDENT_SPACES, Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8"));
+ return lov;
+ }
+
+ @Override
+ public HashMap getParamStates(final Connection conn, final LinkedHashMap params, final List nodes) {
+ return new HashMap<>();
+ }
+
+ private String getPath(final Node node, final Connection conn) {
+ if ("SUITE".equals(node.getId()) || "SUITEPATH".equals(node.getId())) {
+ return DatabaseTools.getUser(conn);
+ } else {
+ return node.getId();
+ }
+ }
+
+ public ArrayList dedup(final List nodes) {
+ final HashSet set = new HashSet<>();
+ for (final Node node : nodes) {
+ set.add(node.getId());
+ }
+ final ArrayList ret = new ArrayList<>();
+ for (final Node node : nodes) {
+ if (!set.contains(node.getParentId())) {
+ ret.add(node);
+ }
+ }
+ return ret;
+ }
+
+ @Override
+ public String generateProlog(final Connection conn, final List nodes) {
+ final ArrayList dedupNodes = dedup(nodes);
+ final LinkedHashMap params = dedupNodes.get(0).getParams();
+ final StringBuilder sb = new StringBuilder();
+ if (YES.equals(params.get(RESET_PACKAGE))) {
+ sb.append("execute dbms_session.reset_package;\n");
+ }
+ sb.append("set serveroutput on size unlimited\n");
+ if (YES.equals(params.get(CLEAR_SCREEN))) {
+ sb.append("clear screen\n");
+ }
+ if (dedupNodes.size() == 1) {
+ sb.append("execute ut.run('");
+ sb.append(getPath(dedupNodes.get(0), conn));
+ sb.append("');\n");
+ } else {
+ final List paths = dedupNodes.stream().map(node -> getPath(node, conn)).collect(Collectors.toList());
+ sb.append("begin\n");
+ sb.append("\tut.run(\n");
+ sb.append("\t\tut_varchar2_list(\n");
+ sb.append(StringTools.getCSV(paths, "\t\t\t"));
+ sb.append("\t\t)\n");
+ sb.append("\t);\n");
+ sb.append("end;\n");
+ sb.append("/\n");
+ }
+ final String ret = sb.toString();
+ return StringTools.replaceTabsWithSpaces(ret, Integer.parseInt(params.get(INDENT_SPACES)));
+ }
+
+ @Override
+ public String generateSeparator(final Connection conn) {
+ return "";
+ }
+
+ @Override
+ public String generateEpilog(final Connection conn, final List nodes) {
+ return "";
+ }
+
+ @Override
+ public String generate(final Connection conn, final Node node) {
+ return "";
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.java b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.java
new file mode 100644
index 00000000..f4602ea6
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.oddgen;
+
+import java.io.File;
+import java.sql.Connection;
+import java.util.*;
+import java.util.logging.Logger;
+
+import org.oddgen.sqldev.generators.OddgenGenerator2;
+import org.oddgen.sqldev.generators.model.Node;
+import org.oddgen.sqldev.generators.model.NodeTools;
+import org.oddgen.sqldev.plugin.templates.TemplateTools;
+import org.utplsql.sqldev.dal.UtplsqlDao;
+import org.utplsql.sqldev.model.DatabaseTools;
+import org.utplsql.sqldev.model.oddgen.GenContext;
+import org.utplsql.sqldev.model.preference.PreferenceModel;
+import org.utplsql.sqldev.resources.UtplsqlResources;
+
+import oracle.ide.config.Preferences;
+
+public class TestGenerator implements OddgenGenerator2 {
+ private static final Logger logger = Logger.getLogger(TestGenerator.class.getName());
+
+ public static final String YES = "Yes";
+ public static final String NO = "No";
+
+ public static final String GENERATE_FILES = UtplsqlResources.getString("PREF_GENERATE_FILES_LABEL");
+ public static final String OUTPUT_DIRECTORY = UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_LABEL");
+ public static final String DELETE_EXISTING_FILES = UtplsqlResources.getString("PREF_DELETE_EXISTING_FILES_LABEL");
+ public static final String TEST_PACKAGE_PREFIX = UtplsqlResources.getString("PREF_TEST_PACKAGE_PREFIX_LABEL");
+ public static final String TEST_PACKAGE_SUFFIX = UtplsqlResources.getString("PREF_TEST_PACKAGE_SUFFIX_LABEL");
+ public static final String TEST_UNIT_PREFIX = UtplsqlResources.getString("PREF_TEST_UNIT_PREFIX_LABEL");
+ public static final String TEST_UNIT_SUFFIX = UtplsqlResources.getString("PREF_TEST_UNIT_SUFFIX_LABEL");
+ public static final String NUMBER_OF_TESTS_PER_UNIT = UtplsqlResources.getString("PREF_NUMBER_OF_TESTS_PER_UNIT_LABEL");
+ public static final String GENERATE_COMMENTS = UtplsqlResources.getString("PREF_GENERATE_COMMENTS_LABEL");
+ public static final String DISABLE_TESTS = UtplsqlResources.getString("PREF_DISABLE_TESTS_LABEL");
+ public static final String SUITE_PATH = UtplsqlResources.getString("PREF_SUITE_PATH_LABEL");
+ public static final String INDENT_SPACES = UtplsqlResources.getString("PREF_INDENT_SPACES_LABEL");
+
+ private final NodeTools nodeTools = new NodeTools();
+ private final TemplateTools templateTools = new TemplateTools();
+ private final ArrayList consoleOutput = new ArrayList<>();
+
+ private GenContext toContext(final Node node) {
+ final GenContext context = new GenContext();
+ context.setObjectType(nodeTools.toObjectType(node));
+ context.setObjectName(nodeTools.toObjectName(node));
+ context.setTestPackagePrefix(node.getParams().get(TEST_PACKAGE_PREFIX).toLowerCase());
+ context.setTestPackageSuffix(node.getParams().get(TEST_PACKAGE_SUFFIX).toLowerCase());
+ context.setTestUnitPrefix(node.getParams().get(TEST_UNIT_PREFIX).toLowerCase());
+ context.setTestUnitSuffix(node.getParams().get(TEST_UNIT_SUFFIX).toLowerCase());
+ context.setNumberOfTestsPerUnit(Integer.parseInt(node.getParams().get(NUMBER_OF_TESTS_PER_UNIT)));
+ context.setGenerateComments(YES.equals(node.getParams().get(GENERATE_COMMENTS)));
+ context.setDisableTests(YES.equals(node.getParams().get(DISABLE_TESTS)));
+ context.setSuitePath(node.getParams().get(SUITE_PATH).toLowerCase());
+ context.setIndentSpaces(Integer.parseInt(node.getParams().get(INDENT_SPACES)));
+ return context;
+ }
+
+ private void resetConsoleOutput() {
+ consoleOutput.clear();
+ }
+
+ private void saveConsoleOutput(final String s) {
+ if (s != null) {
+ consoleOutput.addAll(Arrays.asList(s.split("[\\n\\r]+")));
+ }
+ }
+
+ private void logConsoleOutput() {
+ for (final String line : consoleOutput) {
+ if (line.contains("error") || line.startsWith("Cannot")) {
+ logger.severe(line);
+ } else {
+ logger.fine(line);
+ }
+ }
+ }
+
+ private String deleteFile(final File file) {
+ String ret;
+ if (file.delete()) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(file.getAbsoluteFile());
+ sb.append(" deleted.");
+ ret = sb.toString();
+ } else {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Cannot delete file ");
+ sb.append(file.getAbsoluteFile());
+ sb.append(".");
+ ret = sb.toString();
+ }
+ return ret;
+ }
+
+ private CharSequence deleteFiles(final String directory) {
+ StringBuilder sb = new StringBuilder();
+ final File dir = new File(directory);
+ try {
+ for (final File file : Objects.requireNonNull(dir.listFiles())) {
+ if (!file.isDirectory() && (file.getName().endsWith(".pks") || file.getName().endsWith(".pkb"))) {
+ sb.append(deleteFile(file));
+ sb.append('\n');
+ }
+ }
+ } catch (NullPointerException e) {
+ // ignore
+ }
+ return sb;
+ }
+
+ @Override
+ public boolean isSupported(final Connection conn) {
+ return DatabaseTools.isSupported(conn);
+ }
+
+ @Override
+ public String getName(final Connection conn) {
+ return "Generate test";
+ }
+
+ @Override
+ public String getDescription(final Connection conn) {
+ return "Generates utPLSQL test packages for public units in packages, types, functions and procedures found in the current schema.";
+ }
+
+ @Override
+ public List getFolders(final Connection conn) {
+ final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences());
+ final ArrayList folders = new ArrayList<>();
+ for (String f : preferences.getRootFolderInOddgenView().split(",")) {
+ if (f != null) {
+ folders.add(f.trim());
+ }
+ }
+ return folders;
+ }
+
+ @Override
+ public String getHelp(final Connection conn) {
+ return "not yet available
";
+ }
+
+ @Override
+ public List getNodes(final Connection conn, final String parentNodeId) {
+ final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences());
+ final LinkedHashMap params = new LinkedHashMap<>();
+ params.put(GENERATE_FILES, preferences.isGenerateFiles() ? YES : NO);
+ params.put(OUTPUT_DIRECTORY, preferences.getOutputDirectory());
+ params.put(DELETE_EXISTING_FILES, preferences.isDeleteExistingFiles() ? YES : NO);
+ params.put(TEST_PACKAGE_PREFIX, preferences.getTestPackagePrefix());
+ params.put(TEST_PACKAGE_SUFFIX, preferences.getTestPackageSuffix());
+ params.put(TEST_UNIT_PREFIX, preferences.getTestUnitPrefix());
+ params.put(TEST_UNIT_SUFFIX, preferences.getTestUnitSuffix());
+ params.put(NUMBER_OF_TESTS_PER_UNIT, String.valueOf(preferences.getNumberOfTestsPerUnit()));
+ params.put(GENERATE_COMMENTS, preferences.isGenerateComments() ? YES : NO);
+ params.put(DISABLE_TESTS, preferences.isDisableTests() ? YES : NO);
+ params.put(SUITE_PATH, preferences.getSuitePath());
+ params.put(INDENT_SPACES, String.valueOf(preferences.getIndentSpaces()));
+ if (parentNodeId == null || parentNodeId.isEmpty()) {
+ final Node packageNode = new Node();
+ packageNode.setId("PACKAGE");
+ packageNode.setParams(params);
+ packageNode.setLeaf(false);
+ packageNode.setGeneratable(true);
+ packageNode.setMultiselectable(true);
+ final Node typeNode = new Node();
+ typeNode.setId("TYPE");
+ typeNode.setParams(params);
+ typeNode.setLeaf(false);
+ typeNode.setGeneratable(true);
+ typeNode.setMultiselectable(true);
+ final Node functionNode = new Node();
+ functionNode.setId("FUNCTION");
+ functionNode.setParams(params);
+ functionNode.setLeaf(false);
+ functionNode.setGeneratable(true);
+ functionNode.setMultiselectable(true);
+ final Node procedureNode = new Node();
+ procedureNode.setId("PROCEDURE");
+ procedureNode.setParams(params);
+ procedureNode.setLeaf(false);
+ procedureNode.setGeneratable(true);
+ procedureNode.setMultiselectable(true);
+ return Arrays.asList(packageNode, typeNode, functionNode, procedureNode);
+ } else {
+ final UtplsqlDao dao = new UtplsqlDao(conn);
+ final List nodes = dao.testables(parentNodeId);
+ for (final Node node : nodes) {
+ node.setParams(params);
+ }
+ return nodes;
+ }
+ }
+
+ @Override
+ public HashMap> getLov(final Connection conn, final LinkedHashMap params,
+ final List nodes) {
+ final HashMap> lov = new HashMap<>();
+ lov.put(NUMBER_OF_TESTS_PER_UNIT, Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8", "9", "10"));
+ lov.put(INDENT_SPACES, Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8"));
+ lov.put(GENERATE_COMMENTS, Arrays.asList(YES, NO));
+ lov.put(DISABLE_TESTS, Arrays.asList(YES, NO));
+ lov.put(GENERATE_FILES, Arrays.asList(YES, NO));
+ lov.put(DELETE_EXISTING_FILES, Arrays.asList(YES, NO));
+ return lov;
+ }
+
+ @Override
+ public HashMap getParamStates(final Connection conn, final LinkedHashMap params,
+ final List nodes) {
+ final HashMap paramStates = new HashMap<>();
+ paramStates.put(OUTPUT_DIRECTORY, YES.equals(params.get(GENERATE_FILES)));
+ paramStates.put(DELETE_EXISTING_FILES, YES.equals(params.get(GENERATE_FILES)));
+ return paramStates;
+ }
+
+ @Override
+ public String generateProlog(final Connection conn, final List nodes) {
+ StringBuilder sb = new StringBuilder();
+ final boolean generateFiles = YES.equals(nodes.get(0).getParams().get(GENERATE_FILES));
+ final String outputDirectory = nodes.get(0).getParams().get(OUTPUT_DIRECTORY);
+ final boolean deleteExistingfiles = YES.equals(nodes.get(0).getParams().get(DELETE_EXISTING_FILES));
+ if (generateFiles) {
+ resetConsoleOutput();
+ saveConsoleOutput(templateTools.mkdirs(outputDirectory));
+ if (deleteExistingfiles) {
+ saveConsoleOutput(deleteFiles(outputDirectory).toString());
+ }
+ sb.append("--\n");
+ sb.append("-- install generated utPLSQL test packages\n");
+ sb.append("--\n");
+ }
+ for (final Node node : nodes) {
+ final GenContext context = toContext(node);
+ context.setConn(conn);
+ final TestTemplate testTemplate = new TestTemplate(context);
+ if (generateFiles) {
+ final String packageName = context.getTestPackagePrefix().toLowerCase()
+ + nodeTools.toObjectName(node).toLowerCase()
+ + context.getTestPackageSuffix().toLowerCase();
+ final String packagePath = outputDirectory + File.separator + packageName;
+ saveConsoleOutput(templateTools.writeToFile(packagePath + ".pks", testTemplate.generateSpec()));
+ saveConsoleOutput(templateTools.writeToFile(packagePath + ".pkb", testTemplate.generateBody()));
+ sb.append('@');
+ sb.append(packagePath);
+ sb.append(".pks\n");
+ sb.append('@');
+ sb.append(packagePath);
+ sb.append(".pkb\n");
+ } else {
+ sb.append(testTemplate.generate());
+ sb.append('\n');
+ }
+ }
+ logConsoleOutput();
+ if (generateFiles && consoleOutput.stream().anyMatch(it -> it.contains("error"))) {
+ sb.append('\n');
+ sb.append("--\n");
+ sb.append("-- console output produced during the generation of this script (errors found)\n");
+ sb.append("--\n");
+ sb.append("/*\n\n");
+ for (final String line : consoleOutput) {
+ sb.append(line);
+ sb.append('\n');
+ }
+ sb.append('\n');
+ sb.append("*/\n");
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public String generateSeparator(final Connection conn) {
+ return "";
+ }
+
+ @Override
+ public String generateEpilog(final Connection conn, final List nodes) {
+ return "";
+ }
+
+ @Override
+ public String generate(final Connection conn, final Node node) {
+ return "";
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.java b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.java
new file mode 100644
index 00000000..7bbdff3f
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.oddgen;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+import org.utplsql.sqldev.dal.UtplsqlDao;
+import org.utplsql.sqldev.model.StringTools;
+import org.utplsql.sqldev.model.oddgen.GenContext;
+
+public class TestTemplate {
+ private final GenContext context;
+ private final List units;
+ private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ private final String today = dateTimeFormatter.format(LocalDateTime.now());
+
+ public TestTemplate(final GenContext context) {
+ this.context = context;
+ UtplsqlDao dao = new UtplsqlDao(context.getConn());
+ units = dao.units(context.getObjectType(), context.getObjectName());
+ }
+
+ public String generateSpec() {
+ final StringBuilder sb = new StringBuilder();
+ final String objectName = context.getObjectName().toLowerCase();
+ final String packageName = context.getTestPackagePrefix() + objectName + context.getTestPackageSuffix();
+ sb.append("create or replace package ");
+ sb.append(packageName);
+ sb.append(" is\n");
+ if (context.isGenerateComments()) {
+ sb.append("\n\t-- generated by utPLSQL for SQL Developer on ");
+ sb.append(today);
+ sb.append("\n\n");
+ }
+ sb.append("\t--%suite(");
+ sb.append(packageName);
+ sb.append(")\n");
+ if (!context.getSuitePath().isEmpty()) {
+ sb.append("\t--%suitepath(");
+ sb.append(context.getSuitePath());
+ sb.append(")\n");
+ }
+ sb.append("\n");
+ for (final String u : units) {
+ final String unit = u.toLowerCase();
+ if (context.getNumberOfTestsPerUnit() > 1
+ && ("PACKAGE".equals(context.getObjectType()) || "TYPE".equals(context.getObjectType()))) {
+ sb.append("\t--%context(");
+ sb.append(unit);
+ sb.append(")\n\n");
+ }
+ for (int i=1; i <= context.getNumberOfTestsPerUnit(); i++) {
+ sb.append("\t--%test\n");
+ if (context.isDisableTests()) {
+ sb.append("\t--%disabled\n");
+ }
+ sb.append("\tprocedure ");
+ sb.append(context.getTestUnitPrefix());
+ sb.append(unit);
+ sb.append(context.getTestUnitSuffix());
+ if (context.getNumberOfTestsPerUnit() > 1) {
+ sb.append(i);
+ }
+ sb.append(";\n\n");
+ }
+ if (context.getNumberOfTestsPerUnit() > 1
+ && ("PACKAGE".equals(context.getObjectType()) || "TYPE".equals(context.getObjectType()))) {
+ sb.append("\t--%endcontext\n\n");
+ }
+ }
+ sb.append("end ");
+ sb.append(packageName);
+ sb.append(";\n");
+ sb.append("/");
+ final String ret = sb.toString();
+ return StringTools.replaceTabsWithSpaces(ret, context.getIndentSpaces());
+ }
+
+ public String generateBody() {
+ StringBuilder sb = new StringBuilder();
+ final String objectName = context.getObjectName().toLowerCase();
+ sb.append("create or replace package body ");
+ sb.append(context.getTestPackagePrefix());
+ sb.append(objectName);
+ sb.append(context.getTestPackageSuffix());
+ sb.append(" is\n\n");
+ if (context.isGenerateComments()) {
+ sb.append("\t-- generated by utPLSQL for SQL Developer on ");
+ sb.append(today);
+ sb.append("\n\n");
+ }
+ for (final String u : units) {
+ final String unit = u.toLowerCase();
+ for (int i=1; i <= context.getNumberOfTestsPerUnit(); i++) {
+ final String procedureName = context.getTestUnitPrefix() + unit + context.getTestUnitSuffix()
+ + (context.getNumberOfTestsPerUnit() > 1 ? String.valueOf(i) : "");
+ if (context.isGenerateComments()) {
+ sb.append("\t--\n");
+ sb.append("\t-- test ");
+ sb.append(unit);
+ if (context.getNumberOfTestsPerUnit() > 1) {
+ sb.append(" case ");
+ sb.append(i);
+ sb.append(": ...\n");
+ } else {
+ sb.append("\n");
+ }
+ sb.append("\t--\n");
+ }
+ sb.append("\tprocedure ");
+ sb.append(procedureName);
+ sb.append(" is\n");
+ sb.append("\t\tl_actual integer := 0;\n");
+ sb.append("\t\tl_expected integer := 1;\n");
+ sb.append("\tbegin\n");
+ if (context.isGenerateComments()) {
+ sb.append("\t\t-- arrange\n\n");
+ sb.append("\t\t-- act\n");
+ sb.append("\t\t-- ");
+ sb.append(objectName);
+ sb.append(".");
+ sb.append(unit);
+ sb.append(";\n\n");
+ sb.append("\t\t-- assert\n");
+ }
+ sb.append("\t\tut.expect(l_actual).to_equal(l_expected);\n");
+ sb.append("\tend ");
+ sb.append(procedureName);
+ sb.append(";\n\n");
+ }
+ }
+ sb.append("end ");
+ sb.append(context.getTestPackagePrefix());
+ sb.append(objectName);
+ sb.append(context.getTestPackageSuffix());
+ sb.append(";\n");
+ sb.append("/");
+ final String ret = sb.toString();
+ return StringTools.replaceTabsWithSpaces(ret, context.getIndentSpaces());
+ }
+
+ public CharSequence generate() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(generateSpec());
+ sb.append("\n\n");
+ sb.append(generateBody());
+ sb.append('\n');
+ return sb;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.java b/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.java
new file mode 100644
index 00000000..bf7962b1
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.parser;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import oracle.dbtools.parser.LexerToken;
+import oracle.dbtools.raptor.navigator.plsql.Member;
+import oracle.dbtools.raptor.navigator.plsql.PlSqlArguments;
+import oracle.dbtools.raptor.navigator.plsql.PlsqlStructureParser;
+
+/*
+ * Cannot use this class within SQL Developer because the
+ * package oracle.dbtools.parser is not exported in sqldeveloper OSGI bundle (extension)
+ * (throws ClassNotFoundException at runtime).
+ *
+ * The dbtools-common.jar contains the necessary packages,
+ * but it cannot be distributed with the utPLSQL extension
+ * without violating the Oracle license agreement.
+ */
+public class SqlDevParser {
+
+ @SuppressWarnings("unchecked")
+ public Set getMembers(final String plsql) {
+ final List tokens = LexerToken.parse(plsql);
+ final PlsqlStructureParser parser = new PlsqlStructureParser();
+ parser.parse(tokens, PlSqlArguments.getSort());
+ return parser.children;
+ }
+
+ private int getStartLine(final String plsql, final int offset) {
+ int line = 1;
+ for (int i = 0; i < plsql.length(); i++) {
+ final String c = plsql.substring(i, i + 1);
+ if (i > offset) {
+ return line;
+ } else if ("\n".equals(c)) {
+ line = line + 1;
+ }
+ }
+ return line;
+ }
+
+ public int getMemberStartLine(final String plsql, final String memberName) {
+ final Set members = this.getMembers(plsql);
+ final Optional member = members.stream().filter(it -> it.name.equalsIgnoreCase(memberName)).findFirst();
+ return member.map(value -> this.getStartLine(plsql, value.codeOffset)).orElse(1);
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.java b/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.java
new file mode 100644
index 00000000..8eb5c9ef
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.parser;
+
+import java.sql.Connection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.text.JTextComponent;
+
+import org.utplsql.sqldev.dal.UtplsqlDao;
+import org.utplsql.sqldev.exception.GenericRuntimeException;
+import org.utplsql.sqldev.model.DatabaseTools;
+import org.utplsql.sqldev.model.parser.PlsqlObject;
+import org.utplsql.sqldev.model.parser.Unit;
+import org.utplsql.sqldev.model.ut.Annotation;
+
+public class UtplsqlParser {
+ private String owner;
+ private String plsql;
+ private String plsqlReduced;
+ private ArrayList objects = new ArrayList<>();
+ private ArrayList units = new ArrayList<>();
+
+ public UtplsqlParser(final String plsql, final Connection conn, final String owner) {
+ setPlsql(plsql);
+ setPlsqlReduced();
+ populateObjects();
+ populateUnits();
+ processAnnotations(conn, owner);
+ }
+
+ public UtplsqlParser(final String plsql) {
+ this(plsql, null, null);
+ }
+
+ /**
+ * JTextComponents uses one position for EOL (end-of-line),
+ * even on Windows platforms were it is two characters (CR/LF).
+ * To simplify position calculations and subsequent regular expressions
+ * all new lines are replaced with LF on Windows platforms.
+ */
+ private void setPlsql(final String plsql) {
+ final String lineSep = System.getProperty("line.separator");
+ if (!"\n".equals(lineSep)) {
+ // replace CR/LF with LF on Windows platforms
+ this.plsql = plsql.replace(lineSep, "\n");
+ } else {
+ this.plsql = plsql;
+ }
+ }
+
+ /**
+ * replace the following expressions with space to simplify
+ * and improve performance of subsequent regular expressions:
+ * - multi-line PL/SQL comments
+ * - single-line PL/SQL comments
+ * - string literals
+ * the result is not valid PL/SQL anymore, but good enough
+ * to find PL/SQL objects and units
+ */
+ private void setPlsqlReduced() {
+ final StringBuilder sb = new StringBuilder();
+ final Pattern p = Pattern.compile("(/\\*(.|[\\n])*?\\*/)|(--[^\\n]*\\n)|(\'([^\']|[\\n])*?\')");
+ final Matcher m = p.matcher(plsql);
+ int pos = 0;
+ while (m.find()) {
+ if (pos < m.start()) {
+ sb.append(plsql.substring(pos, m.start()));
+ }
+ for (int i = m.start(); i < m.end(); i++) {
+ final String c = plsql.substring(i, i + 1);
+ if ("\n".equals(c) || "\r".equals(c)) {
+ sb.append(c);
+ } else {
+ sb.append(' ');
+ }
+ }
+ pos = m.end();
+ }
+ if (plsql.length() > pos) {
+ sb.append(plsql.substring(pos, plsql.length()));
+ }
+ plsqlReduced=sb.toString();
+ }
+
+ private void populateObjects() {
+ final Pattern p = Pattern.compile(
+ "(?i)(\\s*)(create(\\s+or\\s+replace)?\\s+(package|type|function|procedure)\\s+(body\\s+)?)([^\\s]+)(\\s+)");
+ final Matcher m = p.matcher(plsqlReduced);
+ while (m.find()) {
+ final PlsqlObject o = new PlsqlObject();
+ o.setType(m.group(4).toUpperCase());
+ o.setName(m.group(6));
+ o.setPosition(m.start());
+ objects.add(o);
+ }
+ }
+
+ private void populateUnits() {
+ final Pattern p = Pattern.compile("(?i)(\\s*)(procedure)(\\s+)([^\\s\\(;]+)");
+ final Matcher m = p.matcher(plsqlReduced);
+ while (m.find()) {
+ final Unit u = new Unit();
+ u.setName(m.group(4));
+ u.setPosition(m.start());
+ u.setPositionOfName(m.start(4));
+ units.add(u);
+ }
+ }
+
+ private void processAnnotations(final Connection conn, final String owner) {
+ this.owner = owner;
+ if (conn != null) {
+ final UtplsqlDao dao = new UtplsqlDao(conn);
+ if (dao.isUtAnnotationManagerInstalled()) {
+ for (final PlsqlObject o : objects) {
+ final List segments = Arrays.asList(fixName(o.getName()).split("\\."));
+ final String schema = owner != null ? owner : DatabaseTools.getSchema(conn);
+ final List annotations = dao.annotations(schema,
+ segments.get(segments.size() - 1).toUpperCase());
+ if (annotations.stream().anyMatch(it -> it.getName().equals("suite"))) {
+ o.setAnnotations(annotations);
+ }
+ }
+ final ArrayList fixedUnits = new ArrayList<>();
+ for (final Unit u : units) {
+ final PlsqlObject o = getObjectAt(u.getPosition());
+ if (o != null && o.getAnnotations() != null
+ && o.getAnnotations().stream().anyMatch(it -> "test".equals(it.getName())
+ && it.getSubobjectName().equalsIgnoreCase(fixName(u.getName())))) {
+ fixedUnits.add(u);
+ }
+ }
+ units = fixedUnits;
+ final ArrayList fixedObjects = new ArrayList<>();
+ for (final PlsqlObject o : objects) {
+ if (o.getAnnotations() != null) {
+ fixedObjects.add(o);
+ }
+ }
+ objects = fixedObjects;
+ }
+ }
+ }
+
+ /**
+ * gets the PL/SQL object based on the current editor position
+ *
+ * @param position
+ * the absolute position as used in
+ * {@link JTextComponent#getCaretPosition()}
+ * @return the PL/SQL object
+ */
+ public PlsqlObject getObjectAt(final int position) {
+ PlsqlObject obj = null;
+ for (final PlsqlObject o : objects) {
+ if (o.getPosition() <= position) {
+ obj = o;
+ }
+ }
+ return obj;
+ }
+
+ /**
+ * converts a line and column to a postion as used in as used in
+ * {@link JTextComponent#getCaretPosition()} used for testing purposes only
+ *
+ * @param line
+ * the line as used in SQL Developer, starting with 1
+ * @param column
+ * the column as used in SQL Developer, starting with 1
+ * @return the position
+ */
+ public int toPosition(final int line, final int column) {
+ int lines = 0;
+ for (int i = 0; i < plsql.length(); i++) {
+ if ("\n".equals(plsql.substring(i, i + 1))) {
+ lines++;
+ if (lines == line - 1) {
+ return i + column;
+ }
+ }
+ }
+ throw new GenericRuntimeException("Line " + line + " not found.");
+ }
+
+ private String getUnitNameAt(final int position) {
+ String name = "";
+ for (final Unit u : units) {
+ if (u.getPosition() <= position) {
+ name = u.getName();
+ }
+ }
+ return name;
+ }
+
+ private String fixName(final String name) {
+ return name.replace("\"", "");
+ }
+
+ public List getObjects() {
+ return objects;
+ }
+
+ public List getUnits() {
+ return units;
+ }
+
+ /**
+ * gets the utPLSQL path based on the current editor position
+ *
+ * @param position
+ * the absolute position as used in
+ * {@link JTextComponent#getCaretPosition()}
+ * @return the utPLSQL path
+ */
+ public String getPathAt(final int position) {
+ final StringBuilder sb = new StringBuilder();
+ final PlsqlObject object = getObjectAt(position);
+ if (object != null && "PACKAGE".equals(object.getType())) {
+ final String unitName = getUnitNameAt(position);
+ if (owner != null) {
+ sb.append(owner);
+ sb.append(".");
+ }
+ sb.append(fixName(object.getName()));
+ if (!unitName.isEmpty()) {
+ sb.append(".");
+ sb.append(fixName(unitName));
+ }
+ }
+ return sb.toString();
+ }
+
+ private int getStartLine(final int position) {
+ int line = 1;
+ for (int i = 0; i < plsql.length(); i++) {
+ final String c = plsql.substring(i, i + 1);
+ if (i > position) {
+ return line;
+ } else if ("\n".equals(c)) {
+ line++;
+ }
+ }
+ return line;
+ }
+
+ /**
+ * get the line of a PL/SQL package unit
+ *
+ * @param unitName
+ * name of the unit. Only procedures are supported
+ * @return the line where the procedure is defined
+ */
+ public int getLineOf(final String unitName) {
+ for (final Unit u : units) {
+ if (u.getName().equalsIgnoreCase(unitName)) {
+ return getStartLine(u.getPositionOfName());
+ }
+ }
+ return 1;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.xtend b/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.xtend
deleted file mode 100644
index dd343e7f..00000000
--- a/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.xtend
+++ /dev/null
@@ -1,223 +0,0 @@
-/* Copyright 2018 Philipp Salvisberg
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.utplsql.sqldev.parser
-
-import java.sql.Connection
-import java.util.ArrayList
-import java.util.Arrays
-import java.util.regex.Pattern
-import javax.swing.text.JTextComponent
-import org.utplsql.sqldev.dal.UtplsqlDao
-import org.utplsql.sqldev.model.parser.PlsqlObject
-import org.utplsql.sqldev.model.parser.Unit
-
-class UtplsqlParser {
- private String plsql
- private String plsqlReduced
- private ArrayList objects = new ArrayList
- private ArrayList units = new ArrayList
-
- new(String plsql, Connection conn) {
- setPlsql(plsql)
- setPlsqlReduced
- populateObjects
- populateUnits
- processAnnotations(conn)
- }
-
- new(String plsql) {
- this(plsql, null)
- }
-
- /**
- * JTextComponents uses one position for EOL (end-of-line),
- * even on Windows platforms were it is two characters (CR/LF).
- * To simplify position calculations and subsequent regular expressions
- * all new lines are replaced with LF on Windows platforms.
- */
- private def setPlsql(String plsql) {
- val lineSep = System.getProperty("line.separator")
- if (lineSep.length > 0) {
- // replace CR/LF with LF on Windows platforms
- this.plsql = plsql.replace(lineSep, "\n")
- } else {
- this.plsql = plsql
- }
- }
-
- /**
- * replace the following expressions with space to simplify
- * and improve performance of subsequent regular expressions:
- * - multi-line PL/SQL comments
- * - single-line PL/SQL comments
- * - string literals
- * the result is not valid PL/SQL anymore, but good enough
- * to find PL/SQL objects and units
- */
- private def setPlsqlReduced() {
- val sb = new StringBuffer
- val p = Pattern.compile("(/\\*(.|[\\n])*?\\*/)|(--[^\\n]*\\n)|('([^']|[\\n])*?')")
- val m = p.matcher(plsql)
- var pos = 0
- while (m.find) {
- if (pos < m.start) {
- sb.append(plsql.substring(pos, m.start))
- }
- for (var i=m.start; i pos) {
- sb.append(plsql.substring(pos, plsql.length))
- }
- plsqlReduced=sb.toString
- }
-
- private def populateObjects() {
- val p = Pattern.compile("(?i)(\\s*)(create(\\s+or\\s+replace)?\\s+(package)\\s+(body\\s+)?)([^\\s]+)(\\s+)")
- val m = p.matcher(plsqlReduced)
- while (m.find) {
- val o = new PlsqlObject
- o.name = m.group(6)
- o.position = m.start
- objects.add(o)
- }
- }
- private def populateUnits() {
- val p = Pattern.compile("(?i)(\\s*)(procedure)(\\s+)([^\\s\\(;]+)")
- val m = p.matcher(plsqlReduced)
- while (m.find) {
- val u = new Unit
- u.name = m.group(4)
- u.position = m.start
- units.add(u)
- }
- }
-
- private def processAnnotations(Connection conn) {
- if (conn !== null) {
- val dao = new UtplsqlDao(conn)
- if (dao.utAnnotationManagerInstalled) {
- for (o : objects) {
- val segments = Arrays.asList(o.name.fixName.split("\\."))
- val annotations = dao.annotations(conn.schema, segments.last.toUpperCase)
- if (annotations.findFirst[it.name == "suite"] !== null) {
- o.annotations = annotations
- }
- }
- val fixedUnits = new ArrayList
- for (u : units) {
- val o = getObjectAt(u.position)
- if (o?.annotations !== null && o.annotations.findFirst [
- it.name == "test" && it.subobjectName.equalsIgnoreCase(u.name.fixName)
- ] !== null) {
- fixedUnits.add(u)
- }
- }
- units = fixedUnits
- val fixedObjects = new ArrayList
- for (o : objects) {
- if (o.annotations !== null) {
- fixedObjects.add(o)
- }
- }
- objects = fixedObjects
- }
- }
- }
-
- private def getObjectAt(int position) {
- var PlsqlObject obj
- for (o : objects) {
- if (o.position <= position) {
- obj = o
- }
- }
- return obj
- }
-
- private def getObjectNameAt(int position) {
- val o = getObjectAt(position)
- return if (o !== null) {o.name} else {""}
- }
-
- private def getUnitNameAt(int position) {
- var name = ""
- for (u : units) {
- if (u.position <= position) {
- name = u.name
- }
- }
- return name
- }
-
- private def fixName(String name) {
- return name.replace("\"", "")
- }
-
- def getObjects() {
- return objects
- }
-
- def getUnits() {
- return units
- }
-
- /**
- * gets the utPLSQL path based on the current editor position
- *
- * @param position the absolute position as used in {@link JTextComponent#getCaretPosition()}
- * @return the utPLSQL path
- */
- def getPathAt(int position) {
- var objectName = getObjectNameAt(position)
- if (!objectName.empty) {
- var unitName = getUnitNameAt(position)
- if (unitName.empty) {
- return objectName.fixName
- } else {
- return '''«objectName.fixName».«unitName.fixName»'''
- }
- }
- return ""
- }
-
- /**
- * gets the utPLSQL path based on the current editor position
- *
- * @param line the line as used in SQL Developer, starting with 1
- * @param column the column as used in SQL Developer, starting with 1
- * @return the utPLSQL path
- */
- def getPathAt(int line, int column) {
- var lines=0
- for (var i=0; i
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.resources;
+
+import java.awt.Image;
+
+import javax.swing.Icon;
+
+import oracle.dbtools.raptor.utils.MessagesBase;
+
+public class UtplsqlResources extends MessagesBase {
+ private static final ClassLoader CLASS_LOADER = UtplsqlResources.class.getClassLoader();
+ private static final String CLASS_NAME = UtplsqlResources.class.getCanonicalName();
+ private static final UtplsqlResources INSTANCE = new UtplsqlResources();
+
+ private UtplsqlResources() {
+ super(CLASS_NAME, CLASS_LOADER);
+ }
+
+ public static String getString(final String paramString) {
+ return INSTANCE.getStringImpl(paramString);
+ }
+
+ public static String get(final String paramString) {
+ return getString(paramString);
+ }
+
+ public static Image getImage(final String paramString) {
+ return INSTANCE.getImageImpl(paramString);
+ }
+
+ public static String format(final String paramString, final Object... paramVarArgs) {
+ return INSTANCE.formatImpl(paramString, paramVarArgs);
+ }
+
+ public static Icon getIcon(final String paramString) {
+ return INSTANCE.getIconImpl(paramString);
+ }
+
+ public static Integer getInteger(final String paramString) {
+ return INSTANCE.getIntegerImpl(paramString);
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/resources/UtplsqlResources.xtend b/sqldev/src/main/java/org/utplsql/sqldev/resources/UtplsqlResources.xtend
deleted file mode 100644
index 57ae3fbf..00000000
--- a/sqldev/src/main/java/org/utplsql/sqldev/resources/UtplsqlResources.xtend
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2018 Philipp Salvisberg
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.utplsql.sqldev.resources
-
-import oracle.dbtools.raptor.utils.MessagesBase
-
-class UtplsqlResources extends MessagesBase {
- private static final ClassLoader CLASS_LOADER = UtplsqlResources.classLoader
- private static final String CLASS_NAME = UtplsqlResources.canonicalName
- private static final UtplsqlResources INSTANCE = new UtplsqlResources()
-
- private new() {
- super(CLASS_NAME, CLASS_LOADER)
- }
-
- def static getString(String paramString) {
- return INSTANCE.getStringImpl(paramString)
- }
-
- def static get(String paramString) {
- return getString(paramString)
- }
-
- def static getImage(String paramString) {
- return INSTANCE.getImageImpl(paramString)
- }
-
- def static format(String paramString, Object... paramVarArgs) {
- return INSTANCE.formatImpl(paramString, paramVarArgs)
- }
-
- def static getIcon(String paramString) {
- return INSTANCE.getIconImpl(paramString)
- }
-
- def static getInteger(String paramString) {
- return INSTANCE.getIntegerImpl(paramString)
- }
-}
\ No newline at end of file
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java
new file mode 100644
index 00000000..6222c2e0
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.runner;
+
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.net.URL;
+import java.sql.Connection;
+import java.util.List;
+import java.util.UUID;
+import java.util.function.Supplier;
+import java.util.logging.Logger;
+
+import javax.swing.JFrame;
+
+import org.utplsql.sqldev.coverage.CodeCoverageReporter;
+import org.utplsql.sqldev.dal.RealtimeReporterDao;
+import org.utplsql.sqldev.dal.RealtimeReporterEventConsumer;
+import org.utplsql.sqldev.exception.GenericRuntimeException;
+import org.utplsql.sqldev.model.DatabaseTools;
+import org.utplsql.sqldev.model.StringTools;
+import org.utplsql.sqldev.model.SystemTools;
+import org.utplsql.sqldev.model.runner.ItemNode;
+import org.utplsql.sqldev.model.runner.PostRunEvent;
+import org.utplsql.sqldev.model.runner.PostSuiteEvent;
+import org.utplsql.sqldev.model.runner.PostTestEvent;
+import org.utplsql.sqldev.model.runner.PreRunEvent;
+import org.utplsql.sqldev.model.runner.PreSuiteEvent;
+import org.utplsql.sqldev.model.runner.PreTestEvent;
+import org.utplsql.sqldev.model.runner.RealtimeReporterEvent;
+import org.utplsql.sqldev.model.runner.Run;
+import org.utplsql.sqldev.model.runner.Suite;
+import org.utplsql.sqldev.model.runner.Test;
+import org.utplsql.sqldev.resources.UtplsqlResources;
+import org.utplsql.sqldev.ui.runner.RunnerFactory;
+import org.utplsql.sqldev.ui.runner.RunnerPanel;
+import org.utplsql.sqldev.ui.runner.RunnerView;
+
+import oracle.dbtools.raptor.runner.DBStarterFactory;
+import oracle.ide.Context;
+import oracle.jdevimpl.runner.debug.DebuggingProcess;
+import oracle.jdevimpl.runner.run.JRunner;
+
+public class UtplsqlRunner implements RealtimeReporterEventConsumer {
+ private static final Logger logger = Logger.getLogger(UtplsqlRunner.class.getName());
+ private static final int DEBUG_TIMEOUT_SECONDS = 60*60;
+
+ private final boolean withCodeCoverage;
+ private final List pathList;
+ private final List schemaList;
+ private final List includeObjectList;
+ private final List excludeObjectList;
+ private Context context;
+ private String connectionName;
+ private Connection producerConn;
+ private Connection consumerConn;
+ private final String realtimeReporterId = UUID.randomUUID().toString().replace("-", "");
+ private final String coverageReporterId = UUID.randomUUID().toString().replace("-", "");
+ private Run run;
+ private RunnerPanel panel;
+ private JFrame frame; // for testing purposes only (outside of SQL Developer)
+ private Thread producerThread;
+ private Thread consumerThread;
+ private boolean debug = false;
+ private final URL htmlReportAssetPath;
+
+ public UtplsqlRunner(final List pathList, final String connectionName) {
+ this.withCodeCoverage = false;
+ this.pathList = pathList;
+ this.schemaList = null;
+ this.includeObjectList = null;
+ this.excludeObjectList = null;
+ this.htmlReportAssetPath = null;
+ setConnection(connectionName);
+ this.context = Context.newIdeContext();
+ }
+
+ public UtplsqlRunner(final List pathList, final List schemaList,
+ final List includeObjectList, final List excludeObjectList,
+ final URL htmlReportAssetPath, final String connectionName) {
+ this.withCodeCoverage = true;
+ this.pathList = pathList;
+ this.schemaList = schemaList;
+ this.includeObjectList = includeObjectList;
+ this.excludeObjectList = excludeObjectList;
+ this.htmlReportAssetPath = htmlReportAssetPath;
+ setConnection(connectionName);
+ this.context = Context.newIdeContext();
+ }
+
+ /**
+ * this constructor is intended for tests only (without code coverage)
+ */
+ public UtplsqlRunner(final List pathList, final Connection producerConn, final Connection consumerConn) {
+ this.withCodeCoverage = false;
+ this.pathList = pathList;
+ this.schemaList = null;
+ this.includeObjectList = null;
+ this.excludeObjectList = null;
+ this.htmlReportAssetPath = null;
+ this.producerConn = producerConn;
+ this.consumerConn = consumerConn;
+ }
+
+ /**
+ * this constructor is intended for tests only (with code coverage and default htmlReportAssetPath)
+ */
+ public UtplsqlRunner(final List pathList, final List schemaList,
+ final List includeObjectList, final List excludeObjectList,
+ final Connection producerConn, final Connection consumerConn) {
+ this.withCodeCoverage = true;
+ this.pathList = pathList;
+ this.schemaList = schemaList;
+ this.includeObjectList = includeObjectList;
+ this.excludeObjectList = excludeObjectList;
+ this.htmlReportAssetPath = null;
+ this.producerConn = producerConn;
+ this.consumerConn = consumerConn;
+ }
+
+ private void setConnection(final String connectionName) {
+ if (connectionName == null) {
+ throw new NullPointerException("Cannot initialize a RealtimeConsumer without a ConnectionName");
+ } else {
+ producerConn = DatabaseTools.cloneConnection(connectionName);
+ consumerConn = DatabaseTools.cloneConnection(connectionName);
+ }
+ this.connectionName = connectionName;
+ }
+
+ public void enableDebugging() {
+ this.debug = true;
+ }
+
+ public void dispose() {
+ // running in SQL Developer
+ DatabaseTools.closeConnection(producerConn);
+ DatabaseTools.closeConnection(consumerConn);
+ if (frame != null) {
+ frame.setVisible(false);
+ }
+ run.setConsumerConn(null);
+ }
+
+ @Override
+ public void process(final RealtimeReporterEvent event) {
+ logger.fine(event::toString);
+ // dynamic dispatching code originally generated by Xtend
+ if (event instanceof PostRunEvent) {
+ doProcess((PostRunEvent) event);
+ } else if (event instanceof PostSuiteEvent) {
+ doProcess((PostSuiteEvent) event);
+ } else if (event instanceof PostTestEvent) {
+ doProcess((PostTestEvent) event);
+ } else if (event instanceof PreRunEvent) {
+ doProcess((PreRunEvent) event);
+ } else if (event instanceof PreSuiteEvent) {
+ doProcess((PreSuiteEvent) event);
+ } else if (event instanceof PreTestEvent) {
+ doProcess((PreTestEvent) event);
+ } else {
+ throw new IllegalArgumentException("Unhandled event: " + event.toString());
+ }
+ }
+
+ public boolean isRunning() {
+ return run != null && run.getEndTime() == null;
+ }
+
+ private void initRun() {
+ run = new Run(realtimeReporterId, connectionName, pathList);
+ run.setStartTime(StringTools.getSysdate());
+ run.getCounter().setDisabled(0);
+ run.getCounter().setSuccess(0);
+ run.getCounter().setFailure(0);
+ run.getCounter().setError(0);
+ run.getCounter().setWarning(0);
+ run.setInfoCount(0);
+ run.setTotalNumberOfTests(-1);
+ run.setCurrentTestNumber(0);
+ run.setStatus(UtplsqlResources.getString("RUNNER_INITIALIZING_TEXT"));
+ run.setConsumerConn(consumerConn);
+ panel.setModel(run);
+ panel.update(realtimeReporterId);
+ }
+
+ private boolean logFalseCondition(boolean condition, Supplier msgToLog) {
+ if (!condition) {
+ logger.severe(msgToLog);
+ }
+ return condition;
+ }
+
+ private void doProcess(final PreRunEvent event) {
+ run.setTotalNumberOfTests(event.getTotalNumberOfTests());
+ run.put(event.getItems());
+ run.setStatus(UtplsqlResources.getString("RUNNER_RUNNING_TEXT"));
+ panel.update(realtimeReporterId);
+ }
+
+ private void doProcess(final PostRunEvent event) {
+ run.setStartTime(event.getStartTime());
+ run.setEndTime(event.getEndTime());
+ run.setExecutionTime(event.getExecutionTime());
+ run.setErrorStack(event.getErrorStack());
+ run.setServerOutput(event.getServerOutput());
+ run.setStatus(UtplsqlResources.getString("RUNNER_FINISHED_TEXT"));
+ panel.update(realtimeReporterId);
+ }
+
+ private void doProcess(final PreSuiteEvent event) {
+ final ItemNode node = run.getItemNodes().get(event.getId());
+ assert logFalseCondition(node != null, () -> "Could not find suite id \"" + event.getId()
+ + "\" when processing PreSuiteEvent " + event.toString() + ".");
+ final Suite suite = (Suite) node.getUserObject();
+ suite.setStartTime(StringTools.getSysdate());
+ panel.update(realtimeReporterId);
+ }
+
+ private void doProcess(final PostSuiteEvent event) {
+ final ItemNode node = run.getItemNodes().get(event.getId());
+ assert logFalseCondition(node != null, () -> "Could not find suite id \"" + event.getId()
+ + "\" when processing PostSuiteEvent " + event.toString() + ".");
+ final Suite suite = (Suite) node.getUserObject();
+ if (suite.getEndTime() == null) {
+ // first occurrence, multiple possible, e.g. ut_tester and ut_user in utPLSQL project
+ suite.setStartTime(event.getStartTime());
+ suite.setEndTime(event.getEndTime());
+ suite.setExecutionTime(event.getExecutionTime());
+ suite.setCounter(event.getCounter());
+ suite.setErrorStack(event.getErrorStack());
+ suite.setWarnings(event.getWarnings());
+ suite.setServerOutput(event.getServerOutput());
+ } else {
+ // subsequent occurrence, aggregate
+ suite.setEndTime(event.getEndTime());
+ suite.setExecutionTime(suite.getExecutionTime() + event.getExecutionTime());
+ suite.getCounter().setWarning(suite.getCounter().getWarning() + event.getCounter().getWarning());
+ suite.getCounter().setDisabled(suite.getCounter().getDisabled() + event.getCounter().getDisabled());
+ suite.getCounter().setSuccess(suite.getCounter().getSuccess() + event.getCounter().getSuccess());
+ suite.getCounter().setFailure(suite.getCounter().getFailure() + event.getCounter().getFailure());
+ suite.getCounter().setError(suite.getCounter().getError() + event.getCounter().getError());
+ if (event.getWarnings() != null) {
+ StringBuilder sb = new StringBuilder();
+ if (suite.getWarnings() != null) {
+ sb.append(suite.getWarnings());
+ sb.append("\n\n");
+ }
+ sb.append(event.getWarnings());
+ suite.setWarnings(sb.toString());
+ }
+ if (event.getServerOutput() != null) {
+ StringBuilder sb = new StringBuilder();
+ if (suite.getServerOutput() != null) {
+ sb.append(suite.getServerOutput());
+ sb.append("\n\n");
+ }
+ sb.append(event.getServerOutput());
+ suite.setServerOutput(sb.toString());
+ }
+ }
+
+ final Test test = run.getCurrentTest();
+ // Errors on suite levels are reported as warnings by the utPLSQL framework,
+ // since an error on suite level does not affect a status of a test.
+ // It is possible that the test is OK, but contains error messages on suite level(s)
+ // Populating test.errorStack would be a) wrong and b) redundant
+ if (event.getWarnings() != null) {
+ if (test.getCounter().getWarning() == 0) {
+ test.getCounter().setWarning(1);
+ test.getCounter().setWarning(run.getCounter().getWarning() + 1);
+ }
+ StringBuilder sb = new StringBuilder();
+ if (test.getWarnings() != null) {
+ sb.append(test.getWarnings());
+ sb.append("\n\n");
+ }
+ sb.append("For suite ");
+ sb.append(event.getId());
+ sb.append(":\n\n");
+ sb.append(event.getWarnings());
+ test.setWarnings(sb.toString());
+ }
+ if (event.getServerOutput() != null) {
+ if (test.getServerOutput() == null) {
+ run.setInfoCount(run.getInfoCount() + 1);
+ }
+ StringBuilder sb = new StringBuilder();
+ if (test.getServerOutput() != null) {
+ sb.append(test.getServerOutput());
+ sb.append("\n\n");
+ }
+ sb.append("For suite ");
+ sb.append(event.getId());
+ sb.append(":\n\n");
+ sb.append(event.getServerOutput());
+ test.setServerOutput(sb.toString());
+ }
+ panel.update(realtimeReporterId, suite);
+ }
+
+ private void doProcess(final PreTestEvent event) {
+ final Test test = run.getTest(event.getId());
+ assert logFalseCondition(test != null, () -> "Could not find test id \"" + event.getId()
+ + "\" when processing PreTestEvent " + event.toString() + ".");
+ test.setStartTime(StringTools.getSysdate());
+ run.setStatus(event.getId() + "...");
+ run.setCurrentTestNumber(event.getTestNumber());
+ run.setCurrentTest(test);
+ panel.update(realtimeReporterId, test);
+ }
+
+ private void doProcess(final PostTestEvent event) {
+ final Test test = run.getTest(event.getId());
+ assert logFalseCondition(test != null, () -> "Could not find test id \"" + event.getId()
+ + "\" when processing PostTestEvent " + event.toString() + ".");
+ test.setStartTime(event.getStartTime());
+ test.setEndTime(event.getEndTime());
+ test.setExecutionTime(event.getExecutionTime());
+ test.setCounter(event.getCounter());
+ test.setErrorStack(event.getErrorStack());
+ test.setServerOutput(event.getServerOutput());
+ if (test.getServerOutput() != null) {
+ run.setInfoCount(run.getInfoCount() + 1);
+ }
+ test.setFailedExpectations(event.getFailedExpectations());
+ test.setWarnings(event.getWarnings());
+ if (test.getWarnings() != null) {
+ test.getCounter().setWarning(1);
+ } else {
+ test.getCounter().setWarning(0);
+ }
+ run.getCounter().setWarning(run.getCounter().getWarning() + test.getCounter().getWarning());
+ run.getCounter().setDisabled(run.getCounter().getDisabled() + event.getCounter().getDisabled());
+ run.getCounter().setSuccess(run.getCounter().getSuccess() + event.getCounter().getSuccess());
+ run.getCounter().setFailure(run.getCounter().getFailure() + event.getCounter().getFailure());
+ run.getCounter().setError(run.getCounter().getError() + event.getCounter().getError());
+ panel.update(realtimeReporterId, test);
+ }
+
+ private void produceReportWithDebugger(String anonymousPlsqlBlock) {
+ try {
+ Context processContext = JRunner.prepareProcessContext(context, false);
+ DebuggingProcess process = new DebuggingProcess(processContext);
+ DBStarterFactory.PlSqlStarter starter = new DBStarterFactory.PlSqlStarter(process, anonymousPlsqlBlock, connectionName, context);
+ starter.start();
+ } catch (Throwable t) {
+ String msg = t.getClass().getName() + " while debugging utPLSQL test.";
+ logger.severe(() -> msg);
+ throw new GenericRuntimeException(msg);
+ }
+ }
+
+ private void produce() {
+ try {
+ logger.fine(() -> "Running utPLSQL tests and producing events via reporter id " + realtimeReporterId + "...");
+ final RealtimeReporterDao dao = new RealtimeReporterDao(producerConn);
+ if (withCodeCoverage) {
+ dao.produceReportWithCoverage(realtimeReporterId, coverageReporterId, pathList, schemaList, includeObjectList, excludeObjectList, htmlReportAssetPath);
+ } else {
+ if (!debug) {
+ dao.produceReport(realtimeReporterId, pathList);
+ } else {
+ produceReportWithDebugger(dao.getProduceReportPlsql(realtimeReporterId, pathList));
+ }
+ }
+ logger.fine(() -> "All events produced for reporter id " + realtimeReporterId + ".");
+ } catch (Exception e) {
+ logger.severe(() -> "Error while producing events for reporter id " + realtimeReporterId + ": " + e.getMessage() + ".");
+ }
+ }
+
+ private void consume() {
+ try {
+ try {
+ logger.fine(() -> "Consuming events from reporter id " + realtimeReporterId + " in realtime...");
+ final RealtimeReporterDao dao = new RealtimeReporterDao(consumerConn);
+ if (!debug) {
+ dao.consumeReport(realtimeReporterId, this);
+ } else {
+ dao.consumeReport(realtimeReporterId, this, DEBUG_TIMEOUT_SECONDS);
+ }
+ logger.fine(() -> "All events consumed.");
+ if (withCodeCoverage) {
+ String html = dao.getHtmlCoverage(coverageReporterId);
+ CodeCoverageReporter.openInBrowser(html);
+ }
+ } catch (Exception e) {
+ logger.severe(() -> "Error while consuming events for reporter id " + realtimeReporterId + ": " + e.getMessage() + ".");
+ }
+ } finally {
+ if (run.getTotalNumberOfTests() < 0) {
+ run.setStatus(UtplsqlResources.getString("RUNNER_NO_TESTS_FOUND_TEXT"));
+ run.setExecutionTime((System.currentTimeMillis() - Double.valueOf(run.getStart())) / 1000);
+ run.setEndTime(StringTools.getSysdate());
+ run.setTotalNumberOfTests(0);
+ panel.update(realtimeReporterId);
+ }
+ if (isRunningInSqlDeveloper()) {
+ dispose();
+ }
+ }
+ }
+
+ private boolean isRunningInSqlDeveloper() {
+ return (connectionName != null);
+ }
+
+ private boolean initGUI() {
+ RunnerView dockable = null;
+ if (isRunningInSqlDeveloper() && (dockable = RunnerFactory.getDockable()) == null) {
+ logger.severe(() -> "Error getting utPLSQL dockable. Cannot run utPLSQL test.");
+ return false;
+ } else {
+ if (isRunningInSqlDeveloper()) {
+ RunnerFactory.showDockable();
+ panel = dockable.getRunnerPanel();
+ context.setView(dockable);
+ } else {
+ frame = new JFrame("utPLSQL Runner Panel");
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ panel = new RunnerPanel();
+ frame.add(panel.getGUI());
+ frame.setPreferredSize(new Dimension(600, 800));
+ frame.pack();
+ final Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
+ frame.setLocation(dim.width / 2 - frame.getSize().width / 2,
+ dim.height / 2 - frame.getSize().height / 2);
+ frame.setVisible(true);
+ }
+ initRun();
+ return true;
+ }
+ }
+
+ public void runTestAsync() {
+ // start tests when the GUI has been successfully initialized.
+ if (initGUI()) {
+ // the consumer
+ consumerThread = new Thread(this::consume);
+ consumerThread.setName("realtime consumer");
+ consumerThread.start();
+ // avoid concurrency on output header table to fix issue #80
+ SystemTools.sleep(100);
+ // the producer
+ producerThread = new Thread(this::produce);
+ producerThread.setName("realtime producer");
+ producerThread.start();
+ }
+ }
+
+ public Thread getProducerThread() {
+ return producerThread;
+ }
+
+ public Thread getConsumerThread() {
+ return consumerThread;
+ }
+
+ // for testing purposes only
+ public Run getRun() {
+ return run;
+ }
+
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.java b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.java
new file mode 100644
index 00000000..5a0773b2
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.runner;
+
+import java.awt.Container;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.swing.JSplitPane;
+
+import oracle.dbtools.raptor.runner.DBStarterFactory;
+import oracle.ide.Context;
+import oracle.jdevimpl.runner.debug.DebuggingProcess;
+import oracle.jdevimpl.runner.run.JRunner;
+import org.utplsql.sqldev.exception.GenericDatabaseAccessException;
+import org.utplsql.sqldev.exception.GenericRuntimeException;
+import org.utplsql.sqldev.model.DatabaseTools;
+import org.utplsql.sqldev.model.StringTools;
+import org.utplsql.sqldev.model.SystemTools;
+import org.utplsql.sqldev.model.preference.PreferenceModel;
+import org.utplsql.sqldev.resources.UtplsqlResources;
+
+import oracle.dbtools.worksheet.WorksheetResultPanel;
+import oracle.dbtools.worksheet.editor.OpenWorksheetWizard;
+import oracle.dbtools.worksheet.editor.Worksheet;
+import oracle.dbtools.worksheet.utils.WorksheetUtil;
+import oracle.ide.Ide;
+import oracle.ide.config.Preferences;
+import oracle.ide.controller.IdeAction;
+
+public class UtplsqlWorksheetRunner {
+ private static final Logger logger = Logger.getLogger(UtplsqlWorksheetRunner.class.getName());
+
+ private final PreferenceModel preferences;
+ private final List pathList;
+ private String connectionName;
+ private boolean debug = false;
+
+ public UtplsqlWorksheetRunner(final List pathList, final String connectionName) {
+ this.pathList = pathList;
+ preferences = PreferenceModel.getInstance(Preferences.getPreferences());
+ setConnection(connectionName);
+ }
+
+ public void enableDebugging() {
+ this.debug = true;
+ }
+
+ private void setConnection(final String connectionName) {
+ if (connectionName != null && preferences.isUnsharedWorksheet()) {
+ try {
+ this.connectionName = DatabaseTools.createTemporaryOrPrivateConnection(connectionName);
+ } catch (GenericDatabaseAccessException e) {
+ final String msg = "Failed to create temporary/private connection based on " + connectionName + ".";
+ logger.severe(() -> msg);
+ throw new GenericDatabaseAccessException(msg, e);
+ }
+ } else {
+ this.connectionName = connectionName;
+ }
+ }
+
+ private CharSequence getCode() {
+ StringBuilder sb = new StringBuilder();
+ if (!debug) {
+ if (preferences.isResetPackage()) {
+ sb.append("execute dbms_session.reset_package;\n");
+ }
+ sb.append("set serveroutput on size unlimited\n");
+ if (preferences.isClearScreen()) {
+ sb.append("clear screen\n");
+ }
+ if (pathList.size() == 1) {
+ sb.append("execute ut.run('");
+ sb.append(pathList.get(0));
+ sb.append("');\n");
+ } else {
+ // we want a horizontal dense output because we resize the worksheet to fit the command in common cases
+ sb.append("execute ut.run(ut_varchar2_list(");
+ sb.append(StringTools.getCSV(pathList, "").replace("\n", ""));
+ sb.append("));\n");
+ }
+ } else {
+ sb.append("begin\n");
+ sb.append(" ut.run(\n");
+ sb.append(" ut_varchar2_list(\n");
+ sb.append(StringTools.getCSV(pathList, 9));
+ sb.append(" )\n");
+ sb.append(" );\n");
+ sb.append("end;\n");
+ }
+ return sb;
+ }
+
+ private Worksheet openWorksheet() {
+ final Worksheet worksheet = (Worksheet) OpenWorksheetWizard.openNewTempWorksheet(connectionName,
+ getCode().toString());
+ if (connectionName == null) {
+ worksheet.setComboConnection(null);
+ }
+ WorksheetUtil.setWorksheetTabName(worksheet.getContext().getNode().getURL(),
+ UtplsqlResources.getString("WORKSHEET_TITLE"));
+ worksheet.getContext().getNode().markDirty(false);
+ return worksheet;
+ }
+
+ private void resizeResultPanel(final Worksheet worksheet) {
+ SystemTools.sleep(200);
+ Container splitPane = null;
+ WorksheetResultPanel selectedResultPanel = worksheet.getSelectedResultPanel();
+ if (selectedResultPanel != null && selectedResultPanel.getGUI() != null && selectedResultPanel.getGUI().getParent() != null
+ && selectedResultPanel.getGUI().getParent().getParent() != null && selectedResultPanel.getGUI().getParent().getParent().getParent() != null) {
+ splitPane = selectedResultPanel.getGUI().getParent().getParent().getParent();
+ }
+ if (splitPane instanceof JSplitPane) {
+ ((JSplitPane) splitPane).setDividerLocation(0.15);
+ } else {
+ final String msg = "Could not adjust size of worksheet. Expected JSplitPane but got "
+ + (splitPane != null ? splitPane.getClass().getName() : "null") + ".";
+ logger.severe(msg);
+ }
+ }
+
+ // cannot use IdeAction to run debugger, because text has to be set in inaccessible PLSQLController.updateAction
+ private void runDebugger(final Worksheet worksheet, final String anonymousPlsqlBlock) {
+ try {
+ Context processContext = JRunner.prepareProcessContext(worksheet.getContext(), false);
+ DebuggingProcess process = new DebuggingProcess(processContext);
+ DBStarterFactory.PlSqlStarter starter = new DBStarterFactory.PlSqlStarter(process, anonymousPlsqlBlock, connectionName, worksheet.getContext());
+ starter.start();
+ } catch (Throwable t) {
+ String msg = t.getClass().getName() + " while debugging utPLSQL test.";
+ logger.severe(() -> msg);
+ throw new GenericRuntimeException(msg);
+ }
+ }
+
+ private void runScript(final Worksheet worksheet) {
+ if (preferences.isAutoExecute()) {
+ SystemTools.sleep(100);
+ if (debug) {
+ runDebugger(worksheet, worksheet.getFocusedEditorPane().getText());
+ } else {
+ final IdeAction action = ((IdeAction) Ide.getIdeActionMap().get(Ide.findCmdID("Worksheet.RunScript")));
+ if (action != null) {
+ try {
+ action.performAction(worksheet.getContext());
+ } catch (Exception e) {
+ logger.severe(() -> "Could not run script in worksheet due to " + e.getMessage() + ".");
+ }
+ if (!debug) {
+ resizeResultPanel(worksheet);
+ }
+ }
+ }
+ }
+ }
+
+ private void runTest() {
+ final Worksheet worksheet = openWorksheet();
+ runScript(worksheet);
+ logger.fine(() -> "utPLSQL test called for " + pathList + " in " + connectionName + ".");
+ }
+
+ public void runTestAsync() {
+ final Thread thread = new Thread(this::runTest);
+ thread.setName("utPLSQL run test");
+ thread.start();
+ }
+
+ public static void openWithCode(final String code, final String connectionName) {
+ final Worksheet worksheet = (Worksheet) OpenWorksheetWizard.openNewTempWorksheet(connectionName, code);
+ if (connectionName == null) {
+ worksheet.setComboConnection(null);
+ }
+ WorksheetUtil.setWorksheetTabName(worksheet.getContext().getNode().getURL(),
+ UtplsqlResources.getString("WORKSHEET_TITLE"));
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/snippet/SnippetMerger.java b/sqldev/src/main/java/org/utplsql/sqldev/snippet/SnippetMerger.java
new file mode 100644
index 00000000..1a10e5c9
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/snippet/SnippetMerger.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2019 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.snippet;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.nio.charset.Charset;
+import java.nio.file.Paths;
+import java.util.stream.Collectors;
+
+import javax.xml.parsers.DocumentBuilder;
+
+import org.utplsql.sqldev.model.FileTools;
+import org.utplsql.sqldev.model.XMLTools;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import oracle.dbtools.util.Resource;
+
+public class SnippetMerger {
+ private final XMLTools xmlTools = new XMLTools();
+ private final File userSnippetsFile;
+ private final String utplsqlSnippets;
+
+ public String getUtplsqlSnippetsAsString() {
+ final InputStream stream = getClass()
+ .getResourceAsStream("/org/utplsql/sqldev/resources/UtplsqlSnippets.xml");
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, Charset.defaultCharset()));
+ return reader.lines().collect(Collectors.joining(System.lineSeparator()));
+ }
+
+ public SnippetMerger() {
+ // works in SQL Developer only, otherwise a ExceptionInInitializerError is thrown
+ this(new File(Resource.RAPTOR_USER.getAbsolutePath() + File.separator + "UserSnippets.xml"));
+ }
+
+ public SnippetMerger(final File file) {
+ utplsqlSnippets = getUtplsqlSnippetsAsString();
+ userSnippetsFile = file;
+ }
+
+ public void merge() {
+ String result;
+ if (userSnippetsFile.exists()) {
+ // file exists, proper merge required
+ final String userSnippets = new String(FileTools.readFile(Paths.get(userSnippetsFile.getAbsolutePath())));
+ final DocumentBuilder docBuilder = xmlTools.createDocumentBuilder();
+ final Document userSnippetsDoc = xmlTools.parse(docBuilder, new InputSource(new StringReader(userSnippets)));
+ final NodeList userSnippetsGroups = xmlTools.getNodeList(userSnippetsDoc,
+ "/snippets/group[not(@category=\"utPLSQL Annotations\" or @category=\"utPLSQL Expectations\")]");
+ final Document utplsqlSnippetsDoc = xmlTools.parse(docBuilder, new InputSource(new StringReader(utplsqlSnippets)));
+ final NodeList utplsqlSnippetsGroups = xmlTools.getNodeList(utplsqlSnippetsDoc, "/snippets/group");
+ StringBuilder sb = new StringBuilder();
+ sb.append("\n");
+ sb.append("\n");
+ for (int i = 0; i < userSnippetsGroups.getLength(); i++) {
+ sb.append(" ");
+ sb.append(xmlTools.nodeToString(userSnippetsGroups.item(i), "code"));
+ sb.append('\n');
+ }
+ for (int i = 0; i < utplsqlSnippetsGroups.getLength(); i ++) {
+ sb.append(" ");
+ sb.append(xmlTools.nodeToString(utplsqlSnippetsGroups.item(i), "code"));
+ sb.append('\n');
+ }
+ sb.append("\n");
+ result = sb.toString();
+ } else {
+ // just copy
+ result = utplsqlSnippets;
+ }
+ FileTools.writeFile(Paths.get(userSnippetsFile.getAbsolutePath()), result.getBytes());
+ }
+
+ public String getTemplate() {
+ return utplsqlSnippets;
+ }
+
+ public File getFile() {
+ return userSnippetsFile;
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/common/DirectoryChooser.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/common/DirectoryChooser.java
new file mode 100644
index 00000000..2d2fc8e9
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/common/DirectoryChooser.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.ui.common;
+
+import java.io.File;
+import java.util.logging.Logger;
+
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JTextField;
+
+public class DirectoryChooser {
+ private static final Logger logger = Logger.getLogger(DirectoryChooser.class.getName());
+
+ // do not instantiate this class
+ private DirectoryChooser() {
+ super();
+ }
+
+ public static String choose(final JFrame parentFrame, final String title, final String initialDirectory) {
+ logger.finest(() -> "parantFrame: " + parentFrame);
+ String ret = null;
+ final JFileChooser chooser = new JFileChooser();
+ chooser.setCurrentDirectory(new File(initialDirectory));
+ chooser.setDialogTitle(title);
+ chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ chooser.setAcceptAllFileFilterUsed(false);
+ if (chooser.showOpenDialog(parentFrame) == JFileChooser.APPROVE_OPTION) {
+ ret = chooser.getSelectedFile().getAbsolutePath();
+ }
+ return ret;
+ }
+
+ public static void choose(final JFrame parentFrame, final String title, final JTextField textField) {
+ final String dir = choose(parentFrame, title, textField.getText());
+ if (dir != null) {
+ textField.setText(dir);
+ }
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.java
new file mode 100644
index 00000000..cfaebf81
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.ui.coverage;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.WindowEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.SwingUtilities;
+
+import org.utplsql.sqldev.coverage.CodeCoverageReporter;
+import org.utplsql.sqldev.model.DatabaseTools;
+import org.utplsql.sqldev.model.StringTools;
+import org.utplsql.sqldev.resources.UtplsqlResources;
+
+public class CodeCoverageReporterDialog extends JFrame implements ActionListener, FocusListener {
+ private static final long serialVersionUID = 5503685225300993401L;
+
+ private final CodeCoverageReporter reporter;
+ private final JButton runButton;
+ private final JButton cancelButton;
+ private final JPanel paneParams;
+ private int paramPos = (-1);
+ private final JTextField schemasTextField = new JTextField();
+ private final JTextArea includeObjectsTextArea = new JTextArea();
+ private final JTextArea excludeObjectsTextArea = new JTextArea();
+
+ public static void createAndShow(final CodeCoverageReporter reporter) {
+ SwingUtilities.invokeLater(() -> CodeCoverageReporterDialog.createAndShowWithinEventThread(reporter));
+ }
+
+ private static void createAndShowWithinEventThread(final CodeCoverageReporter reporter) {
+ // create and layout the dialog
+ final CodeCoverageReporterDialog frame = new CodeCoverageReporterDialog(reporter);
+ reporter.setFrame(frame);
+ frame.pack();
+ // center dialog
+ final Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
+ frame.setLocation(dim.width / 2 - frame.getSize().width / 2, dim.height / 2 - frame.getSize().height / 2);
+ frame.setAlwaysOnTop(true);
+ frame.setVisible(true);
+ }
+
+ public CodeCoverageReporterDialog(final CodeCoverageReporter reporter) {
+ super(UtplsqlResources.getString("WINDOW_CODE_COVERAGE_REPORT_LABEL"));
+ this.reporter = reporter;
+ final Container pane = getContentPane();
+ pane.setLayout(new GridBagLayout());
+ final GridBagConstraints c = new GridBagConstraints();
+
+ // parameters pane
+ paneParams = new JPanel(new GridBagLayout());
+ JTextArea pathsTextArea = new JTextArea();
+ pathsTextArea.setEditable(false);
+ pathsTextArea.setEnabled(false);
+ addParam(UtplsqlResources.getString("WINDOW_PATHS_LABEL"), StringTools.getSimpleCSV(reporter.getPathList()), pathsTextArea, 50, 2);
+ addParam(UtplsqlResources.getString("WINDOW_SCHEMAS_LABEL"), reporter.getSchemas(), schemasTextField, 0, 0);
+ addParam(UtplsqlResources.getString("WINDOW_INCLUDE_OBJECS_LABEL"), StringTools.getSimpleCSV(reporter.getIncludeObjectList()), includeObjectsTextArea, 66, 4);
+ addParam(UtplsqlResources.getString("WINDOW_EXCLUDE_OBJECS_LABEL"), "", excludeObjectsTextArea, 34, 1);
+ final JScrollPane scrollPane = new JScrollPane(paneParams);
+ scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
+ scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+ scrollPane.setBorder(BorderFactory.createEmptyBorder());
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 2;
+ c.insets = new Insets(10, 10, 0, 10); // top, left, bottom, right
+ c.anchor = GridBagConstraints.NORTH;
+ c.fill = GridBagConstraints.BOTH;
+ c.weightx = 1;
+ c.weighty = 1;
+ pane.add(scrollPane, c);
+
+ // Buttons pane
+ final JPanel panelButtons = new JPanel(new GridBagLayout());
+ runButton = new JButton(UtplsqlResources.getString("WINDOW_RUN_BUTTON"));
+ runButton.addActionListener(this);
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 1;
+ c.insets = new Insets(0, 0, 0, 0); // top, left, bottom, right
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0;
+ c.weighty = 0;
+ panelButtons.add(runButton, c);
+ cancelButton = new JButton(UtplsqlResources.getString("WINDOW_CANCEL_BUTTON"));
+ cancelButton.addActionListener(this);
+ c.gridx = 1;
+ c.insets = new Insets(0, 10, 0, 0); // top, left, bottom, right
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0;
+ c.weighty = 0;
+ panelButtons.add(cancelButton, c);
+ c.gridx = 1;
+ c.gridy = 1;
+ c.gridwidth = 1;
+ c.insets = new Insets(30, 10, 10, 10); // top, left, bottom, right
+ c.anchor = GridBagConstraints.EAST;
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0;
+ c.weighty = 0;
+ pane.add(panelButtons, c);
+ pane.setPreferredSize(new Dimension(500, 320));
+ SwingUtilities.getRootPane(runButton).setDefaultButton(runButton);
+ }
+
+ private void addParam(final String label, final String text, final Component component, final int height,
+ final double weighty) {
+ paramPos++;
+ final GridBagConstraints c = new GridBagConstraints();
+ final JLabel paramLabel = new JLabel(label);
+ c.gridx = 0;
+ c.gridy = paramPos;
+ c.gridwidth = 1;
+ c.insets = new Insets(10, 10, 0, 0); // top, left, bottom, right
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 0;
+ c.weighty = 0;
+ paneParams.add(paramLabel, c);
+ c.gridx = 1;
+ c.gridwidth = GridBagConstraints.REMAINDER;
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.BOTH;
+ c.insets = new Insets(10, 10, 0, 10); // top, left, bottom, right
+ c.weightx = 1;
+ c.weighty = weighty;
+ if (component instanceof JTextField) {
+ ((JTextField) component).setText(text);
+ paneParams.add(component, c);
+ } else if (component instanceof JTextArea) {
+ ((JTextArea) component).setText(text);
+ ((JTextArea) component).setLineWrap(true);
+ ((JTextArea) component).setWrapStyleWord(true);
+ JScrollPane scrollPane = new JScrollPane(component);
+ scrollPane.getViewport().setPreferredSize(new Dimension(200, height));
+ paneParams.add(scrollPane, c);
+ }
+ component.addFocusListener(this);
+ }
+
+ public void exit() {
+ dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
+ }
+
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ if (e.getSource() == runButton) {
+ reporter.setSchemas(schemasTextField.getText());
+ reporter.setIncludeObjects(includeObjectsTextArea.getText());
+ reporter.setExcludeObjects(excludeObjectsTextArea.getText());
+ schemasTextField.setEnabled(false);
+ includeObjectsTextArea.setEnabled(false);
+ excludeObjectsTextArea.setEnabled(false);
+ runButton.setEnabled(false);
+ reporter.runAsync();
+ } else {
+ if (e.getSource() == cancelButton) {
+ if (runButton.isEnabled()) {
+ // report is not yet started, just close the window
+ exit();
+ } else {
+ // report is being created...
+ // frame will close as soon as the connection is technically aborted
+ // database session is not cancelled. This is not a bug.
+ // to cancel the session you have to kill it via "ALTER SYSTEM KILL SESSION".
+ // However, the abort frees all resources on the client side.
+ DatabaseTools.abortConnection(reporter.getConnection());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void focusGained(final FocusEvent e) {
+ if (paneParams.isAncestorOf(e.getComponent())) {
+ // make component at cursor position is visible
+ final int x = e.getComponent().getLocationOnScreen().x - paneParams.getLocationOnScreen().x;
+ final int y = e.getComponent().getLocationOnScreen().y - paneParams.getLocationOnScreen().y;
+ final int width = e.getComponent().getBounds().width;
+ final int height = e.getComponent().getBounds().height;
+ final Rectangle rect = new Rectangle(x, y, width, height);
+ paneParams.scrollRectToVisible(rect);
+ }
+ }
+
+ @Override
+ public void focusLost(final FocusEvent e) {
+ // ignore
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.java
new file mode 100644
index 00000000..703766ff
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2018 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.ui.preference;
+
+import java.util.Map;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextField;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.table.DefaultTableModel;
+
+import org.utplsql.sqldev.model.StringTools;
+import org.utplsql.sqldev.model.preference.PreferenceModel;
+import org.utplsql.sqldev.resources.UtplsqlResources;
+import org.utplsql.sqldev.snippet.SnippetMerger;
+import org.utplsql.sqldev.ui.common.DirectoryChooser;
+
+import oracle.dbtools.raptor.templates.CodeTemplateUtil;
+import oracle.ide.panels.DefaultTraversablePanel;
+import oracle.ide.panels.TraversableContext;
+import oracle.ide.panels.TraversalException;
+import oracle.javatools.ui.layout.FieldLayoutBuilder;
+
+public class PreferencePanel extends DefaultTraversablePanel {
+ private static final long serialVersionUID = -2583957375062007813L;
+ private final JPanel runTestPanel = new JPanel();
+ private final JCheckBox useRealtimeReporterCheckBox = new JCheckBox();
+ private final JCheckBox unsharedWorksheetCheckBox = new JCheckBox();
+ private final JCheckBox resetPackageCheckBox = new JCheckBox();
+ private final JCheckBox clearScreenCheckBox = new JCheckBox();
+ private final JCheckBox autoExecuteCheckBox = new JCheckBox();
+ private final JCheckBox checkRunUtplsqlTestCheckBox = new JCheckBox();
+ private final JCheckBox useSmartTimesCheckBox = new JCheckBox();
+ private final JButton importSnippetsButton = new JButton(
+ UtplsqlResources.getString("PREF_IMPORT_SNIPPETS_BUTTON_LABEL"));
+ private final JPanel realtimeReporterPanel = new JPanel();
+ private final SpinnerNumberModel numberOfRunsInHistoryModel = new SpinnerNumberModel(1, 1, 100, 1);
+ private final JSpinner numberOfRunsInHistorySpinner = new JSpinner(numberOfRunsInHistoryModel);
+ private final JCheckBox showDisabledCounterCheckBox = new JCheckBox();
+ private final JCheckBox showWarningsCounterCheckBox = new JCheckBox();
+ private final JCheckBox showInfoCounterCheckBox = new JCheckBox();
+ private final JCheckBox showWarningIndicatorCheckBox = new JCheckBox();
+ private final JCheckBox showInfoIndicatorCheckBox = new JCheckBox();
+ private final JCheckBox showSuccessfulTestsCheckBox = new JCheckBox();
+ private final JCheckBox showDisabledTestsCheckBox = new JCheckBox();
+ private final JCheckBox showTestDescriptionCheckBox = new JCheckBox();
+ private final JCheckBox syncDetailTabCheckBox = new JCheckBox();
+ private final JCheckBox showSuitesCheckBox = new JCheckBox();
+ private final JPanel generateTestPanel = new JPanel();
+ private final JTextField testPackagePrefixTextField = new JTextField();
+ private final JTextField testPackageSuffixTextField = new JTextField();
+ private final JTextField testUnitPrefixTextField = new JTextField();
+ private final JTextField testUnitSuffixTextField = new JTextField();
+ private final SpinnerNumberModel numberOfTestsPerUnitModel = new SpinnerNumberModel(1, 1, 10, 1);
+ private final JSpinner numberOfTestsPerUnitSpinner = new JSpinner(numberOfTestsPerUnitModel);
+ private final JCheckBox checkGenerateUtplsqlTestCheckBox = new JCheckBox();
+ private final DefaultTableModel codeTemplatesModel = new DefaultTableModel(new Object[] { "Id", "Template" }, 0);
+ private final JButton createCodeTemplatesButton = new JButton();
+ private final JCheckBox generateCommentsCheckBox = new JCheckBox();
+ private final JCheckBox disableTestsCheckBox = new JCheckBox();
+ private final JTextField suitePathTextField = new JTextField();
+ private final SpinnerNumberModel indentSpacesModel = new SpinnerNumberModel(1, 1, 8, 1);
+ private final JSpinner indentSpacesSpinner = new JSpinner(indentSpacesModel);
+ private final JPanel oddgenPanel = new JPanel();
+ private final JTextField rootFolderInOddgenViewTextField = new JTextField();
+ private final JCheckBox generateFilesCheckBox = new JCheckBox();
+ private final JTextField outputDirectoryTextField = new JTextField();
+ private final JButton outputDirectoryBrowse = new JButton();
+ private final JCheckBox deleteExistingFilesCheckBox = new JCheckBox();
+
+ public PreferencePanel() {
+ layoutControls();
+ }
+
+ private void layoutControls() {
+ // run test group
+ final FieldLayoutBuilder runTab = new FieldLayoutBuilder(runTestPanel);
+ runTab.setAlignLabelsLeft(true);
+ runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_USE_REALTIME_REPORTER_LABEL"))
+ .component(useRealtimeReporterCheckBox)
+ .withHint(UtplsqlResources.getString("PREF_USE_REALTIME_REPORTER_HINT")));
+ runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_UNSHARED_WORKSHEET_LABEL"))
+ .component(unsharedWorksheetCheckBox));
+ runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_RESET_PACKAGE_LABEL"))
+ .component(resetPackageCheckBox));
+ runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_CLEAR_SCREEN_LABEL"))
+ .component(clearScreenCheckBox));
+ runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_AUTO_EXECUTE_LABEL"))
+ .component(autoExecuteCheckBox));
+ runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_CHECK_RUN_UTPLSQL_TEST_LABEL"))
+ .component(checkRunUtplsqlTestCheckBox));
+ runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_USE_SMART_TIMES_LABEL"))
+ .component(useSmartTimesCheckBox));
+ runTab.addVerticalGap();
+ runTab.addRow(importSnippetsButton);
+ runTab.addVerticalSpring();
+
+ // realtime reporter group
+ final FieldLayoutBuilder rrTab = new FieldLayoutBuilder(realtimeReporterPanel);
+ rrTab.setAlignLabelsLeft(true);
+ rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_NUMBER_OF_RUNS_IN_HISTORY_LABEL"))
+ .component(numberOfRunsInHistorySpinner));
+ rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_DISABLED_COUNTER_LABEL"))
+ .component(showDisabledCounterCheckBox));
+ rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_WARNINGS_COUNTER_LABEL"))
+ .component(showWarningsCounterCheckBox));
+ rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_INFO_COUNTER_LABEL"))
+ .component(showInfoCounterCheckBox));
+ rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_WARNING_INDICATOR_LABEL"))
+ .component(showWarningIndicatorCheckBox));
+ rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_INFO_INDICATOR_LABEL"))
+ .component(showInfoIndicatorCheckBox));
+ rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_SUCCESSFUL_TESTS_LABEL"))
+ .component(showSuccessfulTestsCheckBox));
+ rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_DISABLED_TESTS_LABEL"))
+ .component(showDisabledTestsCheckBox));
+ rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_TEST_DESCRIPTION_LABEL"))
+ .component(showTestDescriptionCheckBox));
+ rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SYNC_DETAIL_TAB_LABEL"))
+ .component(syncDetailTabCheckBox));
+ rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_SUITES_LABEL"))
+ .component(showSuitesCheckBox));
+ rrTab.addVerticalSpring();
+
+ // generate test group
+ final FieldLayoutBuilder generateTab = new FieldLayoutBuilder(generateTestPanel);
+ generateTab.setAlignLabelsLeft(true);
+ generateTab.setStretchComponentsWithNoButton(true);
+ generateTab
+ .add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_TEST_PACKAGE_PREFIX_LABEL"))
+ .component(testPackagePrefixTextField));
+ generateTab
+ .add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_TEST_PACKAGE_SUFFIX_LABEL"))
+ .component(testPackageSuffixTextField));
+ generateTab.add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_TEST_UNIT_PREFIX_LABEL"))
+ .component(testUnitPrefixTextField));
+ generateTab.add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_TEST_UNIT_SUFFIX_LABEL"))
+ .component(testUnitSuffixTextField));
+ generateTab.add(
+ generateTab.field().label().withText(UtplsqlResources.getString("PREF_NUMBER_OF_TESTS_PER_UNIT_LABEL"))
+ .component(numberOfTestsPerUnitSpinner));
+ generateTab.add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_GENERATE_COMMENTS_LABEL"))
+ .component(generateCommentsCheckBox));
+ generateTab.add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_DISABLE_TESTS_LABEL"))
+ .component(disableTestsCheckBox));
+ generateTab.add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_SUITE_PATH_LABEL"))
+ .component(suitePathTextField));
+ generateTab.add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_INDENT_SPACES_LABEL"))
+ .component(indentSpacesSpinner));
+ generateTab.add(generateTab.field().label()
+ .withText(UtplsqlResources.getString("PREF_CHECK_GENERATE_UTPLSQL_TEST_LABEL"))
+ .component(checkGenerateUtplsqlTestCheckBox).button(createCodeTemplatesButton)
+ .withText(UtplsqlResources.getString("PREF_CREATE_CODE_TEMPLATES_BUTTON_LABEL")));
+ generateTab.addVerticalSpring();
+
+ // oddgen group
+ final FieldLayoutBuilder oddgenTab = new FieldLayoutBuilder(oddgenPanel);
+ oddgenTab.setAlignLabelsLeft(true);
+ oddgenTab.setStretchComponentsWithNoButton(true);
+ oddgenTab.add(
+ oddgenTab.field().label().withText(UtplsqlResources.getString("PREF_ROOT_FOLDER_IN_ODDGEN_VIEW_LABEL"))
+ .component(rootFolderInOddgenViewTextField));
+ oddgenTab.add(oddgenTab.field().label().withText(UtplsqlResources.getString("PREF_GENERATE_FILES_LABEL"))
+ .component(generateFilesCheckBox));
+ oddgenTab.add(oddgenTab.field().label().withText(UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_LABEL"))
+ .component(outputDirectoryTextField).button(outputDirectoryBrowse)
+ .withText(UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_BUTTON_LABEL")));
+ oddgenTab.add(oddgenTab.field().label().withText(UtplsqlResources.getString("PREF_DELETE_EXISTING_FILES_LABEL"))
+ .component(deleteExistingFilesCheckBox));
+ oddgenTab.addVerticalSpring();
+
+ // putting groups into tabbed panes
+ final JTabbedPane tabbedPane = new JTabbedPane();
+ tabbedPane.add(UtplsqlResources.getString("MENU_RUN_TEST_LABEL"), runTestPanel);
+ tabbedPane.add(UtplsqlResources.getString("MENU_REALTIME_REPORTER_LABEL"), realtimeReporterPanel);
+ tabbedPane.add(UtplsqlResources.getString("MENU_GENERATE_TEST_LABEL"), generateTestPanel);
+ tabbedPane.add("oddgen", oddgenPanel);
+ final FieldLayoutBuilder builder = new FieldLayoutBuilder(this);
+ builder.setAlignLabelsLeft(true);
+ builder.addVerticalField("", tabbedPane);
+ builder.addVerticalSpring();
+
+ // register action listener for import snippets button
+ importSnippetsButton.addActionListener(event -> importSnippets());
+
+ // register action listener for create code template button
+ createCodeTemplatesButton.addActionListener(event -> saveCodeTemplates());
+
+ // register action listener for directory chooser
+ outputDirectoryBrowse.addActionListener(event -> DirectoryChooser.choose(null,
+ UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_LABEL"), outputDirectoryTextField));
+ }
+
+ private void importSnippets() {
+ final SnippetMerger snippetMerger = new SnippetMerger();
+ snippetMerger.merge();
+ final String file = snippetMerger.getFile().getAbsolutePath();
+ final String message = String.format(UtplsqlResources.getString("PREF_CONFIRM_IMPORT_MESSAGE"), file);
+ JOptionPane.showMessageDialog(null, message, UtplsqlResources.getString("PREF_CONFIRM_IMPORT_TITLE"),
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+
+ private void loadCodeTemplates() {
+ final Map map = CodeTemplateUtil.loadFiles();
+ for (final Map.Entry entry : map.entrySet()) {
+ codeTemplatesModel.addRow(new Object[] { entry.getKey(), entry.getValue() });
+ }
+ }
+
+ private void saveCodeTemplates() {
+ Integer indentSpaces = (Integer) indentSpacesSpinner.getValue();
+ codeTemplatesModel
+ .addRow(new Object[] { "ut_spec", StringTools.replaceTabsWithSpaces(utSpecTemplate(), indentSpaces) });
+ codeTemplatesModel.addRow(new Object[] { "ut_spec_proc",
+ trimPlusNewLine(StringTools.replaceTabsWithSpaces(utSpecProcTemplate(), indentSpaces)) });
+ codeTemplatesModel
+ .addRow(new Object[] { "ut_body", StringTools.replaceTabsWithSpaces(utBodyTemplate(), indentSpaces) });
+ codeTemplatesModel.addRow(new Object[] { "ut_body_proc",
+ trimPlusNewLine(StringTools.replaceTabsWithSpaces(utBodyProcTemplate(), indentSpaces)) });
+ CodeTemplateUtil.save(codeTemplatesModel);
+ }
+
+ private String trimPlusNewLine(final String input) {
+ return input.trim() + System.lineSeparator();
+ }
+
+ private CharSequence utSpecTemplate() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("create or replace package ");
+ sb.append(testPackagePrefixTextField.getText());
+ sb.append("[package_name]");
+ sb.append(testPackageSuffixTextField.getText());
+ sb.append(" is\n\n");
+ sb.append("\t--%suite\n");
+ if (!suitePathTextField.getText().isEmpty()) {
+ sb.append("\t--%suitepath(");
+ sb.append(suitePathTextField.getText());
+ sb.append(")\n");
+ }
+ sb.append("\n\t");
+ sb.append(utSpecProcTemplate().toString().trim().replace("\n", "\n\t"));
+ sb.append("\n\n");
+ sb.append("end ");
+ sb.append(testPackagePrefixTextField.getText());
+ sb.append("[package_name]");
+ sb.append(testPackageSuffixTextField.getText());
+ sb.append(";\n");
+ sb.append("/\n");
+ return sb;
+ }
+
+ private CharSequence utSpecProcTemplate() {
+ StringBuilder sb = new StringBuilder();
+ final Integer numberOfTestsPerUnit = (Integer) numberOfTestsPerUnitModel.getValue();
+ final boolean withContext = numberOfTestsPerUnit > 1;
+ if (withContext) {
+ sb.append("--%context([procedure_name])\n\n");
+ }
+ for (int i = 1; i <= numberOfTestsPerUnit; i ++) {
+ sb.append("--%test\n");
+ if (disableTestsCheckBox.isSelected()) {
+ sb.append("--%disabled\n");
+ }
+ sb.append("procedure ");
+ sb.append(testUnitPrefixTextField.getText());
+ sb.append("[procedure_name]");
+ sb.append(testUnitSuffixTextField.getText());
+ if (withContext) {
+ sb.append(i);
+ }
+ sb.append(";\n\n");
+ }
+ if (withContext) {
+ sb.append("--%endcontext\n\n");
+ }
+ return sb;
+ }
+
+ private CharSequence utBodyTemplate() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("create or replace package body ");
+ sb.append(testPackagePrefixTextField.getText());
+ sb.append("[package_name]");
+ sb.append(testPackageSuffixTextField.getText());
+ sb.append(" is\n\n\t");
+ sb.append(utBodyProcTemplate().toString().trim().replace("\n", "\n\t"));
+ sb.append("\n\n");
+ sb.append("end ");
+ sb.append(testPackagePrefixTextField.getText());
+ sb.append("[package_name]");
+ sb.append(testPackageSuffixTextField.getText());
+ sb.append(";\n");
+ sb.append("/\n");
+ return sb;
+ }
+
+ private CharSequence utBodyProcTemplate() {
+ StringBuilder sb = new StringBuilder();
+ final Integer numberOfTestsPerUnit = (Integer) numberOfTestsPerUnitModel.getValue();
+ final boolean withContext = numberOfTestsPerUnit > 1;
+ for (int i = 1; i <= numberOfTestsPerUnit; i++) {
+ if (generateCommentsCheckBox.isSelected()) {
+ sb.append("--\n");
+ sb.append("-- test");
+ if (withContext) {
+ sb.append(" [procedure_name] case ");
+ sb.append(i);
+ sb.append(": ...");
+ }
+ sb.append('\n');
+ sb.append("--\n");
+ }
+ sb.append("procedure ");
+ sb.append(testUnitPrefixTextField.getText());
+ sb.append("[procedure_name]");
+ sb.append(testUnitSuffixTextField.getText());
+ if (withContext) {
+ sb.append(i);
+ }
+ sb.append(" is\n");
+ sb.append("\tl_actual integer := 0;\n");
+ sb.append("\tl_expected integer := 1;\n");
+ sb.append("begin\n");
+ if (generateCommentsCheckBox.isSelected()) {
+ sb.append("\t-- arrange\n\n");
+ sb.append("\t-- act\n\n");
+ sb.append("\t-- assert\n");
+ }
+ sb.append("\tut.expect(l_actual).to_equal(l_expected);\n");
+ sb.append("end ");
+ sb.append(testUnitPrefixTextField.getText());
+ sb.append("[procedure_name]");
+ sb.append(testUnitSuffixTextField.getText());
+ if (withContext) {
+ sb.append(i);
+ }
+ sb.append(";\n\n");
+ }
+ return sb;
+ }
+
+ @Override
+ public void onEntry(final TraversableContext traversableContext) {
+ PreferenceModel info = getUserInformation(traversableContext);
+ useRealtimeReporterCheckBox.setSelected(info.isUseRealtimeReporter());
+ unsharedWorksheetCheckBox.setSelected(info.isUnsharedWorksheet());
+ resetPackageCheckBox.setSelected(info.isResetPackage());
+ clearScreenCheckBox.setSelected(info.isClearScreen());
+ autoExecuteCheckBox.setSelected(info.isAutoExecute());
+ checkRunUtplsqlTestCheckBox.setSelected(info.isCheckRunUtplsqlTest());
+ useSmartTimesCheckBox.setSelected(info.isUseSmartTimes());
+ numberOfRunsInHistorySpinner.setValue(info.getNumberOfRunsInHistory());
+ showDisabledCounterCheckBox.setSelected(info.isShowDisabledCounter());
+ showWarningsCounterCheckBox.setSelected(info.isShowWarningsCounter());
+ showInfoCounterCheckBox.setSelected(info.isShowInfoCounter());
+ showWarningIndicatorCheckBox.setSelected(info.isShowWarningIndicator());
+ showInfoIndicatorCheckBox.setSelected(info.isShowInfoIndicator());
+ showSuccessfulTestsCheckBox.setSelected(info.isShowSuccessfulTests());
+ showDisabledTestsCheckBox.setSelected(info.isShowDisabledTests());
+ showTestDescriptionCheckBox.setSelected(info.isShowTestDescription());
+ syncDetailTabCheckBox.setSelected(info.isSyncDetailTab());
+ showSuitesCheckBox.setSelected(info.isShowSuites());
+ testPackagePrefixTextField.setText(info.getTestPackagePrefix());
+ testPackageSuffixTextField.setText(info.getTestPackageSuffix());
+ testUnitPrefixTextField.setText(info.getTestUnitPrefix());
+ testUnitSuffixTextField.setText(info.getTestUnitSuffix());
+ numberOfTestsPerUnitSpinner.setValue(info.getNumberOfTestsPerUnit());
+ checkGenerateUtplsqlTestCheckBox.setSelected(info.isCheckGenerateUtplsqlTest());
+ loadCodeTemplates();
+ generateCommentsCheckBox.setSelected(info.isGenerateComments());
+ disableTestsCheckBox.setSelected(info.isDisableTests());
+ suitePathTextField.setText(info.getSuitePath());
+ indentSpacesSpinner.setValue(info.getIndentSpaces());
+ rootFolderInOddgenViewTextField.setText(info.getRootFolderInOddgenView());
+ generateFilesCheckBox.setSelected(info.isGenerateFiles());
+ outputDirectoryTextField.setText(info.getOutputDirectory());
+ deleteExistingFilesCheckBox.setSelected(info.isDeleteExistingFiles());
+ super.onEntry(traversableContext);
+ }
+
+ @Override
+ public void onExit(final TraversableContext traversableContext) throws TraversalException {
+ PreferenceModel info = getUserInformation(traversableContext);
+ info.setUseRealtimeReporter(useRealtimeReporterCheckBox.isSelected());
+ info.setUnsharedWorksheet(unsharedWorksheetCheckBox.isSelected());
+ info.setResetPackage(resetPackageCheckBox.isSelected());
+ info.setClearScreen(clearScreenCheckBox.isSelected());
+ info.setAutoExecute(autoExecuteCheckBox.isSelected());
+ info.setNumberOfRunsInHistory((Integer) numberOfRunsInHistorySpinner.getValue());
+ info.setCheckRunUtplsqlTest(checkRunUtplsqlTestCheckBox.isSelected());
+ info.setUseSmartTimes(useSmartTimesCheckBox.isSelected());
+ info.setShowDisabledCounter(showDisabledCounterCheckBox.isSelected());
+ info.setShowWarningsCounter(showWarningsCounterCheckBox.isSelected());
+ info.setShowInfoCounter(showInfoCounterCheckBox.isSelected());
+ info.setShowWarningIndicator(showWarningIndicatorCheckBox.isSelected());
+ info.setShowInfoIndicator(showInfoIndicatorCheckBox.isSelected());
+ info.setShowSuccessfulTests(showSuccessfulTestsCheckBox.isSelected());
+ info.setShowDisabledTests(showDisabledTestsCheckBox.isSelected());
+ info.setShowTestDescription(showTestDescriptionCheckBox.isSelected());
+ info.setSyncDetailTab(syncDetailTabCheckBox.isSelected());
+ info.setShowSuites(showSuitesCheckBox.isSelected());
+ info.setTestPackagePrefix(testPackagePrefixTextField.getText());
+ info.setTestPackageSuffix(testPackageSuffixTextField.getText());
+ info.setTestUnitPrefix(testUnitPrefixTextField.getText());
+ info.setTestUnitSuffix(testUnitSuffixTextField.getText());
+ info.setNumberOfTestsPerUnit((Integer) numberOfTestsPerUnitSpinner.getValue());
+ info.setCheckGenerateUtplsqlTest(checkGenerateUtplsqlTestCheckBox.isSelected());
+ info.setGenerateComments(generateCommentsCheckBox.isSelected());
+ info.setDisableTests(disableTestsCheckBox.isSelected());
+ info.setSuitePath(suitePathTextField.getText());
+ info.setIndentSpaces((Integer) indentSpacesSpinner.getValue());
+ info.setRootFolderInOddgenView(rootFolderInOddgenViewTextField.getText());
+ info.setGenerateFiles(generateFilesCheckBox.isSelected());
+ info.setOutputDirectory(outputDirectoryTextField.getText());
+ info.setDeleteExistingFiles(deleteExistingFilesCheckBox.isSelected());
+ super.onExit(traversableContext);
+ }
+
+ private static PreferenceModel getUserInformation(final TraversableContext tc) {
+ return PreferenceModel.getInstance(tc.getPropertyStorage());
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.java
similarity index 55%
rename from sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.xtend
rename to sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.java
index f9bc93d2..77cf6644 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.xtend
+++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 Philipp Salvisberg
+ * Copyright 2019 Philipp Salvisberg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,16 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.utplsql.sqldev.model.parser
+package org.utplsql.sqldev.ui.runner;
-import java.util.List
-import org.eclipse.xtend.lib.annotations.Accessors
-import org.utplsql.sqldev.model.AbstractModel
-import org.utplsql.sqldev.model.ut.Annotation
+import java.util.AbstractMap;
-@Accessors
-class PlsqlObject extends AbstractModel {
- String name
- Integer position
- List annotations
-}
\ No newline at end of file
+public class ComboBoxItem extends AbstractMap.SimpleEntry {
+ private static final long serialVersionUID = 7869442222989031548L;
+
+ public ComboBoxItem(final K key, final V value) {
+ super(key, value);
+ }
+
+ @Override
+ public String toString() {
+ return this.getValue().toString();
+ }
+}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.java
new file mode 100644
index 00000000..67777bbf
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2019 Philipp Salvisberg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.utplsql.sqldev.ui.runner;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.table.DefaultTableModel;
+
+import org.utplsql.sqldev.model.runner.Expectation;
+import org.utplsql.sqldev.resources.UtplsqlResources;
+
+public class FailuresTableModel extends DefaultTableModel {
+ private static final long serialVersionUID = 8119453059788497567L;
+ private List failedExpectations;
+ private final List