diff --git a/.gitignore b/.gitignore
index ba2ce844..b81a66d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,8 +4,6 @@
# Log file
*.log
-
-
# Package Files
*.jar
*.war
@@ -31,6 +29,7 @@ hs_err_pid*
.project
.classpath
**/.settings
+.vscode
# IntelliJ
**/.idea
diff --git a/README.md b/README.md
index 7f5a7ff5..fa77ed47 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,11 @@
## Introduction
-utPLSQL for SQL Developer extends Oracle's SQL Developer to
+utPLSQL for SQL Developer extends Oracle's SQL Developer to
* run utPLSQL unit tests
* debug utPLSQL unit tests
-* run utPLSQL code coverage reports
+* run utPLSQL code coverage reports
* generate utPLSQL unit tests (skeletons)
* provide utPLSQL annotations and expectations as snippets
@@ -18,7 +18,7 @@ In the Connections window, select a connection or one or more packages or packag
### 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.
+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.
@@ -32,11 +32,11 @@ The initialization timeout for the realtime reporter is increased to 1 hour. The
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.
+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`.
+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.
@@ -89,7 +89,7 @@ utPLSQL for SQL Developer implements the [OddgenGenerator2 interface](https://ww
* 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.
+The use of [oddgen for SQL Developer](https://www.oddgen.org/) is optional.
#### Run utPLSQL test
@@ -128,6 +128,7 @@ Binary releases are published [here](https://github.com/utPLSQL/utPLSQL-SQLDevel
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)
@@ -138,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 19.4.0
-2. [Download](https://maven.apache.org/download.cgi) and install Apache Maven 3.6.3
-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/SQLDeveloper19.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 can 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/generate_utplsql_test.png b/images/generate_utplsql_test.png
index 2ba8f9c4..49f45b58 100644
Binary files a/images/generate_utplsql_test.png 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
index 40e7fc40..af3ae0a5 100644
Binary files a/images/generate_utplsql_test_from_template.png 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
index 2ffeb7c9..f9438d03 100644
Binary files a/images/oddgen_generate_utplsql_test.png 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
index 9643d83a..72f5a2a1 100644
Binary files a/images/oddgen_run_utplsql_test.png and b/images/oddgen_run_utplsql_test.png differ
diff --git a/images/run_utplsql_test.png b/images/run_utplsql_test.png
index a8d600f9..44c76099 100644
Binary files a/images/run_utplsql_test.png 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
index 9cf8a7bf..6c7400f8 100644
Binary files a/images/snippets_annotations.png and b/images/snippets_annotations.png differ
diff --git a/images/snippets_expectations.png b/images/snippets_expectations.png
index af23dc17..ccbd7836 100644
Binary files a/images/snippets_expectations.png and b/images/snippets_expectations.png differ
diff --git a/sqldev/pom.xml b/sqldev/pom.xml
index 2372e0ca..a2340e3d 100644
--- a/sqldev/pom.xml
+++ b/sqldev/pom.xml
@@ -6,178 +6,203 @@
org.utplsqlorg.utplsql.sqldev
- 1.2.1-SNAPSHOT
+ 2.1.0-SNAPSHOTbundleUTF-8
- 1.8
- 1.8
-
- /Applications/SQLDeveloper19.4.0.app/Contents/Resources/sqldeveloper
+ 17
+ 24.3.1
+
+ /Applications/SQLDeveloper${sqldev.version}.app/Contents/Resources/sqldeveloperutplsql_for_SQLDev_${project.version}
-
-
- -noverify -Djava.util.logging.config.file=${project.basedir}/src/test/resources/logging.conf
-
+
-
+
oracleidert
- 13.0.0
+ ${sqldev.version}system${sqldev.basedir}/ide/lib/idert.jaroracle
- javatools-nodeps
- 13.0.0
+ oracle.javatools-nodeps
+ ${sqldev.version}system
- ${sqldev.basedir}/modules/oracle.javatools/javatools-nodeps.jar
+ ${sqldev.basedir}/external/oracle.javatools-nodeps.jaroraclejavatools
- 13.0.0
+ ${sqldev.version}system${sqldev.basedir}/ide/lib/javatools.jaroracleoracle.ide.ceditor
- 13.0.0
+ ${sqldev.version}system${sqldev.basedir}/ide/extensions/oracle.ide.ceditor.jaroracleoracle.ide
- 13.0.0
+ ${sqldev.version}system${sqldev.basedir}/ide/extensions/oracle.ide.jaroracleuic
- 13.0.0
+ ${sqldev.version}system${sqldev.basedir}/ide/lib/uic.jaroracleoracle.ide.navigator
- 13.0.0
+ ${sqldev.version}system${sqldev.basedir}/ide/extensions/oracle.ide.navigator.jaroraclejavax-ide
- 13.0.0
+ ${sqldev.version}system${sqldev.basedir}/ide/lib/javax-ide.jaroracleoracle.dbtools-common
- 19.4.0
+ ${sqldev.version}system${sqldev.basedir}/sqldeveloper/lib/dbtools-common.jar
+
+ oracle
+ oracle.dbtools-arbori
+ ${sqldev.version}
+ system
+ ${sqldev.basedir}/sqldeveloper/lib/dbtools-arbori.jar
+ oracleoracle.sqldeveloper
- 19.3.0
+ ${sqldev.version}system${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.jaroracleoracle.sqldeveloper.utils
- 19.3.0
+ ${sqldev.version}system${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.utils.jaroracleoracle.sqldeveloper.worksheet
- 19.3.0
+ ${sqldev.version}system${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.worksheet.jaroracle
- ojdbc8
- 19.3.0
+ oracle.sqldeveloper.schemabrowser
+ ${sqldev.version}system
- ${sqldev.basedir}/jdbc/lib/ojdbc8.jar
+ ${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.schemabrowser.jar
+
+
+ oracle
+ ojdbc11
+ ${sqldev.version}
+ system
+ ${sqldev.basedir}/jdbc/lib/ojdbc11.jaroracledbapi
- 19.3.0
+ ${sqldev.version}system${sqldev.basedir}/ide/lib/dbapi.jaroracleoracle.ide.db
- 19.3.0
+ ${sqldev.version}system${sqldev.basedir}/ide/extensions/oracle.ide.db.jaroracleoracle.jdeveloper.db.connection
- 19.3.0
+ ${sqldev.version}system${sqldev.basedir}/jdev/extensions/oracle.jdeveloper.db.connection.jaroracleoracle.jdeveloper.java.core.jar
- 13.0.0
+ ${sqldev.version}system${sqldev.basedir}/jdev/extensions/oracle.jdeveloper.java.core.jaroracleoracle.jdeveloper.runner.jar
- 13.0.0
+ ${sqldev.version}system${sqldev.basedir}/jdev/extensions/oracle.jdeveloper.runner.jaroracleoracle.ide.runner
- 19.3.0
+ ${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
+ oraclejewt4.jar
- 13.0.0
+ ${sqldev.version}system${sqldev.basedir}/modules/oracle.bali.jewt/jewt4.jaroracleshare.jar
- 13.0.0
+ ${sqldev.version}system${sqldev.basedir}/modules/oracle.bali.share/share.jar
-
+ com.google.code.findbugs
+ jsr305
+ 3.0.2
+ provided
+
+
+
org.osgiorg.osgi.core
- 5.0.0
+ 6.0.0provided
@@ -185,19 +210,19 @@
org.springframeworkspring-jdbc
- 5.2.6.RELEASE
+ 7.0.8org.springframeworkspring-core
- 5.2.6.RELEASE
+ 7.0.8org.springframeworkspring-web
- 5.2.6.RELEASE
+ 7.0.8
@@ -209,13 +234,13 @@
junitjunit
- 4.12
+ 4.13.2testorg.jetbrainsannotations
- 13.0
+ 26.1.0compile
@@ -223,7 +248,6 @@
${project.basedir}/src/main/java
- ${project.basedir}/src/test/javasrc/main/resources
@@ -232,40 +256,43 @@
+ ${project.basedir}/src/test/java
+
+
+ src/test/resources
+
+ **/*.*
+
+
+ org.apache.maven.plugins
- 3.8.1
+ 3.15.0maven-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.4org.apache.maven.pluginsmaven-surefire-plugin
- 2.22.2
+ 3.5.6
@@ -295,7 +322,7 @@
org.apache.maven.pluginsmaven-antrun-plugin
- 3.0.0
+ 3.1.0prepare-package
@@ -316,7 +343,7 @@
org.codehaus.mojoproperties-maven-plugin
- 1.0.0
+ 1.1.0initialize
@@ -336,7 +363,7 @@
org.codehaus.mojobuild-helper-maven-plugin
- 3.1.0
+ 3.3.0parse-version
@@ -406,7 +433,7 @@
org.apache.felixmaven-bundle-plugin
- 4.2.1
+ 6.0.2true${project.name}
@@ -423,18 +450,36 @@
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.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,
@@ -445,6 +490,7 @@
oracle.ide.db,
oracle.ide.runner,
oracle.sqldeveloper,
+ oracle.sqldeveloper.schemabrowser,
oracle.sqldeveloper.utils,
oracle.sqldeveloper.worksheet,
oracle.uic
@@ -458,7 +504,7 @@
maven-assembly-plugin
- 3.3.0
+ 3.8.0${final.name}false
@@ -480,7 +526,7 @@
net.nicoulaj.maven.pluginschecksum-maven-plugin
- 1.9
+ 1.11calculate-checksums
@@ -494,7 +540,7 @@
org.jacocojacoco-maven-plugin
- 0.8.5
+ 0.8.15
@@ -560,12 +606,21 @@
org.codehaus.mojobuildnumber-maven-plugin
- 1.4
+ 3.3.0
-
+
+
+
+
+ org.javassist
+ javassist
+ 3.30.2-GA
+
+
+ org.utplsql.sqldevExtension for running unit tests in SQL Developer.
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java
index 8c86ea7a..79bcaa79 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java
@@ -17,17 +17,25 @@
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.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+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;
@@ -42,15 +50,17 @@
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 List pathList;
- private List includeObjectList;
+ 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) {
@@ -58,6 +68,7 @@ public CodeCoverageReporter(final List pathList, final List incl
this.includeObjectList = includeObjectList;
setDefaultSchema();
setConnection(connectionName);
+ setAssetDir();
}
// constructor for testing purposes only
@@ -67,6 +78,7 @@ public CodeCoverageReporter(final List pathList, final List incl
this.includeObjectList = includeObjectList;
this.conn = conn;
setDefaultSchema();
+ setAssetDir();
}
private void setConnection(final String connectionName) {
@@ -105,6 +117,60 @@ private void setDefaultSchema() {
}
}
+ 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()) {
@@ -124,7 +190,7 @@ private void run() {
PreferenceModel preferences;
try {
preferences = PreferenceModel.getInstance(Preferences.getPreferences());
- } catch (NoClassDefFoundError error) {
+ } catch (NoClassDefFoundError | ExceptionInInitializerError error) {
// not running in SQL Developer (in tests)
preferences = PreferenceModel.getInstance(null);
}
@@ -142,7 +208,7 @@ private void run() {
private void runCodeCoverageWithRealtimeReporter() {
final UtplsqlRunner runner = new UtplsqlRunner(pathList, toStringList(schemas), toStringList(includeObjects),
- toStringList(excludeObjects), connectionName);
+ toStringList(excludeObjects), getHtmlReportAssetPath(), connectionName);
runner.runTestAsync();
}
@@ -152,7 +218,7 @@ private void runCodeCoverageStandalone() {
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));
+ toStringList(includeObjects), toStringList(excludeObjects), getHtmlReportAssetPath());
openInBrowser(html);
} finally {
try {
@@ -174,7 +240,7 @@ public static void openInBrowser(String html) {
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) && url != null) {
+ if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
desktop.browse(url.toURI());
logger.fine(() -> url.toExternalForm() + " opened in browser.");
} else {
@@ -229,9 +295,7 @@ public void setExcludeObjects(final String excludeObjects) {
}
public Thread runAsync() {
- final Thread thread = new Thread(() -> {
- run();
- });
+ final Thread thread = new Thread(this::run);
thread.setName("code coverage reporter");
thread.start();
return thread;
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java
index beface0c..1c7191a5 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java
@@ -17,6 +17,7 @@
import java.io.IOException;
import java.io.StringReader;
+import java.net.URL;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.ResultSet;
@@ -51,7 +52,6 @@
import oracle.jdbc.OracleTypes;
-@SuppressWarnings("StringBufferReplaceableByString")
public class RealtimeReporterDao {
private static final Logger logger = Logger.getLogger(RealtimeReporterDao.class.getName());
private static final int FIRST_VERSION_WITH_REALTIME_REPORTER = 3001004;
@@ -108,11 +108,11 @@ public void produceReport(final String reporterId, final List pathList)
public void produceReportWithCoverage(final String realtimeReporterId, final String coverageReporterId,
final List pathList, final List schemaList, final List includeObjectList,
- final List excludeObjectList) {
+ 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();\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");
@@ -143,7 +143,7 @@ public void produceReportWithCoverage(final String realtimeReporterId, final Str
sb.append(" sys.dbms_output.disable;\n");
sb.append("END;");
final String plsql = sb.toString();
- final Object[] binds = { realtimeReporterId, coverageReporterId };
+ final Object[] binds = { htmlReportAssetPath == null ? null : htmlReportAssetPath.toExternalForm(), realtimeReporterId, coverageReporterId };
jdbcTemplate.update(plsql, binds);
}
@@ -245,7 +245,6 @@ private RealtimeReporterEvent convert(final String itemType, final String text)
}
}
- @SuppressWarnings("DuplicatedCode")
private RealtimeReporterEvent convertToPreRunEvent(final Document doc) {
final PreRunEvent event = new PreRunEvent();
final Node totalNumberOfTestsNode = xmlTools.getNode(doc, "/event/totalNumberOfTests");
@@ -326,7 +325,6 @@ private RealtimeReporterEvent convertToPostTestEvent(final Document doc) {
return event;
}
- @SuppressWarnings("DuplicatedCode")
private void populate(final Suite suite, final Node node) {
if (node instanceof Element) {
suite.setId(xmlTools.getAttributeValue(node, "id"));
@@ -357,6 +355,7 @@ private void populate(final Test test, final Node node) {
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")));
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterEventConsumer.java b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterEventConsumer.java
index 65427f35..cede3ca7 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterEventConsumer.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterEventConsumer.java
@@ -18,5 +18,5 @@
import org.utplsql.sqldev.model.runner.RealtimeReporterEvent;
public interface RealtimeReporterEventConsumer {
- public abstract void process(final RealtimeReporterEvent event);
+ 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
index 48bfc696..605d376b 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java
@@ -15,11 +15,10 @@
*/
package org.utplsql.sqldev.dal;
-import java.sql.CallableStatement;
+import java.net.URL;
import java.sql.Connection;
-import java.sql.SQLException;
import java.sql.Types;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -37,13 +36,12 @@
public class UtplsqlDao {
public static final String UTPLSQL_PACKAGE_NAME = "UT";
- public static final int NOT_INSTALLED = 0;
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 JdbcTemplate jdbcTemplate;
+ private final JdbcTemplate jdbcTemplate;
// cache fields
private Boolean cachedDbaViewAccessible;
private String cachedUtplsqlSchema;
@@ -89,7 +87,7 @@ public int normalizedUtPlsqlVersionNumber() {
final String minor = m.group();
m.find();
final String bugfix = m.group();
- return Integer.valueOf(major) * 1000000 + Integer.valueOf(minor) * 1000 + Integer.valueOf(bugfix);
+ return Integer.parseInt(major) * 1000000 + Integer.parseInt(minor) * 1000 + Integer.parseInt(bugfix);
}
/**
@@ -103,13 +101,10 @@ public String getUtPlsqlVersion() {
sb.append("END;");
final String sql = sb.toString();
try {
- cachedUtPlsqlVersion = jdbcTemplate.execute(sql, new CallableStatementCallback() {
- @Override
- public String doInCallableStatement(final CallableStatement cs) throws SQLException {
- cs.registerOutParameter(1, Types.VARCHAR);
- cs.execute();
- return cs.getString(1);
- }
+ cachedUtPlsqlVersion = jdbcTemplate.execute(sql, (CallableStatementCallback) cs -> {
+ cs.registerOutParameter(1, Types.VARCHAR);
+ cs.execute();
+ return cs.getString(1);
});
} catch (DataAccessException e) {
// ignore error
@@ -140,7 +135,7 @@ public boolean isDbaViewAccessible() {
cachedDbaViewAccessible = false;
}
}
- return cachedDbaViewAccessible.booleanValue();
+ return cachedDbaViewAccessible;
}
public String getDbaView(String viewName) {
@@ -177,8 +172,7 @@ public String getUtplsqlSchema() {
sb.append("'");
final String sql = sb.toString();
try {
- final String schema = jdbcTemplate.queryForObject(sql, String.class);
- cachedUtplsqlSchema = schema;
+ cachedUtplsqlSchema = jdbcTemplate.queryForObject(sql, String.class);
} catch (EmptyResultDataAccessException e) {
cachedUtplsqlSchema = null;
}
@@ -228,18 +222,16 @@ public boolean containsUtplsqlTest(final String owner, final String objectName,
sb.append(" ? := l_return;\n");
sb.append("END;");
final String sql = sb.toString();
- return jdbcTemplate.execute(sql, new CallableStatementCallback() {
- @Override
- public Boolean doInCallableStatement(final CallableStatement cs) throws SQLException {
- cs.setString(1, owner);
- cs.setString(2, objectName);
- cs.setString(3, subobjectName);
- cs.registerOutParameter(4, Types.VARCHAR);
- cs.execute();
- final String ret = cs.getString(4);
- return "1".equals(ret);
- }
+ 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();
@@ -249,8 +241,9 @@ public Boolean doInCallableStatement(final CallableStatement cs) throws SQLExcep
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 Integer found = jdbcTemplate.queryForObject(sql, Integer.class, binds);
- return found > 0;
+ 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();
@@ -281,7 +274,7 @@ public Boolean doInCallableStatement(final CallableStatement cs) throws SQLExcep
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 > 0;
+ return found != null && found > 0;
}
} catch (EmptyResultDataAccessException e) {
return false;
@@ -301,17 +294,14 @@ public boolean containsUtplsqlTest(final String owner) {
sb.append(" ? := l_return;\n");
sb.append("END;");
final String sql = sb.toString();
- return jdbcTemplate.execute(sql, new CallableStatementCallback() {
- @Override
- public Boolean doInCallableStatement(final CallableStatement cs)
- throws SQLException {
- cs.setString(1, owner);
- cs.registerOutParameter(2, Types.VARCHAR);
- cs.execute();
- final String ret = cs.getString(2);
- return "1".equals(ret);
- }
+ 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);
}
@@ -329,18 +319,15 @@ public boolean containsUtplsqlTest(final String owner, final String objectName)
sb.append(" ? := l_return;\n");
sb.append("END;");
final String sql = sb.toString();
- return jdbcTemplate.execute(sql, new CallableStatementCallback() {
- @Override
- public Boolean doInCallableStatement(final CallableStatement cs)
- throws SQLException {
- cs.setString(1, owner);
- cs.setString(2, objectName);
- cs.registerOutParameter(3, Types.VARCHAR);
- cs.execute();
- final String ret = cs.getString(3);
- return "1".equals(ret);
- }
+ 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);
}
@@ -409,7 +396,7 @@ public List units(final String objectType, final String objectName) {
final Object[] binds = new Object[] {objectType, objectName};
return jdbcTemplate.queryForList(sql, String.class, binds);
} else {
- return Arrays.asList(objectName);
+ return Collections.singletonList(objectName);
}
}
@@ -867,30 +854,34 @@ public String getDbmsOutput(final int bufferSize) {
sb.append(" sys.dbms_output.get_lines(?, ?);\n");
sb.append("END;");
final String sql = sb.toString();
- OutputLines ret = null;
+ OutputLines ret;
do {
- ret = jdbcTemplate.execute(sql, new CallableStatementCallback() {
- @Override
- public OutputLines doInCallableStatement(final CallableStatement cs) throws SQLException {
- cs.registerOutParameter(1, Types.ARRAY, "DBMSOUTPUT_LINESARRAY");
- cs.registerOutParameter(2, Types.INTEGER);
- cs.setInt(2, bufferSize);
- cs.execute();
- final OutputLines out = new OutputLines();
+ 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));
- return out;
+ } catch (NullPointerException e) {
+ out.setLines(null);
+ out.setNumlines(0);
}
+ return out;
});
- for (int i = 0; i < ret.getNumlines(); i++) {
- final String line = ret.getLines()[i];
- if (line != null) {
- resultSb.append(ret.getLines()[i]);
+ 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());
}
- resultSb.append(System.lineSeparator());
}
- } while (ret.getNumlines() > 0);
+ } while (ret != null && ret.getNumlines() != null && ret.getNumlines() > 0);
return resultSb.toString();
}
@@ -907,12 +898,14 @@ public OutputLines doInCallableStatement(final CallableStatement cs) throws SQLE
* @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 List includeObjectList, final List excludeObjectList, final URL htmlReportAssetPath) {
StringBuilder sb = new StringBuilder();
sb.append("SELECT column_value\n");
sb.append(" FROM table(\n");
@@ -935,7 +928,14 @@ public String htmlCodeCoverage(final List pathList, final List s
sb.append(StringTools.getCSV(excludeObjectList, 16));
sb.append(" ),\n");
}
- sb.append(" a_reporter => ut_coverage_html_reporter()\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();
@@ -1022,16 +1022,13 @@ public String getSource(final String owner, final String objectType, final Strin
sb.append(" );\n");
sb.append("END;");
final String sql = sb.toString();
- return jdbcTemplate.execute(sql, new CallableStatementCallback() {
- @Override
- public String doInCallableStatement(final CallableStatement cs) throws SQLException {
- cs.registerOutParameter(1, Types.CLOB);
- cs.setString(2, owner);
- cs.setString(3, fixedObjectType);
- cs.setString(4, objectName);
- cs.execute();
- return cs.getString(1);
- }
+ 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);
});
}
@@ -1061,6 +1058,6 @@ public String getObjectType(final String owner, final String objectName) {
sb.append(" WHERE rownum = 1");
final String sql = sb.toString();
final Object[] binds = new Object[] {owner, objectName};
- return jdbcTemplate.queryForObject(sql, binds, String.class);
+ return jdbcTemplate.queryForObject(sql, String.class, binds);
}
}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.java b/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.java
index a4f59ba0..505e53ca 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.java
@@ -50,6 +50,7 @@
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;
@@ -135,7 +136,7 @@ public boolean update(final IdeAction action, final Context context) {
action.setEnabled(true);
}
}
- } else if (view instanceof DBNavigatorWindow) {
+ } 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++) {
@@ -192,7 +193,7 @@ public boolean update(final IdeAction action, final Context context) {
action.setEnabled(true);
}
}
- } else if (view instanceof DBNavigatorWindow) {
+ } 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];
@@ -367,7 +368,7 @@ public void runTest(final Context context, boolean withDebug) {
worksheet.runTestAsync();
}
}
- } else if (view instanceof DBNavigatorWindow) {
+ } else if (view instanceof DBNavigatorWindow || view instanceof SBWindow) {
final URL url = getURL(context);
if (url != null) {
final String connectionName = URLTools.getConnectionName(url);
@@ -469,7 +470,7 @@ public void codeCoverage(final Context context) {
final CodeCoverageReporter reporter = new CodeCoverageReporter(getPathList(path), includeObjectList, connectionName);
reporter.showParameterWindow();
}
- } else if (view instanceof DBNavigatorWindow) {
+ } else if (view instanceof DBNavigatorWindow || view instanceof SBWindow) {
logger.finer("Code coverage from DB navigator");
final URL url = getURL(context);
if (url != null) {
@@ -529,7 +530,7 @@ public void generateTest(final Context context) {
}
}
} else {
- if (view instanceof DBNavigatorWindow) {
+ if (view instanceof DBNavigatorWindow || view instanceof SBWindow) {
final URL url = getURL(context);
if (url != null) {
final String connectionName = URLTools.getConnectionName(url);
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/JsonToStringStyler.java b/sqldev/src/main/java/org/utplsql/sqldev/model/JsonToStringStyler.java
index 2fd04e73..7b4d429e 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/JsonToStringStyler.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/JsonToStringStyler.java
@@ -21,14 +21,16 @@
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 ToStringStyler INSTANCE = new JsonToStringStyler();
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));
@@ -45,7 +47,7 @@ private String getIndentSpaces(int indentOffset) {
private String getStringStyle(String value) {
StringBuilder sb = new StringBuilder();
sb.append('"');
- sb.append(value.replace("\"", "\\\"").replace("\n", "\\n").replace("\r", ""));
+ sb.append(value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "").replace("\t", "\\t"));
sb.append('"');
return sb.toString();
}
@@ -95,9 +97,13 @@ private String getMapStyle(Map, ?> map) {
private String getDefaultStyle(Object value) {
return String.valueOf(value);
}
-
+
+ public static ToStringStyler getInstance() {
+ return threadLocal.get();
+ }
+
@Override
- public void styleStart(StringBuilder buffer, Object obj) {
+ public void styleStart(@NotNull StringBuilder buffer, Object obj) {
indent++;
if (!obj.getClass().isArray()) {
buffer.append("{");
@@ -114,7 +120,7 @@ public void styleStart(StringBuilder buffer, Object obj) {
}
@Override
- public void styleEnd(StringBuilder buffer, Object obj) {
+ public void styleEnd(@NotNull StringBuilder buffer, Object obj) {
indent--;
newLine(buffer);
if (!obj.getClass().isArray()) {
@@ -125,7 +131,7 @@ public void styleEnd(StringBuilder buffer, Object obj) {
}
@Override
- public void styleField(StringBuilder buffer, String fieldName, @Nullable Object value) {
+ public void styleField(@NotNull StringBuilder buffer, @NotNull String fieldName, @Nullable Object value) {
newLine(buffer);
buffer.append('"');
buffer.append(fieldName);
@@ -135,7 +141,7 @@ public void styleField(StringBuilder buffer, String fieldName, @Nullable Object
}
@Override
- public void styleValue(StringBuilder buffer, Object value) {
+ public void styleValue(StringBuilder buffer, @Nullable Object value) {
buffer.append(style(value));
}
@@ -144,6 +150,7 @@ public void styleFieldSeparator(StringBuilder buffer) {
buffer.append(",");
}
+ @NotNull
@Override
public String style(Object value) {
if (value == null) {
@@ -157,7 +164,7 @@ public String style(Object value) {
} else if (value instanceof Map) {
return getMapStyle((Map, ?>) value);
} else {
- return getDefaultStyle(value);
+ 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
index de702252..550e6d72 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.java
@@ -18,7 +18,7 @@
import java.util.LinkedHashMap;
import java.util.Map;
-public class LimitedLinkedHashMap extends LinkedHashMap {
+public class LimitedLinkedHashMap extends LinkedHashMap {
private static final long serialVersionUID = -4184317926729190411L;
private final int maxEntries;
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/StringTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/StringTools.java
index 23fc20f9..23323043 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/StringTools.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/StringTools.java
@@ -15,9 +15,14 @@
*/
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() {
@@ -38,11 +43,11 @@ public static String getCSV(List list, String indent) {
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) {
@@ -74,4 +79,45 @@ public static String formatDateTime(final String 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/URLTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.java
index de225bcf..26190df3 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.java
@@ -43,7 +43,9 @@ 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"));
+ 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 "";
}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.java
index b9f2b982..090cf8d6 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.java
@@ -106,7 +106,7 @@ public String nodeToString(final Node node, final String cdataSectionElements) {
public String getAttributeValue(final Node node, final String namedItem) {
String value = null;
if (node instanceof Element) {
- final NamedNodeMap attributes = ((Element) node).getAttributes();
+ final NamedNodeMap attributes = node.getAttributes();
if (attributes != null) {
final Node item = attributes.getNamedItem(namedItem);
if (item != null) {
@@ -130,13 +130,13 @@ 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) {
+ 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 {
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
index 30fdfbf9..41fc456e 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/oddgen/GenContext.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/oddgen/GenContext.java
@@ -40,7 +40,7 @@ public Connection getConn() {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append("conn", conn)
.append("objectType", objectType)
.append("objectName", objectName)
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
index 122cfc1c..51806d53 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.java
@@ -29,7 +29,7 @@ public class PlsqlObject {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append("name", name)
.append("type", type)
.append("position", position)
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
index 990e877c..c6bdc5f8 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.java
@@ -25,7 +25,7 @@ public class Unit {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append("name", name)
.append("position", position)
.append("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
index 693ace05..13bd9794 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.java
@@ -53,6 +53,7 @@ public static PreferenceModel getInstance(final PropertyStorage prefs) {
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";
@@ -70,7 +71,7 @@ public static PreferenceModel getInstance(final PropertyStorage prefs) {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append(KEY_USE_REALTIME_REPORTER, isUseRealtimeReporter())
.append(KEY_UNSHARED_WORKSHEET, isUnsharedWorksheet())
.append(KEY_RESET_PACKAGE, isResetPackage())
@@ -88,6 +89,7 @@ public String toString() {
.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())
@@ -170,7 +172,7 @@ public void setNumberOfRunsInHistory(final int runs) {
}
public boolean isShowDisabledCounter() {
- return getHashStructure().getBoolean(KEY_SHOW_DISABLED_COUNTER, false);
+ return getHashStructure().getBoolean(KEY_SHOW_DISABLED_COUNTER, true);
}
public void setShowDisabledCounter(final boolean showDisabledCounter) {
@@ -178,7 +180,7 @@ public void setShowDisabledCounter(final boolean showDisabledCounter) {
}
public boolean isShowWarningsCounter() {
- return getHashStructure().getBoolean(KEY_SHOW_WARNINGS_COUNTER, false);
+ return getHashStructure().getBoolean(KEY_SHOW_WARNINGS_COUNTER, true);
}
public void setShowWarningsCounter(final boolean showWarningCounter) {
@@ -186,7 +188,7 @@ public void setShowWarningsCounter(final boolean showWarningCounter) {
}
public boolean isShowInfoCounter() {
- return getHashStructure().getBoolean(KEY_SHOW_INFO_COUNTER, false);
+ return getHashStructure().getBoolean(KEY_SHOW_INFO_COUNTER, true);
}
public void setShowInfoCounter(final boolean showInfoCounter) {
@@ -194,7 +196,7 @@ public void setShowInfoCounter(final boolean showInfoCounter) {
}
public boolean isShowWarningIndicator() {
- return getHashStructure().getBoolean(KEY_SHOW_WARNING_INDICATOR, false);
+ return getHashStructure().getBoolean(KEY_SHOW_WARNING_INDICATOR, true);
}
public void setShowWarningIndicator(final boolean showWarningIndicator) {
@@ -202,7 +204,7 @@ public void setShowWarningIndicator(final boolean showWarningIndicator) {
}
public boolean isShowInfoIndicator() {
- return getHashStructure().getBoolean(KEY_SHOW_INFO_INDICATOR, false);
+ return getHashStructure().getBoolean(KEY_SHOW_INFO_INDICATOR, true);
}
public void setShowInfoIndicator(final boolean showInfoIndicator) {
@@ -226,7 +228,7 @@ public void setShowDisabledTests(final boolean showDisabledTests) {
}
public boolean isShowTestDescription() {
- return getHashStructure().getBoolean(KEY_SHOW_TEST_DESCRIPTION, false);
+ return getHashStructure().getBoolean(KEY_SHOW_TEST_DESCRIPTION, true);
}
public void setShowTestDescription(final boolean showTestDescription) {
@@ -241,6 +243,14 @@ 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_");
}
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
index e14dc387..255a52cc 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.java
@@ -35,7 +35,7 @@ public Counter() {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append("disabled", disabled)
.append("success", success)
.append("failure", failure)
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
index 2738bce1..2ca29452 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.java
@@ -28,7 +28,7 @@ public class Expectation {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append("description", description)
.append("message", message)
.append("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
index 059102c5..76b859c2 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.java
@@ -15,11 +15,16 @@
*/
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;
@@ -34,8 +39,10 @@ public Item() {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append("id", id)
+ .append("name", name)
+ .append("description", description)
.append("startTime", startTime)
.append("endTime", endTime)
.append("executionTime", executionTime)
@@ -43,8 +50,66 @@ public String toString() {
.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;
@@ -54,6 +119,22 @@ 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;
}
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
index f24f8f52..73b4599a 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.java
@@ -29,7 +29,7 @@ public abstract class PostEvent extends RealtimeReporterEvent {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append("startTime", startTime)
.append("endTime", endTime)
.append("executionTime", executionTime)
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
index 8c8dbe2f..0a167432 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.java
@@ -22,7 +22,7 @@ public class PostRunEvent extends PostEvent {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
// ancestor
.append("startTime", getStartTime())
.append("endTime", getEndTime())
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
index 292671c4..94671c33 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.java
@@ -23,7 +23,7 @@ public class PostSuiteEvent extends PostEvent {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
// ancestor
.append("startTime", getStartTime())
.append("endTime", getEndTime())
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
index 92a43484..36b99abd 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.java
@@ -29,7 +29,7 @@ public class PostTestEvent extends PostEvent {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
// ancestor
.append("startTime", getStartTime())
.append("endTime", getEndTime())
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
index dd1f0db8..3e150fec 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.java
@@ -27,7 +27,7 @@ public class PreRunEvent extends RealtimeReporterEvent {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append("items", items)
.append("totalNumberOfTests", totalNumberOfTests)
.toString();
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.java
index 7b3ccf5c..7026952a 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.java
@@ -23,7 +23,7 @@ public class PreSuiteEvent extends RealtimeReporterEvent {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append("id", id)
.toString();
}
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
index 1fdbd362..30019335 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.java
@@ -25,7 +25,7 @@ public class PreTestEvent extends RealtimeReporterEvent {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append("id", id)
.append("testNumber", testNumber)
.append("totalNumberOfTests", totalNumberOfTests)
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
index 9d6b3358..09d38e2f 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.java
@@ -17,12 +17,15 @@
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;
-@SuppressWarnings("unused")
public class Run {
private String reporterId;
private String connectionName;
@@ -37,7 +40,9 @@ public class Run {
private Integer infoCount;
private String errorStack;
private String serverOutput;
- private LinkedHashMap tests;
+ private final Set items;
+ private Map tests;
+ private Map itemNodes;
private String status;
private Long start;
// to abort connections, producerConn is handled by UtplsqlRunner
@@ -45,7 +50,7 @@ public class Run {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append("reporterId", reporterId)
.append("connectionName", connectionName)
.append("pathList", pathList)
@@ -60,6 +65,7 @@ public String toString() {
.append("errorStack", errorStack)
.append("serverOutput", serverOutput)
.append("tests", tests)
+ .append("rootNode", itemNodes.get(reporterId))
.append("status", status)
.append("start", start)
.append("endTime", endTime)
@@ -72,7 +78,10 @@ public Run(final String reporterId, final String connectionName, final List();
tests = new LinkedHashMap<>();
+ itemNodes = new LinkedHashMap<>();
+ createRootNode();
}
public void setStartTime(final String startTime) {
@@ -82,21 +91,58 @@ public void setStartTime(final String startTime) {
public String getName() {
final String time = startTime.substring(11, 19);
- final String conn = connectionName != null ? connectionName.substring(15) : "n/a";
+ 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) {
- if (item instanceof Test) {
- tests.put(item.getId(), (Test) item);
- }
+ this.items.add(item);
if (item instanceof Suite) {
- put(((Suite) item).getItems());
+ 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);
}
@@ -216,12 +262,20 @@ public void setServerOutput(final String serverOutput) {
}
public LinkedHashMap getTests() {
- return tests;
+ 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;
@@ -246,4 +300,5 @@ public Connection getConsumerConn() {
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
index b6ec4be2..f9f036db 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.java
@@ -22,8 +22,6 @@
import org.utplsql.sqldev.model.JsonToStringStyler;
public class Suite extends Item {
- private String name;
- private String description;
private List items;
public Suite() {
@@ -32,9 +30,11 @@ public Suite() {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ 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())
@@ -42,29 +42,15 @@ public String toString() {
.append("errorStack", getErrorStack())
.append("serverOutput", getServerOutput())
.append("warnings", getWarnings())
+ .append("parentId", getParentId())
+ .append("statusIcon", getStatusIcon())
+ .append("warningIcon", getWarningIcon())
+ .append("infoIcon", getInfoIcon())
// local
- .append("name", name)
- .append("description", description)
.append("items", items)
.toString();
}
- 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 List getItems() {
return 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
index 69338662..a0e893f8 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.java
@@ -17,29 +17,26 @@
import java.util.List;
-import javax.swing.Icon;
-
import org.springframework.core.style.ToStringCreator;
import org.utplsql.sqldev.model.JsonToStringStyler;
-import org.utplsql.sqldev.resources.UtplsqlResources;
-@SuppressWarnings("unused")
public class Test extends Item {
private String executableType;
private String ownerName;
private String objectName;
private String procedureName;
private Boolean disabled;
- private String name;
- private String description;
+ private String disabledReason;
private Integer testNumber;
private List failedExpectations;
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ 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())
@@ -47,58 +44,22 @@ public String toString() {
.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("name", name)
- .append("description", description)
+ .append("disabledReason", disabledReason)
.append("testNumber", testNumber)
.append("failedExpectations", failedExpectations)
- .append("statusIcon", getStatusIcon())
- .append("warningIcon", getWarningIcon())
- .append("infoIcon", getInfoIcon())
.toString();
}
- public Icon getStatusIcon() {
- Icon icon = null;
- if (getStartTime() != null && getEndTime() == null) {
- icon = UtplsqlResources.getIcon("PROGRESS_ICON");
- } else {
- if (getCounter() != null) {
- if (getCounter().getSuccess() > 0) {
- icon = UtplsqlResources.getIcon("SUCCESS_ICON");
- } else if (getCounter().getError() > 0) {
- icon = UtplsqlResources.getIcon("ERROR_ICON");
- } else if (getCounter().getFailure() > 0) {
- icon = UtplsqlResources.getIcon("FAILURE_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 getExecutableType() {
return executableType;
}
@@ -139,20 +100,12 @@ public void setDisabled(final Boolean disabled) {
this.disabled = disabled;
}
- public String getName() {
- return name;
- }
-
- public void setName(final String name) {
- this.name = name;
- }
-
- public String getDescription() {
- return description;
+ public String getDisabledReason() {
+ return disabledReason;
}
- public void setDescription(final String description) {
- this.description = description;
+ public void setDisabledReason(final String disabledReason) {
+ this.disabledReason = disabledReason;
}
public Integer getTestNumber() {
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
index 3f69ef77..47e792c7 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/ut/Annotation.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/ut/Annotation.java
@@ -27,7 +27,7 @@ public class Annotation {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append("objectOwner", objectOwner)
.append("objectName", objectName)
.append("name", name)
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
index a8a041ae..75343d6b 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/model/ut/OutputLines.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/model/ut/OutputLines.java
@@ -24,7 +24,7 @@ public class OutputLines {
@Override
public String toString() {
- return new ToStringCreator(this, JsonToStringStyler.INSTANCE)
+ return new ToStringCreator(this, JsonToStringStyler.getInstance())
.append("lines", lines)
.append("numlines", numlines)
.toString();
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/RunGenerator.java b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/RunGenerator.java
index 215baf2f..f88331ba 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/RunGenerator.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/RunGenerator.java
@@ -138,29 +138,29 @@ public String generateProlog(final Connection conn, final List 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("execute dbms_session.reset_package;\n");
}
- sb.append("SET SERVEROUTPUT ON SIZE UNLIMITED\n");
+ sb.append("set serveroutput on size unlimited\n");
if (YES.equals(params.get(CLEAR_SCREEN))) {
- sb.append("CLEAR SCREEN\n");
+ sb.append("clear screen\n");
}
if (dedupNodes.size() == 1) {
- sb.append("EXECUTE ut.run('");
+ 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("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("end;\n");
sb.append("/\n");
}
final String ret = sb.toString();
- return StringTools.replaceTabsWithSpaces(ret, Integer.valueOf(params.get(INDENT_SPACES)));
+ return StringTools.replaceTabsWithSpaces(ret, Integer.parseInt(params.get(INDENT_SPACES)));
}
@Override
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.java b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.java
index 77b2aa42..f4602ea6 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.java
@@ -17,11 +17,7 @@
import java.io.File;
import java.sql.Connection;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
+import java.util.*;
import java.util.logging.Logger;
import org.oddgen.sqldev.generators.OddgenGenerator2;
@@ -67,11 +63,11 @@ private GenContext toContext(final Node node) {
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.valueOf(node.getParams().get(NUMBER_OF_TESTS_PER_UNIT))).intValue());
+ 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.valueOf(node.getParams().get(INDENT_SPACES))).intValue());
+ context.setIndentSpaces(Integer.parseInt(node.getParams().get(INDENT_SPACES)));
return context;
}
@@ -81,9 +77,7 @@ private void resetConsoleOutput() {
private void saveConsoleOutput(final String s) {
if (s != null) {
- for (final String line : s.split("[\\n\\r]+")) {
- consoleOutput.add(line);
- }
+ consoleOutput.addAll(Arrays.asList(s.split("[\\n\\r]+")));
}
}
@@ -98,7 +92,7 @@ private void logConsoleOutput() {
}
private String deleteFile(final File file) {
- String ret = null;
+ String ret;
if (file.delete()) {
StringBuilder sb = new StringBuilder();
sb.append(file.getAbsoluteFile());
@@ -117,11 +111,15 @@ private String deleteFile(final File file) {
private CharSequence deleteFiles(final String directory) {
StringBuilder sb = new StringBuilder();
final File dir = new File(directory);
- for (final File file : dir.listFiles()) {
- if (!file.isDirectory() && (file.getName().endsWith(".pks") || file.getName().endsWith(".pkb"))) {
- sb.append(deleteFile(file));
- sb.append('\n');
+ 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;
}
@@ -241,9 +239,9 @@ public String generateProlog(final Connection conn, final List nodes) {
if (generateFiles) {
resetConsoleOutput();
saveConsoleOutput(templateTools.mkdirs(outputDirectory));
- if (deleteExistingfiles) {
- saveConsoleOutput(deleteFiles(outputDirectory).toString());
- }
+ if (deleteExistingfiles) {
+ saveConsoleOutput(deleteFiles(outputDirectory).toString());
+ }
sb.append("--\n");
sb.append("-- install generated utPLSQL test packages\n");
sb.append("--\n");
@@ -253,8 +251,9 @@ public String generateProlog(final Connection conn, final List nodes) {
context.setConn(conn);
final TestTemplate testTemplate = new TestTemplate(context);
if (generateFiles) {
- final String packageName = context.getTestPackagePrefix() + nodeTools.toObjectName(node)
- + context.getTestPackageSuffix();
+ 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()));
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.java b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.java
index 036b5995..7bbdff3f 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.java
@@ -24,15 +24,14 @@
import org.utplsql.sqldev.model.oddgen.GenContext;
public class TestTemplate {
- private GenContext context;
- private UtplsqlDao dao;
- private List units;
- private DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
- private String today = dateTimeFormatter.format(LocalDateTime.now());
+ 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;
- dao = new UtplsqlDao(context.getConn());
+ UtplsqlDao dao = new UtplsqlDao(context.getConn());
units = dao.units(context.getObjectType(), context.getObjectName());
}
@@ -40,13 +39,13 @@ 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("create or replace package ");
sb.append(packageName);
- sb.append(" IS\n\n");
+ sb.append(" is\n");
if (context.isGenerateComments()) {
- sb.append("\t/* generated by utPLSQL for SQL Developer on ");
+ sb.append("\n\t-- generated by utPLSQL for SQL Developer on ");
sb.append(today);
- sb.append(" */\n\n");
+ sb.append("\n\n");
}
sb.append("\t--%suite(");
sb.append(packageName);
@@ -54,8 +53,9 @@ public String generateSpec() {
if (!context.getSuitePath().isEmpty()) {
sb.append("\t--%suitepath(");
sb.append(context.getSuitePath());
- sb.append(")\n\n");
+ sb.append(")\n");
}
+ sb.append("\n");
for (final String u : units) {
final String unit = u.toLowerCase();
if (context.getNumberOfTestsPerUnit() > 1
@@ -69,7 +69,7 @@ public String generateSpec() {
if (context.isDisableTests()) {
sb.append("\t--%disabled\n");
}
- sb.append("\tPROCEDURE ");
+ sb.append("\tprocedure ");
sb.append(context.getTestUnitPrefix());
sb.append(unit);
sb.append(context.getTestUnitSuffix());
@@ -83,7 +83,7 @@ public String generateSpec() {
sb.append("\t--%endcontext\n\n");
}
}
- sb.append("END ");
+ sb.append("end ");
sb.append(packageName);
sb.append(";\n");
sb.append("/");
@@ -94,15 +94,15 @@ public String generateSpec() {
public String generateBody() {
StringBuilder sb = new StringBuilder();
final String objectName = context.getObjectName().toLowerCase();
- sb.append("CREATE OR REPLACE PACKAGE BODY ");
+ sb.append("create or replace package body ");
sb.append(context.getTestPackagePrefix());
sb.append(objectName);
sb.append(context.getTestPackageSuffix());
- sb.append(" IS\n\n");
+ sb.append(" is\n\n");
if (context.isGenerateComments()) {
- sb.append("\t/* generated by utPLSQL for SQL Developer on ");
+ sb.append("\t-- generated by utPLSQL for SQL Developer on ");
sb.append(today);
- sb.append(" */\n\n");
+ sb.append("\n\n");
}
for (final String u : units) {
final String unit = u.toLowerCase();
@@ -117,33 +117,34 @@ public String generateBody() {
sb.append(" case ");
sb.append(i);
sb.append(": ...\n");
+ } else {
+ sb.append("\n");
}
sb.append("\t--\n");
}
- sb.append("\tPROCEDURE ");
+ 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");
+ 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-- populate actual\n");
+ 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-- populate expected\n");
- sb.append("\t\t-- ...\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("\tend ");
sb.append(procedureName);
sb.append(";\n\n");
}
}
- sb.append("END ");
+ sb.append("end ");
sb.append(context.getTestPackagePrefix());
sb.append(objectName);
sb.append(context.getTestPackageSuffix());
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.java b/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.java
index 4bda72a6..bf7962b1 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.java
@@ -59,10 +59,6 @@ private int getStartLine(final String plsql, final int offset) {
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();
- if (member.isPresent()) {
- return this.getStartLine(plsql, member.get().codeOffset);
- } else {
- return 1;
- }
+ return member.map(value -> this.getStartLine(plsql, value.codeOffset)).orElse(1);
}
}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java
index a4038bd4..6222c2e0 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java
@@ -17,25 +17,23 @@
import java.awt.Dimension;
import java.awt.Toolkit;
+import java.net.URL;
import java.sql.Connection;
-import java.text.SimpleDateFormat;
-import java.util.Date;
import java.util.List;
import java.util.UUID;
+import java.util.function.Supplier;
import java.util.logging.Logger;
import javax.swing.JFrame;
-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.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;
@@ -44,12 +42,18 @@
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;
@@ -71,6 +75,7 @@ public class UtplsqlRunner implements RealtimeReporterEventConsumer {
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;
@@ -78,17 +83,20 @@ public UtplsqlRunner(final List pathList, final String connectionName) {
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 String connectionName) {
+ 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();
}
@@ -102,21 +110,23 @@ public UtplsqlRunner(final List pathList, final Connection producerConn,
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)
+ * 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) {
+ 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;
}
@@ -145,7 +155,6 @@ public void dispose() {
run.setConsumerConn(null);
}
- @SuppressWarnings("StatementWithEmptyBody")
@Override
public void process(final RealtimeReporterEvent event) {
logger.fine(event::toString);
@@ -159,7 +168,7 @@ public void process(final RealtimeReporterEvent event) {
} else if (event instanceof PreRunEvent) {
doProcess((PreRunEvent) event);
} else if (event instanceof PreSuiteEvent) {
- // not processed
+ doProcess((PreSuiteEvent) event);
} else if (event instanceof PreTestEvent) {
doProcess((PreTestEvent) event);
} else {
@@ -167,19 +176,13 @@ public void process(final RealtimeReporterEvent event) {
}
}
- public static String getSysdate() {
- final Date dateTime = new Date(System.currentTimeMillis());
- final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'000'");
- return df.format(dateTime);
- }
-
public boolean isRunning() {
return run != null && run.getEndTime() == null;
}
private void initRun() {
run = new Run(realtimeReporterId, connectionName, pathList);
- run.setStartTime(getSysdate());
+ run.setStartTime(StringTools.getSysdate());
run.getCounter().setDisabled(0);
run.getCounter().setSuccess(0);
run.getCounter().setFailure(0);
@@ -194,6 +197,13 @@ private void initRun() {
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());
@@ -210,8 +220,59 @@ private void doProcess(final PostRunEvent event) {
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.
@@ -248,52 +309,46 @@ private void doProcess(final PostSuiteEvent event) {
sb.append(event.getServerOutput());
test.setServerOutput(sb.toString());
}
- panel.update(realtimeReporterId);
+ panel.update(realtimeReporterId, suite);
}
private void doProcess(final PreTestEvent event) {
final Test test = run.getTest(event.getId());
- if (test == null) {
- logger.severe(() -> "Could not find test id \"" + event.getId() + "\" when processing PreTestEvent "
- + event.toString() + ".");
- } else {
- test.setStartTime(getSysdate());
- }
+ 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);
+ panel.update(realtimeReporterId, test);
}
private void doProcess(final PostTestEvent event) {
final Test test = run.getTest(event.getId());
- if (test == null) {
- logger.severe(() -> "Could not find test id \"" + event.getId() + "\" when processing PostTestEvent "
- + event.toString() + ".");
+ 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.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());
+ 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);
+ panel.update(realtimeReporterId, test);
}
private void produceReportWithDebugger(String anonymousPlsqlBlock) {
@@ -314,7 +369,7 @@ private void produce() {
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);
+ dao.produceReportWithCoverage(realtimeReporterId, coverageReporterId, pathList, schemaList, includeObjectList, excludeObjectList, htmlReportAssetPath);
} else {
if (!debug) {
dao.produceReport(realtimeReporterId, pathList);
@@ -350,7 +405,7 @@ private void consume() {
if (run.getTotalNumberOfTests() < 0) {
run.setStatus(UtplsqlResources.getString("RUNNER_NO_TESTS_FOUND_TEXT"));
run.setExecutionTime((System.currentTimeMillis() - Double.valueOf(run.getStart())) / 1000);
- run.setEndTime(getSysdate());
+ run.setEndTime(StringTools.getSysdate());
run.setTotalNumberOfTests(0);
panel.update(realtimeReporterId);
}
@@ -414,4 +469,10 @@ public Thread getProducerThread() {
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
index ebc0e7cc..5a0773b2 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.java
@@ -77,30 +77,30 @@ private CharSequence getCode() {
StringBuilder sb = new StringBuilder();
if (!debug) {
if (preferences.isResetPackage()) {
- sb.append("EXECUTE dbms_session.reset_package;\n");
+ sb.append("execute dbms_session.reset_package;\n");
}
- sb.append("SET SERVEROUTPUT ON SIZE UNLIMITED\n");
+ sb.append("set serveroutput on size unlimited\n");
if (preferences.isClearScreen()) {
- sb.append("CLEAR SCREEN\n");
+ sb.append("clear screen\n");
}
if (pathList.size() == 1) {
- sb.append("EXECUTE ut.run('");
+ 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("execute ut.run(ut_varchar2_list(");
sb.append(StringTools.getCSV(pathList, "").replace("\n", ""));
sb.append("));\n");
}
} else {
- sb.append("BEGIN\n");
+ 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");
+ sb.append("end;\n");
}
return sb;
}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/snippet/SnippetMerger.java b/sqldev/src/main/java/org/utplsql/sqldev/snippet/SnippetMerger.java
index ce940803..1a10e5c9 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/snippet/SnippetMerger.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/snippet/SnippetMerger.java
@@ -36,8 +36,8 @@
public class SnippetMerger {
private final XMLTools xmlTools = new XMLTools();
- private File userSnippetsFile;
- private String utplsqlSnippets;
+ private final File userSnippetsFile;
+ private final String utplsqlSnippets;
public String getUtplsqlSnippetsAsString() {
final InputStream stream = getClass()
@@ -57,7 +57,7 @@ public SnippetMerger(final File file) {
}
public void merge() {
- String result = null;
+ String result;
if (userSnippetsFile.exists()) {
// file exists, proper merge required
final String userSnippets = new String(FileTools.readFile(Paths.get(userSnippetsFile.getAbsolutePath())));
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
index 525e5516..cfaebf81 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.java
@@ -48,12 +48,11 @@
public class CodeCoverageReporterDialog extends JFrame implements ActionListener, FocusListener {
private static final long serialVersionUID = 5503685225300993401L;
- private CodeCoverageReporter reporter;
- private JButton runButton;
- private JButton cancelButton;
- private JPanel paneParams;
+ private final CodeCoverageReporter reporter;
+ private final JButton runButton;
+ private final JButton cancelButton;
+ private final JPanel paneParams;
private int paramPos = (-1);
- private final JTextArea pathsTextArea = new JTextArea();
private final JTextField schemasTextField = new JTextField();
private final JTextArea includeObjectsTextArea = new JTextArea();
private final JTextArea excludeObjectsTextArea = new JTextArea();
@@ -83,6 +82,7 @@ public CodeCoverageReporterDialog(final CodeCoverageReporter reporter) {
// 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);
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
index 89786dae..703766ff 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.java
@@ -63,6 +63,7 @@ public class PreferencePanel extends DefaultTraversablePanel {
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();
@@ -135,6 +136,8 @@ private void layoutControls() {
.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
@@ -241,21 +244,21 @@ private String trimPlusNewLine(final String input) {
private CharSequence utSpecTemplate() {
StringBuilder sb = new StringBuilder();
- sb.append("CREATE OR REPLACE PACKAGE ");
+ 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(" 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');
- sb.append('\t');
- sb.append(utSpecProcTemplate());
- sb.append("END ");
+ 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());
@@ -271,12 +274,12 @@ private CharSequence utSpecProcTemplate() {
if (withContext) {
sb.append("--%context([procedure_name])\n\n");
}
- for (int i = 0; i < numberOfTestsPerUnit; i ++) {
+ for (int i = 1; i <= numberOfTestsPerUnit; i ++) {
sb.append("--%test\n");
if (disableTestsCheckBox.isSelected()) {
sb.append("--%disabled\n");
}
- sb.append("PROCEDURE ");
+ sb.append("procedure ");
sb.append(testUnitPrefixTextField.getText());
sb.append("[procedure_name]");
sb.append(testUnitSuffixTextField.getText());
@@ -293,15 +296,14 @@ private CharSequence utSpecProcTemplate() {
private CharSequence utBodyTemplate() {
StringBuilder sb = new StringBuilder();
- sb.append("CREATE OR REPLACE PACKAGE BODY ");
+ sb.append("create or replace package body ");
sb.append(testPackagePrefixTextField.getText());
sb.append("[package_name]");
sb.append(testPackageSuffixTextField.getText());
- sb.append(" IS\n\n");
- sb.append('\t');
- sb.append(utBodyProcTemplate());
- sb.append('\n');
- sb.append("END ");
+ 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());
@@ -314,7 +316,7 @@ private CharSequence utBodyProcTemplate() {
StringBuilder sb = new StringBuilder();
final Integer numberOfTestsPerUnit = (Integer) numberOfTestsPerUnitModel.getValue();
final boolean withContext = numberOfTestsPerUnit > 1;
- for (int i = 0; i < numberOfTestsPerUnit; i++) {
+ for (int i = 1; i <= numberOfTestsPerUnit; i++) {
if (generateCommentsCheckBox.isSelected()) {
sb.append("--\n");
sb.append("-- test");
@@ -326,26 +328,24 @@ private CharSequence utBodyProcTemplate() {
sb.append('\n');
sb.append("--\n");
}
- sb.append("PROCEDURE ");
+ 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");
+ 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-- populate actual\n");
- sb.append("\t-- ...\n\n");
- sb.append("\t-- populate expected\n");
- sb.append("\t-- ...\n\n");
+ 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);");
- sb.append("END ");
+ 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());
@@ -377,6 +377,7 @@ public void onEntry(final TraversableContext traversableContext) {
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());
@@ -415,6 +416,7 @@ public void onExit(final TraversableContext traversableContext) throws Traversal
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());
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.java
index ac8f701b..77cf6644 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.java
@@ -17,7 +17,7 @@
import java.util.AbstractMap;
-public class ComboBoxItem extends AbstractMap.SimpleEntry {
+public class ComboBoxItem extends AbstractMap.SimpleEntry {
private static final long serialVersionUID = 7869442222989031548L;
public ComboBoxItem(final K key, final V value) {
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
index 3acf505c..67777bbf 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.java
@@ -26,7 +26,7 @@
public class FailuresTableModel extends DefaultTableModel {
private static final long serialVersionUID = 8119453059788497567L;
private List failedExpectations;
- private List columnNames = Arrays.asList("#", UtplsqlResources.getString("RUNNER_ASSERT_DESCRIPTION_COLUMN"));
+ private final List columnNames = Arrays.asList("#", UtplsqlResources.getString("RUNNER_ASSERT_DESCRIPTION_COLUMN"));
public FailuresTableModel() {
super();
@@ -81,13 +81,9 @@ public boolean isCellEditable(final int row, final int column) {
@Override
public Class> getColumnClass(final int col) {
- switch (col) {
- case 0:
+ if (col == 0) {
return Integer.class;
- case 1:
- return String.class;
- default:
- return String.class;
}
+ return String.class;
}
}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.java
index 9f760cd5..5f225a84 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.java
@@ -31,6 +31,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -38,6 +39,7 @@
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.DefaultComboBoxModel;
+import javax.swing.Icon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JComponent;
@@ -51,6 +53,7 @@
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
+import javax.swing.JTree;
import javax.swing.LookAndFeel;
import javax.swing.RepaintManager;
import javax.swing.RowFilter;
@@ -65,6 +68,8 @@
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableRowSorter;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.TreePath;
import org.springframework.web.util.HtmlUtils;
import org.utplsql.sqldev.coverage.CodeCoverageReporter;
@@ -76,6 +81,8 @@
import org.utplsql.sqldev.model.preference.PreferenceModel;
import org.utplsql.sqldev.model.runner.Counter;
import org.utplsql.sqldev.model.runner.Expectation;
+import org.utplsql.sqldev.model.runner.Item;
+import org.utplsql.sqldev.model.runner.ItemNode;
import org.utplsql.sqldev.model.runner.Run;
import org.utplsql.sqldev.model.runner.Test;
import org.utplsql.sqldev.parser.UtplsqlParser;
@@ -86,8 +93,10 @@
import oracle.dbtools.raptor.controls.grid.DefaultDrillLink;
import oracle.ide.config.Preferences;
import oracle.javatools.ui.table.ToolbarButton;
+import oracle.javatools.ui.treetable.JFastTreeTable;
public class RunnerPanel {
+ private static final Logger logger = Logger.getLogger(RunnerPanel.class.getName());
private static final Color GREEN = new Color(0, 153, 0);
private static final Color RED = new Color(153, 0, 0);
private static final int INDICATOR_WIDTH = 20;
@@ -114,7 +123,10 @@ public class RunnerPanel {
private JCheckBoxMenuItem showInfoCounterCheckBoxMenuItem;
private JProgressBar progressBar;
private TestOverviewTableModel testOverviewTableModel;
+ private TestOverviewTreeTableModel testOverviewTreeTableModel;
private JTable testOverviewTable;
+ private JFastTreeTable testOverviewTreeTable;
+ private JScrollPane testOverviewScrollPane;
private JMenuItem testOverviewRunMenuItem;
private JMenuItem testOverviewRunWorksheetMenuItem;
private JMenuItem testOverviewDebugMenuItem;
@@ -125,10 +137,12 @@ public class RunnerPanel {
private JCheckBoxMenuItem showSuccessfulTestsCheckBoxMenuItem;
private JCheckBoxMenuItem showDisabledTestsCheckBoxMenuItem;
private JCheckBoxMenuItem syncDetailTabCheckBoxMenuItem;
+ private JCheckBoxMenuItem showSuitesCheckBoxMenuItem;
private RunnerTextField testOwnerTextField;
private RunnerTextField testPackageTextField;
private RunnerTextField testProcedureTextField;
private RunnerTextArea testDescriptionTextArea;
+ private RunnerTextArea testDisabledReasonTextArea;
private RunnerTextArea testIdTextArea;
private RunnerTextField testStartTextField;
private FailuresTableModel failuresTableModel;
@@ -153,14 +167,14 @@ public Component getTableCellRendererComponent(final JTable table, final Object
label.setIcon(UtplsqlResources.getIcon("STATUS_ICON"));
label.setHorizontalAlignment(JLabel.CENTER);
} else if (col == 1) {
+ label.setIcon(null);
+ label.setHorizontalAlignment(JLabel.LEFT);
+ } else if (col == 2) {
label.setIcon(UtplsqlResources.getIcon("WARNING_ICON"));
label.setHorizontalAlignment(JLabel.CENTER);
- } else if (col == 2) {
+ } else if (col == 3) {
label.setIcon(UtplsqlResources.getIcon("INFO_ICON"));
label.setHorizontalAlignment(JLabel.CENTER);
- } else if (col == 3) {
- label.setIcon(null);
- label.setHorizontalAlignment(JLabel.LEFT);
} else if (col == 4) {
label.setIcon(null);
label.setHorizontalAlignment(JLabel.RIGHT);
@@ -169,6 +183,33 @@ public Component getTableCellRendererComponent(final JTable table, final Object
}
}
+ // used in multiple components, therefore an inner class
+ private static class TestTreeTableHeaderRenderer extends DefaultTableCellRenderer {
+ private static final long serialVersionUID = -1784754761029185815L;
+
+ @Override
+ public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected,
+ final boolean hasFocus, final int row, final int col) {
+ final TableCellRenderer renderer = table.getTableHeader().getDefaultRenderer();
+ final JLabel label = ((JLabel) renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus,
+ row, col));
+ if (col == 0) {
+ label.setIcon(null);
+ label.setHorizontalAlignment(JLabel.LEFT);
+ } else if (col == 1) {
+ label.setIcon(UtplsqlResources.getIcon("WARNING_ICON"));
+ label.setHorizontalAlignment(JLabel.CENTER);
+ } else if (col == 2) {
+ label.setIcon(UtplsqlResources.getIcon("INFO_ICON"));
+ label.setHorizontalAlignment(JLabel.CENTER);
+ } else if (col == 3) {
+ label.setIcon(null);
+ label.setHorizontalAlignment(JLabel.RIGHT);
+ }
+ return label;
+ }
+ }
+
// used in multiple components, therefore an inner class
private static class FailuresTableHeaderRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 5059401447983514596L;
@@ -209,6 +250,7 @@ private void resetDerived() {
testPackageTextField.setText(null);
testProcedureTextField.setText(null);
testDescriptionTextArea.setText(null);
+ testDisabledReasonTextArea.setText(null);
testStartTextField.setText(null);
failuresTableModel.setModel(null);
failuresTableModel.fireTableDataChanged();
@@ -257,12 +299,14 @@ private void applyShowWarningsCounter() {
private void applyShowInfoCounter() {
infoCounterValueLabel.getParent().setVisible(showInfoCounterCheckBoxMenuItem.isSelected());
}
-
+
private void applyShowTestDescription() {
+ // table
testOverviewTableModel.updateModel(showTestDescriptionCheckBoxMenuItem.isSelected());
- final TableColumn idColumn = testOverviewTable.getColumnModel().getColumn(3);
- idColumn.setHeaderValue(testOverviewTableModel.getTestIdColumnName());
- testOverviewTable.getTableHeader().repaint();
+ fixColumnHeader(testOverviewTableModel.getTestIdColumnName(), testOverviewTable, 1);
+ // tree-table
+ testOverviewTreeTableModel.updateModel(showTestDescriptionCheckBoxMenuItem.isSelected());
+ fixColumnHeader(testOverviewTreeTableModel.getTreeColumnName(), testOverviewTreeTable, 0);
}
private void showColumn(final boolean show, TableColumn col) {
@@ -280,36 +324,106 @@ private void showColumn(final boolean show, TableColumn col) {
}
private void applyShowWarningIndicator(final boolean show) {
- showColumn(show, testOverviewTable.getColumnModel().getColumn(1));
+ showColumn(show, testOverviewTable.getColumnModel().getColumn(2));
+ showColumn(show, testOverviewTreeTable.getColumnModel().getColumn(1));
}
private void applyShowInfoIndicator(final boolean show) {
- showColumn(show, testOverviewTable.getColumnModel().getColumn(2));
+ showColumn(show, testOverviewTable.getColumnModel().getColumn(3));
+ showColumn(show, testOverviewTreeTable.getColumnModel().getColumn(2));
+ }
+
+ private void selectTestInTestOverviewTable(Test test) {
+ if (test != null) {
+ for (int i=0; i sorter = ((TableRowSorter) testOverviewTable.getRowSorter());
- final RowFilter filter = new RowFilter() {
- @SuppressWarnings("RedundantIfStatement")
- @Override
- public boolean include(final RowFilter.Entry extends TestOverviewTableModel, ? extends Integer> entry) {
- final Test test = entry.getModel().getTest((entry.getIdentifier()).intValue());
- final Counter counter = test.getCounter();
- if (counter != null) {
- if (counter.getSuccess() > 0 && !showSuccessfulTests) {
- return false;
- }
- if (counter.getDisabled() > 0 && !showDisabledTests) {
- return false;
+ if (!showSuitesCheckBoxMenuItem.isSelected()) {
+ // table
+ @SuppressWarnings("unchecked")
+ final TableRowSorter sorter = ((TableRowSorter) testOverviewTable.getRowSorter());
+ final RowFilter filter = new RowFilter() {
+ @Override
+ public boolean include(final RowFilter.Entry extends TestOverviewTableModel, ? extends Integer> entry) {
+ final Test test = entry.getModel().getTest((entry.getIdentifier()).intValue());
+ final Counter counter = test.getCounter();
+ if (counter != null) {
+ if (counter.getSuccess() > 0 && !showSuccessfulTests) {
+ return false;
+ }
+ if (counter.getDisabled() > 0 && !showDisabledTests) {
+ return false;
+ }
}
+ return true;
}
- return true;
+ };
+ sorter.setRowFilter(filter);
+ } else {
+ // tree-table
+ testOverviewTreeTableModel.updateModel(showSuccessfulTests, showDisabledTests);
+ expandAllNodes(testOverviewTreeTable.getTree(), 0);
+ }
+ }
+
+ private void openItemNode(final ItemNode node) {
+ if (!node.getPackageName().equals("***")) {
+ final UtplsqlDao dao = new UtplsqlDao(DatabaseTools.getConnection(currentRun.getConnectionName()));
+ final String source = dao.getSource(node.getOwnerName(), "PACKAGE", node.getPackageName().toUpperCase()).trim();
+ final UtplsqlParser parser = new UtplsqlParser(source);
+ int line = 1;
+ if (node.getUserObject() instanceof Test) {
+ line = parser.getLineOf(node.getProcedureName());
}
- };
- sorter.setRowFilter(filter);
+ openEditor(node.getOwnerName(), "PACKAGE", node.getPackageName().toUpperCase(), line, 1);
+ }
}
-
+
private void openTest(final Test test) {
final UtplsqlDao dao = new UtplsqlDao(DatabaseTools.getConnection(currentRun.getConnectionName()));
final String source = dao.getSource(test.getOwnerName(), "PACKAGE", test.getObjectName().toUpperCase()).trim();
@@ -319,11 +433,21 @@ private void openTest(final Test test) {
}
private void openSelectedTest() {
- final int rowIndex = testOverviewTable.getSelectedRow();
- if (rowIndex != -1) {
- final int row = testOverviewTable.convertRowIndexToModel(rowIndex);
- final Test test = testOverviewTableModel.getTest(row);
- openTest(test);
+ if (!showSuitesCheckBoxMenuItem.isSelected()) {
+ // table
+ final int rowIndex = testOverviewTable.getSelectedRow();
+ if (rowIndex != -1) {
+ final int row = testOverviewTable.convertRowIndexToModel(rowIndex);
+ final Test test = testOverviewTableModel.getTest(row);
+ openTest(test);
+ }
+ } else {
+ // tree-table
+ TreePath path = testOverviewTreeTable.getTree().getSelectionPath();
+ if (path != null) {
+ ItemNode itemNode = (ItemNode) path.getLastPathComponent();
+ openItemNode(itemNode);
+ }
}
}
@@ -332,19 +456,31 @@ private void openSelectedFailure() {
if (rowIndex != -1) {
final int row = failuresTable.convertRowIndexToModel(rowIndex);
final Expectation expectation = failuresTableModel.getExpectation(row);
- final Test test = testOverviewTableModel
- .getTest(testOverviewTable.convertRowIndexToModel(testOverviewTable.getSelectedRow()));
- final Integer callerLine = expectation.getCallerLine();
- if (callerLine != null) {
- openEditor(test.getOwnerName(), "PACKAGE BODY", test.getObjectName().toUpperCase(),
- expectation.getCallerLine(), 1);
+ Test test = null;
+ if (!showSuitesCheckBoxMenuItem.isSelected()) {
+ // table
+ test = testOverviewTableModel
+ .getTest(testOverviewTable.convertRowIndexToModel(testOverviewTable.getSelectedRow()));
} else {
- openTest(test);
+ // tree-table
+ TreePath path = testOverviewTreeTable.getTree().getSelectionPath();
+ if (path != null) {
+ ItemNode itemNode = (ItemNode) path.getLastPathComponent();
+ test = ((Test)itemNode.getUserObject());
+ }
+ }
+ if (test != null) {
+ final Integer callerLine = expectation.getCallerLine();
+ if (callerLine != null) {
+ openEditor(test.getOwnerName(), "PACKAGE BODY", test.getObjectName().toUpperCase(),
+ expectation.getCallerLine(), 1);
+ } else {
+ openTest(test);
+ }
}
}
}
- @SuppressWarnings("StringBufferReplaceableByString")
private String getHtml(final String text) {
StringBuilder sb = new StringBuilder();
sb.append("\n");
@@ -381,7 +517,6 @@ private void openLink(final String link) {
openEditor(ownerName, objectType, objectName.toUpperCase(), line, 1);
}
- @SuppressWarnings("SameParameterValue")
private void openEditor(final String owner, final String type, final String name, final int line, final int col) {
DefaultDrillLink drillLink = new DefaultDrillLink();
drillLink.setConnName(currentRun.getConnectionName());
@@ -390,34 +525,50 @@ private void openEditor(final String owner, final String type, final String name
"oracle.dbtools.raptor.controls.grid.DefaultDrillLink" });
drillLink.performDrill();
}
+
+ private void syncDetailTab(Item item) {
+ int tabIndex = 0;
+ if (item != null) {
+ if (failuresTableModel.getRowCount() > 0) {
+ tabIndex = 1;
+ } else if (StringTools.isNotBlank(item.getErrorStack())) {
+ tabIndex = 2;
+ } else if (StringTools.isNotBlank(item.getWarnings())) {
+ tabIndex = 3;
+ } else if (StringTools.isNotBlank(item.getServerOutput())) {
+ tabIndex = 4;
+ }
+ }
+ testDetailTabbedPane.setSelectedIndex(tabIndex);
+ }
private void syncDetailTab() {
if (syncDetailTabCheckBoxMenuItem.isSelected()) {
- final int rowIndex = testOverviewTable.getSelectedRow();
- if (rowIndex != -1) {
- final int row = testOverviewTable.convertRowIndexToModel(rowIndex);
- final Test test = testOverviewTableModel.getTest(row);
- int tabIndex = 0;
- if (test != null && test.getCounter() != null) {
- if (test.getCounter().getFailure() != null && test.getCounter().getFailure() > 0) {
- tabIndex = 1;
- } else if (test.getCounter().getError() != null && test.getCounter().getError() > 0) {
- tabIndex = 2;
- } else if (test.getCounter().getWarning() != null && test.getCounter().getWarning() > 0) {
- tabIndex = 3;
- } else if (test.getServerOutput() != null && test.getServerOutput().length() > 0) {
- tabIndex = 4;
- }
+ if (!showSuitesCheckBoxMenuItem.isSelected()) {
+ // table
+ final int rowIndex = testOverviewTable.getSelectedRow();
+ if (rowIndex != -1) {
+ final int row = testOverviewTable.convertRowIndexToModel(rowIndex);
+ final Test test = testOverviewTableModel.getTest(row);
+ syncDetailTab(test);
+ }
+ } else {
+ // tree-table
+ TreePath path = testOverviewTreeTable.getTree().getSelectionPath();
+ if (path != null) {
+ ItemNode itemNode = (ItemNode) path.getLastPathComponent();
+ Item item = (Item) itemNode.getUserObject();
+ syncDetailTab(item);
}
- testDetailTabbedPane.setSelectedIndex(tabIndex);
}
+
}
}
-
+
private PreferenceModel getPreferenceModel() {
try {
return PreferenceModel.getInstance(Preferences.getPreferences());
- } catch (NoClassDefFoundError e) {
+ } catch (NoClassDefFoundError | ExceptionInInitializerError e) {
// running outside of SQL Developer
return PreferenceModel.getInstance(null);
}
@@ -448,107 +599,206 @@ private void applyPreferences() {
showDisabledTestsCheckBoxMenuItem.setSelected(preferences.isShowDisabledTests());
fixCheckBoxMenuItem(showDisabledTestsCheckBoxMenuItem);
applyFilter(showSuccessfulTestsCheckBoxMenuItem.isSelected(), showDisabledTestsCheckBoxMenuItem.isSelected());
+ testOverviewTreeTableModel.updateModel(showSuccessfulTestsCheckBoxMenuItem.isSelected(), showDisabledTestsCheckBoxMenuItem.isSelected());
fixCheckBoxMenuItem(showInfoIndicatorCheckBoxMenuItem);
syncDetailTabCheckBoxMenuItem.setSelected(preferences.isSyncDetailTab());
fixCheckBoxMenuItem(syncDetailTabCheckBoxMenuItem);
+ showSuitesCheckBoxMenuItem.setSelected(preferences.isShowSuites());
+ fixCheckBoxMenuItem(showSuitesCheckBoxMenuItem);
+ applyShowSuites();
useSmartTimes = preferences.isUseSmartTimes();
}
public void setModel(final Run run) {
+ assert run != null && run.getReporterId() != null : "Cannot run without reporterId";
+ setModel(run, false);
+ }
+
+ private void setModel(final Run run, boolean force) {
runs.put(run.getReporterId(), run);
refreshRunsComboBox();
- setCurrentRun(run);
+ setCurrentRun(run, force);
+ }
+
+ private void expandAllNodes(JTree tree, int startingRow) {
+ int rowCount = tree.getRowCount();
+ for (int i = startingRow; i < rowCount; i++) {
+ tree.expandRow(i);
+ }
+ // recursive call until all nodes are expanded
+ if (tree.getRowCount() != rowCount) {
+ expandAllNodes(tree, rowCount);
+ }
+ }
+
+ private void fixColumnHeader(String columnHeader, JTable table, int columnIndex) {
+ final TableColumn column = table.getColumnModel().getColumn(columnIndex);
+ if (!column.getHeaderValue().equals(columnHeader)) {
+ column.setHeaderValue(columnHeader);
+ table.getTableHeader().repaint();
+ }
}
- private void setCurrentRun(final Run run) {
- if (run != currentRun) {
- currentRun = run;
- testOverviewTableModel.setModel(run.getTests(), showTestDescriptionCheckBoxMenuItem.isSelected(),
- useSmartTimes);
- final String header = testOverviewTableModel.getTimeColumnName();
- final TableColumn timeColumn = testOverviewTable.getColumnModel().getColumn(4);
- if (!timeColumn.getHeaderValue().equals(header)) {
- timeColumn.setHeaderValue(header);
- testOverviewTable.getTableHeader().repaint();
+ /**
+ * Sets the current run. This can be forced with the force parameter.
+ *
+ * However, as long as a run is in progress you will technically not be able
+ * to switch to another run because a subsequent {@link #update(String)} call will
+ * switch back to the currently executing run. This behavior is intentional.
+ */
+ private void setCurrentRun(final Run run, boolean force) {
+ boolean switched = false;
+ // Multiple, parallel runs are supported. Ensure that the runner does not switch back and forth.
+ if (force // choosing the run via the combo box
+ || currentRun == null // the very first run
+ || currentRun.getEndTime() != null // the current run is not running
+ || run.getTotalNumberOfTests() == -1) // when initializing a new run (newest wins once)
+ {
+ if (run != currentRun) {
+ currentRun = run;
+ // table
+ testOverviewTableModel.setModel(run.getTests(), showTestDescriptionCheckBoxMenuItem.isSelected(),
+ useSmartTimes);
+ fixColumnHeader(testOverviewTableModel.getTimeColumnName(), testOverviewTable, 4);
+ // tree-table
+ testOverviewTreeTableModel.setModel(run, showTestDescriptionCheckBoxMenuItem.isSelected(), useSmartTimes,
+ showSuccessfulTestsCheckBoxMenuItem.isSelected(), showDisabledTestsCheckBoxMenuItem.isSelected());
+ fixColumnHeader(testOverviewTreeTableModel.getTimeColumnName(), testOverviewTreeTable, 3);
+ testOverviewTreeTableModel.reload();
+ // common
+ resetDerived();
+ final ComboBoxItem item = new ComboBoxItem<>(currentRun.getReporterId(),
+ currentRun.getName());
+ runComboBox.setSelectedItem(item);
+ elapsedTimeTimer.start();
+ switched = true;
}
- resetDerived();
+ }
+ if (switched || !testOverviewTreeTableModel.isComplete()) {
+ // table
+ testOverviewTableModel.fireTableDataChanged();
+ // tree-table
+ testOverviewTreeTableModel.updateModel();
+ testOverviewTreeTableModel.reload();
+ expandAllNodes(testOverviewTreeTable.getTree(), 0);
+ }
+ // ensure that the runComboBox shows always the currentRun
+ @SuppressWarnings("unchecked")
+ final ComboBoxItem currentItem = (ComboBoxItem) runComboBox.getSelectedItem();
+ if (currentItem != null && !currentItem.getKey().equals(currentRun.getReporterId())) {
final ComboBoxItem item = new ComboBoxItem<>(currentRun.getReporterId(),
currentRun.getName());
runComboBox.setSelectedItem(item);
- elapsedTimeTimer.start();
- }
- }
-
- private void showDockable() {
- try {
- if (!RunnerFactory.getDockable().isVisible()) {
- RunnerFactory.showDockable();
- }
- } catch (Throwable t) {
- // ignore
}
}
private void enableOrDisableStopButton() {
stopButton.setEnabled(currentRun.getEndTime() == null);
}
-
+
public synchronized void update(final String reporterId) {
- showDockable();
- enableOrDisableStopButton();
- setCurrentRun(runs.get(reporterId));
- final int row = currentRun.getCurrentTestNumber() - 1;
- final CharSequence header = testOverviewTableModel.getTestIdColumnName();
- final TableColumn idColumn = testOverviewTable.getColumnModel().getColumn(3);
- if (!idColumn.getHeaderValue().equals(header)) {
- idColumn.setHeaderValue(header);
- testOverviewTable.getTableHeader().repaint();
- }
- if (row < 0) {
- testOverviewTableModel.fireTableDataChanged();
- } else {
- if (testOverviewTableModel.getRowCount() > row) {
- final Rectangle positionOfCurrentTest = testOverviewTable
- .getCellRect(testOverviewTable.convertRowIndexToView(row), 0, true);
- testOverviewTable.scrollRectToVisible(positionOfCurrentTest);
- testOverviewTableModel.fireTableRowsUpdated(row, row);
- SystemTools.sleep(5);
- if (!showSuccessfulTestsCheckBoxMenuItem.isSelected()
- || !showDisabledTestsCheckBoxMenuItem.isSelected()) {
- applyFilter(showSuccessfulTestsCheckBoxMenuItem.isSelected(),
- showDisabledTestsCheckBoxMenuItem.isSelected());
+ update(reporterId, null);
+ }
+
+ public synchronized void update(final String reporterId, Item item) {
+ try {
+ setCurrentRun(runs.get(reporterId), false);
+ if (!currentRun.getReporterId().equals(reporterId)) {
+ // this run is currently not active in the runner
+ return;
+ }
+ enableOrDisableStopButton();
+ fixColumnHeader(testOverviewTableModel.getTestIdColumnName(), testOverviewTable, 1);
+ fixColumnHeader(testOverviewTreeTableModel.getTreeColumnName(), testOverviewTreeTable, 0);
+ if (!showSuitesCheckBoxMenuItem.isSelected()) {
+ // table
+ if (item instanceof Test) {
+ final int row = ((Test) item).getTestNumber() - 1;
+ if (row >= 0 && testOverviewTableModel.getRowCount() > row) {
+ final Rectangle positionOfCurrentTest = testOverviewTable
+ .getCellRect(testOverviewTable.convertRowIndexToView(row), 0, true);
+ testOverviewTable.scrollRectToVisible(positionOfCurrentTest);
+ testOverviewTableModel.fireTableRowsUpdated(row, row);
+ SystemTools.sleep(5);
+ if (!showSuccessfulTestsCheckBoxMenuItem.isSelected()
+ || !showDisabledTestsCheckBoxMenuItem.isSelected()) {
+ applyFilter(showSuccessfulTestsCheckBoxMenuItem.isSelected(),
+ showDisabledTestsCheckBoxMenuItem.isSelected());
+ }
+ testOverviewTable.scrollRectToVisible(positionOfCurrentTest);
+ }
+ }
+ }
+ if (showSuitesCheckBoxMenuItem.isSelected()) {
+ // tree-table
+ if (item != null && testOverviewTreeTableModel.isComplete()) {
+ if (item instanceof Test) {
+ final Test test = (Test) item;
+ int treeRow = testOverviewTreeTableModel.getRow(test.getId());
+ if (treeRow >= 0) {
+ final Rectangle positionOfCurrentTestInTree = testOverviewTreeTable
+ .getCellRect(testOverviewTreeTable.convertRowIndexToView(treeRow), 0, true);
+ testOverviewTreeTable.scrollRectToVisible(positionOfCurrentTestInTree);
+ testOverviewTreeTableModel.updateModel(test.getId());
+ }
+ } else {
+ testOverviewTreeTableModel.updateModel(item.getId());
+ }
}
- testOverviewTable.scrollRectToVisible(positionOfCurrentTest);
}
- }
- statusLabel.setText(currentRun.getStatus());
- testCounterValueLabel.setText(currentRun.getTotalNumberOfCompletedTests()
- + (currentRun.getTotalNumberOfTests() >= 0 ? "/" + currentRun.getTotalNumberOfTests() : ""));
- errorCounterValueLabel.setText(String.valueOf(currentRun.getCounter().getError()));
- failureCounterValueLabel.setText(String.valueOf(currentRun.getCounter().getFailure()));
- disabledCounterValueLabel.setText(String.valueOf(currentRun.getCounter().getDisabled()));
- warningsCounterValueLabel.setText(String.valueOf(currentRun.getCounter().getWarning()));
- infoCounterValueLabel.setText(String.valueOf(currentRun.getInfoCount()));
- if (currentRun.getTotalNumberOfTests() == 0) {
- progressBar.setValue(100);
- } else {
- progressBar
- .setValue(100 * currentRun.getTotalNumberOfCompletedTests() / currentRun.getTotalNumberOfTests());
- }
- if (currentRun.getCounter().getError() > 0 || (currentRun.getCounter().getFailure() > 0)) {
- progressBar.setForeground(RED);
- } else {
- progressBar.setForeground(GREEN);
+ statusLabel.setText(currentRun.getStatus());
+ testCounterValueLabel.setText(currentRun.getTotalNumberOfCompletedTests()
+ + (currentRun.getTotalNumberOfTests() >= 0 ? "/" + currentRun.getTotalNumberOfTests() : ""));
+ errorCounterValueLabel.setText(String.valueOf(currentRun.getCounter().getError()));
+ failureCounterValueLabel.setText(String.valueOf(currentRun.getCounter().getFailure()));
+ disabledCounterValueLabel.setText(String.valueOf(currentRun.getCounter().getDisabled()));
+ warningsCounterValueLabel.setText(String.valueOf(currentRun.getCounter().getWarning()));
+ infoCounterValueLabel.setText(String.valueOf(currentRun.getInfoCount()));
+ if (currentRun.getTotalNumberOfTests() == 0) {
+ progressBar.setValue(100);
+ } else {
+ progressBar
+ .setValue(100 * currentRun.getTotalNumberOfCompletedTests() / currentRun.getTotalNumberOfTests());
+ }
+ if (currentRun.getCounter().getError() > 0 || (currentRun.getCounter().getFailure() > 0)) {
+ progressBar.setForeground(RED);
+ } else {
+ progressBar.setForeground(GREEN);
+ }
+ } catch (Exception e) {
+ logger.warning(() -> "Ignored exception " + (e.getMessage() == null ? e.getClass().getSimpleName()
+ : e.getMessage()) + " while processing reporterId " + reporterId + (item == null ? "."
+ : " for item id " + item.getId() + "."));
}
}
private ArrayList getPathListFromSelectedTests() {
final ArrayList pathList = new ArrayList<>();
- for (final int rowIndex : testOverviewTable.getSelectedRows()) {
- final int row = testOverviewTable.convertRowIndexToModel(rowIndex);
- final Test test = testOverviewTableModel.getTest(row);
- final String path = test.getOwnerName() + "." + test.getObjectName() + "." + test.getProcedureName();
- pathList.add(path);
+ if (!showSuitesCheckBoxMenuItem.isSelected()) {
+ // table
+ for (final int rowIndex : testOverviewTable.getSelectedRows()) {
+ final int row = testOverviewTable.convertRowIndexToModel(rowIndex);
+ final Test test = testOverviewTableModel.getTest(row);
+ final String path = test.getOwnerName() + "." + test.getObjectName() + "." + test.getProcedureName();
+ pathList.add(path);
+ }
+ } else {
+ // tree-table
+ TreePath[] selectionPaths = testOverviewTreeTable.getTree().getSelectionPaths();
+ ArrayList selectedNodes = new ArrayList<>();
+ if (selectionPaths != null) {
+ for (final TreePath path : selectionPaths) {
+ selectedNodes.add((ItemNode) path.getLastPathComponent());
+ }
+ for (final ItemNode node : ItemNode.createNonOverlappingSet(selectedNodes)) {
+ if (node.getOwnerName().equals("***")) {
+ // process children, which must be owners only.
+ pathList.addAll(node.getOwners());
+ } else {
+ pathList.add(node.getOwnerName() + ":" + node.getId());
+ }
+ }
+ }
}
return pathList;
}
@@ -575,6 +825,55 @@ private void fixCheckBoxMenuItem(final JCheckBoxMenuItem item) {
}
}
+ private void showFirstRow() {
+ // table
+ final Rectangle positionOfCurrentTest = testOverviewTable
+ .getCellRect(testOverviewTable.convertRowIndexToView(0), 0, true);
+ testOverviewTable.scrollRectToVisible(positionOfCurrentTest);
+ // tree-table
+ final Rectangle positionOfCurrentTestInTree = testOverviewTreeTable
+ .getCellRect(testOverviewTreeTable.convertRowIndexToView(0), 0, true);
+ testOverviewTreeTable.scrollRectToVisible(positionOfCurrentTestInTree);
+ }
+
+ private void showSelectedRow() {
+ if (!showSuitesCheckBoxMenuItem.isSelected()) {
+ // table
+ final int rowIndex = testOverviewTable.getSelectedRow();
+ if (rowIndex != -1) {
+ final int row = testOverviewTable.convertRowIndexToModel(rowIndex);
+ final Rectangle position = testOverviewTable
+ .getCellRect(testOverviewTable.convertRowIndexToView(row), 0, true);
+ testOverviewTable.scrollRectToVisible(position);
+ }
+ } else {
+ // tree-table
+ TreePath path = testOverviewTreeTable.getTree().getSelectionPath();
+ if (path != null) {
+ ItemNode itemNode = (ItemNode) path.getLastPathComponent();
+ Item item = (Item) itemNode.getUserObject();
+ int treeRow = testOverviewTreeTableModel.getRow(item.getId());
+ if (treeRow >= 0) {
+ final Rectangle position = testOverviewTreeTable
+ .getCellRect(testOverviewTreeTable.convertRowIndexToView(treeRow), 0, true);
+ testOverviewTreeTable.scrollRectToVisible(position);
+ }
+ }
+ }
+ }
+
+ private void refreshAction() {
+ // table
+ testOverviewTableModel.fireTableDataChanged();
+ // tree-table
+ testOverviewTreeTableModel.updateModel();
+ expandAllNodes(testOverviewTreeTable.getTree(), 0);
+ // common
+ showFirstRow();
+ resetDerived();
+ testDetailTabbedPane.setSelectedIndex(0);
+ }
+
private void comboBoxAction() {
if (currentRun != null) {
@SuppressWarnings("unchecked")
@@ -583,7 +882,7 @@ private void comboBoxAction() {
if (currentRun.getReporterId() != null && comboBoxItem != null) {
if (!currentRun.getReporterId().equals(comboBoxItem.getKey())) {
update(comboBoxItem.getKey());
- testDetailTabbedPane.setSelectedIndex(0);
+ refreshAction();
}
}
}
@@ -632,6 +931,13 @@ private String getLinkedAndFormattedText(final String text) {
localText = localText.substring(0, start) + title + localText.substring(end);
m = p3.matcher(localText);
}
+ // replaces two consecutive spaces with two non-breaking spaces to fix #140
+ // assume that consecutive spaces do not conflict with previous replacements
+ // using CSS "white-space: pre-wrap;" does not work within Swing, it's simply ignored.
+ // See https://docs.oracle.com/javase/8/docs/api/javax/swing/text/html/CSS.html
+ // putting text in pre tags is not an option, because this suppresses wrap.
+ localText = localText.replaceAll(" ", " ");
+ // add paragraph for each line to preserve line breaks
StringBuilder sb = new StringBuilder();
for (final String p : localText.split("\n")) {
sb.append("
");
@@ -680,12 +986,26 @@ private void runCodeCoverage(boolean selectedOnly) {
final HashSet testPackages = new HashSet<>();
if (selectedOnly) {
// pathList and unique testPackages based on selected tests
- for (final int rowIndex : testOverviewTable.getSelectedRows()) {
- final int row = testOverviewTable.convertRowIndexToModel(rowIndex);
- final Test test = testOverviewTableModel.getTest(row);
- final String path = test.getOwnerName() + "." + test.getObjectName() + "." + test.getProcedureName();
- pathList.add(path);
- testPackages.add(test.getOwnerName() + "." + test.getObjectName());
+ if (!showSuitesCheckBoxMenuItem.isSelected()) {
+ // table
+ for (final int rowIndex : testOverviewTable.getSelectedRows()) {
+ final int row = testOverviewTable.convertRowIndexToModel(rowIndex);
+ final Test test = testOverviewTableModel.getTest(row);
+ final String path = test.getOwnerName() + "." + test.getObjectName() + "." + test.getProcedureName();
+ pathList.add(path);
+ testPackages.add(test.getOwnerName() + "." + test.getObjectName());
+ }
+ } else {
+ // tree-table
+ TreePath[] selectionPaths = testOverviewTreeTable.getTree().getSelectionPaths();
+ if (selectionPaths != null) {
+ for (final TreePath path : selectionPaths) {
+ final ItemNode node = (ItemNode) path.getLastPathComponent();
+ final Item item = (Item) node.getUserObject();
+ pathList.add(":" + item.getId());
+ testPackages.addAll(node.getTestPackages());
+ }
+ }
}
} else {
// pathList and unique testPackages based on currentRun
@@ -714,12 +1034,11 @@ private void fixCountersAndUpdate() {
List incompleteTests = currentRun.getTests().values().stream()
.filter(it -> it.getEndTime() == null && !it.isDisabled()).collect(Collectors.toList());
if (!incompleteTests.isEmpty()) {
- final Double now = (double) System.currentTimeMillis();
- final String sysdate = UtplsqlRunner.getSysdate();
+ final String sysdate = StringTools.getSysdate();
for (Test test : incompleteTests) {
// fix incomplete tests, see https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/107
test.setEndTime(sysdate);
- test.setExecutionTime((now - currentRun.getStart()) / 1000);
+ test.setExecutionTime(StringTools.elapsedTime(test.getStartTime(), test.getEndTime()));
test.setErrorStack(UtplsqlResources.getString("RUNNER_MISSING_TEST_RESULT_MESSAGE"));
test.getCounter().setError(1);
}
@@ -747,15 +1066,13 @@ private void fixCountersAndUpdate() {
currentRun.getCounter().setWarning(currentRun.getCounter().getWarning() + test.getCounter().getWarning());
}
// terminate run
- currentRun.setEndTime(UtplsqlRunner.getSysdate());
- double now = (double) System.currentTimeMillis();
- currentRun.setExecutionTime((now - currentRun.getStart()) / 1000);
+ currentRun.setEndTime(StringTools.getSysdate());
+ currentRun.setExecutionTime(StringTools.elapsedTime(currentRun.getStartTime(), currentRun.getEndTime()));
currentRun.setCurrentTestNumber(0);
// update run in GUI
update(currentRun.getReporterId());
}
- @SuppressWarnings("DuplicatedCode")
private void initializeGUI() {
// Base panel containing all components
basePanel = new JPanel();
@@ -766,14 +1083,19 @@ private void initializeGUI() {
final GradientToolbar toolbar = new GradientToolbar();
toolbar.setFloatable(false);
final EmptyBorder buttonBorder = new EmptyBorder(new Insets(2, 4, 2, 4)); // insets: top, left, bottom, right
+ final ToolbarButton showSuitesButton = new ToolbarButton(UtplsqlResources.getIcon("PACKAGE_FOLDER_ICON"));
+ showSuitesButton.setToolTipText(UtplsqlResources.getString("RUNNER_SHOW_SUITES_BUTTON"));
+ showSuitesButton.setBorder(buttonBorder);
+ showSuitesButton.addActionListener(event -> {
+ showSuitesCheckBoxMenuItem.setSelected(!showSuitesCheckBoxMenuItem.isSelected());
+ applyShowSuites();
+ fixCheckBoxMenuItem(showSuitesCheckBoxMenuItem);
+ });
+ toolbar.add(showSuitesButton);
final ToolbarButton refreshButton = new ToolbarButton(UtplsqlResources.getIcon("REFRESH_ICON"));
refreshButton.setToolTipText(UtplsqlResources.getString("RUNNER_REFRESH_TOOLTIP"));
refreshButton.setBorder(buttonBorder);
- refreshButton.addActionListener(event -> {
- resetDerived();
- testDetailTabbedPane.setSelectedIndex(0);
- testOverviewTableModel.fireTableDataChanged();
- });
+ refreshButton.addActionListener(event -> refreshAction());
toolbar.add(refreshButton);
final ToolbarButton rerunButton = new ToolbarButton(UtplsqlResources.getIcon("RUN_ICON"));
rerunButton.setToolTipText(UtplsqlResources.getString("RUNNER_RERUN_TOOLTIP"));
@@ -813,15 +1135,35 @@ private void initializeGUI() {
if (currentRun.getConsumerConn() != null) {
// Aborts JDBC Connection. Connection might still run in the background. That's expected.
DatabaseTools.abortConnection(currentRun.getConsumerConn());
- List notCompletedTests = currentRun.getTests().values().stream()
- .filter(it -> it.getTestNumber() >= currentRun.getCurrentTestNumber() && it.getEndTime() == null && !it.isDisabled())
+ List notCompletedItems = currentRun.getItemNodes().values().stream()
+ .map(node -> (Item) node.getUserObject())
+ .filter(item -> item.getEndTime() == null && !(item instanceof Test && ((Test) item).isDisabled()))
.collect(Collectors.toList());
- for (Test test : notCompletedTests) {
- test.setDisabled(true);
- test.getCounter().setDisabled(1);
- test.getCounter().setWarning(1);
- test.setWarnings(UtplsqlResources.getString("RUNNER_STOP_TEST_MESSAGE"));
- test.setStartTime(null);
+ String sysdate = StringTools.getSysdate();
+ for (Item item : notCompletedItems) {
+ item.getCounter().setDisabled(1);
+ if (item instanceof Test) {
+ Test test = (Test) item;
+ test.setDisabled(true);
+ test.getCounter().setWarning(1);
+ test.setWarnings(UtplsqlResources.getString("RUNNER_STOP_TEST_MESSAGE"));
+ test.setStartTime(null);
+ } else {
+ if (item.getStartTime() != null) {
+ item.setEndTime(sysdate);
+ if (testOverviewTreeTableModel.ItemNodeHasErrors(item.getId())) {
+ item.getCounter().setError(1);
+ }
+ if (testOverviewTreeTableModel.ItemNodeHasFailedTests(item.getId())) {
+ item.getCounter().setFailure(1);
+ }
+ if (testOverviewTreeTableModel.ItemNodeHasSuccessfulTests(item.getId())) {
+ item.getCounter().setSuccess(1);
+ }
+ item.setExecutionTime(StringTools.elapsedTime(item.getStartTime(), item.getEndTime()));
+ }
+ }
+ testOverviewTreeTableModel.nodeChanged(item.getId());
}
currentRun.setStatus(UtplsqlResources.getString("RUNNER_STOP_RUN_MESSAGE"));
fixCountersAndUpdate();
@@ -844,7 +1186,7 @@ private void initializeGUI() {
final Run run = currentRun;
runs.clear();
currentRun = null;
- setModel(run);
+ setModel(run, true);
update(run.getReporterId());
});
toolbar.add(clearButton);
@@ -967,7 +1309,7 @@ private void initializeGUI() {
fixCheckBoxMenuItem(showInfoCounterCheckBoxMenuItem);
});
countersPopupMenu.add(showInfoCounterCheckBoxMenuItem);
- counterPanel.setComponentPopupMenu(countersPopupMenu);
+ basePanel.setComponentPopupMenu(countersPopupMenu);
// Progress bar
progressBar = new JProgressBar();
@@ -988,7 +1330,7 @@ private void initializeGUI() {
c.weighty = 0;
basePanel.add(progressBar, c);
- // Test overview
+ // Test overview (table variant)
testOverviewTableModel = new TestOverviewTableModel();
testOverviewTable = new JTable(testOverviewTableModel);
testOverviewTable.getTableHeader().setReorderingAllowed(false);
@@ -1004,7 +1346,8 @@ private void initializeGUI() {
testOwnerTextField.setText(test.getOwnerName());
testPackageTextField.setText(test.getObjectName());
testProcedureTextField.setText(test.getProcedureName());
- testDescriptionTextArea.setText(test.getDescription() != null ? test.getDescription().trim() : null);
+ testDescriptionTextArea.setText(StringTools.trim(test.getDescription()));
+ testDisabledReasonTextArea.setText(StringTools.trim(test.getDisabledReason()));
testIdTextArea.setText(test.getId());
testStartTextField.setText(StringTools.formatDateTime(test.getStartTime()));
failuresTableModel.setModel(test.getFailedExpectations());
@@ -1013,16 +1356,15 @@ private void initializeGUI() {
if (test.getFailedExpectations() != null && !test.getFailedExpectations().isEmpty()) {
failuresTable.setRowSelectionInterval(0, 0);
}
- testErrorStackTextPane
- .setText(getHtml(test.getErrorStack() != null ? test.getErrorStack().trim() : null));
- testWarningsTextPane.setText(getHtml(test.getWarnings() != null ? test.getWarnings().trim() : null));
- testServerOutputTextPane
- .setText(getHtml(test.getServerOutput() != null ? test.getServerOutput().trim() : null));
+ testErrorStackTextPane.setText(getHtml(StringTools.trim(test.getErrorStack())));
+ testWarningsTextPane.setText(getHtml(StringTools.trim(test.getWarnings())));
+ testServerOutputTextPane.setText(getHtml(StringTools.trim(test.getServerOutput())));
syncDetailTab();
testOverviewRunMenuItem.setEnabled(true);
testOverviewRunWorksheetMenuItem.setEnabled(true);
testOverviewDebugMenuItem.setEnabled(true);
testOverviewCodeCoverageMenuItem.setEnabled(true);
+
}
});
testOverviewTable.addMouseListener(new MouseAdapter() {
@@ -1044,23 +1386,23 @@ public void mouseClicked(final MouseEvent e) {
overviewTableStatus.setPreferredWidth(INDICATOR_WIDTH);
overviewTableStatus.setMaxWidth(INDICATOR_WIDTH);
overviewTableStatus.setHeaderRenderer(testTableHeaderRenderer);
- final TableColumn overviewTableWarning = testOverviewTable.getColumnModel().getColumn(1);
+ final TableColumn overviewTableId = testOverviewTable.getColumnModel().getColumn(1);
+ overviewTableId.setHeaderRenderer(testTableHeaderRenderer);
+ final TableColumn overviewTableWarning = testOverviewTable.getColumnModel().getColumn(2);
overviewTableWarning.setMinWidth(INDICATOR_WIDTH);
overviewTableWarning.setPreferredWidth(INDICATOR_WIDTH);
overviewTableWarning.setMaxWidth(INDICATOR_WIDTH);
overviewTableWarning.setHeaderRenderer(testTableHeaderRenderer);
- final TableColumn overviewTableInfo = testOverviewTable.getColumnModel().getColumn(2);
+ final TableColumn overviewTableInfo = testOverviewTable.getColumnModel().getColumn(3);
overviewTableInfo.setMinWidth(INDICATOR_WIDTH);
overviewTableInfo.setPreferredWidth(INDICATOR_WIDTH);
overviewTableInfo.setMaxWidth(INDICATOR_WIDTH);
overviewTableInfo.setHeaderRenderer(testTableHeaderRenderer);
- final TableColumn overviewTableId = testOverviewTable.getColumnModel().getColumn(3);
- overviewTableId.setHeaderRenderer(testTableHeaderRenderer);
final TableColumn overviewTableTime = testOverviewTable.getColumnModel().getColumn(4);
overviewTableTime.setPreferredWidth(60);
overviewTableTime.setMaxWidth(100);
overviewTableTime.setHeaderRenderer(testTableHeaderRenderer);
- overviewTableTime.setCellRenderer(new DefaultTableCellRenderer() {
+ final DefaultTableCellRenderer timeColumnRenderer = new DefaultTableCellRenderer() {
private static final long serialVersionUID = 7720067427609773267L;
{
setHorizontalAlignment(JLabel.RIGHT);
@@ -1072,8 +1414,126 @@ public Component getTableCellRendererComponent(final JTable table, final Object
final SmartTime smartTime = new SmartTime(((Double) value), useSmartTimes);
return super.getTableCellRendererComponent(table, smartTime.toString(), isSelected, hasFocus, row, col);
}
+ };
+ overviewTableTime.setCellRenderer(timeColumnRenderer);
+
+ // Test overview (tree-table variant)
+ testOverviewTreeTableModel = new TestOverviewTreeTableModel();
+ testOverviewTreeTable = new JFastTreeTable(testOverviewTreeTableModel);
+ testOverviewTreeTable.setShowGrid(false); // first column is the tree and is not affected in SQLDev, true does not look good
+ testOverviewTreeTable.getTableHeader().setReorderingAllowed(false);
+ testOverviewTreeTable.setAutoCreateRowSorter(false);
+ testOverviewTreeTable.setRowHeight(OVERVIEW_TABLE_ROW_HEIGHT);
+ testOverviewTreeTable.getTableHeader().setPreferredSize(
+ new Dimension(testOverviewTreeTable.getTableHeader().getPreferredSize().width, OVERVIEW_TABLE_ROW_HEIGHT));
+ testOverviewTreeTable.getTree().setRootVisible(false);
+ // calling setDoubleBuffered on tree leads to suppressed painting
+ RepaintManager.currentManager(testOverviewTreeTable).setDoubleBufferingEnabled(true);
+ testOverviewTreeTable.getTree().getSelectionModel().addTreeSelectionListener(event -> {
+ final TreePath path = event.getPath();
+ if (path != null) {
+ final ItemNode node = (ItemNode) path.getLastPathComponent();
+ final Item item = (Item) node.getUserObject();
+ testOwnerTextField.setText(node.getOwnerName());
+ testPackageTextField.setText(node.getPackageName());
+ testProcedureTextField.setText(node.getProcedureName());
+ testDescriptionTextArea.setText(node.getDescription());
+ testIdTextArea.setText(node.getId());
+ testStartTextField.setText(StringTools.formatDateTime(item.getStartTime()));
+ if (item instanceof Test) {
+ Test test = (Test) item;
+ failuresTableModel.setModel(test.getFailedExpectations());
+ failuresTableModel.fireTableDataChanged();
+ testFailureMessageTextPane.setText(null);
+ if (test.getFailedExpectations() != null && !test.getFailedExpectations().isEmpty()) {
+ failuresTable.setRowSelectionInterval(0, 0);
+ }
+ testDisabledReasonTextArea.setText(test.getDisabledReason());
+ } else {
+ failuresTableModel.setModel(null);
+ failuresTableModel.fireTableDataChanged();
+ testFailureMessageTextPane.setText(null);
+ testDisabledReasonTextArea.setText(null);
+ }
+ testErrorStackTextPane.setText(getHtml(StringTools.trim(item.getErrorStack())));
+ testWarningsTextPane.setText(getHtml(StringTools.trim(item.getWarnings())));
+ testServerOutputTextPane.setText(getHtml(StringTools.trim(item.getServerOutput())));
+ syncDetailTab();
+ testOverviewRunMenuItem.setEnabled(true);
+ testOverviewRunWorksheetMenuItem.setEnabled(true);
+ testOverviewDebugMenuItem.setEnabled(true);
+ testOverviewCodeCoverageMenuItem.setEnabled(true);
+ }
+ });
+ testOverviewTreeTable.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ if (failuresTable.getSelectedRowCount() == 1) {
+ // open failure only if a Test node is selected
+ openSelectedFailure();
+ } else {
+ openSelectedTest();
+ }
+ }
+ }
+ });
+ final JTree overviewTreeTableName = testOverviewTreeTable.getTree();
+ overviewTreeTableName.setCellRenderer(new DefaultTreeCellRenderer() {
+ private static final long serialVersionUID = 580783625740405285L;
+
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded,
+ boolean leaf, int row, boolean hasFocus) {
+ this.hasFocus = hasFocus;
+ final ItemNode node = (ItemNode) value;
+ setText((String) testOverviewTreeTableModel.getValueAt(value, 0));
+
+ Color foregroundColor;
+ if (selected) {
+ foregroundColor = getTextSelectionColor();
+ } else {
+ foregroundColor = getTextNonSelectionColor();
+ }
+ setForeground(foregroundColor);
+
+ Icon icon = node.getStatusIcon();
+ if (icon == null) {
+ if (leaf) {
+ icon = getLeafIcon();
+ } else if (expanded) {
+ icon = getOpenIcon();
+ } else {
+ icon = getClosedIcon();
+ }
+ }
+ setIcon(icon);
+ this.selected = selected;
+ return this;
+ }
});
- final JScrollPane testOverviewScrollPane = new JScrollPane(testOverviewTable);
+ final TestTreeTableHeaderRenderer testTreeTableHeaderRenderer = new TestTreeTableHeaderRenderer();
+ final TableColumn overviewTreeTableSuite = testOverviewTreeTable.getColumnModel().getColumn(0);
+ overviewTreeTableSuite.setHeaderRenderer(testTreeTableHeaderRenderer);
+ final TableColumn overviewTreeTableWarning = testOverviewTreeTable.getColumnModel().getColumn(1);
+ overviewTreeTableWarning.setMinWidth(INDICATOR_WIDTH);
+ overviewTreeTableWarning.setPreferredWidth(INDICATOR_WIDTH);
+ overviewTreeTableWarning.setMaxWidth(INDICATOR_WIDTH);
+ overviewTreeTableWarning.setHeaderRenderer(testTreeTableHeaderRenderer);
+ final TableColumn overviewTreeTableInfo = testOverviewTreeTable.getColumnModel().getColumn(2);
+ overviewTreeTableInfo.setMinWidth(INDICATOR_WIDTH);
+ overviewTreeTableInfo.setPreferredWidth(INDICATOR_WIDTH);
+ overviewTreeTableInfo.setMaxWidth(INDICATOR_WIDTH);
+ overviewTreeTableInfo.setHeaderRenderer(testTreeTableHeaderRenderer);
+ final TableColumn overviewTreeTableTime = testOverviewTreeTable.getColumnModel().getColumn(3);
+ overviewTreeTableTime.setPreferredWidth(60);
+ overviewTreeTableTime.setMaxWidth(100);
+ overviewTreeTableTime.setHeaderRenderer(testTreeTableHeaderRenderer);
+ overviewTreeTableTime.setCellRenderer(timeColumnRenderer);
+
+ // Scroll pane for test overview containing either the tree-table or table variant, populated in applyPreferences()
+ testOverviewScrollPane = new JScrollPane();
+ RepaintManager.currentManager(testOverviewScrollPane).setDoubleBufferingEnabled(true);
// Context menu for test overview
final JPopupMenu testOverviewPopupMenu = new JPopupMenu();
@@ -1120,6 +1580,8 @@ public Component getTableCellRendererComponent(final JTable table, final Object
showTestDescriptionCheckBoxMenuItem.addActionListener(event -> {
applyShowTestDescription();
fixCheckBoxMenuItem(showTestDescriptionCheckBoxMenuItem);
+ // force refresh to fix issue #131 (truncated name/description), accepting side-effect, that the selection is lost
+ refreshAction();
});
testOverviewPopupMenu.add(showTestDescriptionCheckBoxMenuItem);
showWarningIndicatorCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_WARNING_INDICATOR_LABEL").replace("?", ""), true);
@@ -1139,9 +1601,19 @@ public Component getTableCellRendererComponent(final JTable table, final Object
syncDetailTab();
fixCheckBoxMenuItem(syncDetailTabCheckBoxMenuItem);
});
+ testOverviewPopupMenu.add(new JSeparator());
testOverviewPopupMenu.add(syncDetailTabCheckBoxMenuItem);
+ showSuitesCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_SUITES_LABEL").replace("?", ""), true);
+ showSuitesCheckBoxMenuItem.addActionListener(event -> {
+ applyShowSuites();
+ fixCheckBoxMenuItem(showSuitesCheckBoxMenuItem);
+ });
+ testOverviewPopupMenu.add(showSuitesCheckBoxMenuItem);
testOverviewTable.setComponentPopupMenu(testOverviewPopupMenu);
testOverviewTable.getTableHeader().setComponentPopupMenu(testOverviewPopupMenu);
+ testOverviewTreeTable.setComponentPopupMenu(testOverviewPopupMenu);
+ testOverviewTreeTable.getTableHeader().setComponentPopupMenu(testOverviewPopupMenu);
+ testOverviewScrollPane.setComponentPopupMenu(testOverviewPopupMenu);
// Test tabbed pane (Test Properties)
final ScrollablePanel testInfoPanel = new ScrollablePanel();
@@ -1246,11 +1718,39 @@ public Component getTableCellRendererComponent(final JTable table, final Object
c.weightx = 1;
c.weighty = 0;
testInfoPanel.add(testDescriptionTextArea, c);
+ // - Disabled Reason
+ final JLabel testDisabledReasonLabel = new JLabel(UtplsqlResources.getString("RUNNER_DISABLED_REASON_LABEL"));
+ testDisabledReasonLabel.setBorder(BorderFactory.createEmptyBorder(isMacLookAndFeel() ? 5 : 3, 0, 0, 0));
+ c.gridx = 0;
+ c.gridy = 4;
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.insets = new Insets(5, 10, 0, 0); // top, left, bottom, right
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.fill = GridBagConstraints.NONE;
+ c.weightx = 0;
+ c.weighty = 0;
+ testInfoPanel.add(testDisabledReasonLabel, c);
+ testDisabledReasonTextArea = new RunnerTextArea();
+ testDisabledReasonTextArea.setEditable(false);
+ testDisabledReasonTextArea.setEnabled(true);
+ testDisabledReasonTextArea.setLineWrap(true);
+ testDisabledReasonTextArea.setWrapStyleWord(true);
+ c.gridx = 1;
+ c.gridy = 4;
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.insets = new Insets(5, 5, 0, 10); // top, left, bottom, right
+ c.anchor = GridBagConstraints.WEST;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.weighty = 0;
+ testInfoPanel.add(testDisabledReasonTextArea, c);
// - Suitepath (id)
final JLabel testIdLabel = new JLabel(UtplsqlResources.getString("RUNNER_TEST_ID_COLUMN"));
testIdLabel.setBorder(BorderFactory.createEmptyBorder(isMacLookAndFeel() ? 5 : 3, 0, 0, 0));
c.gridx = 0;
- c.gridy = 4;
+ c.gridy = 5;
c.gridwidth = 1;
c.gridheight = 1;
c.insets = new Insets(5, 10, 0, 0); // top, left, bottom, right
@@ -1265,7 +1765,7 @@ public Component getTableCellRendererComponent(final JTable table, final Object
testIdTextArea.setLineWrap(true);
testIdTextArea.setWrapStyleWord(false);
c.gridx = 1;
- c.gridy = 4;
+ c.gridy = 5;
c.gridwidth = 1;
c.gridheight = 1;
c.insets = new Insets(5, 5, 0, 10); // top, left, bottom, right
@@ -1277,7 +1777,7 @@ public Component getTableCellRendererComponent(final JTable table, final Object
// - Start
final JLabel testStartLabel = new JLabel(UtplsqlResources.getString("RUNNER_START_LABEL"));
c.gridx = 0;
- c.gridy = 5;
+ c.gridy = 6;
c.gridwidth = 1;
c.gridheight = 1;
c.insets = new Insets(5, 10, 10, 0); // top, left, bottom, right
@@ -1289,7 +1789,7 @@ public Component getTableCellRendererComponent(final JTable table, final Object
testStartTextField = new RunnerTextField();
testStartTextField.setEditable(false);
c.gridx = 1;
- c.gridy = 5;
+ c.gridy = 6;
c.gridwidth = 1;
c.gridheight = 1;
c.insets = new Insets(5, 5, 10, 10); // top, left, bottom, right
@@ -1298,8 +1798,9 @@ public Component getTableCellRendererComponent(final JTable table, final Object
c.weightx = 1;
c.weighty = 0;
testInfoPanel.add(testStartTextField, c);
+ // - Vertical filler
c.gridx = 0;
- c.gridy = 6;
+ c.gridy = 7;
c.gridwidth = 1;
c.gridheight = 1;
c.insets = new Insets(0, 0, 0, 0); // top, left, bottom, right
@@ -1405,7 +1906,12 @@ public void mouseClicked(final MouseEvent e) {
testWarningsTextPane.setContentType("text/html");
testWarningsTextPane.setMinimumSize(TEXTPANE_DIM);
testWarningsTextPane.setPreferredSize(TEXTPANE_DIM);
- testWarningsTextPane.addHyperlinkListener(event -> openLink(event.getDescription()));
+ testWarningsTextPane.addHyperlinkListener(event -> {
+ if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+ final String link = event.getDescription();
+ openLink(link);
+ }
+ });
final JScrollPane testWarningsScrollPane = new JScrollPane(testWarningsTextPane);
c.gridx = 0;
c.gridy = 0;
@@ -1427,7 +1933,12 @@ public void mouseClicked(final MouseEvent e) {
testServerOutputTextPane.setContentType("text/html");
testServerOutputTextPane.setMinimumSize(TEXTPANE_DIM);
testServerOutputTextPane.setPreferredSize(TEXTPANE_DIM);
- testServerOutputTextPane.addHyperlinkListener(event -> openLink(event.getDescription()));
+ testServerOutputTextPane.addHyperlinkListener(event -> {
+ if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+ final String link = event.getDescription();
+ openLink(link);
+ }
+ });
final JScrollPane testServerOutputScrollPane = new JScrollPane(testServerOutputTextPane);
c.gridx = 0;
c.gridy = 0;
@@ -1466,10 +1977,12 @@ public void mouseClicked(final MouseEvent e) {
BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(new Color(219, 219, 219)),
BorderFactory.createEmptyBorder(1, 1, 1, 1)));
testDescriptionTextArea.setBorder(border);
+ testDisabledReasonTextArea.setBorder(border);
testIdTextArea.setBorder(border);
} else {
final Border referenceBorder = testOwnerTextField.getBorder();
testDescriptionTextArea.setBorder(referenceBorder);
+ testDisabledReasonTextArea.setBorder(referenceBorder);
testIdTextArea.setBorder(referenceBorder);
}
}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/SmartTime.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/SmartTime.java
index 8e4e046b..69ea1500 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/SmartTime.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/SmartTime.java
@@ -45,7 +45,7 @@ public Double getSeconds() {
@Override
public String toString() {
- String ret = null;
+ String ret;
if (seconds == null) {
ret = null;
} else if (smart) {
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/TestOverviewTableModel.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/TestOverviewTableModel.java
index 02e85d81..ea2729e5 100644
--- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/TestOverviewTableModel.java
+++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/TestOverviewTableModel.java
@@ -17,7 +17,6 @@
import java.util.ArrayList;
import java.util.LinkedHashMap;
-import java.util.Map.Entry;
import javax.swing.Icon;
import javax.swing.table.DefaultTableModel;
@@ -41,7 +40,7 @@ public TestOverviewTableModel() {
private void calcCommonPrefix() {
if (!commonPrefixCalculated && tests != null && tests.size() > 0) {
- commonPrefix = PrefixTools.commonPrefix(new ArrayList(tests.keySet()));
+ commonPrefix = PrefixTools.commonPrefix(new ArrayList<>(tests.keySet()));
fireTableDataChanged();
commonPrefixCalculated = true;
}
@@ -62,7 +61,7 @@ public void updateModel(final boolean showDescription) {
fireTableDataChanged();
}
- public CharSequence getTestIdColumnName() {
+ public String getTestIdColumnName() {
StringBuilder sb = new StringBuilder();
calcCommonPrefix();
if (commonPrefix == null || commonPrefix.isEmpty()) {
@@ -89,7 +88,7 @@ public String getTimeColumnName() {
}
public Test getTest(final int row) {
- return new ArrayList>(tests.entrySet()).get(row).getValue();
+ return new ArrayList<>(tests.entrySet()).get(row).getValue();
}
@Override
@@ -109,38 +108,38 @@ public int getColumnCount() {
public Object getValueAt(final int row, final int col) {
final Test test = getTest(row);
switch (col) {
- case 0:
- return test.getStatusIcon();
- case 1:
- return test.getWarningIcon();
- case 2:
- return test.getInfoIcon();
- case 3:
- if (showDescription && test.getDescription() != null) {
- return test.getDescription();
- } else {
- return test.getId().substring(commonPrefix == null ? 0 : commonPrefix.length());
- }
- case 4:
- return test.getExecutionTime();
- default:
- return null;
+ case 0:
+ return test.getStatusIcon();
+ case 1:
+ if (showDescription && test.getDescription() != null) {
+ return test.getDescription();
+ } else {
+ return test.getId().substring(commonPrefix == null ? 0 : commonPrefix.length());
+ }
+ case 2:
+ return test.getWarningIcon();
+ case 3:
+ return test.getInfoIcon();
+ case 4:
+ return test.getExecutionTime();
+ default:
+ return null;
}
}
@Override
public String getColumnName(final int col) {
switch (col) {
- case 0:
- case 1:
- case 2:
- return ""; // icons are used instead of descriptions
- case 3:
- return UtplsqlResources.getString(showDescription ? "RUNNER_DESCRIPTION_LABEL" : "RUNNER_TEST_ID_COLUMN");
- case 4:
- return getTimeColumnName();
- default:
- return null;
+ case 1:
+ return UtplsqlResources.getString(showDescription ? "RUNNER_DESCRIPTION_LABEL" : "RUNNER_TEST_ID_COLUMN");
+ case 0:
+ case 2:
+ case 3:
+ return ""; // icons are used instead of descriptions
+ case 4:
+ return getTimeColumnName();
+ default:
+ return null;
}
}
@@ -153,18 +152,14 @@ public boolean isCellEditable(final int row, final int column) {
@Override
public Class> getColumnClass(final int col) {
switch (col) {
- case 0:
- return Icon.class;
- case 1:
- return Icon.class;
- case 2:
- return Icon.class;
- case 3:
- return String.class;
- case 4:
- return Double.class;
- default:
- return String.class;
+ case 0:
+ case 2:
+ case 3:
+ return Icon.class;
+ case 4:
+ return Double.class;
+ default:
+ return String.class;
}
}
}
diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/TestOverviewTreeTableModel.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/TestOverviewTreeTableModel.java
new file mode 100644
index 00000000..1676c3a1
--- /dev/null
+++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/TestOverviewTreeTableModel.java
@@ -0,0 +1,512 @@
+/*
+ * 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.ui.runner;
+
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+
+import javax.swing.Icon;
+import javax.swing.event.EventListenerList;
+import javax.swing.event.TreeModelEvent;
+import javax.swing.event.TreeModelListener;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+
+import org.utplsql.sqldev.model.runner.Counter;
+import org.utplsql.sqldev.model.runner.Item;
+import org.utplsql.sqldev.model.runner.ItemNode;
+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 oracle.javatools.ui.treetable.TreeTableModel;
+
+public class TestOverviewTreeTableModel implements TreeTableModel {
+ private boolean showDescription;
+ private boolean useSmartTimes;
+ private boolean showSuccessfulTests;
+ private boolean showDisabledTests;
+ private String rootId;
+ private LinkedHashMap sources = new LinkedHashMap<>();
+ private final LinkedHashMap nodes = new LinkedHashMap<>();
+ protected final EventListenerList listenerList = new EventListenerList();
+
+ public TestOverviewTreeTableModel() {
+ super();
+ }
+
+ private boolean hasVisibleDisabledTests(ItemNode startNode) {
+ if (!showDisabledTests || startNode.getUserObject() instanceof Test) {
+ return false;
+ }
+ Enumeration> children = sources.get(startNode.getId()).preorderEnumeration();
+ while (children.hasMoreElements()) {
+ ItemNode child = (ItemNode) children.nextElement();
+ Item item = (Item) child.getUserObject();
+ if (item.getStatusIcon() == UtplsqlResources.getIcon("DISABLED_ICON")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determines if a node should be shown in the tree.
+ */
+ private boolean isVisible(ItemNode node) {
+ if (node != null) {
+ Item item = (Item) node.getUserObject();
+ if (item.getStatusIcon() == UtplsqlResources.getIcon("SUCCESS_ICON") && !showSuccessfulTests
+ && !hasVisibleDisabledTests(node)) {
+ return false;
+ }
+ if (item.getStatusIcon() == UtplsqlResources.getIcon("DISABLED_ICON") && !showDisabledTests) {
+ return false;
+ }
+ return true;
+ }
+ return true;
+ }
+
+ /**
+ * Copies the original nodes determined by the run to the local nodes.
+ * Keeps references to items in the run, so changes in the run are automatically applied.
+ * However, the listeners need to by notified about the changes to display them
+ * in the underlying tree and table.
+ */
+ private void setClonedItemNodes() {
+ nodes.clear();
+ for (ItemNode source : sources.values()) {
+ ItemNode node = new ItemNode((Item) source.getUserObject());
+ nodes.put(node.getId(), node);
+ }
+ for (ItemNode source : sources.values()) {
+ if (source.getUserObject() instanceof Suite) {
+ ItemNode parent = nodes.get(source.getId());
+ Enumeration> sourceChildren = source.children();
+ while (sourceChildren.hasMoreElements()) {
+ ItemNode sourceChild = (ItemNode) sourceChildren.nextElement();
+ ItemNode child = nodes.get(sourceChild.getId());
+ if (isVisible(child)) {
+ parent.add(child);
+ }
+ }
+ }
+ }
+ reload();
+ }
+
+ /**
+ * Sets the complete model. For example when changing a run.
+ */
+ public void setModel(final Run run, final boolean showDescription, final boolean useSmartTimes,
+ final boolean showSuccessfulTests, final boolean showDisabledTests) {
+ this.showDescription = showDescription;
+ this.useSmartTimes = useSmartTimes;
+ this.showSuccessfulTests = showSuccessfulTests;
+ this.showDisabledTests = showDisabledTests;
+ this.rootId = run.getReporterId();
+ this.sources = run.getItemNodes();
+ setClonedItemNodes();
+ }
+
+ /**
+ * Updates the description only.
+ */
+ public void updateModel(final boolean showDescription) {
+ this.showDescription = showDescription;
+ }
+
+ /**
+ * Updates filter criteria. If a change is detected the model is re-created from scratch.
+ */
+ public void updateModel(final boolean showSuccessfulTests, final boolean showDisabledTests) {
+ if (this.showSuccessfulTests != showSuccessfulTests || this.showDisabledTests != showDisabledTests) {
+ this.showSuccessfulTests = showSuccessfulTests;
+ this.showDisabledTests = showDisabledTests;
+ setClonedItemNodes();
+ }
+ }
+
+ /**
+ * Re-creates the model from scratch.
+ */
+ public void updateModel() {
+ setClonedItemNodes();
+ }
+
+ /**
+ * Applies the filter criteria for a part of the tree after an update.
+ * Technically it will remove nodes from the tree.
+ */
+ private void removeInvisibleNodes(ItemNode startNode) {
+ TreeNode[] path = startNode.getPath();
+ for (TreeNode node : path) {
+ ItemNode parent = (ItemNode) node.getParent();
+ if (parent != null) {
+ if (!isVisible((ItemNode) node)) {
+ int childIndex = parent.getIndex(node);
+ if (childIndex >= 0) {
+ parent.remove(childIndex);
+ // Delaying the fireTreeNodesRemove call would avoid the following exception:
+ // Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: n >= m
+ // at java.util.Vector.elementAt(Vector.java:479)
+ // at javax.swing.tree.DefaultMutableTreeNode.getChildAt(DefaultMutableTreeNode.java:245)
+ // ...
+ // This exception is raised in the event dispatching thread (another thread). The line numbers
+ // may differ based on the JDK you are using. I suspect that events are not processed
+ // fast enough.
+ //
+ // However, it looks like these errors can be ignored. At least there seems to be no negative
+ // side effect in the runner GUI. In any case the user could always use the refresh action
+ // to get a clean state, should something look wrong. But I've never experienced this.
+ //
+ // These exceptions are thrown only when filtering is enabled, but then it happens quite often.
+ // This means it happens for 5-10% of the nodes.
+ //
+ // Calling fireTreeNodesRemoved in another thread, e.g. via SwingUtilities.invokeLater()
+ // will reduce the number of exceptions significantly (almost zero). However, in this case
+ // the subsequent updates might fail and this will cause an exception in this thread
+ // (in the method {@link #nodeChanged(String id)}. I tried the following:
+ // - calling all fireTreeNode... methods via SwingUtilities.invokeLater().
+ // - catching the exception, trying to re-fire or ignore it
+ // In the end the user experience was always worse. Even if I've got no exceptions
+ // anymore the result in the TreeTable was wrong. I've got wrong rows, even empty rows.
+ //
+ // Therefore I decided to live with some exceptions in the event dispatching thread.
+ fireTreeNodesRemoved(this, parent.getPath(), new int[] { childIndex }, new Object[] { node });
+ // removing the parent removes also all its children, hence no need for further processing
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates a node and its parents and then apply the filter criteria.
+ */
+ public void updateModel(final String id) {
+ nodeChanged(id);
+ ItemNode startNode = nodes.get(id);
+ if (startNode != null) {
+ removeInvisibleNodes(startNode);
+ }
+ }
+
+ /**
+ * Notifies all listeners that the complete tree has changed.
+ * For that a root node must be available.
+ * However, a root node does not mean the model {@link #isComplete()}.
+ */
+ public void reload() {
+ if (getRoot() != null) {
+ fireTreeStructureChanged(this, getRoot().getPath(), null, null);
+ }
+ }
+
+ /**
+ * Notifies all listeners that a node and its parents have changed.
+ */
+ public void nodeChanged(String id) {
+ ItemNode startNode = nodes.get(id);
+ if (startNode != null) {
+ TreeNode[] path = startNode.getPath();
+ for (TreeNode node : path) {
+ ItemNode parent = (ItemNode) node.getParent();
+ if (parent != null) {
+ int childIndex = parent.getIndex(node);
+ if (childIndex >= 0) {
+ fireTreeNodesChanged(this, parent.getPath(), new int[] { childIndex }, new Object[] { node });
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Determines if the model is fully initialized and can be used.
+ * For that it the pseudo root must contain a child.
+ */
+ public boolean isComplete() {
+ return nodes.size() > 1; // return sources.size() != nodes.size();
+ }
+
+ /**
+ * Calculates the row of the underlying table when the tree is fully expanded.
+ */
+ public int getRow(final String id) {
+ // do not count root
+ int i = -1;
+
+ // The order of orderedNodes can differ to nodes.values()
+ // when run is based on list of tests.
+ Enumeration> orderedNodes = getRoot().preorderEnumeration();
+ while (orderedNodes.hasMoreElements()) {
+ ItemNode node = (ItemNode) orderedNodes.nextElement();
+ if (((Item) node.getUserObject()).getId().equals(id)) {
+ return i;
+ }
+ i++;
+ }
+ return -1;
+ }
+
+ public Test getTestOf(final ItemNode startNode) {
+ Enumeration> orderedNodes = startNode.preorderEnumeration();
+ while (orderedNodes.hasMoreElements()) {
+ ItemNode node = (ItemNode) orderedNodes.nextElement();
+ Item item = (Item) node.getUserObject();
+ if (item instanceof Test) {
+ return (Test) item;
+ }
+ }
+ return null;
+ }
+
+ public ItemNode getItemNode(final String id) {
+ return nodes.get(id);
+ }
+
+ private interface CounterChecker {
+ boolean matchedStatus (Counter counter);
+ }
+
+ private boolean ItemNodeStatus(final String id, final CounterChecker checker) {
+ ItemNode startNode = sources.get(id);
+ if (startNode != null) {
+ Enumeration> orderedNodes = startNode.preorderEnumeration();
+ while (orderedNodes.hasMoreElements()) {
+ ItemNode node = (ItemNode) orderedNodes.nextElement();
+ Item item = (Item) node.getUserObject();
+ if (checker.matchedStatus(item.getCounter())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if a node or one of its children have errors.
+ */
+ public boolean ItemNodeHasErrors(final String id) {
+ return ItemNodeStatus(id, counter -> counter.getError() > 0);
+ }
+
+ /**
+ * Returns true if a node or one of its children have failed tests.
+ */
+ public boolean ItemNodeHasFailedTests(final String id) {
+ return ItemNodeStatus(id, counter -> counter.getFailure() > 0);
+ }
+
+ /**
+ * Returns true if a node or one of its children have successful tests.
+ */
+ public boolean ItemNodeHasSuccessfulTests(final String id) {
+ return ItemNodeStatus(id, counter -> counter.getSuccess() > 0);
+ }
+
+ public String getTreeColumnName() {
+ return UtplsqlResources.getString(showDescription ? "RUNNER_DESCRIPTION_LABEL" : "RUNNER_TEST_ID_COLUMN");
+ }
+
+ public String getTimeColumnName() {
+ return UtplsqlResources.getString("RUNNER_TEST_EXECUTION_TIME_COLUMN") + (!useSmartTimes ? " [s]" : "");
+ }
+
+ @Override
+ public ItemNode getRoot() {
+ return nodes.get(rootId);
+ }
+
+ @Override
+ public ItemNode getChild(Object parent, int index) {
+ return (ItemNode) ((ItemNode) parent).getChildAt(index);
+ }
+
+ @Override
+ public int getChildCount(Object parent) {
+ return ((ItemNode) parent).getChildCount();
+ }
+
+ @Override
+ public boolean isLeaf(Object node) {
+ return !((ItemNode) node).getAllowsChildren();
+ }
+
+ @Override
+ public void valueForPathChanged(TreePath path, Object newValue) {
+ // ignore, no implementation required
+ }
+
+ @Override
+ public int getIndexOfChild(Object parent, Object child) {
+ return ((ItemNode) parent).getIndex((ItemNode) child);
+ }
+
+ @Override
+ public void addTreeModelListener(TreeModelListener l) {
+ listenerList.add(TreeModelListener.class, l);
+ }
+
+ @Override
+ public void removeTreeModelListener(TreeModelListener l) {
+ listenerList.remove(TreeModelListener.class, l);
+ }
+
+ /**
+ * Copied from DefaultTreeModel
+ */
+ protected void fireTreeNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children) {
+ Object[] listeners = this.listenerList.getListenerList();
+ TreeModelEvent e = null;
+
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == TreeModelListener.class) {
+ if (e == null) {
+ e = new TreeModelEvent(source, path, childIndices, children);
+ }
+ // might fail with IndexOutOfBoundsException
+ ((TreeModelListener) listeners[i + 1]).treeNodesChanged(e);
+ }
+ }
+ }
+
+ /**
+ * Copied from DefaultTreeModel
+ */
+ protected void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) {
+ Object[] listeners = this.listenerList.getListenerList();
+ TreeModelEvent e = null;
+
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == TreeModelListener.class) {
+ if (e == null) {
+ e = new TreeModelEvent(source, path, childIndices, children);
+ }
+
+ ((TreeModelListener) listeners[i + 1]).treeNodesInserted(e);
+ }
+ }
+ }
+
+ /**
+ * Copied from DefaultTreeModel
+ */
+ protected void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) {
+ Object[] listeners = this.listenerList.getListenerList();
+ TreeModelEvent e = null;
+
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == TreeModelListener.class) {
+ if (e == null) {
+ e = new TreeModelEvent(source, path, childIndices, children);
+ }
+
+ ((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e);
+ }
+ }
+ }
+
+ /**
+ * Copied from DefaultTreeModel
+ */
+ protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) {
+ Object[] listeners = this.listenerList.getListenerList();
+ TreeModelEvent e = null;
+
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == TreeModelListener.class) {
+ if (e == null) {
+ e = new TreeModelEvent(source, path, childIndices, children);
+ }
+
+ ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);
+ }
+ }
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 4;
+ }
+
+ @Override
+ public String getColumnName(int col) {
+ switch (col) {
+ case 0:
+ return getTreeColumnName();
+ case 1:
+ case 2:
+ return ""; // icons are used instead of descriptions
+ case 3:
+ return getTimeColumnName();
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public Class> getColumnClass(int col) {
+ switch (col) {
+ case 0:
+ return TreeTableModel.class;
+ case 1:
+ case 2:
+ return Icon.class;
+ case 3:
+ return Double.class;
+ default:
+ return String.class;
+ }
+ }
+
+ @Override
+ public Object getValueAt(Object node, int col) {
+ final ItemNode itemNode = (ItemNode) node;
+ switch (col) {
+ case 0:
+ if (showDescription && itemNode.getDescription() != null) {
+ return itemNode.getDescription();
+ } else {
+ return itemNode.getName();
+ }
+ case 1:
+ return itemNode.getWarningIcon();
+ case 2:
+ return itemNode.getInfoIcon();
+ case 3:
+ return itemNode.getExecutionTime();
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public boolean isCellEditable(Object node, int col) {
+ // make the tree column editable to forward mouse events for collapse/expand
+ return getColumnClass(col) == TreeTableModel.class;
+ }
+
+ @Override
+ public void setValueAt(Object value, Object node, int col) {
+ // ignore, no implementation required
+ }
+
+}
diff --git a/sqldev/src/main/resources/coverage/assets/application.css b/sqldev/src/main/resources/coverage/assets/application.css
new file mode 100644
index 00000000..11b10293
--- /dev/null
+++ b/sqldev/src/main/resources/coverage/assets/application.css
@@ -0,0 +1,820 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 0.9
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* reset.css */
+
+html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, dialog, figure, footer, header, hgroup, nav, section {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;}
+article, aside, dialog, figure, footer, header, hgroup, nav, section {display:block;}
+body {line-height:1.5;}
+table {border-collapse:separate;border-spacing:0;}
+caption, th, td {text-align:left;font-weight:normal;}
+table, td, th {vertical-align:middle;}
+blockquote:before, blockquote:after, q:before, q:after {content:"";}
+blockquote, q {quotes:"" "";}
+a img {border:none;}
+
+/* typography.css */
+html {font-size:100.01%;}
+body {font-size:82%;color:#222;background:#fff;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;}
+h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#111;}
+h1 {font-size:3em;line-height:1;margin-bottom:0.5em;}
+h2 {font-size:2em;margin-bottom:0.75em;}
+h3 {font-size:1.5em;line-height:1;margin-bottom:1em;}
+h4 {font-size:1.2em;line-height:1.25;margin-bottom:1.25em;}
+h5 {font-size:1em;font-weight:bold;margin-bottom:1.5em;}
+h6 {font-size:1em;font-weight:bold;}
+h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;}
+p {margin:0 0 1.5em;}
+p img.left {float:left;margin:1.5em 1.5em 1.5em 0;padding:0;}
+p img.right {float:right;margin:1.5em 0 1.5em 1.5em;}
+a:focus, a:hover {color:#000;}
+a {color:#009;text-decoration:underline;}
+blockquote {margin:1.5em;color:#666;font-style:italic;}
+strong {font-weight:bold;}
+em, dfn {font-style:italic;}
+dfn {font-weight:bold;}
+sup, sub {line-height:0;}
+abbr, acronym {border-bottom:1px dotted #666;}
+address {margin:0 0 1.5em;font-style:italic;}
+del {color:#666;}
+pre {margin:1.5em 0;white-space:pre;}
+pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;}
+li ul, li ol {margin:0;}
+ul, ol {margin:0 1.5em 1.5em 0;padding-left:3.333em;}
+ul {list-style-type:disc;}
+ol {list-style-type:decimal;}
+dl {margin:0 0 1.5em 0;}
+dl dt {font-weight:bold;}
+dd {margin-left:1.5em;}
+table {margin-bottom:1.4em;width:100%;}
+th {font-weight:bold;}
+thead th {background:#c3d9ff;}
+th, td, caption {padding:4px 10px 4px 5px;}
+tr.even td {background:#efefef;}
+tfoot {font-style:italic;}
+caption {background:#eee;}
+.small {font-size:.8em;margin-bottom:1.875em;line-height:1.875em;}
+.large {font-size:1.2em;line-height:2.5em;margin-bottom:1.25em;}
+.hide {display:none;}
+.quiet {color:#666;}
+.loud {color:#000;}
+.highlight {background:#ff0;}
+.added {background:#060;color:#fff;}
+.removed {background:#900;color:#fff;}
+.first {margin-left:0;padding-left:0;}
+.last {margin-right:0;padding-right:0;}
+.top {margin-top:0;padding-top:0;}
+.bottom {margin-bottom:0;padding-bottom:0;}
+
+/* forms.css */
+label {font-weight:bold;}
+fieldset {padding:1.4em;margin:0 0 1.5em 0;border:1px solid #ccc;}
+legend {font-weight:bold;font-size:1.2em;}
+input[type=text], input[type=password], input.text, input.title, textarea, select {background-color:#fff;border:1px solid #bbb;}
+input[type=text]:focus, input[type=password]:focus, input.text:focus, input.title:focus, textarea:focus, select:focus {border-color:#666;}
+input[type=text], input[type=password], input.text, input.title, textarea, select {margin:0.5em 0;}
+input.text, input.title {width:300px;padding:5px;}
+input.title {font-size:1.5em;}
+textarea {width:390px;height:250px;padding:5px;}
+input[type=checkbox], input[type=radio], input.checkbox, input.radio {position:relative;top:.25em;}
+form.inline {line-height:3;}
+form.inline p {margin-bottom:0;}
+.error, .notice, .success {padding:.8em;margin-bottom:1em;border:2px solid #ddd;}
+.error {background:#FBE3E4;color:#8a1f11;border-color:#FBC2C4;}
+.notice {background:#FFF6BF;color:#514721;border-color:#FFD324;}
+.success {background:#E6EFC2;color:#264409;border-color:#C6D880;}
+.error a {color:#8a1f11;}
+.notice a {color:#514721;}
+.success a {color:#264409;}
+.box {padding:1.5em;margin-bottom:1.5em;background:#E5ECF9;}
+hr {background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:.1em;margin:0 0 1.45em;border:none;}
+hr.space {background:#fff;color:#fff;visibility:hidden;}
+.clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;}
+.clearfix, .container {display:block;}
+.clear {clear:both;}
+/*
+github.com style (c) Vasily Polovnyov
+*/
+
+
+pre code {
+}
+
+pre .comment,
+pre .template_comment,
+pre .diff .header,
+pre .javadoc {
+ color: #998;
+ font-style: italic
+}
+
+pre .keyword,
+pre .css .rule .keyword,
+pre .winutils,
+pre .javascript .title,
+pre .lisp .title {
+ color: #000;
+ font-weight: bold
+}
+
+pre .number,
+pre .hexcolor {
+ color: #458
+}
+
+
+pre .string,
+pre .tag .value,
+pre .phpdoc,
+pre .tex .formula {
+ color: #d14
+}
+
+pre .subst {
+ color: #712;
+}
+
+pre .constant,
+pre .title,
+pre .id {
+ color: #900;
+ font-weight: bold
+}
+
+pre .javascript .title,
+pre .lisp .title,
+pre .subst {
+ font-weight: normal
+}
+
+pre .class .title,
+pre .haskell .label,
+pre .tex .command {
+ color: #458;
+ font-weight: bold
+}
+
+pre .tag,
+pre .tag .title,
+pre .rules .property,
+pre .django .tag .keyword {
+ color: #000080;
+ font-weight: normal
+}
+
+pre .attribute,
+pre .variable,
+pre .instancevar,
+pre .lisp .body {
+ color: #008080
+}
+
+pre .regexp {
+ color: #009926
+}
+
+pre .class {
+ color: #458;
+ font-weight: bold
+}
+
+pre .symbol,
+pre .ruby .symbol .string,
+pre .ruby .symbol .keyword,
+pre .ruby .symbol .keymethods,
+pre .lisp .keyword,
+pre .tex .special,
+pre .input_number {
+ color: #990073
+}
+
+pre .builtin,
+pre .built_in,
+pre .lisp .title {
+ color: #0086b3
+}
+
+pre .preprocessor,
+pre .pi,
+pre .doctype,
+pre .shebang,
+pre .cdata {
+ color: #999;
+ font-weight: bold
+}
+
+pre .deletion {
+ background: #fdd
+}
+
+pre .addition {
+ background: #dfd
+}
+
+pre .diff .change {
+ background: #0086b3
+}
+
+pre .chunk {
+ color: #aaa
+}
+
+pre .tex .formula {
+ opacity: 0.5;
+}
+/*
+ * jQuery UI CSS Framework @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Layout helpers
+----------------------------------*/
+
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+/*
+ * jQuery UI CSS Framework @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; }
+.ui-widget-content a { color: #222222; }
+.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; }
+.ui-widget-header a { color: #222222; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
+.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }
+/*
+ ColorBox Core Style:
+ The following CSS is consistent between example themes and should not be altered.
+*/
+#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;}
+#cboxOverlay{position:fixed; width:100%; height:100%;}
+#cboxMiddleLeft, #cboxBottomLeft{clear:left;}
+#cboxContent{position:relative;}
+#cboxLoadedContent{overflow:auto;}
+#cboxTitle{margin:0;}
+#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%; height:100%;}
+#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;}
+.cboxPhoto{float:left; margin:auto; border:0; display:block; max-width:none;}
+.cboxIframe{width:100%; height:100%; display:block; border:0;}
+#colorbox, #cboxContent, #cboxLoadedContent{box-sizing:content-box;}
+
+/*
+ User Style:
+ Change the following styles to modify the appearance of ColorBox. They are
+ ordered & tabbed in a way that represents the nesting of the generated HTML.
+*/
+#cboxOverlay{background:#000;}
+#colorbox{}
+ #cboxTopLeft{width:14px; height:14px; background:url(colorbox/controls.png) no-repeat 0 0;}
+ #cboxTopCenter{height:14px; background:url(colorbox/border.png) repeat-x top left;}
+ #cboxTopRight{width:14px; height:14px; background:url(colorbox/controls.png) no-repeat -36px 0;}
+ #cboxBottomLeft{width:14px; height:43px; background:url(colorbox/controls.png) no-repeat 0 -32px;}
+ #cboxBottomCenter{height:43px; background:url(colorbox/border.png) repeat-x bottom left;}
+ #cboxBottomRight{width:14px; height:43px; background:url(colorbox/controls.png) no-repeat -36px -32px;}
+ #cboxMiddleLeft{width:14px; background:url(colorbox/controls.png) repeat-y -175px 0;}
+ #cboxMiddleRight{width:14px; background:url(colorbox/controls.png) repeat-y -211px 0;}
+ #cboxContent{background:#fff; overflow:visible;}
+ .cboxIframe{background:#fff;}
+ #cboxError{padding:50px; border:1px solid #ccc;}
+ #cboxLoadedContent{margin-bottom:5px;}
+ #cboxLoadingOverlay{background:url(colorbox/loading_background.png) no-repeat center center;}
+ #cboxLoadingGraphic{background:url(colorbox/loading.gif) no-repeat center center;}
+ #cboxTitle{position:absolute; bottom:-25px; left:0; text-align:center; width:100%; font-weight:bold; color:#7C7C7C;}
+ #cboxCurrent{position:absolute; bottom:-25px; left:58px; font-weight:bold; color:#7C7C7C;}
+
+ #cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{position:absolute; bottom:-29px; background:url(colorbox/controls.png) no-repeat 0px 0px; width:23px; height:23px; text-indent:-9999px;}
+ #cboxPrevious{left:0px; background-position: -51px -25px;}
+ #cboxPrevious:hover{background-position:-51px 0px;}
+ #cboxNext{left:27px; background-position:-75px -25px;}
+ #cboxNext:hover{background-position:-75px 0px;}
+ #cboxClose{right:0; background-position:-100px -25px;}
+ #cboxClose:hover{background-position:-100px 0px;}
+
+ .cboxSlideshow_on #cboxSlideshow{background-position:-125px 0px; right:27px;}
+ .cboxSlideshow_on #cboxSlideshow:hover{background-position:-150px 0px;}
+ .cboxSlideshow_off #cboxSlideshow{background-position:-150px -25px; right:27px;}
+ .cboxSlideshow_off #cboxSlideshow:hover{background-position:-125px 0px;}
+#loading {
+ position: fixed;
+ left: 40%;
+ top: 50%; }
+
+a {
+ color: #333333;
+ text-decoration: none; }
+ a:hover {
+ color: black;
+ text-decoration: underline; }
+
+body {
+ font-family: "Lucida Grande", Helvetica, "Helvetica Neue", Arial, sans-serif;
+ padding: 12px;
+ background-color: #333333; }
+
+h1, h2, h3, h4 {
+ color: #1c2324;
+ margin: 0;
+ padding: 0;
+ margin-bottom: 12px; }
+
+table {
+ width: 100%; }
+
+#content {
+ clear: left;
+ background-color: white;
+ border: 2px solid #dddddd;
+ border-top: 8px solid #dddddd;
+ padding: 18px;
+ -webkit-border-bottom-left-radius: 5px;
+ -webkit-border-bottom-right-radius: 5px;
+ -webkit-border-top-right-radius: 5px;
+ -moz-border-radius-bottomleft: 5px;
+ -moz-border-radius-bottomright: 5px;
+ -moz-border-radius-topright: 5px;
+ border-bottom-left-radius: 5px;
+ border-bottom-right-radius: 5px;
+ border-top-right-radius: 5px; }
+
+.dataTables_filter, .dataTables_info {
+ padding: 2px 6px; }
+
+abbr.timeago {
+ text-decoration: none;
+ border: none;
+ font-weight: bold; }
+
+.timestamp {
+ float: right;
+ color: #dddddd; }
+
+.group_tabs {
+ list-style: none;
+ float: left;
+ margin: 0;
+ padding: 0; }
+ .group_tabs li {
+ display: inline;
+ float: left; }
+ .group_tabs li a {
+ font-family: Helvetica, Arial, sans-serif;
+ display: block;
+ float: left;
+ text-decoration: none;
+ padding: 4px 8px;
+ background-color: #aaaaaa;
+ background: -webkit-gradient(linear, 0 0, 0 bottom, from(#dddddd), to(#aaaaaa));
+ background: -moz-linear-gradient(#dddddd, #aaaaaa);
+ background: linear-gradient(#dddddd, #aaaaaa);
+ text-shadow: #e5e5e5 1px 1px 0px;
+ border-bottom: none;
+ color: #333333;
+ font-weight: bold;
+ margin-right: 8px;
+ border-top: 1px solid #efefef;
+ -webkit-border-top-left-radius: 2px;
+ -webkit-border-top-right-radius: 2px;
+ -moz-border-radius-topleft: 2px;
+ -moz-border-radius-topright: 2px;
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px; }
+ .group_tabs li a:hover {
+ background-color: #cccccc;
+ background: -webkit-gradient(linear, 0 0, 0 bottom, from(#eeeeee), to(#aaaaaa));
+ background: -moz-linear-gradient(#eeeeee, #aaaaaa);
+ background: linear-gradient(#eeeeee, #aaaaaa); }
+ .group_tabs li a:active {
+ padding-top: 5px;
+ padding-bottom: 3px; }
+ .group_tabs li.active a {
+ color: black;
+ text-shadow: white 1px 1px 0px;
+ background-color: #dddddd;
+ background: -webkit-gradient(linear, 0 0, 0 bottom, from(white), to(#dddddd));
+ background: -moz-linear-gradient(white, #dddddd);
+ background: linear-gradient(white, #dddddd); }
+
+.file_list {
+ margin-bottom: 18px; }
+
+a.src_link {
+ background: url("./magnify.png") no-repeat left 50%;
+ padding-left: 18px; }
+
+tr, td {
+ margin: 0;
+ padding: 0; }
+
+th {
+ white-space: nowrap; }
+ th.ui-state-default {
+ cursor: pointer; }
+ th span.ui-icon {
+ float: left; }
+
+td {
+ padding: 4px 8px; }
+ td.strong {
+ font-weight: bold; }
+
+.source_table h3, .source_table h4 {
+ padding: 0;
+ margin: 0;
+ margin-bottom: 4px; }
+.source_table .header {
+ padding: 10px; }
+.source_table pre {
+ margin: 0;
+ padding: 0;
+ white-space: normal;
+ color: black;
+ font-family: "Monaco", "Inconsolata", "Consolas", monospace; }
+.source_table code {
+ color: black;
+ font-family: "Monaco", "Inconsolata", "Consolas", monospace; }
+.source_table pre {
+ background-color: #333333; }
+ .source_table pre ol {
+ margin: 0px;
+ padding: 0px;
+ margin-left: 45px;
+ font-size: 12px;
+ color: white; }
+ .source_table pre li {
+ margin: 0px;
+ padding: 2px 6px;
+ border-left: 5px solid white; }
+ .source_table pre li code {
+ white-space: pre;
+ white-space: pre-wrap; }
+ .source_table pre .hits {
+ float: right;
+ margin-left: 10px;
+ padding: 2px 4px;
+ background-color: #444444;
+ background: -webkit-gradient(linear, 0 0, 0 bottom, from(#222222), to(#666666));
+ background: -moz-linear-gradient(#222222, #666666);
+ background: linear-gradient(#222222, #666666);
+ color: white;
+ font-family: Helvetica, "Helvetica Neue", Arial, sans-serif;
+ font-size: 10px;
+ font-weight: bold;
+ text-align: center;
+ border-radius: 6px; }
+
+ .source_table pre .blocks {
+ float: right;
+ margin-left: 10px;
+ padding: 2px 4px;
+ background-color: #444444;
+ background: -webkit-gradient(linear, 0 0, 0 bottom, from(#222222), to(#666666));
+ background: -moz-linear-gradient(#222222, #666666);
+ background: linear-gradient(#222222, #666666);
+ color: white;
+ font-family: Helvetica, "Helvetica Neue", Arial, sans-serif;
+ font-size: 10px;
+ font-weight: bold;
+ text-align: center;
+ border-radius: 6px; }
+
+#footer {
+ color: #dddddd;
+ font-size: 12px;
+ font-weight: bold;
+ margin-top: 12px;
+ text-align: right; }
+ #footer a {
+ color: #eeeeee;
+ text-decoration: underline; }
+ #footer a:hover {
+ color: white;
+ text-decoration: none; }
+
+.green {
+ color: #009900; }
+
+.red {
+ color: #990000; }
+
+.yellow {
+ color: #ddaa00; }
+
+.source_table .covered {
+ border-color: #009900; }
+.source_table .partcov {
+ border-color: #ddaa00; }
+.source_table .missed {
+ border-color: #990000; }
+.source_table .never {
+ border-color: black; }
+.source_table .skipped {
+ border-color: #ffcc00; }
+.source_table .covered:nth-child(odd) {
+ background-color: #cdf2cd; }
+.source_table .covered:nth-child(even) {
+ background-color: #dbf2db; }
+.source_table .partcov:nth-child(odd) {
+ background-color: #ecd68e; }
+.source_table .partcov:nth-child(even) {
+ background-color: #ecd68e; }
+.source_table .missed:nth-child(odd) {
+ background-color: #f7c0c0; }
+.source_table .missed:nth-child(even) {
+ background-color: #f7cfcf; }
+.source_table .never:nth-child(odd) {
+ background-color: #efefef; }
+.source_table .never:nth-child(even) {
+ background-color: #f4f4f4; }
+.source_table .skipped:nth-child(odd) {
+ background-color: #fbf0c0; }
+.source_table .skipped:nth-child(even) {
+ background-color: #fbffcf; }
+
+
+
diff --git a/sqldev/src/main/resources/coverage/assets/application.js b/sqldev/src/main/resources/coverage/assets/application.js
new file mode 100644
index 00000000..fe65d882
--- /dev/null
+++ b/sqldev/src/main/resources/coverage/assets/application.js
@@ -0,0 +1,1707 @@
+/*!
+ * jQuery JavaScript Library v1.6.2
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Thu Jun 30 14:16:56 2011 -0400
+ */
+
+(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="