diff --git a/.gitignore b/.gitignore index 410561c1..b81a66d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,46 @@ +# Compiled class file *.class -*._trace -*.xtendbin -*.DS_Store -*.log -**/target -**/bin -**/test-output -**/xtend-gen -**/.sonar -**/.project -**/.classpath -**/.settings -**/.idea -# Mobile Tools for Java (J2ME) -.mtj.tmp/ +# Log file +*.log -# Package Files # +# Package Files *.jar *.war +*.nar *.ear +*.zip +*.tar.gz +*.rar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +# Xtend +*._trace +*.xtendbin +**/xtend-gen + +# SonarQube +**/.sonar +**/.scannerwork + +# Eclipse / Visual Studio Code +.project +.classpath +**/.settings +.vscode + +# IntelliJ +**/.idea +*.iml + +# macOS +*.DS_Store + +# Windows Thumbs.db + +# Targets +**/target +**/bin diff --git a/README.md b/README.md index 07f9d626..fa77ed47 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,12 @@ ## Introduction -utPLSQL for SQL Developer extends Oracle's SQL Developer to +utPLSQL for SQL Developer extends Oracle's SQL Developer to -* run existing utPLSQL unit tests -* run utPLSQL code coverage reports -* generate new utPLSQL unit tests (skeletons) +* run utPLSQL unit tests +* debug utPLSQL unit tests +* run utPLSQL code coverage reports +* generate utPLSQL unit tests (skeletons) * provide utPLSQL annotations and expectations as snippets ### Run utPLSQL test @@ -15,15 +16,27 @@ In the Connections window, select a connection or one or more packages or packag ![Run utPLSQL test](images/run_utplsql_test.png) +### Debug utPLSQL test + +Select the tests to debug, the same way you would if you were just running them, but select `Debug utPLSQL test...` in the context menu. + +And then the PL/SQL Debugger is called for the generated, anonymous PL/SQL block. + +![Anonymous PL/SQL block to debug utPLSQL test](images/debug_utplsql_test_anonymous_block.png) + +The initialization timeout for the realtime reporter is increased to 1 hour. The overall timeout is left to the default value of 4 hours. So you have enough time to step through the PL/SQL code and analyse it. Press the `Stop` button at any time to cancel the test run. + +![Stop utPLSQL test run](images/debug_utplsql_test_stop.png) + ### Code coverage... In the Connections window, select a connection or one or more packages for a code coverage report. -In the dialog you can set some parameters. If you run code coverage for a few chosen packages or from the PL/SQL editor the objects to be included are determined according the object dependencies in the Oracle data dictionary. +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. ![Code coverage dialog](images/code_coverage_dialog.png) -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. @@ -76,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 @@ -115,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) @@ -125,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/debug_utplsql_test_anonymous_block.png b/images/debug_utplsql_test_anonymous_block.png new file mode 100644 index 00000000..46391e85 Binary files /dev/null and b/images/debug_utplsql_test_anonymous_block.png differ diff --git a/images/debug_utplsql_test_stop.png b/images/debug_utplsql_test_stop.png new file mode 100644 index 00000000..fa390ef4 Binary files /dev/null and b/images/debug_utplsql_test_stop.png differ diff --git a/images/generate_utplsql_test.png b/images/generate_utplsql_test.png 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 a894d175..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/extension.xml b/sqldev/extension.xml index 835e262c..88d49292 100644 --- a/sqldev/extension.xml +++ b/sqldev/extension.xml @@ -106,6 +106,13 @@ Code-Editor + + + ${MENU_DEBUG_TEST_LABEL} + res:/org/utplsql/sqldev/resources/images/debug.png + Code-Editor + + ${MENU_GENERATE_TEST_LABEL} @@ -120,6 +127,7 @@ + @@ -132,7 +140,8 @@ id="UTPLSQL_MENU" weight="2.0"> - + + @@ -142,7 +151,8 @@
- + +
diff --git a/sqldev/pom.xml b/sqldev/pom.xml index f04528cb..a2340e3d 100644 --- a/sqldev/pom.xml +++ b/sqldev/pom.xml @@ -1,3 +1,4 @@ + 4.0.0 @@ -5,179 +6,226 @@ org.utplsql org.utplsql.sqldev - 1.1.2-SNAPSHOT + 2.1.0-SNAPSHOT bundle UTF-8 - 1.8 - 1.8 - 2.20.0 - - /Applications/SQLDeveloper19.4.0.app/Contents/Resources/sqldeveloper + 17 + 24.3.1 + + /Applications/SQLDeveloper${sqldev.version}.app/Contents/Resources/sqldeveloper utplsql_for_SQLDev_${project.version} - + + + + oracle idert - 12.2.0 + ${sqldev.version} system ${sqldev.basedir}/ide/lib/idert.jar oracle - javatools-nodeps - 12.2.0 + oracle.javatools-nodeps + ${sqldev.version} system - ${sqldev.basedir}/modules/oracle.javatools/javatools-nodeps.jar + ${sqldev.basedir}/external/oracle.javatools-nodeps.jar oracle javatools - 12.2.0 + ${sqldev.version} system ${sqldev.basedir}/ide/lib/javatools.jar oracle oracle.ide.ceditor - 12.2.0 + ${sqldev.version} system ${sqldev.basedir}/ide/extensions/oracle.ide.ceditor.jar oracle oracle.ide - 12.2.0 + ${sqldev.version} system ${sqldev.basedir}/ide/extensions/oracle.ide.jar oracle uic - 12.2.2 + ${sqldev.version} system ${sqldev.basedir}/ide/lib/uic.jar oracle oracle.ide.navigator - 12.2.0 + ${sqldev.version} system ${sqldev.basedir}/ide/extensions/oracle.ide.navigator.jar oracle javax-ide - 12.2.0 + ${sqldev.version} system ${sqldev.basedir}/ide/lib/javax-ide.jar oracle oracle.dbtools-common - 12.2.0 + ${sqldev.version} system ${sqldev.basedir}/sqldeveloper/lib/dbtools-common.jar + + oracle + oracle.dbtools-arbori + ${sqldev.version} + system + ${sqldev.basedir}/sqldeveloper/lib/dbtools-arbori.jar + oracle oracle.sqldeveloper - 12.2.0 + ${sqldev.version} system ${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.jar oracle oracle.sqldeveloper.utils - 12.2.0 + ${sqldev.version} system ${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.utils.jar oracle oracle.sqldeveloper.worksheet - 12.2.0 + ${sqldev.version} system ${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.worksheet.jar oracle - ojdbc8 - 12.2.0.1.0 + oracle.sqldeveloper.schemabrowser + ${sqldev.version} system - ${sqldev.basedir}/jdbc/lib/ojdbc8.jar + ${sqldev.basedir}/sqldeveloper/extensions/oracle.sqldeveloper.schemabrowser.jar + + + oracle + ojdbc11 + ${sqldev.version} + system + ${sqldev.basedir}/jdbc/lib/ojdbc11.jar oracle dbapi - 12.2.1 + ${sqldev.version} system ${sqldev.basedir}/ide/lib/dbapi.jar oracle oracle.ide.db - 12.2.1 + ${sqldev.version} system ${sqldev.basedir}/ide/extensions/oracle.ide.db.jar oracle oracle.jdeveloper.db.connection - 12.2.1 + ${sqldev.version} system ${sqldev.basedir}/jdev/extensions/oracle.jdeveloper.db.connection.jar oracle oracle.jdeveloper.java.core.jar - 12.2.1 + ${sqldev.version} system ${sqldev.basedir}/jdev/extensions/oracle.jdeveloper.java.core.jar + + oracle + oracle.jdeveloper.runner.jar + ${sqldev.version} + system + ${sqldev.basedir}/jdev/extensions/oracle.jdeveloper.runner.jar + + + oracle + oracle.ide.runner + ${sqldev.version} + system + ${sqldev.basedir}/ide/extensions/oracle.ide.runner.jar + + + + oracle + javatools-nodeps + ${sqldev.version} + system + ${sqldev.basedir}/modules/oracle.javatools/javatools-nodeps.jar + oracle jewt4.jar - 12.2.1 + ${sqldev.version} system ${sqldev.basedir}/modules/oracle.bali.jewt/jewt4.jar oracle share.jar - 12.2.1 + ${sqldev.version} system ${sqldev.basedir}/modules/oracle.bali.share/share.jar - + com.google.code.findbugs + jsr305 + 3.0.2 + provided + + + org.osgi org.osgi.core - 5.0.0 + 6.0.0 provided - - org.eclipse.xtend - org.eclipse.xtend.lib - ${xtend.version} + + org.springframework + spring-jdbc + 7.0.8 + org.springframework - spring-jdbc - 5.2.4.RELEASE + spring-core + 7.0.8 + org.springframework spring-web - 5.2.4.RELEASE + 7.0.8 + org.oddgen org.oddgen.sqldev 0.3.1 @@ -186,15 +234,20 @@ junit junit - 4.12 + 4.13.2 test - + + org.jetbrains + annotations + 26.1.0 + compile + + ${project.basedir}/src/main/java - ${project.basedir}/src/test/java src/main/resources @@ -203,72 +256,45 @@ + ${project.basedir}/src/test/java + + + src/test/resources + + **/*.* + + + - - org.eclipse.xtend - xtend-maven-plugin - - ${xtend.version} - - - main - - compile - - - ${jdk.version} - ${project.basedir}/src/main/xtend-gen - - - - test - - testCompile - - - ${jdk.version.test} - ${project.basedir}/src/test/xtend-gen - - - - org.apache.maven.plugins - 3.8.1 + 3.15.0 maven-compiler-plugin - ${jdk.version} - ${jdk.version} - - ${jdk.version.test} - ${jdk.version.test} - - **/*.java - + ${jdk.version} - - - - test-compile - process-test-sources - - testCompile - - - ${jdk.version.test} - ${jdk.version.test} - - - + + + org.apache.maven.plugins + maven-resources-plugin + 3.5.0 + + + org.apache.maven.plugins + maven-install-plugin + 3.1.4 + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.4 org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + 3.5.6 - - -noverify - -Djava.util.logging.config.file=${project.basedir}/src/test/resources/logging.conf + **/*.java @@ -296,7 +322,7 @@ org.apache.maven.plugins maven-antrun-plugin - 1.8 + 3.1.0 prepare-package @@ -317,7 +343,7 @@ org.codehaus.mojo properties-maven-plugin - 1.0.0 + 1.1.0 initialize @@ -337,7 +363,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.1.0 + 3.3.0 parse-version @@ -398,9 +424,16 @@ + + + + + + + org.apache.felix maven-bundle-plugin - 4.2.1 + 6.0.2 true ${project.name} @@ -417,26 +450,47 @@ META-INF/extension.xml=target/classes/META-INF/extension.xml - org.utplsql.sqldev, + org.utplsql.sqldev.coverage, + org.utplsql.sqldev.dal, + org.utplsql.sqldev.exception, org.utplsql.sqldev.menu, + org.utplsql.sqldev.model, + org.utplsql.sqldev.model.oddgen, + org.utplsql.sqldev.model.parser, org.utplsql.sqldev.model.preference, - org.utplsql.sqldev.actions, - org.utplsql.sqldev.resources + org.utplsql.sqldev.model.runner, + org.utplsql.sqldev.model.ut, + org.utplsql.sqldev.oddgen, + org.utplsql.sqldev.parser, + org.utplsql.sqldev.resources, + org.utplsql.sqldev.runner, + org.utplsql.sqldev.snippet, + org.utplsql.sqldev.ui.common, + org.utplsql.sqldev.ui.coverage, + org.utplsql.sqldev.ui.preference, + org.utplsql.sqldev.ui.runner <_exportcontents> - org.eclipse.xtext.xbase.lib, org.aspectj.runtime.internal, org.aspectj.lang, org.aspectj.runtime, - org.aspectj.lang.reflect + org.aspectj.lang.reflect, + org.springframework.core.style, + org.springframework.lang, + org.jspecify.annotations + <_noee>true + oracle.javatools, oracle.javatools-nodeps, oracle.jdeveloper.db.connection, + oracle.jdeveloper.runner, oracle.idert, oracle.ide, oracle.ide.db, + oracle.ide.runner, oracle.sqldeveloper, + oracle.sqldeveloper.schemabrowser, oracle.sqldeveloper.utils, oracle.sqldeveloper.worksheet, oracle.uic @@ -450,7 +504,7 @@ maven-assembly-plugin - 3.2.0 + 3.8.0 ${final.name} false @@ -472,7 +526,7 @@ net.nicoulaj.maven.plugins checksum-maven-plugin - 1.8 + 1.11 calculate-checksums @@ -483,10 +537,30 @@ + + org.jacoco + jacoco-maven-plugin + 0.8.15 + + + + prepare-agent + + + + report + prepare-package + + report + + + + + org.eclipse.m2e lifecycle-mapping 1.0.0 @@ -497,7 +571,7 @@ org.apache.maven.plugins maven-dependency-plugin - [3.1.1,) + [3.1.2,) copy-dependencies @@ -515,7 +589,7 @@ build-helper-maven-plugin - [3.0.0,) + [3.1.0,) parse-version @@ -532,12 +606,21 @@ org.codehaus.mojo buildnumber-maven-plugin - 1.4 + 3.3.0 - + + + + + org.javassist + javassist + 3.30.2-GA + + + org.utplsql.sqldev Extension for running unit tests in SQL Developer. diff --git a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java new file mode 100644 index 00000000..79bcaa79 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.java @@ -0,0 +1,307 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.coverage; + +import java.awt.Desktop; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.sql.Connection; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.utplsql.sqldev.dal.RealtimeReporterDao; +import org.utplsql.sqldev.dal.UtplsqlDao; +import org.utplsql.sqldev.exception.GenericDatabaseAccessException; +import org.utplsql.sqldev.exception.GenericRuntimeException; +import org.utplsql.sqldev.model.DatabaseTools; +import org.utplsql.sqldev.model.FileTools; +import org.utplsql.sqldev.model.preference.PreferenceModel; +import org.utplsql.sqldev.runner.UtplsqlRunner; +import org.utplsql.sqldev.ui.coverage.CodeCoverageReporterDialog; + +import oracle.ide.config.Preferences; + +public class CodeCoverageReporter { + private static final Logger logger = Logger.getLogger(CodeCoverageReporter.class.getName()); + private static final String ASSETS_PATH = "coverage/assets/"; + + private String connectionName; + private Connection conn; + private final List pathList; + private final List includeObjectList; + private CodeCoverageReporterDialog frame; + private String schemas; + private String includeObjects; + private String excludeObjects; + private Path assetDir; + + public CodeCoverageReporter(final List pathList, final List includeObjectList, + final String connectionName) { + this.pathList = pathList; + this.includeObjectList = includeObjectList; + setDefaultSchema(); + setConnection(connectionName); + setAssetDir(); + } + + // constructor for testing purposes only + public CodeCoverageReporter(final List pathList, final List includeObjectList, + final Connection conn) { + this.pathList = pathList; + this.includeObjectList = includeObjectList; + this.conn = conn; + setDefaultSchema(); + setAssetDir(); + } + + private void setConnection(final String connectionName) { + if (connectionName == null) { + final String msg = "Cannot initialize a CodeCoverageReporter without a ConnectionName"; + logger.severe(() -> msg); + throw new NullPointerException(); + } else { + // must be closed manually + this.connectionName = connectionName; + this.conn = DatabaseTools.getConnection(connectionName); + } + } + + private void setDefaultSchema() { + if (includeObjectList != null && !includeObjectList.isEmpty()) { + // use the owner with the most hits in includeObjectList + HashMap owners = new HashMap<>(); + for (String entry : includeObjectList) { + String[] obj = entry.toUpperCase().split("\\."); + if (obj.length == 2) { + // only if objectOwner and objectName are available + Integer count = owners.get(obj[0]); + if (count == null) { + count = 1; + } else { + count++; + } + owners.put(obj[0], count); + } + } + List sortedOwners = owners.entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed()).map(Map.Entry::getKey) + .collect(Collectors.toList()); + schemas = String.join(", ", sortedOwners); + } + } + + private void setAssetDir() { + try { + assetDir = Files.createTempDirectory("utplsql_assets_"); + } catch (IOException e) { + throw new GenericRuntimeException("Cannot create temporary directory for code coverage report assets.", e); + } + populateCoverageAssets(); + } + + // public for testing purposes only + public URL getHtmlReportAssetPath() { + try { + return Paths.get(assetDir.toString()).toUri().toURL(); + } catch (MalformedURLException e) { + throw new GenericRuntimeException("Cannot convert code coverage asset path to URL.", e); + } + } + + private void copyStreamToFile(InputStream inputStream, Path file) throws IOException { + file.toFile().mkdirs(); + Files.copy(inputStream, file, StandardCopyOption.REPLACE_EXISTING); + } + + private void populateCoverageAssets() { + logger.fine(() -> "Copying code coverage report assets to " + assetDir.toString() + "..."); + try { + final File file = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath()); + if (file.isFile()) { + // class loaded from a JAR file + final JarFile jar = new JarFile(file); + final List entries = jar.stream().filter(entry -> !entry.isDirectory() && entry.getName().startsWith(ASSETS_PATH)).collect(Collectors.toList()); + for (JarEntry entry : entries) { + Path f = Paths.get(assetDir.toString() + File.separator + entry.getName().substring(ASSETS_PATH.length())); + copyStreamToFile(jar.getInputStream(entry), f); + } + jar.close(); + } else { + // class loaded from file system (IDE or during test/build) + ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource[] resources = resolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "/" + ASSETS_PATH + "**"); + for (Resource resource : resources) { + if (Objects.requireNonNull(resource.getFilename()).contains(".")) { + // process files but not directories, assume that directories do not contain a period + String path = resource.getURL().getPath(); + Path f = Paths.get(assetDir.toString() + File.separator + path.substring(path.lastIndexOf(ASSETS_PATH) + ASSETS_PATH.length())); + copyStreamToFile(resource.getInputStream(), f); + } + } + } + } catch (IOException e) { + throw new GenericRuntimeException("Error while copying coverage report assets to temporary directory.", e); + } + } + + private ArrayList toStringList(final String s) { + final ArrayList list = new ArrayList<>(); + if (s != null && !s.isEmpty()) { + for (final String item : s.split(",")) { + if (!item.isEmpty()) { + list.add(item.trim()); + } + } + } + return list; + } + + private void run() { + logger.fine(() -> "Running code coverage reporter for " + pathList + "..."); + try { + final RealtimeReporterDao dao = new RealtimeReporterDao(conn); + PreferenceModel preferences; + try { + preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + } catch (NoClassDefFoundError | ExceptionInInitializerError error) { + // not running in SQL Developer (in tests) + preferences = PreferenceModel.getInstance(null); + } + if (preferences.isUseRealtimeReporter() && dao.isSupported() && connectionName != null) { + runCodeCoverageWithRealtimeReporter(); + } else { + runCodeCoverageStandalone(); + } + } finally { + if (frame != null) { + frame.exit(); + } + } + } + + private void runCodeCoverageWithRealtimeReporter() { + final UtplsqlRunner runner = new UtplsqlRunner(pathList, toStringList(schemas), toStringList(includeObjects), + toStringList(excludeObjects), getHtmlReportAssetPath(), connectionName); + runner.runTestAsync(); + } + + private void runCodeCoverageStandalone() { + Connection coverageConn = null; + try { + coverageConn = conn != null ? conn : DatabaseTools.cloneConnection(connectionName); + final UtplsqlDao dao = new UtplsqlDao(coverageConn); + final String html = dao.htmlCodeCoverage(pathList, toStringList(schemas), + toStringList(includeObjects), toStringList(excludeObjects), getHtmlReportAssetPath()); + openInBrowser(html); + } finally { + try { + if (coverageConn != null && conn == null) { + // close only if connection has been cloned + DatabaseTools.closeConnection(coverageConn); + } + } catch (GenericDatabaseAccessException e) { + // ignore + } + } + } + + public static void openInBrowser(String html) { + try { + final File file = File.createTempFile("utplsql_", ".html"); + logger.fine(() -> "Writing result to " + file + "..."); + FileTools.writeFile(file.toPath(), Arrays.asList(html.split(System.lineSeparator())), StandardCharsets.UTF_8); + final URL url = file.toURI().toURL(); + logger.fine(() -> "Opening " + url.toExternalForm() + " in browser..."); + final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; + if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { + desktop.browse(url.toURI()); + logger.fine(() -> url.toExternalForm() + " opened in browser."); + } else { + logger.severe( + () -> "Could not launch " + file + " in browser. No default browser defined on this system."); + } + } catch (Exception e) { + final String msg = "Error while opening code coverage HTML report in browser."; + logger.severe(() -> msg); + throw new GenericRuntimeException(msg, e); + } + } + + public void setFrame(final CodeCoverageReporterDialog frame) { + this.frame = frame; + } + + public CodeCoverageReporterDialog getFrame() { + return frame; + } + + public Connection getConnection() { + return conn; + } + + public List getPathList() { + return pathList; + } + + public List getIncludeObjectList() { + if (includeObjectList == null) { + return new ArrayList<>(); + } else { + return includeObjectList; + } + } + + public void setSchemas(final String schemas) { + this.schemas = schemas; + } + + public String getSchemas() { + return schemas; + } + + public void setIncludeObjects(final String includeObjects) { + this.includeObjects = includeObjects; + } + + public void setExcludeObjects(final String excludeObjects) { + this.excludeObjects = excludeObjects; + } + + public Thread runAsync() { + final Thread thread = new Thread(this::run); + thread.setName("code coverage reporter"); + thread.start(); + return thread; + } + + public void showParameterWindow() { + CodeCoverageReporterDialog.createAndShow(this); + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.xtend b/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.xtend deleted file mode 100644 index 1b4acb50..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/coverage/CodeCoverageReporter.xtend +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.coverage - -import java.awt.Desktop -import java.io.File -import java.net.URL -import java.nio.charset.StandardCharsets -import java.nio.file.Files -import java.nio.file.Paths -import java.sql.Connection -import java.util.ArrayList -import java.util.List -import java.util.logging.Logger -import oracle.dbtools.raptor.utils.Connections -import org.utplsql.sqldev.dal.UtplsqlDao -import org.utplsql.sqldev.ui.coverage.CodeCoverageReporterDialog - -class CodeCoverageReporter { - static val Logger logger = Logger.getLogger(CodeCoverageReporter.name); - - var Connection conn - var List pathList - var List includeObjectList - var CodeCoverageReporterDialog frame - var String schemas - var String includeObjects - var String excludeObjects - - new(List pathList, List includeObjectList, String connectionName) { - this.pathList = pathList - this.includeObjectList = includeObjectList - setConnection(connectionName) - } - - new(List pathList, List includeObjectList, Connection conn) { - this.pathList = pathList - this.includeObjectList = includeObjectList - this.conn = conn - } - - private def setConnection(String connectionName) { - if (connectionName === null) { - throw new RuntimeException("Cannot initialize a CodeCoverageReporter without a ConnectionName") - } else { - // must be closed manually - this.conn = Connections.instance.cloneConnection(Connections.instance.getConnection(connectionName)) - } - } - - private def toStringList(String s) { - val list = new ArrayList - if (s !== null && !s.empty) { - for (item : s.split(",")) { - if (!item.empty) { - list.add(item.trim) - } - } - } - return list - } - - private def void run() { - try { - logger.fine('''Running code coverage reporter for «pathList»...''') - val dal = new UtplsqlDao(conn) - val content = dal.htmlCodeCoverage(pathList, toStringList(schemas), toStringList(includeObjects), toStringList(excludeObjects)) - val file = File.createTempFile("utplsql_", ".html") - logger.fine('''Writing result to «file.absolutePath»...''') - Files.write(Paths.get(file.absolutePath), content.split(System.lineSeparator), StandardCharsets.UTF_8); - val url = file.toURI().toURL().toExternalForm() - logger.fine('''Opening «url» in browser...''') - val Desktop desktop = if (Desktop.isDesktopSupported()) {Desktop.getDesktop()} else {null} - if (desktop !== null && desktop.isSupported(Desktop.Action.BROWSE) && url !== null) { - desktop.browse((new URL(url)).toURI) - logger.fine(url + " opened in browser."); - } else { - logger.severe('''Could not launch «file» in browser. No default browser defined on this system.''') - } - } catch (Exception e) { - logger.severe('''Error when running code coverage: «e?.message»''') - } - finally { - conn.close - if (frame !== null) { - frame.exit - } - } - } - - def setFrame(CodeCoverageReporterDialog frame) { - this.frame = frame; - } - - def getFrame() { - return this.frame - } - - def getConnection() { - return conn - } - - def getPathList() { - return pathList - } - - def getIncludeObjectList() { - if (includeObjectList === null) { - return new ArrayList - } else { - return includeObjectList - } - } - - def setSchemas(String schemas) { - this.schemas = schemas - } - - def setIncludeObjects(String includeObjects) { - this.includeObjects = includeObjects - } - - def setExcludeObjects(String excludeObjects) { - this.excludeObjects = excludeObjects - } - - def Thread runAsync() { - val Runnable runnable = [|run] - val thread = new Thread(runnable) - thread.name = "code coverage reporter" - thread.start - return thread - } - - def showParameterWindow() { - CodeCoverageReporterDialog.createAndShow(this) - } - -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java new file mode 100644 index 00000000..1c7191a5 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.java @@ -0,0 +1,397 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.dal; + +import java.io.IOException; +import java.io.StringReader; +import java.net.URL; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.ResultSet; +import java.util.List; +import java.util.logging.Logger; + +import javax.xml.parsers.DocumentBuilder; + +import org.springframework.jdbc.core.CallableStatementCallback; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SingleConnectionDataSource; +import org.utplsql.sqldev.model.StringTools; +import org.utplsql.sqldev.model.XMLTools; +import org.utplsql.sqldev.model.runner.Counter; +import org.utplsql.sqldev.model.runner.Expectation; +import org.utplsql.sqldev.model.runner.PostEvent; +import org.utplsql.sqldev.model.runner.PostRunEvent; +import org.utplsql.sqldev.model.runner.PostSuiteEvent; +import org.utplsql.sqldev.model.runner.PostTestEvent; +import org.utplsql.sqldev.model.runner.PreRunEvent; +import org.utplsql.sqldev.model.runner.PreSuiteEvent; +import org.utplsql.sqldev.model.runner.PreTestEvent; +import org.utplsql.sqldev.model.runner.RealtimeReporterEvent; +import org.utplsql.sqldev.model.runner.Suite; +import org.utplsql.sqldev.model.runner.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import oracle.jdbc.OracleTypes; + +public class RealtimeReporterDao { + private static final Logger logger = Logger.getLogger(RealtimeReporterDao.class.getName()); + private static final int FIRST_VERSION_WITH_REALTIME_REPORTER = 3001004; + private final XMLTools xmlTools = new XMLTools(); + private final Connection conn; + private final JdbcTemplate jdbcTemplate; + + public RealtimeReporterDao(final Connection conn) { + this.conn = conn; + jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true)); + jdbcTemplate.setFetchSize(UtplsqlDao.FETCH_ROWS); + } + + public boolean isSupported() { + return new UtplsqlDao(conn) + .normalizedUtPlsqlVersionNumber() >= RealtimeReporterDao.FIRST_VERSION_WITH_REALTIME_REPORTER; + } + + // used for execution via PL/SQL Debugger + public String getProduceReportPlsql(final String reporterId, final List pathList) { + return getProduceReportPlsql(reporterId, pathList, false); + } + + private String getProduceReportPlsql(final String reporterId, final List pathList, boolean useBindVariable) { + StringBuilder sb = new StringBuilder(); + sb.append("DECLARE\n"); + sb.append(" l_reporter ut_realtime_reporter := ut_realtime_reporter();\n"); + sb.append("BEGIN\n"); + if (useBindVariable) { + sb.append(" l_reporter.set_reporter_id(?);\n"); + } else { + sb.append(" l_reporter.set_reporter_id('"); + sb.append(reporterId); + sb.append("');\n"); + } + sb.append(" l_reporter.output_buffer.init();\n"); + sb.append(" sys.dbms_output.enable(NULL);\n"); + sb.append(" ut_runner.run(\n"); + sb.append(" a_paths => ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(pathList, 24)); + sb.append(" ),\n"); + sb.append(" a_reporters => ut_reporters(l_reporter)\n"); + sb.append(" );\n"); + sb.append(" sys.dbms_output.disable;\n"); + sb.append("END;"); + return sb.toString(); + } + + public void produceReport(final String reporterId, final List pathList) { + final String plsql = getProduceReportPlsql(reporterId, pathList, true); + final Object[] binds = { reporterId }; + jdbcTemplate.update(plsql, binds); + } + + public void produceReportWithCoverage(final String realtimeReporterId, final String coverageReporterId, + final List pathList, final List schemaList, final List includeObjectList, + final List excludeObjectList, final URL htmlReportAssetPath) { + StringBuilder sb = new StringBuilder(); + sb.append("DECLARE\n"); + sb.append(" l_rt_rep ut_realtime_reporter := ut_realtime_reporter();\n"); + sb.append(" l_cov_rep ut_coverage_html_reporter := ut_coverage_html_reporter(a_html_report_assets_path => ?);\n"); + sb.append("BEGIN\n"); + sb.append(" l_rt_rep.set_reporter_id(?);\n"); + sb.append(" l_rt_rep.output_buffer.init();\n"); + sb.append(" l_cov_rep.set_reporter_id(?);\n"); + sb.append(" l_cov_rep.output_buffer.init();\n"); + sb.append(" sys.dbms_output.enable(NULL);\n"); + sb.append(" ut_runner.run(\n"); + sb.append(" a_paths => ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(pathList, 31)); + sb.append(" ),\n"); + if (schemaList != null && !schemaList.isEmpty()) { + sb.append(" a_coverage_schemes => ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(schemaList, 31)); + sb.append(" ),\n"); + } + if (includeObjectList != null && !includeObjectList.isEmpty()) { + sb.append(" a_include_objects => ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(includeObjectList, 31)); + sb.append(" ),\n"); + } + if (excludeObjectList != null && !excludeObjectList.isEmpty()) { + sb.append(" a_exclude_objects => ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(excludeObjectList, 31)); + sb.append(" ),\n"); + } + sb.append(" a_reporters => ut_reporters(l_rt_rep, l_cov_rep)\n"); + sb.append(" );\n"); + sb.append(" sys.dbms_output.disable;\n"); + sb.append("END;"); + final String plsql = sb.toString(); + final Object[] binds = { htmlReportAssetPath == null ? null : htmlReportAssetPath.toExternalForm(), realtimeReporterId, coverageReporterId }; + jdbcTemplate.update(plsql, binds); + } + + public void consumeReport(final String reporterId, final RealtimeReporterEventConsumer consumer) { + consumeReport(reporterId, consumer, 60); + } + + public void consumeReport(final String reporterId, final RealtimeReporterEventConsumer consumer, final int timeoutSeconds) { + StringBuilder sb = new StringBuilder(); + sb.append("DECLARE\n"); + sb.append(" l_reporter ut_realtime_reporter := ut_realtime_reporter();\n"); + sb.append("BEGIN\n"); + sb.append(" l_reporter.set_reporter_id(?);\n"); + sb.append(" ? := l_reporter.get_lines_cursor(a_initial_timeout => ?);\n"); + sb.append("END;"); + final String plsql = sb.toString(); + jdbcTemplate.setFetchSize(1); + try { + jdbcTemplate.execute(plsql, (CallableStatementCallback) cs -> { + cs.setString(1, reporterId); + cs.setInt(3, timeoutSeconds); + cs.registerOutParameter(2, OracleTypes.CURSOR); + cs.execute(); + final ResultSet rs = (ResultSet) cs.getObject(2); + while (rs.next()) { + final String itemType = rs.getString("item_type"); + final Clob textClob = rs.getClob("text"); + final String textString = textClob.getSubString(1, ((int) textClob.length())); + final RealtimeReporterEvent event = convert(itemType, textString); + if (event != null) { + consumer.process(event); + } + } + rs.close(); + return null; + }); + } finally { + jdbcTemplate.setFetchSize(UtplsqlDao.FETCH_ROWS); + } + } + + public String getHtmlCoverage(final String reporterId) { + StringBuilder sb = new StringBuilder(); + sb.append("DECLARE\n"); + sb.append(" l_reporter ut_coverage_html_reporter := ut_coverage_html_reporter();\n"); + sb.append("BEGIN\n"); + sb.append(" l_reporter.set_reporter_id(?);\n"); + sb.append(" ? := l_reporter.get_lines_cursor();\n"); + sb.append("END;"); + final String plsql = sb.toString(); + return jdbcTemplate.execute(plsql, (CallableStatementCallback) cs -> { + cs.setString(1, reporterId); + cs.registerOutParameter(2, OracleTypes.CURSOR); + cs.execute(); + final StringBuilder sb1 = new StringBuilder(); + final ResultSet rs = (ResultSet) cs.getObject(2); + while (rs.next()) { + final String text = rs.getString("text"); + if (text != null) { + sb1.append(text); + sb1.append('\n'); + } + } + rs.close(); + return sb1.toString(); + }); + } + + private RealtimeReporterEvent convert(final String itemType, final String text) { + logger.fine(() -> "\n---- " + itemType + " ----\n" + text); + try { + final DocumentBuilder docBuilder = xmlTools.createDocumentBuilder(); + final Document doc = docBuilder.parse(new InputSource(new StringReader(text))); + RealtimeReporterEvent event = null; + if ("pre-run".equals(itemType)) { + event = convertToPreRunEvent(doc); + } else if ("post-run".equals(itemType)) { + event = convertToPostRunEvent(doc); + } else if ("pre-suite".equals(itemType)) { + event = convertToPreSuiteEvent(doc); + } else if ("post-suite".equals(itemType)) { + event = convertToPostSuiteEvent(doc); + } else if ("pre-test".equals(itemType)) { + event = convertToPreTestEvent(doc); + } else if ("post-test".equals(itemType)) { + event = convertToPostTestEvent(doc); + } + return event; + } catch (SAXException e) { + // continue processing, see https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/107 + final String msg = "Parse error while processing " + itemType + " with content: " + text; + logger.severe(() -> msg); + return null; + } catch (IOException e) { + // continue processing, see https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/107 + final String msg = "I/O error while processing " + itemType + " with content: " + text; + logger.severe(() -> msg); + return null; + } + } + + private RealtimeReporterEvent convertToPreRunEvent(final Document doc) { + final PreRunEvent event = new PreRunEvent(); + final Node totalNumberOfTestsNode = xmlTools.getNode(doc, "/event/totalNumberOfTests"); + String totalNumberOfTestsTextContent = null; + if (totalNumberOfTestsNode != null) { + totalNumberOfTestsTextContent = totalNumberOfTestsNode.getTextContent(); + } + event.setTotalNumberOfTests(Integer.valueOf(totalNumberOfTestsTextContent != null ? totalNumberOfTestsTextContent : "0")); + final NodeList nodes = xmlTools.getNodeList(doc, "/event/items/*"); + for (int i = 0; i < nodes.getLength(); i++) { + final Node node = nodes.item(i); + final String nodeName = node.getNodeName(); + if ("suite".equals(nodeName)) { + final Suite suite = new Suite(); + event.getItems().add(suite); + populate(suite, node); + } else if ("test".equals(nodeName)) { + final Test test = new Test(); + event.getItems().add(test); + populate(test, node); + } + } + return event; + } + + private RealtimeReporterEvent convertToPostRunEvent(final Document doc) { + final PostRunEvent event = new PostRunEvent(); + populate(event, xmlTools.getNode(doc, "/event/run")); + return event; + } + + private RealtimeReporterEvent convertToPreSuiteEvent(final Document doc) { + final PreSuiteEvent event = new PreSuiteEvent(); + final Node node = xmlTools.getNode(doc, "/event/suite"); + if (node instanceof Element) { + event.setId(xmlTools.getAttributeValue(node, "id")); + } + return event; + } + + private RealtimeReporterEvent convertToPostSuiteEvent(final Document doc) { + final PostSuiteEvent event = new PostSuiteEvent(); + final Node node = xmlTools.getNode(doc, "/event/suite"); + if (node instanceof Element) { + event.setId(xmlTools.getAttributeValue(node, "id")); + populate(event, node); + } + return event; + } + + private RealtimeReporterEvent convertToPreTestEvent(final Document doc) { + final PreTestEvent event = new PreTestEvent(); + final Node node = xmlTools.getNode(doc, "/event/test"); + if (node instanceof Element) { + event.setId(xmlTools.getAttributeValue(node, "id")); + event.setTestNumber(Integer.valueOf(xmlTools.getElementValue(node, "testNumber"))); + event.setTotalNumberOfTests(Integer.valueOf(xmlTools.getElementValue(node, "totalNumberOfTests"))); + } + return event; + } + + private RealtimeReporterEvent convertToPostTestEvent(final Document doc) { + final PostTestEvent event = new PostTestEvent(); + final Node node = xmlTools.getNode(doc, "/event/test"); + if (node instanceof Element) { + event.setId(xmlTools.getAttributeValue(node, "id")); + event.setTestNumber(Integer.valueOf(xmlTools.getElementValue(node, "testNumber"))); + event.setTotalNumberOfTests(Integer.valueOf(xmlTools.getElementValue(node, "totalNumberOfTests"))); + populate(event, node); + final NodeList failedExpectations = xmlTools.getNodeList(node, "failedExpectations/expectation"); + for (int i = 0; i < failedExpectations.getLength(); i++) { + final Node expectationNode = failedExpectations.item(i); + final Expectation expectation = new Expectation(); + event.getFailedExpectations().add(expectation); + populate(expectation, expectationNode); + } + } + return event; + } + + private void populate(final Suite suite, final Node node) { + if (node instanceof Element) { + suite.setId(xmlTools.getAttributeValue(node, "id")); + suite.setName(xmlTools.getElementValue(node, "name")); + suite.setDescription(xmlTools.getElementValue(node, "description")); + final NodeList nodeList = xmlTools.getNodeList(node, "items/*"); + for (int i = 0; i < nodeList.getLength(); i++) { + final Node childNode = nodeList.item(i); + final String nodeName = childNode.getNodeName(); + if ("suite".equals(nodeName)) { + final Suite childSuite = new Suite(); + suite.getItems().add(childSuite); + populate(childSuite, childNode); + } else if ("test".equals(nodeName)) { + final Test childTest = new Test(); + suite.getItems().add(childTest); + populate(childTest, childNode); + } + } + } + } + + private void populate(final Test test, final Node node) { + if (node instanceof Element) { + test.setId(xmlTools.getAttributeValue(node, "id")); + test.setExecutableType(xmlTools.getElementValue(node, "executableType")); + test.setOwnerName(xmlTools.getElementValue(node, "ownerName")); + test.setObjectName(xmlTools.getElementValue(node, "objectName")); + test.setProcedureName(xmlTools.getElementValue(node, "procedureName")); + test.setDisabled("true".equals(xmlTools.getElementValue(node, "disabled"))); + test.setDisabledReason(xmlTools.getElementValue(node, "disabledReason")); + test.setName(xmlTools.getElementValue(node, "name")); + test.setDescription(xmlTools.getElementValue(node, "description")); + test.setTestNumber(Integer.valueOf(xmlTools.getElementValue(node, "testNumber"))); + } + } + + private void populate(final PostEvent event, final Node node) { + if (node instanceof Element) { + event.setStartTime(xmlTools.getElementValue(node, "startTime")); + event.setEndTime(xmlTools.getElementValue(node, "endTime")); + event.setExecutionTime(Double.valueOf(xmlTools.getElementValue(node, "executionTime"))); + populate(event.getCounter(), node); + event.setErrorStack(xmlTools.getElementValue(node, "errorStack")); + event.setServerOutput(xmlTools.getElementValue(node, "serverOutput")); + event.setWarnings(xmlTools.getElementValue(node, "warnings")); + } + } + + private void populate(final Counter counter, final Node node) { + if (node instanceof Element) { + final Node counterNode = xmlTools.getElementNode(node, "counter"); + if (counterNode instanceof Element) { + counter.setDisabled(Integer.valueOf(xmlTools.getElementValue(counterNode, "disabled"))); + counter.setSuccess(Integer.valueOf(xmlTools.getElementValue(counterNode, "success"))); + counter.setFailure(Integer.valueOf(xmlTools.getElementValue(counterNode, "failure"))); + counter.setError(Integer.valueOf(xmlTools.getElementValue(counterNode, "error"))); + counter.setWarning(Integer.valueOf(xmlTools.getElementValue(counterNode, "warning"))); + } + } + } + + private void populate(final Expectation expectation, final Node node) { + if (node instanceof Element) { + expectation.setDescription(xmlTools.getElementValue(node, "description")); + expectation.setMessage(xmlTools.getElementValue(node, "message")); + expectation.setCaller(xmlTools.getElementValue(node, "caller")); + } + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.xtend b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.xtend deleted file mode 100644 index 442027ac..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterDao.xtend +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package org.utplsql.sqldev.dal - -import java.io.StringReader -import java.sql.CallableStatement -import java.sql.Connection -import java.sql.ResultSet -import java.sql.SQLException -import java.util.List -import java.util.logging.Logger -import javax.xml.parsers.DocumentBuilderFactory -import oracle.jdbc.OracleTypes -import org.springframework.dao.DataAccessException -import org.springframework.jdbc.core.CallableStatementCallback -import org.springframework.jdbc.core.JdbcTemplate -import org.springframework.jdbc.datasource.SingleConnectionDataSource -import org.utplsql.sqldev.model.XMLTools -import org.utplsql.sqldev.model.runner.Counter -import org.utplsql.sqldev.model.runner.Expectation -import org.utplsql.sqldev.model.runner.PostEvent -import org.utplsql.sqldev.model.runner.PostRunEvent -import org.utplsql.sqldev.model.runner.PostSuiteEvent -import org.utplsql.sqldev.model.runner.PostTestEvent -import org.utplsql.sqldev.model.runner.PreRunEvent -import org.utplsql.sqldev.model.runner.PreSuiteEvent -import org.utplsql.sqldev.model.runner.PreTestEvent -import org.utplsql.sqldev.model.runner.RealtimeReporterEvent -import org.utplsql.sqldev.model.runner.Suite -import org.utplsql.sqldev.model.runner.Test -import org.w3c.dom.Document -import org.w3c.dom.Element -import org.w3c.dom.Node -import org.xml.sax.InputSource - -class RealtimeReporterDao { - static val Logger logger = Logger.getLogger(RealtimeReporterDao.name); - static val FIRST_VERSION_WITH_REALTIME_REPORTER = 3001004 - val extension XMLTools xmlTools = new XMLTools - var Connection conn - var JdbcTemplate jdbcTemplate - - new(Connection connection) { - conn = connection - jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true)) - jdbcTemplate.fetchSize = 1 - } - - def isSupported() { - return new UtplsqlDao(conn).normalizedUtPlsqlVersionNumber >= FIRST_VERSION_WITH_REALTIME_REPORTER - } - - def produceReport(String reporterId, List pathList) { - var plsql = ''' - DECLARE - l_reporter ut_realtime_reporter := ut_realtime_reporter(); - BEGIN - l_reporter.set_reporter_id(?); - l_reporter.output_buffer.init(); - sys.dbms_output.enable(NULL); - ut_runner.run( - a_paths => ut_varchar2_list( - «FOR path : pathList SEPARATOR ","» - '«path»' - «ENDFOR» - ), - a_reporters => ut_reporters(l_reporter) - ); - sys.dbms_output.disable; - END; - ''' - jdbcTemplate.update(plsql, #[reporterId]) - } - - def consumeReport(String reporterId, RealtimeReporterEventConsumer consumer) { - val plsql = ''' - DECLARE - l_reporter ut_realtime_reporter := ut_realtime_reporter(); - BEGIN - l_reporter.set_reporter_id(?); - ? := l_reporter.get_lines_cursor(); - END; - ''' - jdbcTemplate.execute(plsql, new CallableStatementCallback() { - override doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { - cs.setString(1, reporterId) - cs.registerOutParameter(2, OracleTypes.CURSOR) - cs.execute - val rs = cs.getObject(2) as ResultSet - while(rs.next) { - val itemType = rs.getString("item_type") - val textClob = rs.getClob("text") - val textString = textClob.getSubString(1, textClob.length as int) - val event = convert(itemType, textString) - if (event !== null) { - consumer.process(event) - } - } - rs.close - return null - } - }) - } - - private def RealtimeReporterEvent convert(String itemType, String text) { - logger.fine(''' - ---- «itemType» ---- - «text» - ''') - val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() - val doc = docBuilder.parse(new InputSource(new StringReader(text))) - var RealtimeReporterEvent event - if (itemType == "pre-run") { - event = doc.convertToPreRunEvent - } else if (itemType == "post-run") { - event = doc.convertToPostRunEvent - } else if (itemType == "pre-suite") { - event = doc.convertToPreSuiteEvent - } else if (itemType == "post-suite") { - event = doc.convertToPostSuiteEvent - } else if (itemType == "pre-test") { - event = doc.convertToPreTestEvent - } else if (itemType == "post-test") { - event = doc.convertToPostTestEvent - } - return event - } - - private def RealtimeReporterEvent convertToPreRunEvent(Document doc) { - val event = new PreRunEvent - event.totalNumberOfTests = Integer.valueOf(doc.getNode("/event/totalNumberOfTests")?.textContent) - val nodeList = doc.getNodeList("/event/items/*") - for (i : 0 ..< nodeList.length) { - val node = nodeList.item(i) - if (node.nodeName == "suite") { - val suite = new Suite - event.items.add(suite) - suite.populate(node) - } else if (node.nodeName == "test") { - val test = new Test - event.items.add(test) - test.populate(node) - } - } - return event - } - - private def RealtimeReporterEvent convertToPostRunEvent(Document doc) { - val event = new PostRunEvent - event.populate(doc.getNode("/event/run")) - return event - } - - private def RealtimeReporterEvent convertToPreSuiteEvent(Document doc) { - val event = new PreSuiteEvent - val node = doc.getNode("/event/suite") - if (node instanceof Element) { - event.id = node.attributes?.getNamedItem("id")?.nodeValue - } - return event - } - - private def RealtimeReporterEvent convertToPostSuiteEvent(Document doc) { - val event = new PostSuiteEvent - val node = doc.getNode("/event/suite") - if (node instanceof Element) { - event.id = node.attributes?.getNamedItem("id")?.nodeValue - event.populate(node) - } - return event - } - - private def RealtimeReporterEvent convertToPreTestEvent(Document doc) { - val event = new PreTestEvent - val node = doc.getNode("/event/test") - if (node instanceof Element) { - event.id = node.attributes?.getNamedItem("id")?.nodeValue - event.testNumber = Integer.valueOf(node.getElementsByTagName("testNumber")?.item(0)?.textContent) - event.totalNumberOfTests = Integer.valueOf(node.getElementsByTagName("totalNumberOfTests")?.item(0)?.textContent) - } - return event - } - - private def RealtimeReporterEvent convertToPostTestEvent(Document doc) { - val event = new PostTestEvent - val node = doc.getNode("/event/test") - if (node instanceof Element) { - event.id = node.attributes?.getNamedItem("id")?.nodeValue - event.testNumber = Integer.valueOf(node.getElementsByTagName("testNumber")?.item(0)?.textContent) - event.totalNumberOfTests = Integer.valueOf(node.getElementsByTagName("totalNumberOfTests")?.item(0)?.textContent) - event.populate(node) - val failedExpectations = node.getNodeList("failedExpectations/expectation") - for (i : 0 ..< failedExpectations.length) { - val expectationNode = failedExpectations.item(i) - val expectation = new Expectation - event.failedExpectations.add(expectation) - expectation.populate(expectationNode) - } - } - return event - } - - private def void populate(Suite suite, Node node) { - if (node instanceof Element) { - suite.id = node.attributes?.getNamedItem("id")?.nodeValue - suite.name = node.getElementsByTagName("name")?.item(0)?.textContent - suite.description = node.getElementsByTagName("description")?.item(0)?.textContent - val nodeList = node.getNodeList("items/*") - for (i : 0 ..< nodeList.length) { - val childNode = nodeList.item(i) - if (childNode.nodeName == "suite") { - val childSuite = new Suite - suite.items.add(childSuite) - childSuite.populate(childNode) - } else if (childNode.nodeName == "test") { - val childTest = new Test - suite.items.add(childTest) - childTest.populate(childNode) - } - } - } - } - - private def void populate(Test test, Node node) { - if (node instanceof Element) { - test.id = node.attributes?.getNamedItem("id")?.nodeValue - test.executableType = node.getElementsByTagName("executableType")?.item(0)?.textContent - test.ownerName = node.getElementsByTagName("ownerName")?.item(0)?.textContent - test.objectName = node.getElementsByTagName("objectName")?.item(0)?.textContent - test.procedureName = node.getElementsByTagName("procedureName")?.item(0)?.textContent - test.disabled = node.getElementsByTagName("disabled")?.item(0)?.textContent == "true" - test.name = node.getElementsByTagName("name")?.item(0)?.textContent - test.description = node.getElementsByTagName("description")?.item(0)?.textContent - test.testNumber = Integer.valueOf(node.getElementsByTagName("testNumber")?.item(0)?.textContent) - } - } - - private def void populate(PostEvent event, Node node) { - if (node instanceof Element) { - event.startTime = node.getElementsByTagName("startTime")?.item(0)?.textContent - event.endTime = node.getElementsByTagName("endTime")?.item(0)?.textContent - event.executionTime = Double.valueOf(node.getElementsByTagName("executionTime")?.item(0)?.textContent) - event.counter.populate(node) - event.errorStack = node.getElementsByTagName("errorStack")?.item(0)?.textContent - event.serverOutput = node.getElementsByTagName("serverOutput")?.item(0)?.textContent - event.warnings = node.getElementsByTagName("warnings")?.item(0)?.textContent - } - } - - private def void populate(Counter counter, Node node) { - if (node instanceof Element) { - val counterNode = node.getElementsByTagName("counter")?.item(0) - if (counterNode instanceof Element) { - counter.disabled = Integer.valueOf(counterNode.getElementsByTagName("disabled")?.item(0)?.textContent) - counter.success = Integer.valueOf(counterNode.getElementsByTagName("success")?.item(0)?.textContent) - counter.failure = Integer.valueOf(counterNode.getElementsByTagName("failure")?.item(0)?.textContent) - counter.error = Integer.valueOf(counterNode.getElementsByTagName("error")?.item(0)?.textContent) - counter.warning = Integer.valueOf(counterNode.getElementsByTagName("warning")?.item(0)?.textContent) - } - } - } - - private def void populate(Expectation expectation, Node node) { - if (node instanceof Element) { - expectation.description = node.getElementsByTagName("description")?.item(0)?.textContent - expectation.message = node.getElementsByTagName("message")?.item(0)?.textContent - expectation.caller = node.getElementsByTagName("caller")?.item(0)?.textContent - } - } -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/AbstractModel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterEventConsumer.java similarity index 76% rename from sqldev/src/main/java/org/utplsql/sqldev/model/AbstractModel.xtend rename to sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterEventConsumer.java index f4356033..cede3ca7 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/AbstractModel.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/RealtimeReporterEventConsumer.java @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev.model +package org.utplsql.sqldev.dal; -import org.eclipse.xtext.xbase.lib.util.ToStringBuilder +import org.utplsql.sqldev.model.runner.RealtimeReporterEvent; -abstract class AbstractModel { - override toString() { - new ToStringBuilder(this).addAllFields.toString - } +public interface RealtimeReporterEventConsumer { + void process(final RealtimeReporterEvent event); } diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java new file mode 100644 index 00000000..605d376b --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.java @@ -0,0 +1,1063 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.dal; + +import java.net.URL; +import java.sql.Connection; +import java.sql.Types; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.oddgen.sqldev.generators.model.Node; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.CallableStatementCallback; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SingleConnectionDataSource; +import org.utplsql.sqldev.model.StringTools; +import org.utplsql.sqldev.model.ut.Annotation; +import org.utplsql.sqldev.model.ut.OutputLines; + +public class UtplsqlDao { + public static final String UTPLSQL_PACKAGE_NAME = "UT"; + public static final int FIRST_VERSION_WITH_INTERNAL_ANNOTATION_API = 3000004; + public static final int FIRST_VERSION_WITH_ANNOTATION_API = 3001003; + public static final int FIRST_VERSION_WITHOUT_INTERNAL_API = 3001008; + public static final int FIRST_VERSION_WITH_HAS_SUITES_API = 3001008; + public static final int FETCH_ROWS = 100; + private final JdbcTemplate jdbcTemplate; + // cache fields + private Boolean cachedDbaViewAccessible; + private String cachedUtplsqlSchema; + private String cachedUtPlsqlVersion; + + public UtplsqlDao(final Connection conn) { + jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true)); + jdbcTemplate.setFetchSize(FETCH_ROWS); + } + + /** + * used for testing purposes only + */ + public void setUtPlsqlVersion(final String utPlsqlVersion) { + this.cachedUtPlsqlVersion = utPlsqlVersion; + } + + /** + * returns a normalized utPLSQL version in format 9.9.9 + */ + public String normalizedUtPlsqlVersion() { + final String version = this.getUtPlsqlVersion(); + if (version != null) { + final Pattern p = Pattern.compile("(\\d+\\.\\d+\\.\\d+)"); + final Matcher m = p.matcher(version); + if (m.find()) { + return m.group(0); + } + } + return "0.0.0"; + } + + /** + * get version as number, e.g. 3001004 + */ + public int normalizedUtPlsqlVersionNumber() { + final Pattern p = Pattern.compile("(\\d+)"); + final String version = this.normalizedUtPlsqlVersion(); + final Matcher m = p.matcher(version); + m.find(); + final String major = m.group(); + m.find(); + final String minor = m.group(); + m.find(); + final String bugfix = m.group(); + return Integer.parseInt(major) * 1000000 + Integer.parseInt(minor) * 1000 + Integer.parseInt(bugfix); + } + + /** + * gets version of installed utPLSQL + */ + public String getUtPlsqlVersion() { + if (cachedUtPlsqlVersion == null) { + final StringBuilder sb = new StringBuilder(); + sb.append("BEGIN\n"); + sb.append(" ? := ut.version;\n"); + sb.append("END;"); + final String sql = sb.toString(); + try { + cachedUtPlsqlVersion = jdbcTemplate.execute(sql, (CallableStatementCallback) cs -> { + cs.registerOutParameter(1, Types.VARCHAR); + cs.execute(); + return cs.getString(1); + }); + } catch (DataAccessException e) { + // ignore error + } + } + return cachedUtPlsqlVersion; + } + + public boolean isDbaViewAccessible() { + if (cachedDbaViewAccessible == null) { + try { + final StringBuilder sb = new StringBuilder(); + sb.append("SELECT 1 AS dummy\n"); + sb.append(" FROM dba_objects\n"); + sb.append(" WHERE 1=2\n"); + sb.append("UNION ALL\n"); + sb.append("SELECT 1\n"); + sb.append(" FROM dba_synonyms\n"); + sb.append(" WHERE 1=2\n"); + sb.append("UNION ALL\n"); + sb.append("SELECT 1\n"); + sb.append(" FROM dba_dependencies\n"); + sb.append(" WHERE 1=2\n"); + final String sql = sb.toString(); + jdbcTemplate.execute(sql); + cachedDbaViewAccessible = true; + } catch (DataAccessException e) { + cachedDbaViewAccessible = false; + } + } + return cachedDbaViewAccessible; + } + + public String getDbaView(String viewName) { + StringBuilder sb = new StringBuilder(); + if (isDbaViewAccessible()) { + sb.append("dba"); + } else { + sb.append("all"); + } + sb.append("_"); + sb.append(viewName); + return sb.toString(); + } + + /** + * Gets the schema name of the utPLSQL installation. + * + * @return utPLSQL schema or null if no utPLSQL is not installed + * @throws DataAccessException + * if there is a problem + */ + public String getUtplsqlSchema() { + if (cachedUtplsqlSchema == null) { + final StringBuilder sb = new StringBuilder(); + sb.append("SELECT table_owner\n"); + sb.append(" FROM "); + sb.append(getDbaView("synonyms\n")); + sb.append(" WHERE owner = 'PUBLIC'\n"); + sb.append(" AND synonym_name = '"); + sb.append(UtplsqlDao.UTPLSQL_PACKAGE_NAME); + sb.append("'\n"); + sb.append(" AND table_name = '"); + sb.append(UtplsqlDao.UTPLSQL_PACKAGE_NAME); + sb.append("'"); + final String sql = sb.toString(); + try { + cachedUtplsqlSchema = jdbcTemplate.queryForObject(sql, String.class); + } catch (EmptyResultDataAccessException e) { + cachedUtplsqlSchema = null; + } + } + return cachedUtplsqlSchema; + } + + /** + * Checks if the package ut_annotation_manager is installed. This package has + * been introduced with utPLSQL 3.0.4. This version is a prerequisite to + * identify utPLSQL unit test procedures. + * + * @return true if ut_annotation_manager package has been found + * @throws DataAccessException + * if there is a problem + */ + public boolean isUtAnnotationManagerInstalled() { + return normalizedUtPlsqlVersionNumber() >= UtplsqlDao.FIRST_VERSION_WITH_INTERNAL_ANNOTATION_API; + } + + /** + * Checks if utPLSQL tests exist + * + * @param owner + * schema name, mandatory, case-insensitive + * @param objectName + * name of the package or package body, optional, case-insensitive + * @param subobjectName + * name of the procedure, optional, case-insensitive + * @return true if at least one test has been found + * @throws DataAccessException + * if a utPLSQL version less than 3.0.4 is installed or if there are + * other problems + */ + public boolean containsUtplsqlTest(final String owner, final String objectName, final String subobjectName) { + try { + if (normalizedUtPlsqlVersionNumber() >= UtplsqlDao.FIRST_VERSION_WITH_HAS_SUITES_API && objectName != null + && subobjectName != null) { + // use faster check function available since v3.1.3 (reliable in v3.1.8) + final StringBuilder sb = new StringBuilder(); + sb.append("DECLARE\n"); + sb.append(" l_return VARCHAR2(1) := '0';\n"); + sb.append("BEGIN\n"); + sb.append(" IF ut_runner.is_test(?, ?, ?) THEN\n"); + sb.append(" l_return := '1';\n"); + sb.append(" END IF;\n"); + sb.append(" ? := l_return;\n"); + sb.append("END;"); + final String sql = sb.toString(); + final Boolean ret = jdbcTemplate.execute(sql, (CallableStatementCallback) cs -> { + cs.setString(1, owner); + cs.setString(2, objectName); + cs.setString(3, subobjectName); + cs.registerOutParameter(4, Types.VARCHAR); + cs.execute(); + final String ret1 = cs.getString(4); + return "1".equals(ret1); + }); + return ret != null && ret; + } else if (normalizedUtPlsqlVersionNumber() >= FIRST_VERSION_WITH_ANNOTATION_API) { + // using API available since 3.1.3, can handle nulls in objectName and subobjectName + StringBuilder sb = new StringBuilder(); + sb.append("SELECT count(*)\n"); + sb.append(" FROM TABLE(ut_runner.get_suites_info(upper(?), upper(?)))\n"); + sb.append(" WHERE item_type IN ('UT_TEST', 'UT_SUITE')\n"); + sb.append(" AND (item_name = upper(?) or ? IS NULL)\n"); + final String sql = sb.toString(); + final Object[] binds = new Object[] {owner, objectName, subobjectName, subobjectName}; + final int[] types = new int[] {Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR}; + final Integer found = jdbcTemplate.queryForObject(sql, binds, types, Integer.class); + return found != null && found > 0; + } else { + // using internal API (deprecated, not accessible in latest version) + StringBuilder sb = new StringBuilder(); + sb.append("SELECT count(\n"); + sb.append(" CASE\n"); + sb.append(" WHEN a.name = 'test'\n"); + sb.append(" AND (upper(a.subobject_name) = upper(?) OR ? IS NULL)\n"); + sb.append(" THEN\n"); + sb.append(" 1\n"); + sb.append(" ELSE\n"); + sb.append(" NULL\n"); + sb.append(" END\n"); + sb.append(" )\n"); + sb.append(" FROM TABLE("); + sb.append(getUtplsqlSchema()); + sb.append(".ut_annotation_manager.get_annotated_objects(upper(?), 'PACKAGE')) o\n"); + sb.append(" CROSS JOIN TABLE(o.annotations) a\n"); + sb.append(" WHERE (o.object_name = upper(?) OR ? IS NULL)\n"); + sb.append(" AND a.name IN ('test', 'suite')\n"); + sb.append("HAVING count(\n"); + sb.append(" CASE\n"); + sb.append(" WHEN a.name = 'suite' THEN\n"); + sb.append(" 1\n"); + sb.append(" ELSE\n"); + sb.append(" NULL\n"); + sb.append(" END\n"); + sb.append(" ) > 0"); + final String sql = sb.toString(); + final Object[] binds = new Object[] {subobjectName, subobjectName, owner, objectName, objectName}; + final Integer found = jdbcTemplate.queryForObject(sql, Integer.class, binds); + return found != null && found > 0; + } + } catch (EmptyResultDataAccessException e) { + return false; + } + } + + public boolean containsUtplsqlTest(final String owner) { + if (normalizedUtPlsqlVersionNumber() >= org.utplsql.sqldev.dal.UtplsqlDao.FIRST_VERSION_WITH_HAS_SUITES_API) { + // use faster check function available since v3.1.3 (reliable in v3.1.8) + StringBuilder sb = new StringBuilder(); + sb.append("DECLARE\n"); + sb.append(" l_return VARCHAR2(1) := '0';\n"); + sb.append("BEGIN\n"); + sb.append(" IF ut_runner.has_suites(?) THEN\n"); + sb.append(" l_return := '1';\n"); + sb.append(" END IF;\n"); + sb.append(" ? := l_return;\n"); + sb.append("END;"); + final String sql = sb.toString(); + final Boolean ret = jdbcTemplate.execute(sql, (CallableStatementCallback) cs -> { + cs.setString(1, owner); + cs.registerOutParameter(2, Types.VARCHAR); + cs.execute(); + final String ret1 = cs.getString(2); + return "1".equals(ret1); + }); + return ret != null && ret; + } else { + return containsUtplsqlTest(owner, null, null); + } + } + + public boolean containsUtplsqlTest(final String owner, final String objectName) { + if (normalizedUtPlsqlVersionNumber() >= org.utplsql.sqldev.dal.UtplsqlDao.FIRST_VERSION_WITH_HAS_SUITES_API) { + StringBuilder sb = new StringBuilder(); + sb.append("DECLARE\n"); + sb.append(" l_return VARCHAR2(1) := '0';\n"); + sb.append("BEGIN\n"); + sb.append(" IF ut_runner.is_suite(?, ?) THEN\n"); + sb.append(" l_return := '1';\n"); + sb.append(" END IF;\n"); + sb.append(" ? := l_return;\n"); + sb.append("END;"); + final String sql = sb.toString(); + Boolean ret = jdbcTemplate.execute(sql, (CallableStatementCallback) cs -> { + cs.setString(1, owner); + cs.setString(2, objectName); + cs.registerOutParameter(3, Types.VARCHAR); + cs.execute(); + final String ret1 = cs.getString(3); + return "1".equals(ret1); + }); + return ret != null && ret; + } else { + return containsUtplsqlTest(owner, objectName, null); + } + } + + /** + * Gets a list of utPLSQL annotations for a given PL/SQL package specification + * + * @param owner + * schema name, mandatory, case-insensitive + * @param objectName + * name of the package or package body, optional, case-insensitive + * @return list of Annotation with name 'suite' or 'test' + * @throws DataAccessException + * if a utPLSQL version less than 3.0.4 is installed or if there are + * other problems + */ + public List annotations(final String owner, final String objectName) { + StringBuilder sb = new StringBuilder(); + if (normalizedUtPlsqlVersionNumber() >= FIRST_VERSION_WITH_ANNOTATION_API) { + sb.append("SELECT object_owner,\n"); + sb.append(" object_name,\n"); + sb.append(" lower(substr(item_type, 4)) AS name,\n"); + sb.append(" item_name as subobject_name\n"); + sb.append(" FROM TABLE(ut_runner.get_suites_info(upper(?), upper(?)))"); + } else { + sb.append("SELECT o.object_owner,\n"); + sb.append(" o.object_name,\n"); + sb.append(" a.name,\n"); + sb.append(" a.text,\n"); + sb.append(" coalesce(upper(a.subobject_name), o.object_name) AS subobject_name\n"); + sb.append(" FROM TABLE("); + sb.append(getUtplsqlSchema()); + sb.append(".ut_annotation_manager.get_annotated_objects(upper(?), 'PACKAGE')) o\n"); + sb.append(" CROSS JOIN TABLE(o.annotations) a\n"); + sb.append(" WHERE o.object_name = upper(?)"); + } + final String sql = sb.toString(); + final BeanPropertyRowMapper rowMapper = new BeanPropertyRowMapper<>(Annotation.class); + final Object[] binds = new Object[] {owner, objectName}; + return jdbcTemplate.query(sql, rowMapper, binds); + } + + /** + * Gets a list of public units in the object type + * + * @param objectType + * expected object types are PACKAGE, TYPE, FUNCTION, PROCEDURE + * @param objectName + * name of the object + * @return list of the public units in the object type + * @throws DataAccessException + * if there is a problem + */ + public List units(final String objectType, final String objectName) { + if ("PACKAGE".equals(objectType) || "TYPE".equals(objectType)) { + StringBuilder sb = new StringBuilder(); + sb.append("SELECT procedure_name\n"); + sb.append(" FROM user_procedures\n"); + sb.append(" WHERE object_type = ?\n"); + sb.append(" AND object_name = ?\n"); + sb.append(" AND procedure_name IS NOT NULL\n"); + sb.append(" GROUP BY procedure_name\n"); + sb.append(" ORDER BY min(subprogram_id)"); + final String sql = sb.toString(); + final Object[] binds = new Object[] {objectType, objectName}; + return jdbcTemplate.queryForList(sql, String.class, binds); + } else { + return Collections.singletonList(objectName); + } + } + + /** + * Gets a list of oddgen's nodes as candidates to create utPLSQL test packages. + * Candidates are packages, types, functions and procedures in the current user. + * + * This functions must be called from an oddgen generator only, since the Node + * is not defined in the utPLSQL extension. + * + * @param objectType + * expected object types are PACKAGE, TYPE, FUNCTION, PROCEDURE + * @return list of the oddgen nodes for the requested object type + * @throws DataAccessException + * if there is a problem + */ + public List testables(final String objectType) { + StringBuilder sb = new StringBuilder(); + if ("PACKAGE".equals(objectType)) { + if (normalizedUtPlsqlVersionNumber() >= FIRST_VERSION_WITH_ANNOTATION_API) { + // using API available since 3.1.3 + sb.append("SELECT DISTINCT\n"); + sb.append(" object_type || '.' || object_name AS id,\n"); + sb.append(" object_type AS parent_id,\n"); + sb.append(" 1 AS leaf,\n"); + sb.append(" 1 AS generatable,\n"); + sb.append(" 1 AS multiselectable\n"); + sb.append(" FROM user_procedures\n"); + sb.append(" WHERE object_type = ?\n"); + sb.append(" AND procedure_name IS NOT NULL\n"); + sb.append(" AND object_name NOT IN (\n"); + sb.append(" SELECT object_name\n"); + sb.append(" FROM TABLE(ut_runner.get_suites_info(USER))\n"); + sb.append(" WHERE item_type = 'UT_SUITE'\n"); + sb.append(" )"); + } else { + // using internal API (deprecated, not accessible in latest version) + sb.append("SELECT DISTINCT\n"); + sb.append(" object_type || '.' || object_name AS id,\n"); + sb.append(" object_type AS parent_id,\n"); + sb.append(" 1 AS leaf,\n"); + sb.append(" 1 AS generatable,\n"); + sb.append(" 1 AS multiselectable\n"); + sb.append(" FROM user_procedures\n"); + sb.append(" WHERE object_type = ?\n"); + sb.append(" AND procedure_name IS NOT NULL\n"); + sb.append(" AND object_name NOT IN (\n"); + sb.append(" SELECT object_name\n"); + sb.append(" FROM TABLE(\n"); + sb.append(getUtplsqlSchema()); + sb.append(".ut_annotation_manager.get_annotated_objects(USER, 'PACKAGE'))\n"); + sb.append(" )"); + } + } else if ("TYPE".equals(objectType)) { + sb.append("SELECT DISTINCT\n"); + sb.append(" object_type || '.' || object_name AS id,\n"); + sb.append(" object_type AS parent_id,\n"); + sb.append(" 1 AS leaf,\n"); + sb.append(" 1 AS generatable,\n"); + sb.append(" 1 AS multiselectable\n"); + sb.append(" FROM user_procedures\n"); + sb.append(" WHERE object_type = ?\n"); + sb.append(" AND procedure_name IS NOT NULL"); + } else { + sb.append("SELECT object_type || '.' || object_name AS id,\n"); + sb.append(" object_type AS parent_id,\n"); + sb.append(" 1 AS leaf,\n"); + sb.append(" 1 AS generatable,\n"); + sb.append(" 1 AS multiselectable\n"); + sb.append(" FROM user_objects\n"); + sb.append(" WHERE object_type = ?\n"); + sb.append(" AND generated = 'N'"); + } + final String sql = sb.toString(); + final Object[] binds = new Object[] {objectType}; + BeanPropertyRowMapper rowMapper = new BeanPropertyRowMapper<>(Node.class); + return jdbcTemplate.query(sql, rowMapper, binds); + } + + /** + * Gets a list of oddgen's nodes as candidates to run utPLSQL tests. + * + * This functions must be called from an oddgen generator only, since the Node + * is not defined in the utPLSQL extension. + * + * @return list of oddgen nodes (complete hierarchy loaded eagerly) + * @throws DataAccessException + * if there is a problem + */ + public List runnables() { + StringBuilder sb = new StringBuilder(); + if (normalizedUtPlsqlVersionNumber() >= FIRST_VERSION_WITH_ANNOTATION_API) { + // using API available since 3.1.3 + sb.append("WITH\n"); + sb.append(" test AS (\n"); + sb.append(" SELECT object_owner,\n"); + sb.append(" object_name,\n"); + sb.append(" path AS suitepath,\n"); + sb.append(" count(\n"); + sb.append(" CASE\n"); + sb.append(" WHEN item_type = 'UT_TEST' THEN\n"); + sb.append(" 1\n"); + sb.append(" ELSE\n"); + sb.append(" NULL\n"); + sb.append(" END\n"); + sb.append(" ) over (partition by object_owner, object_name) AS test_count,\n"); + sb.append(" item_type,\n"); + sb.append(" item_name,\n"); + sb.append(" item_description\n"); + sb.append(" FROM TABLE(ut_runner.get_suites_info(user))\n"); + sb.append(" ),\n"); + sb.append(" suite_tree AS (\n"); + sb.append(" SELECT null AS parent_id,\n"); + sb.append(" 'SUITE' AS id,\n"); + sb.append(" 'All Suites' AS name,\n"); + sb.append(" 'All utPLSQL test suites' AS description,\n"); + sb.append(" 'PACKAGE_FOLDER_ICON' AS iconName,\n"); + sb.append(" 'No' AS leaf,\n"); + sb.append(" 'Yes' AS generatable,\n"); + sb.append(" 'Yes' AS multiselectable,\n"); + sb.append(" 'Yes' AS relevant\n"); + sb.append(" FROM dual\n"); + sb.append(" UNION ALL\n"); + sb.append(" SELECT DISTINCT\n"); + sb.append(" 'SUITE' AS parent_id,\n"); + sb.append(" object_owner || '.' || object_name AS id,\n"); + sb.append(" object_name AS name,\n"); + sb.append(" null AS description,\n"); + sb.append(" 'PACKAGE_ICON' AS iconName,\n"); + sb.append(" 'No' AS leaf,\n"); + sb.append(" 'Yes' AS generatable,\n"); + sb.append(" 'Yes' AS multiselectable,\n"); + sb.append(" 'Yes' AS relevant\n"); + sb.append(" FROM test\n"); + sb.append(" WHERE item_type IN ('UT_TEST', 'UT_SUITE')\n"); + sb.append(" UNION ALL\n"); + sb.append(" SELECT object_owner || '.' || object_name AS parent_id,\n"); + sb.append(" object_owner || '.' || object_name || '.' || item_name AS id,\n"); + sb.append(" item_name AS name,\n"); + sb.append(" item_description AS description,\n"); + sb.append(" 'PROCEDURE_ICON' AS iconName,\n"); + sb.append(" 'Yes' AS leaf,\n"); + sb.append(" 'Yes' AS generatable,\n"); + sb.append(" 'Yes' AS multiselectable,\n"); + sb.append(" 'Yes' AS relevant\n"); + sb.append(" FROM test\n"); + sb.append(" WHERE item_type = 'UT_TEST'\n"); + sb.append(" ),\n"); + sb.append(" suitepath_tree AS (\n"); + sb.append(" SELECT NULL AS parent_id,\n"); + sb.append(" 'SUITEPATH' AS id,\n"); + sb.append(" 'All Suitepaths' AS name\n,"); + sb.append(" 'All utPLSQL test suitepathes' AS description,\n"); + sb.append(" 'FOLDER_ICON' AS iconName,\n"); + sb.append(" 'No' AS leaf,\n"); + sb.append(" 'Yes' AS generatable,\n"); + sb.append(" 'Yes' AS multiselectable,\n"); + sb.append(" 'Yes' AS relevant\n"); + sb.append(" FROM dual\n"); + sb.append(" UNION ALL\n"); + sb.append(" SELECT CASE\n"); + sb.append(" WHEN regexp_replace(suitepath,'\\.?\\w+$','') IS NULL THEN\n"); + sb.append(" 'SUITEPATH'\n"); + sb.append(" ELSE\n"); + sb.append(" object_owner || ':' || regexp_replace(suitepath,'\\.?\\w+$','')\n"); + sb.append(" END AS parent_id,\n"); + sb.append(" object_owner || ':' || suitepath AS id,\n"); + sb.append(" item_name AS name,\n"); + sb.append(" item_description AS description,\n"); + sb.append(" CASE\n"); + sb.append(" WHEN item_type = 'UT_SUITE' AND test_count > 0 THEN\n"); + sb.append(" 'PACKAGE_ICON'\n"); + sb.append(" WHEN item_type = 'UT_TEST' THEN\n"); + sb.append(" 'PROCEDURE_ICON'\n"); + sb.append(" ELSE\n"); + sb.append(" 'FOLDER_ICON'\n"); + sb.append(" END AS iconName,\n"); + sb.append(" CASE item_type\n"); + sb.append(" WHEN 'UT_TEST' THEN\n"); + sb.append(" 'Yes'\n"); + sb.append(" ELSE\n"); + sb.append(" 'No'\n"); + sb.append(" END AS leaf,\n"); + sb.append(" 'Yes' AS generatable,\n"); + sb.append(" 'Yes' AS multiselectable,\n"); + sb.append(" 'Yes' AS relevant\n"); + sb.append(" FROM test\n"); + sb.append(" ),\n"); + sb.append(" tree AS (\n"); + sb.append(" SELECT parent_id, id, name, description, iconName, leaf, generatable, multiselectable, relevant\n"); + sb.append(" FROM suite_tree\n"); + sb.append(" UNION ALL\n"); + sb.append(" SELECT parent_id, id, name, description, iconName, leaf, generatable, multiselectable, relevant\n"); + sb.append(" FROM suitepath_tree\n"); + sb.append(" )\n"); + sb.append("SELECT parent_id, id, initcap(name) AS name, description, iconName, leaf, generatable, multiselectable, relevant\n"); + sb.append(" FROM tree"); + } else { + // using internal API (deprecated, not accessible in latest version) + sb.append("WITH\n"); + sb.append(" base AS (\n"); + sb.append(" SELECT rownum AS an_id,\n"); + sb.append(" o.object_owner,\n"); + sb.append(" o.object_type,\n"); + sb.append(" o.object_name,\n"); + sb.append(" lower(a.name) AS name,\n"); + sb.append(" a.text,\n"); + sb.append(" a.subobject_name\n"); + sb.append(" FROM table("); + sb.append(getUtplsqlSchema()); + sb.append(".ut_annotation_manager.get_annotated_objects(user, 'PACKAGE')) o\n"); + sb.append(" CROSS JOIN table(o.annotations) a\n"); + sb.append(" WHERE lower(a.name) in ('suite', 'suitepath', 'endcontext', 'test')\n"); + sb.append(" OR lower(a.name) = 'context' AND regexp_like(text, '(\\w+)(\\.\\w+)*')\n"); + sb.append(" ),\n"); + sb.append(" suite AS (\n"); + sb.append(" SELECT object_owner, object_type, object_name, text AS suite_description\n"); + sb.append(" FROM base\n"); + sb.append(" WHERE name = 'suite'\n"); + sb.append(" ),\n"); + sb.append(" suitepath as (\n"); + sb.append(" SELECT object_owner, object_type, object_name, lower(text) AS suitepath\n"); + sb.append(" FROM base\n"); + sb.append(" WHERE name = 'suitepath'"); + sb.append(" ),\n"); + sb.append(" context_base AS (\n"); + sb.append(" SELECT an_id,\n"); + sb.append(" lead(an_id) over (partition by object_owner, object_type, object_name order by an_id) AS an_id_end,\n"); + sb.append(" object_owner,\n"); + sb.append(" object_type,\n"); + sb.append(" object_name,\n"); + sb.append(" name,\n"); + sb.append(" lead(name) over (partition by object_owner, object_type, object_name order by an_id) AS name_end,\n"); + sb.append(" text AS context\n"); + sb.append(" FROM base\n"); + sb.append(" WHERE name IN ('context', 'endcontext')\n"); + sb.append(" ),\n"); + sb.append(" context AS (\n"); + sb.append(" SELECT an_id, an_id_end, object_owner, object_type, object_name, context\n"); + sb.append(" FROM context_base\n"); + sb.append(" WHERE name = 'context'\n"); + sb.append(" AND name_end = 'endcontext'\n"); + sb.append(" ),\n"); + sb.append(" test AS (\n"); + sb.append(" SELECT b.an_id,\n"); + sb.append(" b.object_owner,\n"); + sb.append(" b.object_type,\n"); + sb.append(" b.object_name,\n"); + sb.append(" p.suitepath,\n"); + sb.append(" c.context,\n"); + sb.append(" b.subobject_name,\n"); + sb.append(" b.text AS test_description\n"); + sb.append(" FROM base b\n"); + sb.append(" LEFT JOIN suitepath p\n"); + sb.append(" ON p.object_owner = b.object_owner\n"); + sb.append(" AND p.object_type = b.object_type\n"); + sb.append(" AND p.object_name = b.object_name\n"); + sb.append(" LEFT JOIN context c\n"); + sb.append(" ON c.object_owner = b.object_owner\n"); + sb.append(" AND c.object_type = b.object_type\n"); + sb.append(" AND c.object_name = b.object_name\n"); + sb.append(" AND b.an_id BETWEEN c.an_id AND c.an_id_end\n"); + sb.append(" WHERE name = 'test'\n"); + sb.append(" AND (b.object_owner, b.object_type, b.object_name) IN (\n"); + sb.append(" SELECT object_owner, object_type, object_name\n"); + sb.append(" FROM suite\n"); + sb.append(" )\n"); + sb.append(" ),\n"); + sb.append(" suite_tree AS (\n"); + sb.append(" SELECT null AS parent_id,\n"); + sb.append(" 'SUITE' AS id,\n"); + sb.append(" 'All Suites' AS name,\n"); + sb.append(" 'All utPLSQL test suites' AS description,\n"); + sb.append(" 'PACKAGE_FOLDER_ICON' AS iconName,\n"); + sb.append(" 'No' AS leaf,\n"); + sb.append(" 'Yes' AS generatable,\n"); + sb.append(" 'Yes' AS multiselectable,\n"); + sb.append(" 'Yes' AS relevant\n"); + sb.append(" FROM dual\n"); + sb.append(" UNION ALL\n"); + sb.append(" SELECT DISTINCT\n"); + sb.append(" 'SUITE' AS parent_id,\n"); + sb.append(" 'object_owner || '.' || object_name AS id,\n"); + sb.append(" object_name AS name,\n"); + sb.append(" NULL AS description,\n"); + sb.append(" 'PACKAGE_ICON' AS iconName,\n"); + sb.append(" 'No' AS leaf,\n"); + sb.append(" 'Yes' AS generatable,\n"); + sb.append(" 'Yes' AS multiselectable,\n"); + sb.append(" 'Yes' AS relevant\n"); + sb.append(" FROM test\n"); + sb.append(" UNION ALL\n"); + sb.append(" SELECT object_owner || '.' || object_name AS parent_id,\n"); + sb.append(" object_owner || '.' || object_name || '.' || upper(subobject_name) AS id,\n"); + sb.append(" subobject_name AS name,\n"); + sb.append(" NULL AS description,\n"); + sb.append(" 'PROCEDURE_ICON' AS iconName,\n"); + sb.append(" 'Yes' AS leaf,\n"); + sb.append(" 'Yes' AS generatable,\n"); + sb.append(" 'Yes' AS multiselectable,\n"); + sb.append(" 'Yes' AS relevant\n"); + sb.append(" FROM test\n"); + sb.append(" ),\n"); + sb.append(" suitepath_base AS (\n"); + sb.append(" SELECT DISTINCT\n"); + sb.append(" suitepath\n"); + sb.append(" FROM suitepath\n"); + sb.append(" ),\n"); + sb.append(" gen AS (\n"); + sb.append(" SELECT rownum AS pos\n"); + sb.append(" FROM xmltable('1 to 100')\n"); + sb.append(" ),\n"); + sb.append(" suitepath_part AS (\n"); + sb.append(" SELECT DISTINCT\n"); + sb.append(" lower(substr(suitepath, 1, instr(suitepath || '.', '.', 1, g.pos) -1)) AS suitepath\n"); + sb.append(" FROM suitepath_base b\n"); + sb.append(" JOIN gen g\n"); + sb.append(" ON g.pos <= regexp_count(suitepath, '\\w+')\n"); + sb.append(" ),\n"); + sb.append(" suitepath_tree AS (\n"); + sb.append(" SELECT NULL AS parent_id,\n"); + sb.append(" 'SUITEPATH' AS id,\n"); + sb.append(" 'All Suitepaths' AS name,\n"); + sb.append(" 'All utPLSQL test suitepathes' AS description,\n"); + sb.append(" 'FOLDER_ICON' AS iconName,\n"); + sb.append(" 'No' AS leaf,\n"); + sb.append(" 'Yes' AS generatable,\n"); + sb.append(" 'Yes' AS multiselectable,\n"); + sb.append(" 'Yes' AS relevant\n"); + sb.append(" FROM dual\n"); + sb.append(" UNION ALL\n"); + sb.append(" SELECT CASE\n"); + sb.append(" WHEN regexp_replace(suitepath,'\\.?\\w+$','') IS NULL THEN\n"); + sb.append(" 'SUITEPATH'\n"); + sb.append(" ELSE\n"); + sb.append(" USER || ':' || regexp_replace(suitepath,'\\.?\\w+$','')"); + sb.append(" END AS parent_id,\n"); + sb.append(" USER || ':' || suitepath AS id,\n"); + sb.append(" regexp_substr(suitepath, '\\.?(\\w+$)', 1, 1, NULL, 1) AS name,\n"); + sb.append(" NULL AS description,\n"); + sb.append(" 'FOLDER_ICON' AS iconName,\n"); + sb.append(" 'No' AS leaf,\n"); + sb.append(" 'Yes' AS generatable,\n"); + sb.append(" 'Yes' AS multiselectable,\n"); + sb.append(" 'Yes' AS relevant\n"); + sb.append(" FROM suitepath_part\n"); + sb.append(" UNION ALL\n"); + sb.append(" SELECT DISTINCT\n"); + sb.append(" object_owner || ':' || suitepath AS parent_id,\n"); + sb.append(" object_owner || ':' || suitepath || '.' || lower(object_name) AS id,\n"); + sb.append(" object_name AS name,\n"); + sb.append(" NULL AS description,\n"); + sb.append(" 'PACKAGE_ICON' AS iconName,\n"); + sb.append(" 'No' AS leaf,\n"); + sb.append(" 'Yes' AS generatable,\n"); + sb.append(" 'Yes' AS multiselectable,\n"); + sb.append(" 'Yes' AS relevant\n"); + sb.append(" FROM test\n"); + sb.append(" WHERE suitepath IS NOT NULL\n"); + sb.append(" UNION ALL\n"); + sb.append(" SELECT DISTINCT\n"); + sb.append(" object_owner || ':' || suitepath || '.' || lower(object_name) AS parent_id,\n"); + sb.append(" object_owner || ':' || suitepath || '.' || lower(object_name) || '.' || context AS id,\n"); + sb.append(" context AS name,\n"); + sb.append(" NULL AS description,\n"); + sb.append(" 'FOLDER_ICON' AS iconName,\n"); + sb.append(" 'No' AS leaf,\n"); + sb.append(" 'Yes' AS generatable,\n"); + sb.append(" 'Yes' AS multiselectable,\n"); + sb.append(" 'Yes' AS relevant\n"); + sb.append(" FROM test\n"); + sb.append(" WHERE suitepath IS NOT NULL\n"); + sb.append(" AND context IS NOT NULL\n"); + sb.append(" UNION ALL\n"); + sb.append(" SELECT object_owner || ':' || suitepath || '.' || lower(object_name) || CASE WHEN context IS NOT NULL THEN '.' || context END AS parent_id,\n"); + sb.append(" object_owner || ':' || suitepath || '.' || lower(object_name) || CASE WHEN context IS NOT NULL THEN '.' || context END || '.' || lower(subobject_name) AS id,\n"); + sb.append(" subobject_name AS name,\n"); + sb.append(" NULL AS description,\n"); + sb.append(" 'PROCEDURE_ICON' AS iconName,\n"); + sb.append(" 'Yes' AS leaf,\n"); + sb.append(" 'Yes' AS generatable,\n"); + sb.append(" 'Yes' AS multiselectable\n,"); + sb.append(" 'Yes' AS relevant\n"); + sb.append(" FROM test\n"); + sb.append(" WHERE suitepath IS NOT NULL\n"); + sb.append(" ),\n"); + sb.append(" tree AS (\n"); + sb.append(" SELECT parent_id, id, name, description, iconName, leaf, generatable, multiselectable, relevant\n"); + sb.append(" FROM suite_tree\n"); + sb.append(" UNION ALL\n"); + sb.append(" SELECT parent_id, id, name, description, iconName, leaf, generatable, multiselectable, relevant\n"); + sb.append(" FROM suitepath_tree\n"); + sb.append(" )\n"); + sb.append("SELECT parent_id, id, initcap(name) AS name, description, iconName, leaf, generatable, multiselectable, relevant\n"); + sb.append(" FROM tree"); + } + BeanPropertyRowMapper rowMapper = new BeanPropertyRowMapper<>(Node.class); + final String sql = sb.toString(); + return jdbcTemplate.query(sql, rowMapper); + } + + /** + * enable DBMS_OUTPUT + * + * @throws DataAccessException + * if there is a problem + */ + public void enableDbmsOutput() { + // equivalent to "set serveroutput on size unlimited" + StringBuilder sb = new StringBuilder(); + sb.append("BEGIN"); + sb.append(" sys.dbms_output.enable(NULL);\n"); + sb.append("END;"); + jdbcTemplate.update(sb.toString()); + } + + /** + * disable DBMS_OUTPUT + * + * @throws DataAccessException + * if there is a problem + */ + public void disableDbmsOutput() { + StringBuilder sb = new StringBuilder(); + sb.append("BEGIN\n"); + sb.append(" sys.dbms_output.disable;\n"); + sb.append("END;"); + jdbcTemplate.update(sb.toString()); + } + + /** + * return the content of DBMS_OUTPUT as String + * + * @throws DataAccessException + * if there is a problem + */ + public String getDbmsOutput() { + return getDbmsOutput(1000); + } + + /** + * return the content of DBMS_OUTPUT as String + * + * @param bufferSize + * maximum number of rows to be read from the DBMS_OUTPUT buffer in + * one network round trip + * @return content of DBMS_OUTPUT as String + * @throws DataAccessException + * if there is a problem + */ + public String getDbmsOutput(final int bufferSize) { + final StringBuilder resultSb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); + sb.append("BEGIN"); + sb.append(" sys.dbms_output.get_lines(?, ?);\n"); + sb.append("END;"); + final String sql = sb.toString(); + OutputLines ret; + do { + ret = jdbcTemplate.execute(sql, (CallableStatementCallback) cs -> { + cs.registerOutParameter(1, Types.ARRAY, "DBMSOUTPUT_LINESARRAY"); + cs.registerOutParameter(2, Types.INTEGER); + cs.setInt(2, bufferSize); + cs.execute(); + final OutputLines out = new OutputLines(); + try { + Object array = cs.getArray(1).getArray(); + out.setLines((String[]) array); + out.setNumlines(cs.getInt(2)); + } catch (NullPointerException e) { + out.setLines(null); + out.setNumlines(0); + } + return out; + }); + if (ret != null && ret.getNumlines() != null) { + for (int i = 0; i < ret.getNumlines(); i++) { + final String line = ret.getLines()[i]; + if (line != null) { + resultSb.append(ret.getLines()[i]); + } + resultSb.append(System.lineSeparator()); + } + } + } while (ret != null && ret.getNumlines() != null && ret.getNumlines() > 0); + return resultSb.toString(); + } + + /** + * gets the HTML code coverage report as String + * + * @param pathList + * utPLSQL path list + * @param schemaList + * list of schemas under tests. Current schema, if empty + * @param includeObjectList + * list of objects to be included for coverage analysis. All, if + * empty + * @param excludeObjectList + * list of objects to be excluded from coverage analysis. None, if + * empty + * @param htmlReportAssetPath + * path of the assets for the coverage report. Default, if null + * @return HTML code coverage report in HTML format + * @throws DataAccessException + * if there is a problem + */ + public String htmlCodeCoverage(final List pathList, final List schemaList, + final List includeObjectList, final List excludeObjectList, final URL htmlReportAssetPath) { + StringBuilder sb = new StringBuilder(); + sb.append("SELECT column_value\n"); + sb.append(" FROM table(\n"); + sb.append(" ut.run(\n"); + sb.append(" a_paths => ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(pathList, 16)); + sb.append(" ),\n"); + if (schemaList != null && !schemaList.isEmpty()) { + sb.append(" a_coverage_schemes => ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(schemaList, 16)); + sb.append(" ),\n"); + } + if (includeObjectList != null && !includeObjectList.isEmpty()) { + sb.append(" a_include_objects => ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(includeObjectList, 16)); + sb.append(" ),\n"); + } + if (excludeObjectList != null && excludeObjectList.isEmpty()) { + sb.append(" a_exclude_objects => ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(excludeObjectList, 16)); + sb.append(" ),\n"); + } + sb.append(" a_reporter => ut_coverage_html_reporter(\n"); + sb.append(" a_html_report_assets_path => '"); + if (htmlReportAssetPath != null) { + // empty string is handled as NULL in Oracle Database + sb.append(htmlReportAssetPath.toExternalForm()); + } + sb.append("'\n"); + sb.append(" )\n"); + sb.append(" )\n"); + sb.append(" )"); + final String sql = sb.toString(); + final List lines = jdbcTemplate.queryForList(sql, String.class); + final StringBuilder resultSb = new StringBuilder(); + for (String line : lines) { + if (line != null) { + resultSb.append(line); + resultSb.append("\n"); + } + } + return resultSb.toString(); + } + + /** + * gets dependencies of a given object. + * + * The result can be used as input for the includeObjectList in htmlCodeCoverage + * The scope is reduced to non-oracle maintained schemas. + * + * Oracle introduced the column ORACLE_MAINTAINED in 12.1. To simplify the query + * and compatibility the result of the following query is included + * + * SELECT '''' || listagg(username, ''', ''') || '''' AS oracle_maintained_users + * FROM dba_users WHERE oracle_maintained = 'Y' ORDER BY username; + * + * The result may include test packages + * + * @param name + * test package name + * @return list of dependencies in the current schema + */ + public List includes(final String owner, final String name) { + StringBuilder sb = new StringBuilder(); + sb.append("SELECT referenced_owner || '.' || referenced_name AS dep_name\n"); + sb.append(" FROM "); + sb.append(getDbaView("dependencies\n")); + sb.append(" WHERE owner = upper(?)\n"); + sb.append(" AND name = upper(?)\n"); + sb.append(" AND referenced_owner NOT IN (\n"); + sb.append(" 'SYS', 'SYSTEM', 'XS$NULL', 'OJVMSYS', 'LBACSYS', 'OUTLN', 'SYS$UMF',\n"); + sb.append(" 'DBSNMP', 'APPQOSSYS', 'DBSFWUSER', 'GGSYS', 'ANONYMOUS', 'CTXSYS',\n"); + sb.append(" 'SI_INFORMTN_SCHEMA', 'DVF', 'DVSYS', 'GSMADMIN_INTERNAL', 'ORDPLUGINS',\n"); + sb.append(" 'MDSYS', 'OLAPSYS', 'ORDDATA', 'XDB', 'WMSYS', 'ORDSYS', 'GSMCATUSER',\n"); + sb.append(" 'MDDATA', 'REMOTE_SCHEDULER_AGENT', 'SYSBACKUP', 'GSMUSER', 'APEX_PUBLIC_USER',\n"); + sb.append(" 'SYSRAC', 'AUDSYS', 'DIP', 'SYSKM', 'ORACLE_OCM', 'APEX_INSTANCE_ADMIN_USER',\n"); + sb.append(" 'SYSDG', 'FLOWS_FILES', 'ORDS_METADATA', 'ORDS_PUBLIC_USER'\n"); + sb.append(" )\n"); + sb.append(" AND referenced_owner NOT LIKE 'APEX\\_______'"); + sb.append(" AND referenced_type IN ('PACKAGE', 'TYPE', 'PROCEDURE', 'FUNCTION', 'TRIGGER')"); + final String sql = sb.toString(); + final Object[] binds = new Object[] {owner, name}; + return jdbcTemplate.queryForList(sql, String.class, binds); + } + + /** + * gets source of an object from the database via DBMS_METADATA + * + * @param owner + * owner of the object (schema) + * @param objectType + * expected object types are PACKAGE, PACKAGE BODY + * @param objectName + * name of the object + * @return the source code of the object + * @throws DataAccessException + * if there is a problem + */ + public String getSource(final String owner, final String objectType, final String objectName) { + String fixedObjectType; + if ("PACKAGE".equals(objectType)) { + fixedObjectType = "PACKAGE_SPEC"; + } else if ("PACKAGE BODY".equals(objectType)) { + fixedObjectType = "PACKAGE_BODY"; + } else { + fixedObjectType = objectType; + } + StringBuilder sb = new StringBuilder(); + sb.append("BEGIN\n"); + sb.append(" ? := sys.dbms_metadata.get_ddl(\n"); + sb.append(" schema => ?,\n"); + sb.append(" object_type => ?,\n"); + sb.append(" name => ?\n"); + sb.append(" );\n"); + sb.append("END;"); + final String sql = sb.toString(); + return jdbcTemplate.execute(sql, (CallableStatementCallback) cs -> { + cs.registerOutParameter(1, Types.CLOB); + cs.setString(2, owner); + cs.setString(3, fixedObjectType); + cs.setString(4, objectName); + cs.execute(); + return cs.getString(1); + }); + } + + /** + * gets the object type of a database object + * + * The object types "PACKAGE BODY", "TYPE BODY" have higher priority. "PACKAGE" + * OR "TYPE" will be returned only when no body exists. + * + * @param owner + * owner of the object (schema) + * @param objectName + * name of the object + * @return the object type, e.g. PACKAGE BODY, TYPE BODY, PROCEDURE, FUNCTION + */ + public String getObjectType(final String owner, final String objectName) { + StringBuilder sb = new StringBuilder(); + sb.append("SELECT object_type\n"); + sb.append(" FROM (\n"); + sb.append(" SELECT object_type\n"); + sb.append(" FROM "); + sb.append(getDbaView("objects\n")); + sb.append(" WHERE owner = ?\n"); + sb.append(" AND object_name = ?\n"); + sb.append(" ORDER BY decode(object_type, 'PACKAGE', 10, 'TYPE', 10, 'SYNONYM', 20, 1)\n"); + sb.append(" )\n"); + sb.append(" WHERE rownum = 1"); + final String sql = sb.toString(); + final Object[] binds = new Object[] {owner, objectName}; + return jdbcTemplate.queryForObject(sql, String.class, binds); + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.xtend b/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.xtend deleted file mode 100644 index e03c5851..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/dal/UtplsqlDao.xtend +++ /dev/null @@ -1,1012 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package org.utplsql.sqldev.dal - -import java.sql.CallableStatement -import java.sql.Connection -import java.sql.SQLException -import java.sql.Types -import java.util.List -import java.util.regex.Pattern -import org.oddgen.sqldev.generators.model.Node -import org.springframework.dao.DataAccessException -import org.springframework.dao.EmptyResultDataAccessException -import org.springframework.jdbc.core.BeanPropertyRowMapper -import org.springframework.jdbc.core.CallableStatementCallback -import org.springframework.jdbc.core.JdbcTemplate -import org.springframework.jdbc.datasource.SingleConnectionDataSource -import org.utplsql.sqldev.model.ut.Annotation -import org.utplsql.sqldev.model.ut.OutputLines - -class UtplsqlDao { - public static val UTPLSQL_PACKAGE_NAME = "UT" - public static val NOT_INSTALLED = 0000000 - public static val FIRST_VERSION_WITH_INTERNAL_ANNOTATION_API = 3000004 - public static val FIRST_VERSION_WITH_ANNOTATION_API = 3001003 - public static val FIRST_VERSION_WITHOUT_INTERNAL_API = 3001008 - public static val FIRST_VERSION_WITH_HAS_SUITES_API = 3001008 - var Connection conn - var JdbcTemplate jdbcTemplate - // cache fields - Boolean cachedDbaViewAccessible - String cachedUtplsqlSchema - String cachedUtPlsqlVersion - - new(Connection connection) { - conn = connection - jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn, true)) - } - - /** - * used for testing purposes only - */ - def setUtPlsqlVersion(String utPlsqlVersion) { - cachedUtPlsqlVersion = utPlsqlVersion - } - - /** - * returns a normalized utPLSQL version in format 9.9.9 - */ - def String normalizedUtPlsqlVersion() { - val version = getUtPlsqlVersion() - if (version !== null) { - val p = Pattern.compile("(\\d+\\.\\d+\\.\\d+)") - val m = p.matcher(version) - if (m.find) { - return m.group(0) - } - } - return "0.0.0" - } - - /** - * get version as number, e.g. 3001004 - */ - def int normalizedUtPlsqlVersionNumber() { - val p = Pattern.compile("(\\d+)") - val version = normalizedUtPlsqlVersion() - val m = p.matcher(version) - m.find - val major = m.group - m.find - val minor = m.group - m.find - val bugfix = m.group - val versionNumber = Integer.valueOf(major)*1000000 + Integer.valueOf(minor)*1000 + Integer.valueOf(bugfix) - return versionNumber - } - - /** - * gets version of installed utPLSQL - */ - def String getUtPlsqlVersion() { - if (cachedUtPlsqlVersion === null) { - val sql = ''' - BEGIN - ? := ut.version; - END; - ''' - try { - cachedUtPlsqlVersion = jdbcTemplate.execute(sql, new CallableStatementCallback() { - override String doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { - cs.registerOutParameter(1, Types.VARCHAR); - cs.execute - val version = cs.getString(1) - return version - } - }) - } catch (SQLException e) { - // ignore error - } catch (DataAccessException e) { - // ignore error - } - } - return cachedUtPlsqlVersion - } - - def boolean isDbaViewAccessible() { - if (cachedDbaViewAccessible === null) { - try { - val sql = ''' - SELECT 1 - FROM dba_objects - WHERE 1=2 - ''' - jdbcTemplate.execute(sql) - cachedDbaViewAccessible = true - } catch (DataAccessException e) { - cachedDbaViewAccessible = false - } - } - return cachedDbaViewAccessible.booleanValue - } - - /** - * Gets the schema name of the utPLSQL installation. - * - * @return utPLSQL schema or null if no utPLSQL is not installed - * @throws DataAccessException if there is a problem - */ - def String getUtplsqlSchema() { - if (cachedUtplsqlSchema === null) { - val sql = ''' - SELECT table_owner - FROM «IF dbaViewAccessible»dba«ELSE»all«ENDIF»_synonyms - WHERE owner = 'PUBLIC' - AND synonym_name = '«UTPLSQL_PACKAGE_NAME»' - AND table_name = '«UTPLSQL_PACKAGE_NAME»' - ''' - try { - val schema = jdbcTemplate.queryForObject(sql, String) - cachedUtplsqlSchema = schema - } catch (EmptyResultDataAccessException e) { - cachedUtplsqlSchema = null - } - } - return cachedUtplsqlSchema - } - - /** - * Checks if the package ut_annotation_manager is installed. - * This package has been introduced with utPLSQL 3.0.4. - * This version is a prerequisite to identify - * utPLSQL unit test procedures. - * - * @return true if ut_annotation_manager package has been found - * @throws DataAccessException if there is a problem - */ - def boolean isUtAnnotationManagerInstalled() { - return normalizedUtPlsqlVersionNumber >= FIRST_VERSION_WITH_INTERNAL_ANNOTATION_API - } - - /** - * Checks if utPLSQL tests exist - * - * @param owner schema name, mandatory, case-insensitive - * @param objectName name of the package or package body, optional, case-insensitive - * @param subobjectName name of the procedure, optional, case-insensitive - * @return true if at least one test has been found - * @throws DataAccessException if a utPLSQL version less than 3.0.4 is installed or if there are other problems - */ - def boolean containsUtplsqlTest(String owner, String objectName, String subobjectName) { - try { - if (normalizedUtPlsqlVersionNumber >= org.utplsql.sqldev.dal.UtplsqlDao.FIRST_VERSION_WITH_HAS_SUITES_API && objectName !== null && subobjectName !== null) { - // use faster check function available since v3.1.3 (reliable in v3.1.8) - val sql = ''' - DECLARE - l_return VARCHAR2(1) := '0'; - BEGIN - IF ut_runner.is_test(?, ?, ?) THEN - l_return := '1'; - END IF; - ? := l_return; - END; - ''' - val ret = jdbcTemplate.execute(sql, new CallableStatementCallback() { - override Boolean doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { - cs.setString(1, owner) - cs.setString(2, objectName) - cs.setString(3, subobjectName) - cs.registerOutParameter(4, Types.VARCHAR); - cs.execute - val ret = cs.getString(4) - return ret == "1" - } - }) - return ret - } else if (normalizedUtPlsqlVersionNumber >= FIRST_VERSION_WITH_ANNOTATION_API) { - // using API available since 3.1.3, can handle nulls in objectName and subobjectName - val sql = ''' - SELECT count(*) - FROM TABLE(ut_runner.get_suites_info(upper(?), upper(?))) - WHERE item_type IN ('UT_TEST', 'UT_SUITE') - AND (item_name = upper(?) or ? IS NULL) - ''' - val found = jdbcTemplate.queryForObject(sql, Integer, #[owner, objectName, subobjectName, subobjectName]) - return found > 0 - } else { - // using internal API (deprecated) - val sql = ''' - SELECT count( - CASE - WHEN a.name = 'test' - AND (upper(a.subobject_name) = upper(?) OR ? IS NULL) - THEN - 1 - ELSE - NULL - END - ) - FROM TABLE(«utplsqlSchema».ut_annotation_manager.get_annotated_objects(upper(?), 'PACKAGE')) o - CROSS JOIN TABLE(o.annotations) a - WHERE (o.object_name = upper(?) OR ? IS NULL) - AND a.name IN ('test', 'suite') - HAVING count( - CASE - WHEN a.name = 'suite' THEN - 1 - ELSE - NULL - END - ) > 0 - ''' - val found = jdbcTemplate.queryForObject(sql, Integer, #[subobjectName, subobjectName, owner, objectName, objectName]) - return found > 0 - } - } catch (EmptyResultDataAccessException e) { - return false - } - } - - def boolean containsUtplsqlTest(String owner) { - if (normalizedUtPlsqlVersionNumber >= org.utplsql.sqldev.dal.UtplsqlDao.FIRST_VERSION_WITH_HAS_SUITES_API) { - // use faster check function available since v3.1.3 (reliable in v3.1.8) - val sql = ''' - DECLARE - l_return VARCHAR2(1) := '0'; - BEGIN - IF ut_runner.has_suites(?) THEN - l_return := '1'; - END IF; - ? := l_return; - END; - ''' - val ret = jdbcTemplate.execute(sql, new CallableStatementCallback() { - override Boolean doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { - cs.setString(1, owner) - cs.registerOutParameter(2, Types.VARCHAR); - cs.execute - val ret = cs.getString(2) - return ret == "1" - } - }) - return ret - } else { - return containsUtplsqlTest(owner, null, null) - } - } - - def boolean containsUtplsqlTest(String owner, String objectName) { - if (normalizedUtPlsqlVersionNumber >= org.utplsql.sqldev.dal.UtplsqlDao.FIRST_VERSION_WITH_HAS_SUITES_API) { - // use faster check function available since v3.1.3 (reliable in v3.1.8) - val sql = ''' - DECLARE - l_return VARCHAR2(1) := '0'; - BEGIN - IF ut_runner.is_suite(?, ?) THEN - l_return := '1'; - END IF; - ? := l_return; - END; - ''' - val ret = jdbcTemplate.execute(sql, new CallableStatementCallback() { - override Boolean doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { - cs.setString(1, owner) - cs.setString(2, objectName) - cs.registerOutParameter(3, Types.VARCHAR); - cs.execute - val ret = cs.getString(3) - return ret == "1" - } - }) - return ret - } else { - return containsUtplsqlTest(owner, objectName, null) - } - } - - /** - * Gets a list of utPLSQL annotations for a given PL/SQL package specification - * - * @param owner schema name, mandatory, case-insensitive - * @param objectName name of the package or package body, optional, case-insensitive - * @return list of Annotation with name 'suite' or 'test' - * @throws DataAccessException if a utPLSQL version less than 3.0.4 is installed or if there are other problems - */ - def List annotations(String owner, String objectName) { - var String sql - if (normalizedUtPlsqlVersionNumber >= FIRST_VERSION_WITH_ANNOTATION_API) { - // using API available since 3.1.3 - sql = ''' - SELECT object_owner, - object_name, - lower(substr(item_type, 4)) AS name, - item_name as subobject_name - FROM TABLE(ut_runner.get_suites_info(upper(?), upper(?))) - ''' - - } else { - // using internal API (deprecated) - sql = ''' - SELECT o.object_owner, - o.object_name, - a.name, - a.text, - coalesce(upper(a.subobject_name), o.object_name) AS subobject_name - FROM TABLE(«utplsqlSchema».ut_annotation_manager.get_annotated_objects(upper(?), 'PACKAGE')) o - CROSS JOIN TABLE(o.annotations) a - WHERE o.object_name = upper(?) - ''' - } - val result = jdbcTemplate.query(sql, new BeanPropertyRowMapper(Annotation), #[owner, objectName]) - return result - } - - /** - * Gets a list of public units in the object type - * - * @param objectType expected object types are PACKAGE, TYPE, FUNCTION, PROCEDURE - * @param objectName name of the object - * @return list of the public units in the object type - * @throws DataAccessException if there is a problem - */ - def List units(String objectType, String objectName) { - if (objectType == "PACKAGE" || objectType == "TYPE") { - val sql = ''' - SELECT procedure_name - FROM user_procedures - WHERE object_type = ? - AND object_name = ? - AND procedure_name IS NOT NULL - GROUP BY procedure_name - ORDER BY min(subprogram_id) - ''' - val result = jdbcTemplate.queryForList(sql, String, #[objectType, objectName]) - return result - } else { - return #[objectName] - } - } - - /** - * Gets a list of oddgen's nodes as candidates to create utPLSQL test packages. - * Candidates are packages, types, functions and procedures in the current user. - * - * This functions must be called from an oddgen generator only, since the Node is not - * defined in the utPLSQL extension. - * - * @param objectType expected object types are PACKAGE, TYPE, FUNCTION, PROCEDURE - * @return list of the oddgen nodes for the requested object type - * @throws DataAccessException if there is a problem - */ - def List testables(String objectType) { - var String sql; - if (objectType == "PACKAGE") { - if (normalizedUtPlsqlVersionNumber >= FIRST_VERSION_WITH_ANNOTATION_API) { - // using API available since 3.1.3 - sql = ''' - SELECT DISTINCT - object_type || '.' || object_name AS id, - object_type AS parent_id, - 1 AS leaf, - 1 AS generatable, - 1 AS multiselectable - FROM user_procedures - WHERE object_type = ? - AND procedure_name IS NOT NULL - AND object_name NOT IN ( - SELECT object_name - FROM TABLE(ut_runner.get_suites_info(USER)) - WHERE item_type = 'UT_SUITE' - ) - ''' - } else { - // using internal API (deprecated) - sql = ''' - SELECT DISTINCT - object_type || '.' || object_name AS id, - object_type AS parent_id, - 1 AS leaf, - 1 AS generatable, - 1 AS multiselectable - FROM user_procedures - WHERE object_type = ? - AND procedure_name IS NOT NULL - AND object_name NOT IN ( - SELECT object_name - FROM TABLE(«utplsqlSchema».ut_annotation_manager.get_annotated_objects(USER, 'PACKAGE')) - ) - ''' - } - } - else if (objectType == "TYPE") { - sql = ''' - SELECT DISTINCT - object_type || '.' || object_name AS id, - object_type AS parent_id, - 1 AS leaf, - 1 AS generatable, - 1 AS multiselectable - FROM user_procedures - WHERE object_type = ? - AND procedure_name IS NOT NULL - ''' - } - else { - sql = ''' - SELECT object_type || '.' || object_name AS id, - object_type AS parent_id, - 1 AS leaf, - 1 AS generatable, - 1 AS multiselectable - FROM user_objects - WHERE object_type = ? - AND generated = 'N' - ''' - } - val nodes = jdbcTemplate.query(sql, new BeanPropertyRowMapper(Node), #[objectType]) - return nodes - } - - /** - * Gets a list of oddgen's nodes as candidates to run utPLSQL tests. - * - * This functions must be called from an oddgen generator only, since the Node is not - * defined in the utPLSQL extension. - * - * @return list of oddgen nodes (complete hierarchy loaded eagerly) - * @throws DataAccessException if there is a problem - */ - def List runnables() { - var String sql - if (normalizedUtPlsqlVersionNumber >= FIRST_VERSION_WITH_ANNOTATION_API) { - // using API available since 3.1.3 - sql = ''' - WITH - test AS ( - SELECT object_owner, - object_name, - path AS suitepath, - count( - CASE - WHEN item_type = 'UT_TEST' THEN - 1 - ELSE - NULL - END - ) over (partition by object_owner, object_name) AS test_count, - item_type, - item_name, - item_description - FROM TABLE(ut_runner.get_suites_info(user)) - ), - suite_tree AS ( - SELECT null AS parent_id, - 'SUITE' AS id, - 'All Suites' AS name, - 'All utPLSQL test suites' AS description, - 'PACKAGE_FOLDER_ICON' AS iconName, - 'No' AS leaf, - 'Yes' AS generatable, - 'Yes' AS multiselectable, - 'Yes' AS relevant - FROM dual - UNION ALL - SELECT DISTINCT - 'SUITE' AS parent_id, - object_owner || '.' || object_name AS id, - object_name AS name, - null AS description, - 'PACKAGE_ICON' AS iconName, - 'No' AS leaf, - 'Yes' AS generatable, - 'Yes' AS multiselectable, - 'Yes' AS relevant - FROM test - WHERE item_type IN ('UT_TEST', 'UT_SUITE') - UNION ALL - SELECT object_owner || '.' || object_name AS parent_id, - object_owner || '.' || object_name || '.' || item_name AS id, - item_name AS name, - item_description AS description, - 'PROCEDURE_ICON' AS iconName, - 'Yes' AS leaf, - 'Yes' AS generatable, - 'Yes' AS multiselectable, - 'Yes' AS relevant - FROM test - WHERE item_type = 'UT_TEST' - ), - suitepath_tree AS ( - SELECT NULL AS parent_id, - 'SUITEPATH' AS id, - 'All Suitepaths' AS name, - 'All utPLSQL test suitepathes' AS description, - 'FOLDER_ICON' AS iconName, - 'No' AS leaf, - 'Yes' AS generatable, - 'Yes' AS multiselectable, - 'Yes' AS relevant - FROM dual - UNION ALL - SELECT CASE - WHEN regexp_replace(suitepath,'\.?\w+$','') IS NULL THEN - 'SUITEPATH' - ELSE - object_owner || ':' || regexp_replace(suitepath,'\.?\w+$','') - END AS parent_id, - object_owner || ':' || suitepath AS id, - item_name AS name, - item_description AS description, - CASE - WHEN item_type = 'UT_SUITE' AND test_count > 0 THEN - 'PACKAGE_ICON' - WHEN item_type = 'UT_TEST' THEN - 'PROCEDURE_ICON' - ELSE - 'FOLDER_ICON' - END AS iconName, - CASE item_type - WHEN 'UT_TEST' THEN - 'Yes' - ELSE - 'No' - END AS leaf, - 'Yes' AS generatable, - 'Yes' AS multiselectable, - 'Yes' AS relevant - FROM test - ), - tree AS ( - SELECT parent_id, id, name, description, iconName, leaf, generatable, multiselectable, relevant - FROM suite_tree - UNION ALL - SELECT parent_id, id, name, description, iconName, leaf, generatable, multiselectable, relevant - FROM suitepath_tree - ) - SELECT parent_id, id, initcap(name) AS name, description, iconName, leaf, generatable, multiselectable, relevant - FROM tree - ''' - } else { - // using internal API (deprecated) - sql = ''' - WITH - base AS ( - SELECT rownum AS an_id, - o.object_owner, - o.object_type, - o.object_name, - lower(a.name) AS name, - a.text, - a.subobject_name - FROM table(«utplsqlSchema».ut_annotation_manager.get_annotated_objects(user, 'PACKAGE')) o - CROSS JOIN table(o.annotations) a - WHERE lower(a.name) in ('suite', 'suitepath', 'endcontext', 'test') - OR lower(a.name) = 'context' AND regexp_like(text, '(\w+)(\.\w+)*') - ), - suite AS ( - SELECT object_owner, object_type, object_name, text AS suite_description - FROM base - WHERE name = 'suite' - ), - suitepath as ( - SELECT object_owner, object_type, object_name, lower(text) AS suitepath - FROM base - WHERE name = 'suitepath' - ), - context_base AS ( - SELECT an_id, - lead(an_id) over (partition by object_owner, object_type, object_name order by an_id) AS an_id_end, - object_owner, - object_type, - object_name, - name, - lead(name) over (partition by object_owner, object_type, object_name order by an_id) AS name_end, - text as context - FROM base - WHERE name IN ('context', 'endcontext') - ), - context as ( - SELECT an_id, an_id_end, object_owner, object_type, object_name, context - FROM context_base - WHERE name = 'context' - AND name_end = 'endcontext' - ), - test AS ( - SELECT b.an_id, - b.object_owner, - b.object_type, - b.object_name, - p.suitepath, - c.context, - b.subobject_name, - b.text AS test_description - FROM base b - LEFT JOIN suitepath p - ON p.object_owner = b.object_owner - AND p.object_type = b.object_type - AND p.object_name = b.object_name - LEFT JOIN context c - ON c.object_owner = b.object_owner - AND c.object_type = b.object_type - AND c.object_name = b.object_name - AND b.an_id BETWEEN c.an_id AND c.an_id_end - WHERE name = 'test' - AND (b.object_owner, b.object_type, b.object_name) IN ( - SELECT object_owner, object_type, object_name - FROM suite - ) - ), - suite_tree AS ( - SELECT null AS parent_id, - 'SUITE' AS id, - 'All Suites' AS name, - 'All utPLSQL test suites' AS description, - 'PACKAGE_FOLDER_ICON' AS iconName, - 'No' AS leaf, - 'Yes' AS generatable, - 'Yes' AS multiselectable, - 'Yes' AS relevant - FROM dual - UNION ALL - SELECT DISTINCT - 'SUITE' AS parent_id, - object_owner || '.' || object_name AS id, - object_name AS name, - null AS description, - 'PACKAGE_ICON' AS iconName, - 'No' AS leaf, - 'Yes' AS generatable, - 'Yes' AS multiselectable, - 'Yes' AS relevant - FROM test - UNION ALL - SELECT object_owner || '.' || object_name AS parent_id, - object_owner || '.' || object_name || '.' || upper(subobject_name) AS id, - subobject_name AS name, - null AS description, - 'PROCEDURE_ICON' AS iconName, - 'Yes' AS leaf, - 'Yes' AS generatable, - 'Yes' AS multiselectable, - 'Yes' AS relevant - FROM test - ), - suitepath_base AS ( - SELECT DISTINCT - suitepath - FROM suitepath - ), - gen AS ( - SELECT rownum AS pos - FROM xmltable('1 to 100') - ), - suitepath_part AS ( - SELECT DISTINCT - lower(substr(suitepath, 1, instr(suitepath || '.', '.', 1, g.pos) -1)) AS suitepath - FROM suitepath_base b - JOIN gen g - ON g.pos <= regexp_count(suitepath, '\w+') - ), - suitepath_tree AS ( - SELECT NULL AS parent_id, - 'SUITEPATH' AS id, - 'All Suitepaths' AS name, - 'All utPLSQL test suitepathes' AS description, - 'FOLDER_ICON' AS iconName, - 'No' AS leaf, - 'Yes' AS generatable, - 'Yes' AS multiselectable, - 'Yes' AS relevant - FROM dual - UNION ALL - SELECT CASE - WHEN regexp_replace(suitepath,'\.?\w+$','') IS NULL THEN - 'SUITEPATH' - ELSE - USER || ':' || regexp_replace(suitepath,'\.?\w+$','') - END AS parent_id, - USER || ':' || suitepath AS id, - regexp_substr(suitepath, '\.?(\w+$)', 1, 1, NULL, 1) AS name, - null AS description, - 'FOLDER_ICON' AS iconName, - 'No' AS leaf, - 'Yes' AS generatable, - 'Yes' AS multiselectable, - 'Yes' AS relevant - FROM suitepath_part - UNION ALL - SELECT DISTINCT - object_owner || ':' || suitepath AS parent_id, - object_owner || ':' || suitepath || '.' || lower(object_name) AS id, - object_name AS name, - null AS description, - 'PACKAGE_ICON' AS iconName, - 'No' AS leaf, - 'Yes' AS generatable, - 'Yes' AS multiselectable, - 'Yes' AS relevant - FROM test - WHERE suitepath IS NOT NULL - UNION ALL - SELECT DISTINCT - object_owner || ':' || suitepath || '.' || lower(object_name) AS parent_id, - object_owner || ':' || suitepath || '.' || lower(object_name) || '.' || context AS id, - context AS name, - null AS description, - 'FOLDER_ICON' AS iconName, - 'No' AS leaf, - 'Yes' AS generatable, - 'Yes' AS multiselectable, - 'Yes' AS relevant - FROM test - WHERE suitepath IS NOT NULL - AND context IS NOT NULL - UNION ALL - SELECT object_owner || ':' || suitepath || '.' || lower(object_name) || CASE WHEN context IS NOT NULL THEN '.' || context END AS parent_id, - object_owner || ':' || suitepath || '.' || lower(object_name) || CASE WHEN context IS NOT NULL THEN '.' || context END || '.' || lower(subobject_name) AS id, - subobject_name AS name, - null AS description, - 'PROCEDURE_ICON' AS iconName, - 'Yes' AS leaf, - 'Yes' AS generatable, - 'Yes' AS multiselectable, - 'Yes' AS relevant - FROM test - WHERE suitepath IS NOT NULL - ), - tree AS ( - SELECT parent_id, id, name, description, iconName, leaf, generatable, multiselectable, relevant - FROM suite_tree - UNION ALL - SELECT parent_id, id, name, description, iconName, leaf, generatable, multiselectable, relevant - FROM suitepath_tree - ) - SELECT parent_id, id, initcap(name) AS name, description, iconName, leaf, generatable, multiselectable, relevant - FROM tree - ''' - } - val nodes = jdbcTemplate.query(sql, new BeanPropertyRowMapper(Node)) - return nodes - } - - /** - * enable DBMS_OUTPUT - * - * @throws DataAccessException if there is a problem - */ - def void enableDbmsOutput() { - // equivalent to "set serveroutput on size unlimited" - jdbcTemplate.update(''' - BEGIN - sys.dbms_output.enable(NULL); - END; - ''') - } - - /** - * disable DBMS_OUTPUT - * - * @throws DataAccessException if there is a problem - */ - def void disableDbmsOutput() { - jdbcTemplate.update(''' - BEGIN - sys.dbms_output.disable; - END; - ''') - } - - /** - * return the content of DBMS_OUTPUT as String - * - * @throws DataAccessException if there is a problem - */ - def String getDbmsOutput() { - return getDbmsOutput(1000) - } - - /** - * return the content of DBMS_OUTPUT as String - - * @param bufferSize maximum number of rows to be read from the DBMS_OUTPUT buffer in one network round trip - * @return content of DBMS_OUTPUT as String - * @throws DataAccessException if there is a problem - */ - def String getDbmsOutput(int bufferSize) { - val sb = new StringBuffer - val sql = ''' - BEGIN - sys.dbms_output.get_lines(?, ?); - END; - ''' - var OutputLines ret - do { - ret = jdbcTemplate.execute(sql, new CallableStatementCallback() { - override OutputLines doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { - cs.registerOutParameter(1, Types.ARRAY, "DBMSOUTPUT_LINESARRAY"); - cs.registerOutParameter(2, Types.INTEGER) - cs.setInt(2, bufferSize) - cs.execute - val out = new OutputLines - out.lines = cs.getArray(1).array as String[] - out.numlines = cs.getInt(2) - return out - } - }) - for (i : 0 ..< ret.numlines) { - val line = ret.lines.get(i) - if (line !== null) { - sb.append(ret.lines.get(i)) - } - sb.append(System.lineSeparator) - } - } while (ret.numlines > 0) - return sb.toString - } - - /** - * gets the HTML code coverage report as String - * - * @param pathList utPLSQL path list - * @param schemaList list of schemas under tests. Current schema, if empty - * @param includeObjectList list of objects to be included for coverage analysis. All, if empty - * @param excludeObjectList list of objects to be excluded from coverage analysis. None, if empty - * @return HTML code coverage report in HTML format - * @throws DataAccessException if there is a problem - */ - def String htmlCodeCoverage(List pathList, List schemaList, List includeObjectList, List excludeObjectList) { - val sql = ''' - SELECT column_value - FROM table( - ut.run( - a_paths => ut_varchar2_list( - «FOR path : pathList SEPARATOR ", "» - '«path»' - «ENDFOR» - ), - «IF schemaList.size > 0» - a_coverage_schemes => ut_varchar2_list( - «FOR schema : schemaList SEPARATOR ", "» - '«schema»' - «ENDFOR» - ), - «ENDIF» - «IF includeObjectList.size > 0» - a_include_objects => ut_varchar2_list( - «FOR includeObject : includeObjectList SEPARATOR ", "» - '«includeObject»' - «ENDFOR» - ), - «ENDIF» - «IF excludeObjectList.size > 0» - a_exclude_objects => ut_varchar2_list( - «FOR excludeObject : excludeObjectList SEPARATOR ", "» - '«excludeObject»' - «ENDFOR» - ), - «ENDIF» - a_reporter => ut_coverage_html_reporter() - ) - ) - ''' - val lines = jdbcTemplate.queryForList(sql, String) - val sb = new StringBuilder - for (line : lines.filter[it !== null]) { - sb.append(line) - sb.append("\n") - } - return sb.toString - } - - /** - * gets dependencies of a given object. - * - * The result can be used as input for the includeObjectList in htmlCodeCoverage - * The scope is reduced to non-oracle maintained schemas. - * - * Oracle introduced the column ORACLE_MAINTAINED in 12.1. - * To simplify the query and compatibility the result of the following - * query is included - * - * SELECT '''' || listagg(username, ''', ''') || '''' AS oracle_maintained_users - * FROM dba_users - * WHERE oracle_maintained = 'Y' - * ORDER BY username; - * - * The result may include test packages - * - * @param name test package name - * @return list of dependencies in the current schema - */ - def List includes(String owner, String name) { - val sql = ''' - select referenced_owner || '.' || referenced_name AS dep_name - from «IF dbaViewAccessible»dba«ELSE»all«ENDIF»_dependencies - WHERE owner = upper(?) - AND name = upper(?) - AND referenced_owner NOT IN ( - 'SYS', 'SYSTEM', 'XS$NULL', 'OJVMSYS', 'LBACSYS', 'OUTLN', 'SYS$UMF', - 'DBSNMP', 'APPQOSSYS', 'DBSFWUSER', 'GGSYS', 'ANONYMOUS', 'CTXSYS', - 'SI_INFORMTN_SCHEMA', 'DVF', 'DVSYS', 'GSMADMIN_INTERNAL', 'ORDPLUGINS', - 'MDSYS', 'OLAPSYS', 'ORDDATA', 'XDB', 'WMSYS', 'ORDSYS', 'GSMCATUSER', - 'MDDATA', 'REMOTE_SCHEDULER_AGENT', 'SYSBACKUP', 'GSMUSER', 'APEX_PUBLIC_USER', - 'SYSRAC', 'AUDSYS', 'DIP', 'SYSKM', 'ORACLE_OCM', 'APEX_INSTANCE_ADMIN_USER', - 'SYSDG', 'FLOWS_FILES', 'ORDS_METADATA', 'ORDS_PUBLIC_USER' - ) - AND referenced_owner NOT LIKE 'APEX\_______' - AND referenced_type IN ('PACKAGE', 'TYPE', 'PROCEDURE', 'FUNCTION', 'TRIGGER') - ''' - val deps = jdbcTemplate.queryForList(sql, String, #[owner, name]) - return deps - } - - /** - * gets source of an object from the database via DBMS_METADATA - * - * @param owner owner of the object (schema) - * @param objectType expected object types are PACKAGE, PACKAGE BODY - * @param objectName name of the object - * @return the source code of the object - * @throws DataAccessException if there is a problem - */ - def getSource(String owner, String objectType, String objectName) { - // dbms_metadata uses slightly different objectTypes - val fixedObjectType = if (objectType == "PACKAGE") { - "PACKAGE_SPEC" - } else if (objectType == "PACKAGE BODY") { - "PACKAGE_BODY" - } else { - objectType - } - val sql = ''' - BEGIN - ? := sys.dbms_metadata.get_ddl( - schema => ?, - object_type => ?, - name => ? - ); - END; - ''' - val ret = jdbcTemplate.execute(sql, new CallableStatementCallback() { - override String doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { - cs.registerOutParameter(1, Types.CLOB); - cs.setString(2, owner) - cs.setString(3, fixedObjectType) - cs.setString(4, objectName) - cs.execute - return cs.getString(1) - } - }) - return ret - } - - /** - * gets the object type of a database object - * - * The object types "PACKAGE BODY", "TYPE BODY" have higher priority. - * "PACKAGE" OR "TYPE" will be returned only when no body exists. - * - * @param owner owner of the object (schema) - * @param objectName name of the object - * @return the object type, e.g. PACKAGE BODY, TYPE BODY, PROCEDURE, FUNCTION - */ - def getObjectType(String owner, String objectName) { - val sql = ''' - SELECT object_type - FROM ( - SELECT object_type - FROM «IF dbaViewAccessible»dba«ELSE»all«ENDIF»_objects - WHERE owner = ? - AND object_name = ? - ORDER BY decode(object_type, 'PACKAGE', 10, 'TYPE', 10, 'SYNONYM', 20, 1) - ) - WHERE rownum = 1 - ''' - val objectType = jdbcTemplate.queryForObject(sql, #[owner, objectName], String) - return objectType - } -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/exception/GenericDatabaseAccessException.java b/sqldev/src/main/java/org/utplsql/sqldev/exception/GenericDatabaseAccessException.java new file mode 100644 index 00000000..a343592b --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/exception/GenericDatabaseAccessException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.exception; + +public class GenericDatabaseAccessException extends RuntimeException { + private static final long serialVersionUID = -5489500390596695295L; + + public GenericDatabaseAccessException(String message) { + super(message); + } + + public GenericDatabaseAccessException(String message, Throwable cause) { + super(message, cause); + } + + public GenericDatabaseAccessException(Throwable cause) { + super(cause); + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RealtimeReporterEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/exception/GenericRuntimeException.java similarity index 53% rename from sqldev/src/main/java/org/utplsql/sqldev/model/runner/RealtimeReporterEvent.xtend rename to sqldev/src/main/java/org/utplsql/sqldev/exception/GenericRuntimeException.java index 5b6ec099..5a89ad13 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RealtimeReporterEvent.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/exception/GenericRuntimeException.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Philipp Salvisberg + * Copyright 2020 Philipp Salvisberg * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev.model.runner +package org.utplsql.sqldev.exception; -import org.utplsql.sqldev.model.AbstractModel +public class GenericRuntimeException extends RuntimeException { + private static final long serialVersionUID = -6258053040039956647L; -abstract class RealtimeReporterEvent extends AbstractModel { + public GenericRuntimeException(String message) { + super(message); + } + + public GenericRuntimeException(String message, Throwable cause) { + super(message, cause); + } + + public GenericRuntimeException(Throwable cause) { + super(cause); + } } diff --git a/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.java b/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.java new file mode 100644 index 00000000..505e53ca --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.java @@ -0,0 +1,545 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.menu; + +import java.awt.Component; +import java.net.URL; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javax.swing.JEditorPane; + +import org.utplsql.sqldev.coverage.CodeCoverageReporter; +import org.utplsql.sqldev.dal.RealtimeReporterDao; +import org.utplsql.sqldev.dal.UtplsqlDao; +import org.utplsql.sqldev.exception.GenericRuntimeException; +import org.utplsql.sqldev.model.DatabaseTools; +import org.utplsql.sqldev.model.StringTools; +import org.utplsql.sqldev.model.URLTools; +import org.utplsql.sqldev.model.oddgen.GenContext; +import org.utplsql.sqldev.model.parser.PlsqlObject; +import org.utplsql.sqldev.model.preference.PreferenceModel; +import org.utplsql.sqldev.oddgen.TestTemplate; +import org.utplsql.sqldev.parser.UtplsqlParser; +import org.utplsql.sqldev.runner.UtplsqlRunner; +import org.utplsql.sqldev.runner.UtplsqlWorksheetRunner; + +import oracle.dbtools.raptor.navigator.db.DBNavigatorWindow; +import oracle.dbtools.raptor.navigator.db.DatabaseConnection; +import oracle.dbtools.raptor.navigator.impl.ChildObjectElement; +import oracle.dbtools.raptor.navigator.impl.DatabaseSourceNode; +import oracle.dbtools.raptor.navigator.impl.ObjectFolder; +import oracle.dbtools.raptor.navigator.impl.SchemaFolder; +import oracle.dbtools.raptor.navigator.plsql.PlSqlNode; +import oracle.dbtools.raptor.schemabrowser.view.SBWindow; +import oracle.dbtools.raptor.utils.Connections; +import oracle.dbtools.worksheet.editor.Worksheet; +import oracle.ide.Context; +import oracle.ide.Ide; +import oracle.ide.config.Preferences; +import oracle.ide.controller.Controller; +import oracle.ide.controller.IdeAction; +import oracle.ide.editor.Editor; +import oracle.ide.model.Node; +import oracle.ide.view.View; + +@SuppressWarnings("all") +public class UtplsqlController implements Controller { + private static final Logger logger = Logger.getLogger(UtplsqlController.class.getName()); + + public static int UTPLSQL_TEST_CMD_ID = (Ide.findCmdID("utplsql.test")).intValue(); + public static int UTPLSQL_COVERAGE_CMD_ID = (Ide.findCmdID("utplsql.coverage")).intValue(); + public static int UTPLSQL_DEBUG_CMD_ID = (Ide.findCmdID("utplsql.debug")).intValue(); + public static int UTPLSQL_GENERATE_CMD_ID = (Ide.findCmdID("utplsql.generate")).intValue(); + public static final IdeAction UTPLSQL_TEST_ACTION = IdeAction.get(UTPLSQL_TEST_CMD_ID); + public static final IdeAction UTPLSQL_COVERAGE_ACTION = IdeAction.get(UTPLSQL_COVERAGE_CMD_ID); + public static final IdeAction UTPLSQL_DEBUG_ACTION = IdeAction.get(UTPLSQL_DEBUG_CMD_ID); + public static final IdeAction UTPLSQL_GENERATE_ACTION = IdeAction.get(UTPLSQL_GENERATE_CMD_ID); + + @Override + public boolean handleEvent(final IdeAction action, final Context context) { + try { + if (action.getCommandId() == UTPLSQL_TEST_CMD_ID) { + logger.finer(() -> "handle utplsql.test"); + runTest(context, false); + return true; + } else if (action.getCommandId() == UTPLSQL_COVERAGE_CMD_ID) { + logger.finer(() -> "handle utplsql.coverage"); + codeCoverage(context); + return true; + } else if (action.getCommandId() == UTPLSQL_DEBUG_CMD_ID) { + logger.finer(() -> "handle utplsql.debug"); + runTest(context, true); + return true; + } else if (action.getCommandId() == UTPLSQL_GENERATE_CMD_ID) { + logger.finer(() -> "handle utplsql.generate"); + generateTest(context); + return true; + } + } catch (Exception e) { + final String msg = "Failed to handle event for action " + action.toString() + "."; + logger.severe(() -> msg); + throw new GenericRuntimeException(msg, e); + } + return false; + } + + @Override + public boolean update(final IdeAction action, final Context context) { + if (action.getCommandId() == UTPLSQL_TEST_CMD_ID || action.getCommandId() == UTPLSQL_COVERAGE_CMD_ID || + action.getCommandId() == UTPLSQL_DEBUG_CMD_ID) { + final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + action.setEnabled(false); + final View view = context.getView(); + if (view instanceof Editor) { + final Component component = ((Editor) view).getDefaultFocusComponent(); + if (component instanceof JEditorPane) { + if (preferences.isCheckRunUtplsqlTest()) { + final Node node = context.getNode(); + String connectionName = null; + String owner = null; + if (node instanceof DatabaseSourceNode) { + connectionName = ((DatabaseSourceNode) node).getConnectionName(); + owner = ((DatabaseSourceNode) node).getOwner(); + } else { + if (view instanceof Worksheet) { + connectionName = ((Worksheet) view).getConnectionName(); + } + } + logger.fine("connectionName: " + connectionName); + final String text = ((JEditorPane) component).getText(); + final Connection conn = DatabaseTools.getConnection(connectionName); + final UtplsqlParser parser = new UtplsqlParser(text, conn, owner); + if (!parser.getPathAt(((JEditorPane) component).getCaretPosition()).isEmpty()) { + action.setEnabled(true); + } + } else { + action.setEnabled(true); + } + } + } else if (view instanceof DBNavigatorWindow || view instanceof SBWindow) { + action.setEnabled(true); + // disable action if a node in the selection is not runnable + for (int i = 0; i < context.getSelection().length; i++) { + logger.fine("section " + i + " is " + context.getSelection()[i].toString() + " of class " + + context.getSelection()[i].getClass().getName()); + if (action.isEnabled()) { + final Object element = context.getSelection()[i]; + final String connectionName = URLTools.getConnectionName(getURL(context)); + if (Connections.getInstance().isConnectionOpen(connectionName)) { + Connection conn = DatabaseTools.getConnection(connectionName); + final UtplsqlDao dao = new UtplsqlDao(conn); + if (preferences.isCheckRunUtplsqlTest() && dao.isUtAnnotationManagerInstalled()) { + if (element instanceof DatabaseConnection) { + final String schema = DatabaseTools.getSchema((DatabaseConnection) element); + action.setEnabled(dao.containsUtplsqlTest(schema)); + } else if (element instanceof SchemaFolder) { + final String schema = ((SchemaFolder) element).getSchemaName(); + action.setEnabled(dao.containsUtplsqlTest(schema)); + } else if (element instanceof ObjectFolder) { + final String schema = URLTools.getSchema(((ObjectFolder) element).getURL()); + action.setEnabled(dao.containsUtplsqlTest(schema)); + } else if (element instanceof PlSqlNode) { + final String schema = ((PlSqlNode) element).getOwner(); + final String objectName = ((PlSqlNode) element).getObjectName(); + action.setEnabled(dao.containsUtplsqlTest(schema, objectName)); + } else if (element instanceof ChildObjectElement) { + final String schema = URLTools.getSchema(((ChildObjectElement) element).getURL()); + final String objectName = URLTools.getMemberObject(((ChildObjectElement) element).getURL()); + final String subObjectName = ((ChildObjectElement) element).getShortLabel(); + action.setEnabled(dao.containsUtplsqlTest(schema, objectName, subObjectName)); + } + } + } else { + action.setEnabled(false); + } + } + } + } + return true; + } else if (action.getCommandId() == UTPLSQL_GENERATE_CMD_ID) { + action.setEnabled(false); + // enable if generation is possible + final View view = context.getView(); + if (view instanceof Editor) { + final Component component = ((Editor) view).getDefaultFocusComponent(); + if (component instanceof JEditorPane) { + final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + if (preferences.isCheckGenerateUtplsqlTest()) { + final String text = ((JEditorPane) component).getText(); + final UtplsqlParser parser = new UtplsqlParser(text); + PlsqlObject plsqlObject = parser.getObjectAt(((JEditorPane) component).getCaretPosition()); + action.setEnabled(plsqlObject != null); + } else { + action.setEnabled(true); + } + } + } else if (view instanceof DBNavigatorWindow || view instanceof SBWindow) { + // multiselection is not supported, use oddgen to generte tests for multiple objects + if (context.getSelection().length == 1) { + final Object element = context.getSelection()[0]; + if (element instanceof PlSqlNode) { + final String ot = ((PlSqlNode) element).getObjectType(); + if (ot.startsWith("PACKAGE") || ot.startsWith("TYPE") || "FUNCTION".equals(ot) || "PROCEDURE".equals(ot)) { + action.setEnabled(true); + } + } + } + } + } + return false; + } + + private String getPath(final Object element) { + String path = null; + if (element instanceof DatabaseConnection) { + path = DatabaseTools.getSchema((DatabaseConnection) element); + } else if (element instanceof SchemaFolder) { + path = ((SchemaFolder) element).getSchemaName(); + } else if (element instanceof ObjectFolder) { + path = URLTools.getSchema(((ObjectFolder) element).getURL()); + } else if (element instanceof PlSqlNode) { + final StringBuilder sb = new StringBuilder(); + sb.append(((PlSqlNode) element).getOwner()); + sb.append("."); + sb.append(((PlSqlNode) element).getObjectName()); + path = sb.toString(); + } else if (element instanceof ChildObjectElement) { + StringBuilder sb = new StringBuilder(); + sb.append(URLTools.getSchema(((ChildObjectElement) element).getURL())); + sb.append("."); + sb.append(URLTools.getMemberObject(((ChildObjectElement) element).getURL())); + sb.append("."); + sb.append( ((ChildObjectElement) element).getShortLabel()); + path = sb.toString(); + } else { + path = ""; + } + logger.fine("path: " + path); + return path; + } + + private ArrayList getPathList(final Context context) { + final ArrayList pathList = new ArrayList<>(); + for (int i = 0; i < context.getSelection().length; i++) { + final Object element = context.getSelection()[i]; + pathList.add(getPath(element)); + } + return pathList; + } + + private ArrayList getPathList(final String path) { + final ArrayList pathList = new ArrayList<>(); + pathList.add(path); + return pathList; + } + + private ArrayList dedupPathList(final List pathList) { + final HashSet set = new HashSet<>(); + for (final String path : pathList) { + set.add(path); + } + final ArrayList ret = new ArrayList<>(); + final Pattern p = Pattern.compile("(((([^\\.]+)\\.)?[^\\.]+)\\.)?[^\\.]+"); + for (final String path : set) { + final Matcher m = p.matcher(path); + if (m.matches()) { + final String parent1 = m.group(4); // user + final String parent2 = m.group(2); // user.package + if (parent1 == null || !set.contains(parent1)) { + if (parent2 == null || !set.contains(parent2)) { + ret.add(path); + } + } + } else { + logger.severe("path: " + path + " did not match " + p.toString() + ", this is unexected!"); + } + } + return ret; + } + + private URL getURL(final Context context) { + URL url = null; + final Object element = context.getSelection()[0]; + if (element instanceof DatabaseConnection) { + url = ((DatabaseConnection) element).getURL(); + } else if (element instanceof SchemaFolder) { + url = ((SchemaFolder) element).getURL(); + } else if (element instanceof ObjectFolder) { + url = ((ObjectFolder) element).getURL(); + } else if (element instanceof PlSqlNode) { + url = ((PlSqlNode) element).getURL(); + } else if (element instanceof ChildObjectElement) { + url = ((ChildObjectElement) element).getURL(); + } + logger.fine("url: " + url); + return url; + } + + private void populateGenContext(final GenContext genContext, final PreferenceModel preferences) { + genContext.setTestPackagePrefix(preferences.getTestPackagePrefix().toLowerCase()); + genContext.setTestPackageSuffix(preferences.getTestPackageSuffix().toLowerCase()); + genContext.setTestUnitPrefix(preferences.getTestUnitPrefix().toLowerCase()); + genContext.setTestUnitSuffix(preferences.getTestUnitSuffix().toLowerCase()); + genContext.setNumberOfTestsPerUnit(preferences.getNumberOfTestsPerUnit()); + genContext.setGenerateComments(preferences.isGenerateComments()); + genContext.setDisableTests(preferences.isDisableTests()); + genContext.setSuitePath(preferences.getSuitePath().toLowerCase()); + genContext.setIndentSpaces(preferences.getIndentSpaces()); + } + + private GenContext getGenContext(final Context context) { + final String connectionName = URLTools.getConnectionName(getURL(context)); + final GenContext genContext = new GenContext(); + if (Connections.getInstance().isConnectionOpen(connectionName)) { + genContext.setConn(DatabaseTools.getConnection(connectionName)); + final Object element = context.getSelection()[0]; + if (element instanceof PlSqlNode) { + genContext.setObjectType(((PlSqlNode) element).getObjectType().replace(" BODY", "")); + genContext.setObjectName(((PlSqlNode) element).getObjectName()); + final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + populateGenContext(genContext, preferences); + } + } + return genContext; + } + + public void runTest(final Context context, boolean withDebug) { + final View view = context.getView(); + final Node node = context.getNode(); + final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + final StringBuilder sb = new StringBuilder(); + sb.append("Run utPLSQL from view "); + sb.append(view != null ? view.getClass().getName() : "???"); + sb.append(" and node "); + sb.append(node != null ? node.getClass().getName() : "???"); + sb.append("."); + logger.finer(() -> sb.toString()); + if (view instanceof Editor) { + final Component component = ((Editor) view).getDefaultFocusComponent(); + if (component instanceof JEditorPane) { + String connectionName = null; + String owner = null; + if (node instanceof DatabaseSourceNode) { + connectionName = ((DatabaseSourceNode) node).getConnectionName(); + owner = ((DatabaseSourceNode) node).getOwner(); + } else { + if (view instanceof Worksheet) { + connectionName = ((Worksheet) view).getConnectionName(); + } + } + logger.fine("connectionName: " + connectionName); + final Connection conn = DatabaseTools.getConnection(connectionName); + String text = ((JEditorPane) component).getText(); + final UtplsqlParser parser = new UtplsqlParser(text, conn, owner); + final int position = ((JEditorPane) component).getCaretPosition(); + final String path = parser.getPathAt(position); + final RealtimeReporterDao rrDao = new RealtimeReporterDao(conn); + if (preferences.isUseRealtimeReporter() && rrDao.isSupported()) { + final UtplsqlRunner runner = new UtplsqlRunner(getPathList(path), connectionName); + if (withDebug) { + runner.enableDebugging(); + } + runner.runTestAsync(); + } else { + final UtplsqlWorksheetRunner worksheet = new UtplsqlWorksheetRunner(getPathList(path), connectionName); + if (withDebug) { + worksheet.enableDebugging(); + } + worksheet.runTestAsync(); + } + } + } else if (view instanceof DBNavigatorWindow || view instanceof SBWindow) { + final URL url = getURL(context); + if (url != null) { + final String connectionName = URLTools.getConnectionName(url); + logger.fine("connectionName: " + connectionName); + final Connection conn = DatabaseTools.getConnection(connectionName); + final RealtimeReporterDao rrDao = new RealtimeReporterDao(conn); + final ArrayList pathList = dedupPathList(getPathList(context)); + if (preferences.isUseRealtimeReporter() && rrDao.isSupported()) { + final UtplsqlRunner runner = new UtplsqlRunner(pathList, connectionName); + if (withDebug) { + runner.enableDebugging(); + } + runner.runTestAsync(); + } else { + final UtplsqlWorksheetRunner worksheet = new UtplsqlWorksheetRunner(pathList, connectionName); + if (withDebug) { + worksheet.enableDebugging(); + } + worksheet.runTestAsync(); + } + } + } + } + + public List dependencies(final String name, final String connectionName) { + List ret = null; + if (connectionName != null) { + final String owner = DatabaseTools.getSchema(connectionName); + ret = dependencies(owner, name, connectionName); + } + return ret; + } + + public List dependencies(final String owner, final String name, final String connectionName) { + List ret = null; + if (connectionName != null) { + Connection conn = DatabaseTools.getConnection(connectionName); + final UtplsqlDao dao = new UtplsqlDao(conn); + ret = dao.includes(owner, name); + } + return ret; + } + + public List dependencies(final Context context, final String connectionName) { + final HashSet ret = new HashSet(); + for (int i = 0; i < context.getSelection().length; i++) { + final Object element = context.getSelection()[i]; + if (element instanceof PlSqlNode) { + final String owner = ((PlSqlNode) element).getOwner(); + final String objectName = ((PlSqlNode) element).getObjectName(); + final List dep = dependencies(owner, objectName, connectionName); + ret.addAll(dep); + } else { + if (element instanceof ChildObjectElement) { + final String owner = URLTools.getSchema(((ChildObjectElement) element).getURL()); + final String objectName = URLTools.getMemberObject(((ChildObjectElement) element).getURL()); + final List dep = dependencies(owner, objectName, connectionName); + ret.addAll(dep); + } + } + } + return ret.stream().sorted().collect(Collectors.toList()); + } + + public void codeCoverage(final Context context) { + final View view = context.getView(); + final Node node = context.getNode(); + final StringBuilder sb = new StringBuilder(); + sb.append("Code coverage from view "); + sb.append(view != null ? view.getClass().getName() : "???"); + sb.append(" and node "); + sb.append(node != null ? node.getClass().getName() : "???"); + sb.append("."); + logger.finer(() -> sb.toString()); + if (view instanceof Editor) { + final Component component = ((Editor) view).getDefaultFocusComponent(); + if (component instanceof JEditorPane) { + String connectionName = null; + String owner = null; + if (node instanceof DatabaseSourceNode) { + connectionName = ((DatabaseSourceNode) node).getConnectionName(); + } else if (view instanceof Worksheet) { + connectionName = ((Worksheet) view).getConnectionName(); + } + logger.fine("connectionName: " + connectionName); + final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + String text = ((JEditorPane) component).getText(); + Connection conn = null; + if (preferences.isCheckRunUtplsqlTest()) { + conn = DatabaseTools.getConnection(connectionName); + } else { + conn = null; + } + final UtplsqlParser parser = new UtplsqlParser(text, conn, owner); + final int position = ((JEditorPane) component).getCaretPosition(); + final String path = parser.getPathAt(position); + final PlsqlObject object = parser.getObjectAt(position); + final List includeObjectList = dependencies(object.getName(), connectionName); + final CodeCoverageReporter reporter = new CodeCoverageReporter(getPathList(path), includeObjectList, connectionName); + reporter.showParameterWindow(); + } + } else if (view instanceof DBNavigatorWindow || view instanceof SBWindow) { + logger.finer("Code coverage from DB navigator"); + final URL url = getURL(context); + if (url != null) { + final String connectionName = URLTools.getConnectionName(url); + logger.fine(() -> "connectionName: " + connectionName); + final ArrayList pathList = dedupPathList(getPathList(context)); + logger.fine(() -> "pathlist: " + StringTools.getSimpleCSV(pathList)); + final List includeObjectList = dependencies(context, connectionName); + logger.finer(() -> "includeObjectList: " + StringTools.getSimpleCSV(includeObjectList)); + final CodeCoverageReporter reporter = new CodeCoverageReporter(pathList, includeObjectList, connectionName); + logger.finer(() -> "showing code coverage dialog"); + reporter.showParameterWindow(); + logger.finer(() -> "code coverage dialog shown"); + } else { + logger.warning("url is null"); + } + } + } + + public void generateTest(final Context context) { + final View view = context.getView(); + final Node node = context.getNode(); + final StringBuilder sb = new StringBuilder(); + sb.append("Generate utPLSQL test from view "); + sb.append(view != null ? view.getClass().getName() : "???"); + sb.append(" and node "); + sb.append(node != null ? node.getClass().getName() : "???"); + sb.append("."); + logger.finer(() -> sb.toString()); + if (view instanceof Editor) { + final Component component = ((Editor) view).getDefaultFocusComponent(); + if (component instanceof JEditorPane) { + String connectionName = null; + if (node instanceof DatabaseSourceNode) { + connectionName = ((DatabaseSourceNode) node).getConnectionName(); + } else if (view instanceof Worksheet) { + connectionName = ((Worksheet) view).getConnectionName(); + } + if (connectionName != null) { + if (Connections.getInstance().isConnectionOpen(connectionName)) { + final GenContext genContext = new GenContext(); + genContext.setConn(DatabaseTools.getConnection(connectionName)); + String text = ((JEditorPane) component).getText(); + final UtplsqlParser parser = new UtplsqlParser(text); + final int position = ((JEditorPane) component).getCaretPosition(); + final PlsqlObject obj = parser.getObjectAt(position); + if (obj != null) { + genContext.setObjectType(obj.getType().toUpperCase()); + genContext.setObjectName(obj.getName().toUpperCase()); + final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + populateGenContext(genContext, preferences); + final TestTemplate testTemplate = new TestTemplate(genContext); + final String code = testTemplate.generate().toString(); + UtplsqlWorksheetRunner.openWithCode(code, connectionName); + } + } + } + } + } else { + if (view instanceof DBNavigatorWindow || view instanceof SBWindow) { + final URL url = getURL(context); + if (url != null) { + final String connectionName = URLTools.getConnectionName(url); + GenContext genContext = getGenContext(context); + final TestTemplate testTemplate = new TestTemplate(genContext); + final String code = testTemplate.generate().toString(); + UtplsqlWorksheetRunner.openWithCode(code, connectionName); + } + } + } + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.xtend b/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.xtend deleted file mode 100644 index 21e59b58..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/menu/UtplsqlController.xtend +++ /dev/null @@ -1,434 +0,0 @@ -/* Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.menu - -import java.net.URL -import java.util.ArrayList -import java.util.HashSet -import java.util.List -import java.util.logging.Logger -import java.util.regex.Pattern -import javax.swing.JEditorPane -import oracle.dbtools.raptor.navigator.db.DBNavigatorWindow -import oracle.dbtools.raptor.navigator.db.DatabaseConnection -import oracle.dbtools.raptor.navigator.impl.ChildObjectElement -import oracle.dbtools.raptor.navigator.impl.DatabaseSourceNode -import oracle.dbtools.raptor.navigator.impl.ObjectFolder -import oracle.dbtools.raptor.navigator.impl.SchemaFolder -import oracle.dbtools.raptor.navigator.plsql.PlSqlNode -import oracle.dbtools.raptor.utils.Connections -import oracle.dbtools.worksheet.editor.Worksheet -import oracle.ide.Context -import oracle.ide.Ide -import oracle.ide.config.Preferences -import oracle.ide.controller.Controller -import oracle.ide.controller.IdeAction -import oracle.ide.editor.Editor -import org.utplsql.sqldev.coverage.CodeCoverageReporter -import org.utplsql.sqldev.dal.RealtimeReporterDao -import org.utplsql.sqldev.dal.UtplsqlDao -import org.utplsql.sqldev.model.URLTools -import org.utplsql.sqldev.model.oddgen.GenContext -import org.utplsql.sqldev.model.preference.PreferenceModel -import org.utplsql.sqldev.oddgen.TestTemplate -import org.utplsql.sqldev.parser.UtplsqlParser -import org.utplsql.sqldev.runner.UtplsqlRunner -import org.utplsql.sqldev.runner.UtplsqlWorksheetRunner - -class UtplsqlController implements Controller { - static final Logger logger = Logger.getLogger(UtplsqlController.name); - val extension URLTools urlTools = new URLTools - - public static int UTPLSQL_TEST_CMD_ID = Ide.findCmdID("utplsql.test") - public static int UTPLSQL_COVERAGE_CMD_ID = Ide.findCmdID("utplsql.coverage") - public static int UTPLSQL_GENERATE_CMD_ID = Ide.findCmdID("utplsql.generate") - public static final IdeAction UTPLSQL_TEST_ACTION = IdeAction.get(UtplsqlController.UTPLSQL_TEST_CMD_ID) - public static final IdeAction UTPLSQL_COVERAGE_ACTION = IdeAction.get(UtplsqlController.UTPLSQL_COVERAGE_CMD_ID) - public static final IdeAction UTPLSQL_GENERATE_ACTION = IdeAction.get(UtplsqlController.UTPLSQL_GENERATE_CMD_ID) - - override handleEvent(IdeAction action, Context context) { - if (action.commandId === UTPLSQL_TEST_CMD_ID) { - runTest(context) - return true - } else if (action.commandId === UTPLSQL_COVERAGE_CMD_ID) { - codeCoverage(context) - return true - } else if (action.commandId === UTPLSQL_GENERATE_CMD_ID) { - generateTest(context) - return true - } - return false - } - - override update(IdeAction action, Context context) { - if (action.commandId === UTPLSQL_TEST_CMD_ID || action.commandId === UTPLSQL_COVERAGE_CMD_ID) { - val preferences = PreferenceModel.getInstance(Preferences.preferences) - action.enabled = false - val view = context.view - if (view instanceof Editor) { - val component = view.defaultFocusComponent - if (component instanceof JEditorPane) { - if (preferences.checkRunUtplsqlTest) { - val node = context.node - var String connectionName = null; - var String owner = null; - if (node instanceof DatabaseSourceNode) { - connectionName = node.connectionName - owner = node.owner - } else if (view instanceof Worksheet) { - connectionName = view.connectionName - } - logger.fine('''connectionName: «connectionName»''') - val parser = new UtplsqlParser(component.text, Connections.instance.getConnection(connectionName), owner) - if (!parser.getPathAt(component.caretPosition).empty) { - action.enabled = true - } - } else { - action.enabled = true - } - } - } else if (view instanceof DBNavigatorWindow) { - action.enabled = true - // disable action if a node in the selection is not runnable - for (i : 0 ..< context.selection.length) { - logger.fine('''section «i» is «context.selection.get(i).toString» of class «context.selection.get(i).class.name»''') - if (action.enabled) { - val element = context.selection.get(i) - if (Connections.instance.isConnectionOpen(context.URL.connectionName)) { - val dao = new UtplsqlDao(Connections.instance.getConnection(context.URL.connectionName)) - if (preferences.checkRunUtplsqlTest && dao.utAnnotationManagerInstalled) { - if (element instanceof DatabaseConnection) { - action.enabled = dao.containsUtplsqlTest(element.connection.schema) - } else if (element instanceof SchemaFolder) { - action.enabled = dao.containsUtplsqlTest(element.schemaName) - } else if (element instanceof ObjectFolder) { - action.enabled = dao.containsUtplsqlTest(element.URL.schema) - } else if (element instanceof PlSqlNode) { - action.enabled = dao.containsUtplsqlTest(element.owner, element.objectName) - } else if (element instanceof ChildObjectElement) { - action.enabled = dao.containsUtplsqlTest(element.URL.schema, element.URL.memberObject, element.shortLabel) - } - } - } else { - action.enabled = false - } - } - } - } - return true - } else if (action.commandId === UTPLSQL_GENERATE_CMD_ID) { - action.enabled = false - // enable if generation is possible - val view = context.view - if (view instanceof Editor) { - val component = view.defaultFocusComponent - if (component instanceof JEditorPane) { - val preferences = PreferenceModel.getInstance(Preferences.preferences) - if (preferences.checkGenerateUtplsqlTest) { - val parser = new UtplsqlParser(component.text) - action.enabled = parser.getObjectAt(component.caretPosition) !== null - } else { - action.enabled = true - } - } - } else if (view instanceof DBNavigatorWindow) { - // multiselection is not supported, use oddgen to generte tests for multiple objects - if (context.selection.length == 1) { - val element = context.selection.get(0) - if (element instanceof PlSqlNode) { - val ot = element.objectType - if (ot.startsWith("PACKAGE") || ot.startsWith("TYPE") || ot == "FUNCTION" || ot == "PROCEDURE") { - action.enabled = true - } - } - } - } - } - return false - } - - private def getPath(Object element) { - var String path - if (element instanceof DatabaseConnection) { - path = element.connection.schema - } else if (element instanceof SchemaFolder) { - path = element.schemaName - } else if (element instanceof ObjectFolder) { - path = element.URL.schema - } else if (element instanceof PlSqlNode) { - path = '''«element.owner».«element.objectName»''' - } else if (element instanceof ChildObjectElement) { - path = '''«element.URL.schema».«element.URL.memberObject».«element.shortLabel»''' - } else { - path = "" - } - logger.fine('''path: «path»''') - return path - } - - private def getPathList(Context context) { - val pathList = new ArrayList() - for (i : 0 ..< context.selection.length) { - val element = context.selection.get(i) - pathList.add(element.path) - } - return pathList - } - - private def getPathList(String path) { - val pathList = new ArrayList - pathList.add(path) - return pathList - } - - private def dedupPathList(List pathList) { - val set = new HashSet - for (path : pathList) { - set.add(path) - } - val ret = new ArrayList - val p = Pattern.compile("(((([^\\.]+)\\.)?[^\\.]+)\\.)?[^\\.]+") - for (path : set) { - val m = p.matcher(path) - if (m.matches()) { - val parent1 = m.group(4) // user - val parent2 = m.group(2) // user.package - if (parent1 === null || !set.contains(parent1)) { - if (parent2 === null || !set.contains(parent2)) { - ret.add(path) - } - } - } else { - logger.severe('''path: «path» did not match «p.toString», this is unexected!''') - } - } - return ret - } - - private def getURL(Context context) { - var URL url - val element = context.selection.get(0) - if (element instanceof DatabaseConnection) { - url = element.URL - } else if (element instanceof SchemaFolder) { - url = element.URL - } else if (element instanceof ObjectFolder) { - url = element.URL - } else if (element instanceof PlSqlNode) { - url = element.URL - } else if (element instanceof ChildObjectElement) { - url = element.URL - } - logger.fine('''url: «url»''') - return url - } - - private def void populateGenContext(GenContext genContext, PreferenceModel preferences) { - genContext.testPackagePrefix = preferences.testPackagePrefix.toLowerCase - genContext.testPackageSuffix = preferences.testPackageSuffix.toLowerCase - genContext.testUnitPrefix = preferences.testUnitPrefix.toLowerCase - genContext.testUnitSuffix = preferences.testUnitSuffix.toLowerCase - genContext.numberOfTestsPerUnit = preferences.numberOfTestsPerUnit - genContext.generateComments = preferences.generateComments - genContext.disableTests = preferences.disableTests - genContext.suitePath = preferences.suitePath.toLowerCase - genContext.indentSpaces = preferences.indentSpaces - } - - private def getGenContext(Context context) { - val connectionName = context.URL.connectionName - val genContext = new GenContext - if (Connections.instance.isConnectionOpen(connectionName)) { - genContext.conn = Connections.instance.getConnection(connectionName) - val element = context.selection.get(0) - if (element instanceof PlSqlNode) { - genContext.objectType = element.objectType.replace(" BODY", "") - genContext.objectName = element.objectName - val preferences = PreferenceModel.getInstance(Preferences.preferences) - populateGenContext(genContext, preferences) - } - } - return genContext - } - - def runTest(Context context) { - val view = context.view - val node = context.node - val preferences = PreferenceModel.getInstance(Preferences.preferences) - logger.finer('''Run utPLSQL from view «view?.class?.name» and node «node?.class?.name».''') - if (view instanceof Editor) { - val component = view.defaultFocusComponent - if (component instanceof JEditorPane) { - var String connectionName = null; - var String owner = null; - if (node instanceof DatabaseSourceNode) { - connectionName = node.connectionName - owner = node.owner - } else if (view instanceof Worksheet) { - connectionName = view.connectionName - } - logger.fine('''connectionName: «connectionName»''') - // issue 59 - always use a connection to ensure the utPL/SQL annotation API is used - val conn = Connections.instance.getConnection(connectionName) - val parser = new UtplsqlParser(component.text, conn, owner) - val position = component.caretPosition - val path = parser.getPathAt(position) - val rrDao = new RealtimeReporterDao(conn) - if (preferences.useRealtimeReporter && rrDao.supported) { - val runner = new UtplsqlRunner(path.pathList, connectionName) - runner.runTestAsync - - } else { - val worksheet = new UtplsqlWorksheetRunner(path.pathList, connectionName) - worksheet.runTestAsync - } - } - } else if (view instanceof DBNavigatorWindow) { - val url=context.URL - if (url !== null) { - val connectionName = url.connectionName - logger.fine('''connectionName: «connectionName»''') - val conn = Connections.instance.getConnection(connectionName) - val rrDao = new RealtimeReporterDao(conn) - val pathList=context.pathList.dedupPathList - if (preferences.useRealtimeReporter && rrDao.supported) { - val runner = new UtplsqlRunner(pathList, connectionName) - runner.runTestAsync - } else { - val worksheet = new UtplsqlWorksheetRunner(pathList, connectionName) - worksheet.runTestAsync - } - } - } - } - - def List dependencies(String name, String connectionName) { - var List ret = null - if (connectionName !== null) { - val owner = Connections.instance.getConnection(connectionName).schema - ret = dependencies(owner, name, connectionName) - } - return ret - } - - def List dependencies(String owner, String name, String connectionName) { - var List ret = null - if (connectionName !== null) { - val dao = new UtplsqlDao(Connections.instance.getConnection(connectionName)) - ret = dao.includes(owner, name) - } - return ret - } - - def List dependencies(Context context, String connectionName) { - val HashSet ret = new HashSet - for (i : 0 ..< context.selection.length) { - val element = context.selection.get(i) - if (element instanceof PlSqlNode) { - val dep = dependencies(element.owner, element.objectName, connectionName) - for (d : dep) { - ret.add(d) - } - } else if (element instanceof ChildObjectElement) { - val dep = dependencies(element.URL.schema, element.URL.memberObject, connectionName) - for (d : dep) { - ret.add(d) - } - } - } - return ret.toList.sortBy[it] - } - - def codeCoverage(Context context) { - val view = context.view - val node = context.node - logger.finer('''Code coverage from view «view?.class?.name» and node «node?.class?.name».''') - if (view instanceof Editor) { - val component = view.defaultFocusComponent - if (component instanceof JEditorPane) { - var String connectionName = null; - var String owner = null; - if (node instanceof DatabaseSourceNode) { - connectionName = node.connectionName - } else if (view instanceof Worksheet) { - connectionName = view.connectionName - } - logger.fine('''connectionName: «connectionName»''') - val preferences = PreferenceModel.getInstance(Preferences.preferences) - val parser = new UtplsqlParser(component.text, if (preferences.checkRunUtplsqlTest) {Connections.instance.getConnection(connectionName)} else {null}, owner) - val position = component.caretPosition - val path = parser.getPathAt(position) - val object = parser.getObjectAt(position) - val includeObjectList = dependencies(object.name, connectionName) - val reporter = new CodeCoverageReporter(path.pathList, includeObjectList, connectionName) - reporter.showParameterWindow - } - } else if (view instanceof DBNavigatorWindow) { - val url=context.URL - if (url !== null) { - val connectionName = url.connectionName - logger.fine('''connectionName: «connectionName»''') - val pathList=context.pathList.dedupPathList - val includeObjectList = dependencies(context, connectionName) - val reporter = new CodeCoverageReporter(pathList, includeObjectList, connectionName) - reporter.showParameterWindow - } - } - } - - def generateTest(Context context) { - val view = context.view - val node = context.node - logger.finer('''Generate utPLSQL test from view «view?.class?.name» and node «node?.class?.name».''') - if (view instanceof Editor) { - val component = view.defaultFocusComponent - if (component instanceof JEditorPane) { - var String connectionName = null; - if (node instanceof DatabaseSourceNode) { - connectionName = node.connectionName - } else if (view instanceof Worksheet) { - connectionName = view.connectionName - } - if (connectionName !== null) { - if (Connections.instance.isConnectionOpen(connectionName)) { - val genContext = new GenContext - genContext.conn = Connections.instance.getConnection(connectionName) - val parser = new UtplsqlParser(component.text) - val position = component.caretPosition - val obj = parser.getObjectAt(position) - if (obj !== null) { - genContext.objectType = obj.type.toUpperCase - genContext.objectName = obj.name.toUpperCase - val preferences = PreferenceModel.getInstance(Preferences.preferences) - populateGenContext(genContext, preferences) - val testTemplate = new TestTemplate(genContext) - val code = testTemplate.generate.toString - UtplsqlWorksheetRunner.openWithCode(code, connectionName) - } - } - } - } - - } else if (view instanceof DBNavigatorWindow) { - val url=context.URL - if (url !== null) { - val connectionName = url.connectionName - val testTemplate = new TestTemplate(context.genContext) - val code = testTemplate.generate.toString - UtplsqlWorksheetRunner.openWithCode(code, connectionName) - } - } - } -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/DatabaseTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/DatabaseTools.java new file mode 100644 index 00000000..df12153b --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/DatabaseTools.java @@ -0,0 +1,159 @@ +/* + * Copyright 2020 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.SQLException; + +import javax.sql.DataSource; + +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.utplsql.sqldev.exception.GenericDatabaseAccessException; +import org.utplsql.sqldev.exception.GenericRuntimeException; + +import oracle.dbtools.raptor.navigator.db.DatabaseConnection; +import oracle.dbtools.raptor.utils.Connections; +import oracle.javatools.db.DBException; +import oracle.jdeveloper.db.ConnectionException; + +public class DatabaseTools { + // do not instantiate this class + private DatabaseTools() { + super(); + } + + public static Connection getConnection(DataSource dataSource) { + try { + return dataSource.getConnection(); + } catch (SQLException e) { + throw new GenericDatabaseAccessException("Error getting connection.", e); + } + } + + public static Connection getConnection(DatabaseConnection conn) { + try { + return conn.getConnection(); + } catch (IOException e) { + final String msg = "Error getting connection for " + conn.getConnectionName() + "."; + throw new GenericDatabaseAccessException(msg, e); + } + } + + public static Connection getConnection(String connectionName) { + try { + return Connections.getInstance().getConnection(connectionName); + } catch (DBException e) { + final String msg = "Error getting connection for " + connectionName + "."; + throw new GenericDatabaseAccessException(msg, e); + } + } + + public static Connection cloneConnection(String connectionName) { + final Connection conn = getConnection(connectionName); + try { + return Connections.getInstance().cloneConnection(conn); + } catch (ConnectionException e) { + final String msg = "Error cloning connection " + connectionName + "."; + throw new GenericDatabaseAccessException(msg, e); + } + } + + private static String createTemporaryConnection(String connectionName) { + try { + return Connections.getInstance().createTemporaryConnection(connectionName); + } catch (Throwable e) { + final String msg = "Error creating temporary connection based on " + connectionName + "."; + throw new GenericDatabaseAccessException(msg, e); + } + } + + private static String createPrivateConnection(String connectionName) { + try { + return Connections.getInstance().createPrivateConnection(connectionName); + } catch (Throwable e) { + final String msg = "Error creating private connection based on " + connectionName + "."; + throw new GenericDatabaseAccessException(msg, e); + } + } + + public static String createTemporaryOrPrivateConnection(String connectionName) { + // Private connections are closed in SQL Developer < 17.4.0 when the worksheet + // is closed, but in SQL Developer > 17.4.0 private connections are not closed. + // Temporary connections have been introduced in SQL Developer 17.4.0. They will + // be always closed, when a worksheet is closed. + // Hence we try to use temporary connections whenever possible. See also + // https://github.com/utPLSQL/utPLSQL-SQLDeveloper/issues/47 . + try { + return createTemporaryConnection(connectionName); + } catch (GenericDatabaseAccessException e) { + return createPrivateConnection(connectionName); + } + } + + public static void closeConnection(Connection conn) { + abortConnection(conn); + } + + public static void abortConnection(Connection conn) { + final SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); + try { + conn.abort(taskExecutor); + } catch (SQLException e) { + throw new GenericDatabaseAccessException("Could not abort connection."); + } + } + + public static String getSchema(Connection conn) { + try { + return conn.getSchema(); + } catch (SQLException e) { + throw new GenericRuntimeException("Error getting schema name of connection.", e); + } + } + + public static String getUser(Connection conn) { + try { + return conn.getMetaData().getUserName(); + } catch (SQLException e) { + throw new GenericRuntimeException("Error getting user name of connection.", e); + } + } + + public static String getSchema(DatabaseConnection conn) { + return getSchema(getConnection(conn)); + } + + + public static String getSchema(String connectionName) { + return getSchema(getConnection(connectionName)); + } + + public static boolean isSupported(final Connection conn) { + try { + boolean ret = false; + if (conn != null && conn.getMetaData().getDatabaseProductName().startsWith("Oracle") + && (conn.getMetaData().getDatabaseMajorVersion() == 11 + && conn.getMetaData().getDatabaseMinorVersion() >= 2 + || conn.getMetaData().getDatabaseMajorVersion() > 11)) { + ret = true; + } + return ret; + } catch (SQLException e) { + throw new GenericDatabaseAccessException("Error while getting product version of connection.", e); + } + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/FileTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/FileTools.java new file mode 100644 index 00000000..02b32e5f --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/FileTools.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.utplsql.sqldev.exception.GenericRuntimeException; + +public class FileTools { + // do not instantiate this class + private FileTools() { + super(); + } + + public static byte[] readFile(Path path) { + try { + return Files.readAllBytes(path); + } catch (IOException e) { + final String msg = "Cannot read file " + path.toString() + "."; + throw new GenericRuntimeException(msg, e); + } + } + + public static void writeFile(Path path, byte[] bytes) { + try { + Files.write(path, bytes); + } catch (IOException e) { + final String msg = "Cannot write file " + path.toString() + "."; + throw new GenericRuntimeException(msg, e); + } + } + + public static void writeFile(Path path, Iterable lines, Charset cs) { + try { + Files.write(path, lines, cs); + } catch (IOException e) { + final String msg = "Cannot write file " + path.toString() + "."; + throw new GenericRuntimeException(msg, e); + } + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/JsonToStringStyler.java b/sqldev/src/main/java/org/utplsql/sqldev/model/JsonToStringStyler.java new file mode 100644 index 00000000..7b4d429e --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/JsonToStringStyler.java @@ -0,0 +1,170 @@ +/* + * Copyright 2020 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model; + +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; + +import javax.annotation.Nullable; + +import org.jetbrains.annotations.NotNull; +import org.springframework.core.style.ToStringStyler; +import org.springframework.core.style.ValueStyler; + +public class JsonToStringStyler implements ToStringStyler, ValueStyler{ + public static final String INDENT_SPACES = " "; + private int indent = 0; + + private static final ThreadLocal threadLocal = ThreadLocal.withInitial(JsonToStringStyler::new); + + private void newLine(StringBuilder buffer) { + buffer.append('\n'); + buffer.append(getIndentSpaces(0)); + } + + private String getIndentSpaces(int indentOffset) { + StringBuilder sb = new StringBuilder(); + for (int i=0; i list) { + if (list.isEmpty()) { + return "[]"; + } + + StringJoiner result = new StringJoiner(",\n" + getIndentSpaces(1), "[\n" + getIndentSpaces(1) , "\n" + getIndentSpaces(0) + "]"); + indent++; + for (Object o : list) { + result.add(style(o)); + } + indent--; + return result.toString(); + } + + private String getMapStyle(Map map) { + if (map.isEmpty()) { + return "[]"; + } + + StringJoiner result = new StringJoiner(",\n" + getIndentSpaces(1), "[\n" + getIndentSpaces(1) , "\n" + getIndentSpaces(0) + "]"); + indent++; + for (Object o : map.values()) { + result.add(style(o)); + } + indent--; + return result.toString(); + } + + private String getDefaultStyle(Object value) { + return String.valueOf(value); + } + + public static ToStringStyler getInstance() { + return threadLocal.get(); + } + + @Override + public void styleStart(@NotNull StringBuilder buffer, Object obj) { + indent++; + if (!obj.getClass().isArray()) { + buffer.append("{"); + newLine(buffer); + buffer.append("\"className\": "); + buffer.append('"'); + buffer.append(obj.getClass().getSimpleName()); + buffer.append('"'); + buffer.append(','); + } else { + buffer.append('['); + styleValue(buffer, obj); + } + } + + @Override + public void styleEnd(@NotNull StringBuilder buffer, Object obj) { + indent--; + newLine(buffer); + if (!obj.getClass().isArray()) { + buffer.append('}'); + } else { + buffer.append(']'); + } + } + + @Override + public void styleField(@NotNull StringBuilder buffer, @NotNull String fieldName, @Nullable Object value) { + newLine(buffer); + buffer.append('"'); + buffer.append(fieldName); + buffer.append('"'); + buffer.append(": "); + styleValue(buffer, value); + } + + @Override + public void styleValue(StringBuilder buffer, @Nullable Object value) { + buffer.append(style(value)); + } + + @Override + public void styleFieldSeparator(StringBuilder buffer) { + buffer.append(","); + } + + @NotNull + @Override + public String style(Object value) { + if (value == null) { + return "null"; + } else if (value instanceof String) { + return getStringStyle((String) value); + } else if (value instanceof Object[]) { + return getArrayStyle((Object[]) value); + } else if (value instanceof List) { + return getListStyle((List) value); + } else if (value instanceof Map) { + return getMapStyle((Map) value); + } else { + return getDefaultStyle(value.toString()); + } + } +} diff --git a/sqldev/src/test/java/org/utplsql/sqldev/test/dal/TestRealtimerReporterEventTimedConsumer.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.java similarity index 51% rename from sqldev/src/test/java/org/utplsql/sqldev/test/dal/TestRealtimerReporterEventTimedConsumer.xtend rename to sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.java index ff46d749..550e6d72 100644 --- a/sqldev/src/test/java/org/utplsql/sqldev/test/dal/TestRealtimerReporterEventTimedConsumer.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.java @@ -13,25 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev.test.dal +package org.utplsql.sqldev.model; -import java.util.HashMap -import org.utplsql.sqldev.dal.RealtimeReporterEventConsumer -import org.utplsql.sqldev.model.runner.RealtimeReporterEvent -import org.utplsql.sqldev.model.runner.PostTestEvent +import java.util.LinkedHashMap; +import java.util.Map; -class TestRealtimerReporterEventTimedConsumer implements RealtimeReporterEventConsumer { - - val postTestEvents = new HashMap - - def getPostTestEvents() { - return postTestEvents - } - - override void process(RealtimeReporterEvent event) { - if (event instanceof PostTestEvent) { - postTestEvents.put(event.id, System.currentTimeMillis) - } - } +public class LimitedLinkedHashMap extends LinkedHashMap { + private static final long serialVersionUID = -4184317926729190411L; + private final int maxEntries; -} \ No newline at end of file + public LimitedLinkedHashMap(final int maxEntries) { + super((maxEntries + 1), 1.0f, false); + this.maxEntries = maxEntries; + } + + @Override + public boolean removeEldestEntry(final Map.Entry eldest) { + return (size() > maxEntries); + } + + public int getMaxEntries() { + return maxEntries; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.xtend deleted file mode 100644 index 2d3605d9..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/LimitedLinkedHashMap.xtend +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model - -import java.util.LinkedHashMap -import java.util.Map - -class LimitedLinkedHashMap extends LinkedHashMap { - val int maxEntries - - new (int maxEntries) { - super(maxEntries + 1, 1.0f, false) - - this.maxEntries = maxEntries; - } - - override removeEldestEntry(Map.Entry eldest) { - return size > maxEntries - } - - def getMaxEntries() { - return maxEntries - } -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/PrefixTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/PrefixTools.java new file mode 100644 index 00000000..e17ce69d --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/PrefixTools.java @@ -0,0 +1,88 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model; + +import java.util.List; + +//converted to Xtend based on Java code on https://www.geeksforgeeks.org/longest-common-prefix-using-binary-search/ +//converted back to Java with some amendments +public class PrefixTools { + + // do not instantiate this class + private PrefixTools() { + super(); + } + + public static int findMinLength(final String[] arr, final int n) { + int min = Integer.MAX_VALUE; + for (int i=0; i < n; i++) { + if (arr[i].length() < min) { + min = arr[i].length(); + } + } + return min; + } + + public static boolean allContainsPrefix(final String[] arr, final int n, final String str, final int start, final int end) { + for (int i=0; i < n; i++) { + String item = arr[i]; + for (int j = start; j <= end; j++) { + if (item.charAt(j) != str.charAt(j)) { + return false; + } + } + } + return true; + } + + public static String commonPrefix(final String[] arr, final int n) { + int index = findMinLength(arr, n); + StringBuilder prefix = new StringBuilder(); + int low = 0; + int high = index; // index-1 is wrong + while (low <= high) { + int mid = low + (high - low) / 2; + if (allContainsPrefix(arr, n, arr[0], low, mid)) { + prefix.append(arr[0].substring(low, mid + 1)); + low = mid + 1; + } else { + high = mid - 1; + } + } + return prefix.toString(); + } + + public static String commonPrefix(final List list) { + try { + if (list.isEmpty()) { + return ""; + } else if (list.size() == 1) { + final int pos = list.get(0).lastIndexOf('.'); + if (pos > 0) { + return list.get(0).substring(0, pos + 1); + } else { + return ""; + } + } else { + final String[] testArray = new String[list.size()]; + return commonPrefix(list.toArray(testArray), list.size()); + } + + } catch (Exception e) { + return ""; + } + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/PrefixTools.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/PrefixTools.xtend deleted file mode 100644 index c1fc822f..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/PrefixTools.xtend +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model - -import java.util.List - -// converted to Xtend based on Java code on https://www.geeksforgeeks.org/longest-common-prefix-using-binary-search/ -class PrefixTools { - def static int findMinLength(String[] arr, int n) { - var int min = Integer.MAX_VALUE - for (var int i = 0; i < n; i++) { - if ({ - val _rdIndx_arr = i - arr.get(_rdIndx_arr) - }.length() < min) { - min = { - val _rdIndx_arr = i - arr.get(_rdIndx_arr) - }.length() - } - } - return min - } - - def static boolean allContainsPrefix(String[] arr, int n, String str, int start, int end) { - for (var int i = 0; i < n; i++) { - var String arr_i = { - val _rdIndx_arr = i - arr.get(_rdIndx_arr) - } - for (var int j = start; j <= end; j++) { - if (arr_i.charAt(j) !== str.charAt(j)) { - return false - } - } - } - return true - } - - def static String commonPrefix(String[] arr, int n) { - var int index = findMinLength(arr, n) - var String prefix = "" - var int low = 0 - var int high = index - while (low <= high) { - var int mid = low + (high - low) / 2 - if (allContainsPrefix(arr, n, arr.get(0), low, mid)) { - prefix = prefix + arr.get(0).substring(low, mid + 1) - low = mid + 1 - } else { - high = mid - 1 - } - } - return prefix - } - - def static String commonPrefix(List list) { - try { - if (list.size === 0) { - return "" - } else if (list.size === 1) { - val pos = list.get(0).lastIndexOf("."); - if (pos > 0) { - return list.get(0).substring(0, pos + 1) - } else { - return "" - } - } else { - var String[] testArray = newArrayOfSize(list.size) - var prefix = commonPrefix(list.toArray(testArray), list.size) - return prefix - } - } catch (Exception e) { - return "" - } - } - -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/StringTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/StringTools.java new file mode 100644 index 00000000..23323043 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/StringTools.java @@ -0,0 +1,123 @@ +/* + * Copyright 2020 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.utplsql.sqldev.exception.GenericRuntimeException; + +public class StringTools { + // do not instantiate this class + private StringTools() { + super(); + } + + public static String getCSV(List list, String indent) { + final StringBuilder sb = new StringBuilder(); + for (final String item : list) { + if (sb.length() > 0) { + sb.append(",\n"); + } + sb.append(indent); + sb.append("'"); + sb.append(item); + sb.append("'"); + } + sb.append("\n"); + return sb.toString(); + } + + public static String getCSV(List list, int indentSpaces) { + return getCSV(list, repeat(" ", indentSpaces)); + } + + public static String getSimpleCSV(List list) { + final StringBuilder sb = new StringBuilder(); + for (final String item : list) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(item); + } + return sb.toString(); + } + + public static String repeat(String s, int times) { + return String.join("", Collections.nCopies(times, s)); + } + + public static String replaceTabsWithSpaces(final CharSequence input, int indentSpaces) { + final String spaces = StringTools.repeat(" ", indentSpaces); + return input.toString().replace("\t", spaces); + } + + public static String formatDateTime(final String dateTime) { + if (dateTime == null) { + return null; + } else { + if (dateTime.length() == 26) { + return dateTime.replace("T", " ").substring(0, 23); + } else { + return dateTime; + } + } + } + + public static String millisToDateTimeString(long millis) { + final Date dateTime = new Date(millis); + final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'000'"); + return df.format(dateTime); + } + + public static String getSysdate() { + return millisToDateTimeString(System.currentTimeMillis()); + } + + public static long dateTimeStringToMillis(final String dateTime) { + // handle milliseconds separately since they get lost (rounded) when converted to date + final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + Date date; + try { + date = df.parse(dateTime.substring(0, 20)); + } catch (ParseException e) { + throw new GenericRuntimeException("cannot parse datetime string " + dateTime + ".", e); + } + long millis = Long.parseLong(dateTime.substring(20, 23)); + return date.getTime() + millis; + } + + public static double elapsedTime(String startDateTime, String endDateTime) { + double start = (double) dateTimeStringToMillis(startDateTime); + double end = (double) dateTimeStringToMillis(endDateTime); + return (end - start) / 1000; + } + + public static boolean isNotBlank(String value) { + return value != null && !value.trim().isEmpty(); + } + + public static String trim(String value) { + if (value == null) { + return null; + } + return value.trim(); + } + +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/SystemTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/SystemTools.java new file mode 100644 index 00000000..30a75172 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/SystemTools.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model; + +public class SystemTools { + // do not instantiate this class + private SystemTools() { + super(); + } + + public static void sleep(int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + public static void waitForThread(Thread thread, int maxTimeInMillis) { + try { + thread.join(maxTimeInMillis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.java new file mode 100644 index 00000000..26190df3 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.java @@ -0,0 +1,83 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model; + +import java.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class URLTools { + + // do not instantiate this class + private URLTools() { + super(); + } + + public static String replaceHexChars(final String input) { + String output = input; + final Pattern p = Pattern.compile("%([0-9A-F]{2})"); + final Matcher m = p.matcher(input); + while (m.find()) { + final String what = m.group(0); + final int decimal = Integer.parseInt(m.group(1), 16); + final String with = String.valueOf((char) decimal); + output = output.replace(what, with); + } + return output; + } + + public static String getConnectionName(final URL url) { + final Pattern p = Pattern.compile("(sqldev.nav:)([^/]+)(//)?"); + final Matcher m = p.matcher(url.toString()); + if (m.find()) { + return replaceHexChars(m.group(2) + .replace("IdeConnections%2523", "IdeConnections%23")) // remove connection prefix + .replace("+", " "); // spaces are encoded als plus signs, fix that, see #118 + } else { + return ""; + } + } + + public static String getSchema(final URL url) { + final Pattern p = Pattern.compile("(//)([^/]+)"); + final Matcher m = p.matcher(url.toString()); + if (m.find()) { + return m.group(2); + } else { + return ""; + } + } + + public static String getObjectType(final URL url) { + final Pattern p = Pattern.compile("(//)([^/]+)(/)([^/]+)"); + final Matcher m = p.matcher(url.toString()); + if (m.find()) { + return m.group(4); + } else { + return ""; + } + } + + public static String getMemberObject(final URL url) { + final Pattern p = Pattern.compile("(/)([^/]+)(#MEMBER)"); + final Matcher m = p.matcher(url.toString()); + if (m.find()) { + return m.group(2); + } else { + return ""; + } + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.xtend deleted file mode 100644 index 507d7035..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/URLTools.xtend +++ /dev/null @@ -1,74 +0,0 @@ -/* Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model - -import java.net.URL -import java.util.regex.Pattern - -class URLTools { - def replaceHexChars(String input) { - var String output = input; - val p = Pattern.compile("%([0-9A-F]{2})") - val m = p.matcher(input) - while (m.find) { - val what = m.group(0); - val decimal = Integer.parseInt(m.group(1), 16) - val with = String.valueOf(decimal as char) - output = output.replace(what, with) - } - return output - } - - def getConnectionName(URL url) { - val p = Pattern.compile("(sqldev.nav:)([^/]+)(//)?") - val m = p.matcher(url.toString) - if (m.find) { - return m.group(2).replace("IdeConnections%2523", "IdeConnections%23").replaceHexChars - } else { - return "" - } - } - - def getSchema(URL url) { - val p = Pattern.compile("(//)([^/]+)") - val m = p.matcher(url.toString) - if (m.find) { - return m.group(2) - } else { - return "" - } - } - - def getObjectType(URL url) { - val p = Pattern.compile("(//)([^/]+)(/)([^/]+)") - val m = p.matcher(url.toString) - if (m.find) { - return m.group(4) - } else { - return "" - } - } - - def getMemberObject(URL url) { - val p = Pattern.compile("(/)([^/]+)(#MEMBER)") - val m = p.matcher(url.toString) - - if (m.find) { - return m.group(2) - } else { - return "" - } - } -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.java b/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.java new file mode 100644 index 00000000..090cf8d6 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.java @@ -0,0 +1,161 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.logging.Logger; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.utplsql.sqldev.exception.GenericRuntimeException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +public class XMLTools { + private static final Logger logger = Logger.getLogger(XMLTools.class.getName()); + private final XPathFactory xpathFactory = XPathFactory.newInstance(); + private final XPath xpath = xpathFactory.newXPath(); + + public NodeList getNodeList(final Node doc, final String xpathString) { + try { + final XPathExpression expr = xpath.compile(xpathString); + return ((NodeList) expr.evaluate(doc, XPathConstants.NODESET)); + } catch (XPathExpressionException e) { + final String msg = "XPathExpressionException for " + xpathString + "."; + logger.severe(() -> msg); + throw new GenericRuntimeException(msg, e); + } + } + + public Node getNode(final Node doc, final String xpathString) { + try { + final XPathExpression expr = xpath.compile(xpathString); + return ((Node) expr.evaluate(doc, XPathConstants.NODE)); + } catch (XPathExpressionException e) { + final String msg = "XPathExpressionException for " + xpathString + "."; + logger.severe(() -> msg); + throw new GenericRuntimeException(msg, e); + } + } + + public void trimWhitespace(final Node node) { + final NodeList children = node.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + final Node child = children.item(i); + if (child.getNodeType() == Node.TEXT_NODE) { + child.setTextContent(child.getTextContent().trim()); + } + trimWhitespace(child); + } + } + + public String nodeToString(final Node node, final String cdataSectionElements) { + try { + trimWhitespace(node); + final StringWriter writer = new StringWriter(); + TransformerFactory factory = TransformerFactory.newInstance(); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + final Transformer transformer = factory.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3"); + transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, cdataSectionElements); + transformer.transform( new DOMSource(node), new StreamResult(writer)); + final String result = writer.toString(); + return result.replaceAll("", ""); + } catch (TransformerException e) { + final String msg = "TransformerException for " + cdataSectionElements + "."; + logger.severe(() -> msg); + throw new GenericRuntimeException(msg, e); + } + } + + public String getAttributeValue(final Node node, final String namedItem) { + String value = null; + if (node instanceof Element) { + final NamedNodeMap attributes = node.getAttributes(); + if (attributes != null) { + final Node item = attributes.getNamedItem(namedItem); + if (item != null) { + value = item.getNodeValue(); + } + } + } + return value; + } + + public String getElementValue(final Node node, final String tagName) { + String value = null; + final Node item = getElementNode(node, tagName); + if (item != null) { + value = item.getTextContent(); + } + return value; + } + + public Node getElementNode(final Node node, final String tagName) { + Node resultNode = null; + if (node instanceof Element) { + NodeList list = ((Element) node).getElementsByTagName(tagName); + if (list != null && list.getLength() > 0 && list.item(0).getParentNode() == node) { + resultNode = list.item(0); + } + } + return resultNode; + } + + public DocumentBuilder createDocumentBuilder() { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + try { + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); + return factory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + final String msg = "Could not create no document builder."; + logger.severe(() -> msg); + throw new GenericRuntimeException(msg, e); + } + } + + public Document parse(final DocumentBuilder builder, final InputSource inputSource) { + try { + return builder.parse(inputSource); + } catch (SAXException | IOException e) { + final String msg = "Could not parse XML input."; + logger.severe(() -> msg); + throw new GenericRuntimeException(msg, e); + } + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.xtend deleted file mode 100644 index 5126486a..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/XMLTools.xtend +++ /dev/null @@ -1,67 +0,0 @@ -/* Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model - -import java.io.StringWriter -import javax.xml.transform.OutputKeys -import javax.xml.transform.TransformerFactory -import javax.xml.transform.dom.DOMSource -import javax.xml.transform.stream.StreamResult -import javax.xml.xpath.XPathConstants -import javax.xml.xpath.XPathFactory -import org.w3c.dom.Node -import org.w3c.dom.NodeList - -class XMLTools { - val xpathFactory = XPathFactory.newInstance() - val xpath = xpathFactory.newXPath() - - def getNodeList(Node doc, String xpathString) { - val expr = xpath.compile(xpathString); - val NodeList nodeList = expr.evaluate(doc, XPathConstants.NODESET) as NodeList - return nodeList - } - - def getNode(Node doc, String xpathString) { - val expr = xpath.compile(xpathString); - val Node node = expr.evaluate(doc, XPathConstants.NODE) as Node - return node - } - - def void trimWhitespace(Node node) { - val children = node.childNodes - for (i : 0 ..< children.length) { - val child = children.item(i) - if (child.nodeType == Node.TEXT_NODE) { - child.textContent = child.textContent.trim - } - trimWhitespace(child); - } - } - - def nodeToString(Node node, String cdataSectionElements) { - node.trimWhitespace - val writer = new StringWriter() - val factory = TransformerFactory.newInstance().newTransformer() - factory.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes") - factory.setOutputProperty(OutputKeys.INDENT, "yes") - factory.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3"); - factory.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, cdataSectionElements) - factory.transform(new DOMSource(node), new StreamResult(writer)) - val result = writer.toString() - val fixedResult = result.replaceAll('''''',"") - return fixedResult - } -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/oddgen/GenContext.java b/sqldev/src/main/java/org/utplsql/sqldev/model/oddgen/GenContext.java new file mode 100644 index 00000000..41fc456e --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/oddgen/GenContext.java @@ -0,0 +1,150 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.oddgen; + +import java.sql.Connection; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public class GenContext { + private Connection conn; + private String objectType; + private String objectName; + private String testPackagePrefix; + private String testPackageSuffix; + private String testUnitPrefix; + private String testUnitSuffix; + private int numberOfTestsPerUnit; + private boolean generateComments; + private boolean disableTests; + private String suitePath; + private int indentSpaces; + + public Connection getConn() { + return conn; + } + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append("conn", conn) + .append("objectType", objectType) + .append("objectName", objectName) + .append("testPackagePrefix", testPackagePrefix) + .append("testPackageSuffix", testPackageSuffix) + .append("testUnitPrefix", testUnitPrefix) + .append("testUnitSuffix", testUnitSuffix) + .append("numberOfTestsPerUnit", numberOfTestsPerUnit) + .append("generateComments", generateComments) + .append("disableTests", disableTests) + .append("suitePath", suitePath) + .append("indentSpaces", indentSpaces) + .toString(); + } + + public void setConn(final Connection conn) { + this.conn = conn; + } + + public String getObjectType() { + return objectType; + } + + public void setObjectType(final String objectType) { + this.objectType = objectType; + } + + public String getObjectName() { + return objectName; + } + + public void setObjectName(final String objectName) { + this.objectName = objectName; + } + + public String getTestPackagePrefix() { + return testPackagePrefix; + } + + public void setTestPackagePrefix(final String testPackagePrefix) { + this.testPackagePrefix = testPackagePrefix; + } + + public String getTestPackageSuffix() { + return testPackageSuffix; + } + + public void setTestPackageSuffix(final String testPackageSuffix) { + this.testPackageSuffix = testPackageSuffix; + } + + public String getTestUnitPrefix() { + return testUnitPrefix; + } + + public void setTestUnitPrefix(final String testUnitPrefix) { + this.testUnitPrefix = testUnitPrefix; + } + + public String getTestUnitSuffix() { + return testUnitSuffix; + } + + public void setTestUnitSuffix(final String testUnitSuffix) { + this.testUnitSuffix = testUnitSuffix; + } + + public int getNumberOfTestsPerUnit() { + return numberOfTestsPerUnit; + } + + public void setNumberOfTestsPerUnit(final int numberOfTestsPerUnit) { + this.numberOfTestsPerUnit = numberOfTestsPerUnit; + } + + public boolean isGenerateComments() { + return generateComments; + } + + public void setGenerateComments(final boolean generateComments) { + this.generateComments = generateComments; + } + + public boolean isDisableTests() { + return disableTests; + } + + public void setDisableTests(final boolean disableTests) { + this.disableTests = disableTests; + } + + public String getSuitePath() { + return suitePath; + } + + public void setSuitePath(final String suitePath) { + this.suitePath = suitePath; + } + + public int getIndentSpaces() { + return indentSpaces; + } + + public void setIndentSpaces(final int indentSpaces) { + this.indentSpaces = indentSpaces; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.java b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.java new file mode 100644 index 00000000..51806d53 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.java @@ -0,0 +1,71 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.parser; + +import java.util.List; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; +import org.utplsql.sqldev.model.ut.Annotation; + +public class PlsqlObject { + private String name; + private String type; + private Integer position; + private List annotations; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append("name", name) + .append("type", type) + .append("position", position) + .append("annotations", annotations) + .toString(); + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(final String type) { + this.type = type; + } + + public Integer getPosition() { + return position; + } + + public void setPosition(final Integer position) { + this.position = position; + } + + public List getAnnotations() { + return annotations; + } + + public void setAnnotations(final List annotations) { + this.annotations = annotations; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.xtend deleted file mode 100644 index 96b71db1..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/PlsqlObject.xtend +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.parser - -import java.util.List -import org.eclipse.xtend.lib.annotations.Accessors -import org.utplsql.sqldev.model.AbstractModel -import org.utplsql.sqldev.model.ut.Annotation - -@Accessors -class PlsqlObject extends AbstractModel { - String name - String type - Integer position - List annotations -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.java b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.java new file mode 100644 index 00000000..c6bdc5f8 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.java @@ -0,0 +1,58 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.parser; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public class Unit { + private String name; + private Integer position; + private Integer positionOfName; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append("name", name) + .append("position", position) + .append("positionOfName", positionOfName) + .toString(); + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public Integer getPosition() { + return position; + } + + public void setPosition(final Integer position) { + this.position = position; + } + + public Integer getPositionOfName() { + return positionOfName; + } + + public void setPositionOfName(final Integer positionOfName) { + this.positionOfName = positionOfName; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.xtend deleted file mode 100644 index a913f70b..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/parser/Unit.xtend +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.parser - -import org.eclipse.xtend.lib.annotations.Accessors -import org.utplsql.sqldev.model.AbstractModel - -@Accessors -class Unit extends AbstractModel { - String name - Integer position - Integer positionOfName -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.java b/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.java new file mode 100644 index 00000000..13bd9794 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.java @@ -0,0 +1,367 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.preference; + +import java.io.File; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +import oracle.javatools.data.HashStructure; +import oracle.javatools.data.HashStructureAdapter; +import oracle.javatools.data.PropertyStorage; + +public class PreferenceModel extends HashStructureAdapter { + public static final String DEFAULT_OUTPUT_DIRECTORY = System.getProperty("user.home") + File.separator + "utplsql" + File.separator + "generated"; + private static final String DATA_KEY = "utplsql"; + + private PreferenceModel(final HashStructure hash) { + super(hash); + } + + public static PreferenceModel getInstance(final PropertyStorage prefs) { + return new PreferenceModel(findOrCreate(prefs, DATA_KEY)); + } + + private static final String KEY_USE_REALTIME_REPORTER = "useRealtimeRorter"; + private static final String KEY_UNSHARED_WORKSHEET = "unsharedWorksheet"; + private static final String KEY_RESET_PACKAGE = "resetPackage"; + private static final String KEY_CLEAR_SCREEN = "clearScreen"; + private static final String KEY_AUTO_EXECUTE = "autoExecute"; + private static final String KEY_CHECK_RUN_UTPLSQL_TEST = "checkRunUtplsqlTest"; + private static final String KEY_USE_SMART_TIMES = "useSmartTimes"; + private static final String KEY_NUMBER_OF_RUNS_IN_HISTORY = "numberOfRunsInHistory"; + private static final String KEY_SHOW_DISABLED_COUNTER = "showDisabledCounter"; + private static final String KEY_SHOW_WARNINGS_COUNTER = "showWarningsCounter"; + private static final String KEY_SHOW_INFO_COUNTER = "showInfoCounter"; + private static final String KEY_SHOW_WARNING_INDICATOR = "showWarningIndicator"; + private static final String KEY_SHOW_INFO_INDICATOR = "showInfoIndicator"; + private static final String KEY_SHOW_SUCCESSFUL_TESTS = "showSuccessfulTests"; + private static final String KEY_SHOW_DISABLED_TESTS = "showDisabledTests"; + private static final String KEY_SHOW_TEST_DESCRIPTION = "showTestDescription"; + private static final String KEY_SYNC_DETAIL_TAB = "syncDetailTab"; + private static final String KEY_SHOW_SUITES = "showSuites"; + private static final String KEY_TEST_PACKAGE_PREFIX = "testPackagePrefix"; + private static final String KEY_TEST_PACKAGE_SUFFIX = "testPackageSuffix"; + private static final String KEY_TEST_UNIT_PREFIX = "testUnitPrefix"; + private static final String KEY_TEST_UNIT_SUFFIX = "testUnitSuffix"; + private static final String KEY_NUMBER_OF_TESTS_PER_UNIT = "numberOfTestsPerUnit"; + private static final String KEY_CHECK_GENERATE_UTPLSQL_TEST = "checkGenerateUtplsqlTest"; + private static final String KEY_GENERATE_COMMENTS = "generateComments"; + private static final String KEY_DISABLE_TESTS = "disableTests"; + private static final String KEY_SUITE_PATH = "suitePath"; + private static final String KEY_INDENT_SPACES = "indentSpaces"; + private static final String KEY_GENERATE_FILES = "generateFiles"; + private static final String KEY_OUTPUT_DIRECTORY = "outputDirectory"; + private static final String KEY_DELETE_EXISTING_FILES = "deleteExistingFiles"; + private static final String KEY_ROOT_FOLDER_IN_ODDGEN_VIEW = "rootFolderInOddgenView"; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append(KEY_USE_REALTIME_REPORTER, isUseRealtimeReporter()) + .append(KEY_UNSHARED_WORKSHEET, isUnsharedWorksheet()) + .append(KEY_RESET_PACKAGE, isResetPackage()) + .append(KEY_CLEAR_SCREEN, isClearScreen()) + .append(KEY_AUTO_EXECUTE, isAutoExecute()) + .append(KEY_CHECK_RUN_UTPLSQL_TEST, isCheckRunUtplsqlTest()) + .append(KEY_USE_SMART_TIMES, isUseSmartTimes()) + .append(KEY_NUMBER_OF_RUNS_IN_HISTORY, getNumberOfRunsInHistory()) + .append(KEY_SHOW_DISABLED_COUNTER, isShowDisabledCounter()) + .append(KEY_SHOW_WARNINGS_COUNTER, isShowWarningsCounter()) + .append(KEY_SHOW_INFO_COUNTER, isShowInfoCounter()) + .append(KEY_SHOW_WARNING_INDICATOR, isShowWarningIndicator()) + .append(KEY_SHOW_INFO_INDICATOR, isShowInfoIndicator()) + .append(KEY_SHOW_SUCCESSFUL_TESTS, isShowSuccessfulTests()) + .append(KEY_SHOW_DISABLED_TESTS, isShowDisabledTests()) + .append(KEY_SHOW_TEST_DESCRIPTION, isShowTestDescription()) + .append(KEY_SYNC_DETAIL_TAB, isSyncDetailTab()) + .append(KEY_SHOW_SUITES, isShowSuites()) + .append(KEY_TEST_PACKAGE_PREFIX, getTestPackagePrefix()) + .append(KEY_TEST_PACKAGE_SUFFIX, getTestPackageSuffix()) + .append(KEY_TEST_UNIT_PREFIX, getTestUnitPrefix()) + .append(KEY_TEST_UNIT_SUFFIX, getTestUnitSuffix()) + .append(KEY_NUMBER_OF_TESTS_PER_UNIT, getNumberOfTestsPerUnit()) + .append(KEY_CHECK_GENERATE_UTPLSQL_TEST, isCheckGenerateUtplsqlTest()) + .append(KEY_GENERATE_COMMENTS, isGenerateComments()) + .append(KEY_DISABLE_TESTS, isDisableTests()) + .append(KEY_SUITE_PATH, getSuitePath()) + .append(KEY_INDENT_SPACES, getIndentSpaces()) + .append(KEY_GENERATE_FILES, isGenerateFiles()) + .append(KEY_OUTPUT_DIRECTORY, getOutputDirectory()) + .append(KEY_DELETE_EXISTING_FILES, isDeleteExistingFiles()) + .append(KEY_ROOT_FOLDER_IN_ODDGEN_VIEW, getRootFolderInOddgenView()) + .toString(); + } + + public boolean isUseRealtimeReporter() { + return getHashStructure().getBoolean(KEY_USE_REALTIME_REPORTER, true); + } + + public void setUseRealtimeReporter(final boolean useRealtimeReporter) { + getHashStructure().putBoolean(KEY_USE_REALTIME_REPORTER, useRealtimeReporter); + } + + public boolean isUnsharedWorksheet() { + return getHashStructure().getBoolean(KEY_UNSHARED_WORKSHEET, true); + } + + public void setUnsharedWorksheet(final boolean unsharedWorksheet) { + getHashStructure().putBoolean(KEY_UNSHARED_WORKSHEET, unsharedWorksheet); + } + + public boolean isResetPackage() { + return getHashStructure().getBoolean(KEY_RESET_PACKAGE, false); + } + + public void setResetPackage(final boolean resetPackage) { + getHashStructure().putBoolean(KEY_RESET_PACKAGE, resetPackage); + } + + public boolean isClearScreen() { + return getHashStructure().getBoolean(KEY_CLEAR_SCREEN, false); + } + + public void setClearScreen(final boolean clearScreen) { + getHashStructure().putBoolean(KEY_CLEAR_SCREEN, clearScreen); + } + + public boolean isAutoExecute() { + return getHashStructure().getBoolean(KEY_AUTO_EXECUTE, true); + } + + public void setAutoExecute(final boolean autoExecute) { + getHashStructure().putBoolean(KEY_AUTO_EXECUTE, autoExecute); + } + + public boolean isCheckRunUtplsqlTest() { + return getHashStructure().getBoolean(KEY_CHECK_RUN_UTPLSQL_TEST, false); + } + + public void setCheckRunUtplsqlTest(final boolean checkRunUtplsqlTest) { + getHashStructure().putBoolean(KEY_CHECK_RUN_UTPLSQL_TEST, checkRunUtplsqlTest); + } + + public boolean isUseSmartTimes() { + return getHashStructure().getBoolean(KEY_USE_SMART_TIMES, false); + } + + public void setUseSmartTimes(final boolean useSmartTimes) { + getHashStructure().putBoolean(KEY_USE_SMART_TIMES, useSmartTimes); + } + + public int getNumberOfRunsInHistory() { + return getHashStructure().getInt(KEY_NUMBER_OF_RUNS_IN_HISTORY, 10); + } + + public void setNumberOfRunsInHistory(final int runs) { + getHashStructure().putInt(KEY_NUMBER_OF_RUNS_IN_HISTORY, runs); + } + + public boolean isShowDisabledCounter() { + return getHashStructure().getBoolean(KEY_SHOW_DISABLED_COUNTER, true); + } + + public void setShowDisabledCounter(final boolean showDisabledCounter) { + getHashStructure().putBoolean(KEY_SHOW_DISABLED_COUNTER, showDisabledCounter); + } + + public boolean isShowWarningsCounter() { + return getHashStructure().getBoolean(KEY_SHOW_WARNINGS_COUNTER, true); + } + + public void setShowWarningsCounter(final boolean showWarningCounter) { + getHashStructure().putBoolean(KEY_SHOW_WARNINGS_COUNTER, showWarningCounter); + } + + public boolean isShowInfoCounter() { + return getHashStructure().getBoolean(KEY_SHOW_INFO_COUNTER, true); + } + + public void setShowInfoCounter(final boolean showInfoCounter) { + getHashStructure().putBoolean(KEY_SHOW_INFO_COUNTER, showInfoCounter); + } + + public boolean isShowWarningIndicator() { + return getHashStructure().getBoolean(KEY_SHOW_WARNING_INDICATOR, true); + } + + public void setShowWarningIndicator(final boolean showWarningIndicator) { + getHashStructure().putBoolean(KEY_SHOW_WARNING_INDICATOR, showWarningIndicator); + } + + public boolean isShowInfoIndicator() { + return getHashStructure().getBoolean(KEY_SHOW_INFO_INDICATOR, true); + } + + public void setShowInfoIndicator(final boolean showInfoIndicator) { + getHashStructure().putBoolean(KEY_SHOW_INFO_INDICATOR, showInfoIndicator); + } + + public boolean isShowSuccessfulTests() { + return getHashStructure().getBoolean(KEY_SHOW_SUCCESSFUL_TESTS, true); + } + + public void setShowSuccessfulTests(final boolean showSuccessfulTests) { + getHashStructure().putBoolean(KEY_SHOW_SUCCESSFUL_TESTS, showSuccessfulTests); + } + + public boolean isShowDisabledTests() { + return getHashStructure().getBoolean(KEY_SHOW_DISABLED_TESTS, true); + } + + public void setShowDisabledTests(final boolean showDisabledTests) { + getHashStructure().putBoolean(KEY_SHOW_DISABLED_TESTS, showDisabledTests); + } + + public boolean isShowTestDescription() { + return getHashStructure().getBoolean(KEY_SHOW_TEST_DESCRIPTION, true); + } + + public void setShowTestDescription(final boolean showTestDescription) { + getHashStructure().putBoolean(KEY_SHOW_TEST_DESCRIPTION, showTestDescription); + } + + public boolean isSyncDetailTab() { + return getHashStructure().getBoolean(KEY_SYNC_DETAIL_TAB, true); + } + + public void setSyncDetailTab(final boolean syncDetailTab) { + getHashStructure().putBoolean(KEY_SYNC_DETAIL_TAB, syncDetailTab); + } + + public boolean isShowSuites() { + return getHashStructure().getBoolean(KEY_SHOW_SUITES, true); + } + + public void setShowSuites(final boolean showSuites) { + getHashStructure().putBoolean(KEY_SHOW_SUITES, showSuites); + } + + public String getTestPackagePrefix() { + return getHashStructure().getString(KEY_TEST_PACKAGE_PREFIX, "test_"); + } + + public void setTestPackagePrefix(final String testPackagePrefix) { + getHashStructure().putString(KEY_TEST_PACKAGE_PREFIX, testPackagePrefix); + } + + public String getTestPackageSuffix() { + return getHashStructure().getString(KEY_TEST_PACKAGE_SUFFIX, ""); + } + + public void setTestPackageSuffix(final String testPackageSuffix) { + getHashStructure().putString(KEY_TEST_PACKAGE_SUFFIX, testPackageSuffix); + } + + public String getTestUnitPrefix() { + return getHashStructure().getString(KEY_TEST_UNIT_PREFIX, ""); + } + + public void setTestUnitPrefix(final String testUnitPrefix) { + getHashStructure().putString(KEY_TEST_UNIT_PREFIX, testUnitPrefix); + } + + public String getTestUnitSuffix() { + return getHashStructure().getString(KEY_TEST_UNIT_SUFFIX, ""); + } + + public void setTestUnitSuffix(final String testUnitSuffix) { + getHashStructure().putString(KEY_TEST_UNIT_SUFFIX, testUnitSuffix); + } + + public int getNumberOfTestsPerUnit() { + return getHashStructure().getInt(KEY_NUMBER_OF_TESTS_PER_UNIT, 1); + } + + public void setNumberOfTestsPerUnit(final int numberOfTestsPerUnit) { + getHashStructure().putInt(KEY_NUMBER_OF_TESTS_PER_UNIT, numberOfTestsPerUnit); + } + + public boolean isCheckGenerateUtplsqlTest() { + return getHashStructure().getBoolean(KEY_CHECK_GENERATE_UTPLSQL_TEST, false); + } + + public void setCheckGenerateUtplsqlTest(final boolean checkGenerateUtplsqlTest) { + getHashStructure().putBoolean(KEY_CHECK_GENERATE_UTPLSQL_TEST, checkGenerateUtplsqlTest); + } + + public boolean isGenerateComments() { + return getHashStructure().getBoolean(KEY_GENERATE_COMMENTS, true); + } + + public void setGenerateComments(final boolean generateComments) { + getHashStructure().putBoolean(KEY_GENERATE_COMMENTS, generateComments); + } + + public boolean isDisableTests() { + return getHashStructure().getBoolean(KEY_DISABLE_TESTS, false); + } + + public void setDisableTests(final boolean disableTests) { + getHashStructure().putBoolean(KEY_DISABLE_TESTS, disableTests); + } + + public String getSuitePath() { + return getHashStructure().getString(KEY_SUITE_PATH, "alltests"); + } + + public void setSuitePath(final String suitePath) { + getHashStructure().putString(KEY_SUITE_PATH, suitePath); + } + + public int getIndentSpaces() { + return getHashStructure().getInt(KEY_INDENT_SPACES, 3); + } + + public void setIndentSpaces(final int indentSpaces) { + getHashStructure().putInt(KEY_INDENT_SPACES, indentSpaces); + } + + public boolean isGenerateFiles() { + return getHashStructure().getBoolean(KEY_GENERATE_FILES, true); + } + + public void setGenerateFiles(final boolean generateFiles) { + getHashStructure().putBoolean(KEY_GENERATE_FILES, generateFiles); + } + + public String getOutputDirectory() { + return getHashStructure().getString(KEY_OUTPUT_DIRECTORY, DEFAULT_OUTPUT_DIRECTORY); + } + + public void setOutputDirectory(final String outputDirectory) { + final String dir = outputDirectory.isEmpty() ? DEFAULT_OUTPUT_DIRECTORY : outputDirectory; + getHashStructure().putString(KEY_OUTPUT_DIRECTORY, dir); + } + + public boolean isDeleteExistingFiles() { + return getHashStructure().getBoolean(KEY_DELETE_EXISTING_FILES, false); + } + + public void setDeleteExistingFiles(final boolean deleteExistingFiles) { + getHashStructure().putBoolean(KEY_DELETE_EXISTING_FILES, deleteExistingFiles); + } + + public String getRootFolderInOddgenView() { + return getHashStructure().getString(KEY_ROOT_FOLDER_IN_ODDGEN_VIEW, "utPLSQL"); + } + + public void setRootFolderInOddgenView(final String rootFolder) { + final String folder = rootFolder.isEmpty() ? "utPLSQL" : rootFolder; + getHashStructure().putString(KEY_ROOT_FOLDER_IN_ODDGEN_VIEW, folder); + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.xtend deleted file mode 100644 index 6ae9ea80..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/preference/PreferenceModel.xtend +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.preference - -import java.io.File -import oracle.javatools.data.HashStructure -import oracle.javatools.data.HashStructureAdapter -import oracle.javatools.data.PropertyStorage -import org.eclipse.xtext.xbase.lib.util.ToStringBuilder - -class PreferenceModel extends HashStructureAdapter { - public static final String DEFAULT_OUTPUT_DIRECTORY = '''«System.getProperty("user.home")»«File.separator»utplsql«File.separator»generated''' - static final String DATA_KEY = "utplsql" - - private new(HashStructure hash) { - super(hash) - } - - def static getInstance(PropertyStorage prefs) { - return new PreferenceModel(findOrCreate(prefs, DATA_KEY)) - } - - static final String KEY_USE_REALTIME_REPORTER = "useRealtimeRorter" - static final String KEY_UNSHARED_WORKSHEET = "unsharedWorksheet" - static final String KEY_RESET_PACKAGE = "resetPackage" - static final String KEY_CLEAR_SCREEN = "clearScreen" - static final String KEY_AUTO_EXECUTE = "autoExecute" - static final String KEY_CHECK_RUN_UTPLSQL_TEST = "checkRunUtplsqlTest" - static final String KEY_USE_SMART_TIMES = "useSmartTimes" - static final String KEY_NUMBER_OF_RUNS_IN_HISTORY = "numberOfRunsInHistory" - static final String KEY_SHOW_DISABLED_COUNTER = "showDisabledCounter" - static final String KEY_SHOW_WARNINGS_COUNTER = "showWarningsCounter" - static final String KEY_SHOW_INFO_COUNTER = "showInfoCounter" - static final String KEY_SHOW_WARNING_INDICATOR = "showWarningIndicator" - static final String KEY_SHOW_INFO_INDICATOR = "showInfoIndicator" - static final String KEY_SHOW_SUCCESSFUL_TESTS = "showSuccessfulTests" - static final String KEY_SHOW_DISABLED_TESTS = "showDisabledTests" - static final String KEY_SHOW_TEST_DESCRIPTION = "showTestDescription" - static final String KEY_SYNC_DETAIL_TAB = "syncDetailTab" - static final String KEY_TEST_PACKAGE_PREFIX = "testPackagePrefix" - static final String KEY_TEST_PACKAGE_SUFFIX = "testPackageSuffix" - static final String KEY_TEST_UNIT_PREFIX = "testUnitPrefix" - static final String KEY_TEST_UNIT_SUFFIX = "testUnitSuffix" - static final String KEY_NUMBER_OF_TESTS_PER_UNIT = "numberOfTestsPerUnit" - static final String KEY_CHECK_GENERATE_UTPLSQL_TEST = "checkGenerateUtplsqlTest" - static final String KEY_GENERATE_COMMENTS = "generateComments" - static final String KEY_DISABLE_TESTS = "disableTests" - static final String KEY_SUITE_PATH="suitePath" - static final String KEY_INDENT_SPACES="indentSpaces" - static final String KEY_GENERATE_FILES="generateFiles" - static final String KEY_OUTPUT_DIRECTORY = "outputDirectory" - static final String KEY_DELETE_EXISTING_FILES="deleteExistingFiles" - static final String KEY_ROOT_FOLDER_IN_ODDGEN_VIEW = "rootFolderInOddgenView" - - def isUseRealtimeReporter() { - return getHashStructure.getBoolean(PreferenceModel.KEY_USE_REALTIME_REPORTER, true) - } - - def setUseRealtimeReporter(boolean useRealtimeReporter) { - getHashStructure.putBoolean(PreferenceModel.KEY_USE_REALTIME_REPORTER, useRealtimeReporter) - } - - def isUnsharedWorksheet() { - return getHashStructure.getBoolean(PreferenceModel.KEY_UNSHARED_WORKSHEET, true) - } - - def setUnsharedWorksheet(boolean unsharedWorksheet) { - getHashStructure.putBoolean(PreferenceModel.KEY_UNSHARED_WORKSHEET, unsharedWorksheet) - } - - def isResetPackage() { - return getHashStructure.getBoolean(PreferenceModel.KEY_RESET_PACKAGE, false) - } - - def setResetPackage(boolean resetPackage) { - getHashStructure.putBoolean(PreferenceModel.KEY_RESET_PACKAGE, resetPackage) - } - - def isClearScreen() { - return getHashStructure.getBoolean(PreferenceModel.KEY_CLEAR_SCREEN, false) - } - - def setClearScreen(boolean clearScreen) { - getHashStructure.putBoolean(PreferenceModel.KEY_CLEAR_SCREEN, clearScreen) - } - - def isAutoExecute() { - return getHashStructure.getBoolean(PreferenceModel.KEY_AUTO_EXECUTE, true) - } - - def setAutoExecute(boolean autoExecute) { - getHashStructure.putBoolean(PreferenceModel.KEY_AUTO_EXECUTE, autoExecute) - } - - def isCheckRunUtplsqlTest() { - return getHashStructure.getBoolean(PreferenceModel.KEY_CHECK_RUN_UTPLSQL_TEST, false) - } - - def setCheckRunUtplsqlTest(boolean checkRunUtplsqlTest) { - getHashStructure.putBoolean(PreferenceModel.KEY_CHECK_RUN_UTPLSQL_TEST, checkRunUtplsqlTest) - } - - def isUseSmartTimes() { - return getHashStructure.getBoolean(PreferenceModel.KEY_USE_SMART_TIMES, false) - } - - def setUseSmartTimes(boolean useSmartTimes) { - getHashStructure.putBoolean(PreferenceModel.KEY_USE_SMART_TIMES, useSmartTimes) - } - - def getNumberOfRunsInHistory() { - return getHashStructure.getInt(PreferenceModel.KEY_NUMBER_OF_RUNS_IN_HISTORY, 10) - } - - def setNumberOfRunsInHistory(int runs) { - getHashStructure.putInt(PreferenceModel.KEY_NUMBER_OF_RUNS_IN_HISTORY, runs) - } - - def isShowDisabledCounter() { - return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_DISABLED_COUNTER, false) - } - - def setShowDisabledCounter(boolean showDisabledCounter) { - getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_DISABLED_COUNTER, showDisabledCounter) - } - - def isShowWarningsCounter() { - return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_WARNINGS_COUNTER, false) - } - - def setShowWarningsCounter(boolean showWarningCounter) { - getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_WARNINGS_COUNTER, showWarningCounter) - } - - def isShowInfoCounter() { - return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_INFO_COUNTER, false) - } - - def setShowInfoCounter(boolean showInfoCounter) { - getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_INFO_COUNTER, showInfoCounter) - } - - def isShowWarningIndicator() { - return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_WARNING_INDICATOR, false) - } - - def setShowWarningIndicator(boolean showWarningIndicator) { - getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_WARNING_INDICATOR, showWarningIndicator) - } - - def isShowInfoIndicator() { - return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_INFO_INDICATOR, false) - } - - def setShowInfoIndicator(boolean showInfoIndicator) { - getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_INFO_INDICATOR, showInfoIndicator) - } - - def isShowSuccessfulTests() { - return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_SUCCESSFUL_TESTS, true) - } - - def setShowSuccessfulTests(boolean showSuccessfulTests) { - getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_SUCCESSFUL_TESTS, showSuccessfulTests) - } - - def isShowDisabledTests() { - return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_DISABLED_TESTS, true) - } - - def setShowDisabledTests(boolean showDisabledTests) { - getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_DISABLED_TESTS, showDisabledTests) - } - - def isShowTestDescription() { - return getHashStructure.getBoolean(PreferenceModel.KEY_SHOW_TEST_DESCRIPTION, false) - } - - def setShowTestDescription(boolean showTestDescription) { - getHashStructure.putBoolean(PreferenceModel.KEY_SHOW_TEST_DESCRIPTION, showTestDescription) - } - - def isSyncDetailTab() { - return getHashStructure.getBoolean(PreferenceModel.KEY_SYNC_DETAIL_TAB, true) - } - - def setSyncDetailTab(boolean syncDetailTab) { - getHashStructure.putBoolean(PreferenceModel.KEY_SYNC_DETAIL_TAB, syncDetailTab) - } - - def getTestPackagePrefix() { - return getHashStructure.getString(PreferenceModel.KEY_TEST_PACKAGE_PREFIX, "test_") - } - - def setTestPackagePrefix(String testPackagePrefix) { - getHashStructure.putString(PreferenceModel.KEY_TEST_PACKAGE_PREFIX, testPackagePrefix) - } - - def getTestPackageSuffix() { - return getHashStructure.getString(PreferenceModel.KEY_TEST_PACKAGE_SUFFIX, "") - } - - def setTestPackageSuffix(String testPackageSuffix) { - getHashStructure.putString(PreferenceModel.KEY_TEST_PACKAGE_SUFFIX, testPackageSuffix) - } - - def getTestUnitPrefix() { - return getHashStructure.getString(PreferenceModel.KEY_TEST_UNIT_PREFIX, "") - } - - def setTestUnitPrefix(String testUnitPrefix) { - getHashStructure.putString(PreferenceModel.KEY_TEST_UNIT_PREFIX, testUnitPrefix) - } - - def getTestUnitSuffix() { - return getHashStructure.getString(PreferenceModel.KEY_TEST_UNIT_SUFFIX, "") - } - - def setTestUnitSuffix(String testUnitSuffix) { - getHashStructure.putString(PreferenceModel.KEY_TEST_UNIT_SUFFIX, testUnitSuffix) - } - - def getNumberOfTestsPerUnit() { - return getHashStructure.getInt(PreferenceModel.KEY_NUMBER_OF_TESTS_PER_UNIT, 1) - } - - def setNumberOfTestsPerUnit(int numberOfTestsPerUnit) { - getHashStructure.putInt(PreferenceModel.KEY_NUMBER_OF_TESTS_PER_UNIT, numberOfTestsPerUnit) - } - - def isCheckGenerateUtplsqlTest() { - return getHashStructure.getBoolean(PreferenceModel.KEY_CHECK_GENERATE_UTPLSQL_TEST, false) - } - - def setCheckGenerateUtplsqlTest(boolean checkGenerateUtplsqlTest) { - getHashStructure.putBoolean(PreferenceModel.KEY_CHECK_GENERATE_UTPLSQL_TEST, checkGenerateUtplsqlTest) - } - - def isGenerateComments() { - return getHashStructure.getBoolean(PreferenceModel.KEY_GENERATE_COMMENTS, true) - } - - def setGenerateComments(boolean generateComments) { - getHashStructure.putBoolean(PreferenceModel.KEY_GENERATE_COMMENTS, generateComments) - } - - def isDisableTests() { - return getHashStructure.getBoolean(PreferenceModel.KEY_DISABLE_TESTS, false) - } - - def setDisableTests(boolean disableTests) { - getHashStructure.putBoolean(PreferenceModel.KEY_DISABLE_TESTS, disableTests) - } - - def getSuitePath() { - return getHashStructure.getString(PreferenceModel.KEY_SUITE_PATH, "alltests") - } - - def setSuitePath(String suitePath) { - getHashStructure.putString(PreferenceModel.KEY_SUITE_PATH, suitePath) - } - - def getIndentSpaces() { - return getHashStructure.getInt(PreferenceModel.KEY_INDENT_SPACES, 3) - } - - def setIndentSpaces(int indentSpaces) { - getHashStructure.putInt(PreferenceModel.KEY_INDENT_SPACES, indentSpaces) - } - - def isGenerateFiles() { - return getHashStructure.getBoolean(PreferenceModel.KEY_GENERATE_FILES, true) - } - - def setGenerateFiles(boolean generateFiles) { - getHashStructure.putBoolean(PreferenceModel.KEY_GENERATE_FILES, generateFiles) - } - - def getOutputDirectory() { - return getHashStructure.getString(PreferenceModel.KEY_OUTPUT_DIRECTORY, DEFAULT_OUTPUT_DIRECTORY) - } - - def setOutputDirectory(String outputDirectory) { - val dir = if (outputDirectory.empty) {DEFAULT_OUTPUT_DIRECTORY} else {outputDirectory} - getHashStructure.putString(PreferenceModel.KEY_OUTPUT_DIRECTORY, dir) - } - - def isDeleteExistingFiles() { - return getHashStructure.getBoolean(PreferenceModel.KEY_DELETE_EXISTING_FILES, false) - } - - def setDeleteExistingFiles(boolean deleteExistingFiles) { - getHashStructure.putBoolean(PreferenceModel.KEY_DELETE_EXISTING_FILES, deleteExistingFiles) - } - - def getRootFolderInOddgenView() { - return getHashStructure.getString(PreferenceModel.KEY_ROOT_FOLDER_IN_ODDGEN_VIEW, "utPLSQL") - } - - def setRootFolderInOddgenView(String rootFolder) { - val folder = if (rootFolder.empty) {"utPLSQL"} else {rootFolder} - getHashStructure.putString(PreferenceModel.KEY_ROOT_FOLDER_IN_ODDGEN_VIEW, folder) - } - - override toString() { - new ToStringBuilder(this).addAllFields.toString - } -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.java new file mode 100644 index 00000000..255a52cc --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.java @@ -0,0 +1,86 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.runner; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public class Counter { + private Integer disabled; + private Integer success; + private Integer failure; + private Integer error; + private Integer warning; + + public Counter() { + disabled = 0; + success = 0; + failure = 0; + error = 0; + warning = 0; + } + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append("disabled", disabled) + .append("success", success) + .append("failure", failure) + .append("error", error) + .append("warning", warning) + .toString(); + } + + public Integer getDisabled() { + return disabled; + } + + public void setDisabled(final Integer disabled) { + this.disabled = disabled; + } + + public Integer getSuccess() { + return success; + } + + public void setSuccess(final Integer success) { + this.success = success; + } + + public Integer getFailure() { + return failure; + } + + public void setFailure(final Integer failure) { + this.failure = failure; + } + + public Integer getError() { + return error; + } + + public void setError(final Integer error) { + this.error = error; + } + + public Integer getWarning() { + return warning; + } + + public void setWarning(final Integer warning) { + this.warning = warning; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.xtend deleted file mode 100644 index 0d496e42..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Counter.xtend +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.runner - -import org.eclipse.xtend.lib.annotations.Accessors -import org.utplsql.sqldev.model.AbstractModel - -@Accessors -class Counter extends AbstractModel { - Integer disabled - Integer success - Integer failure - Integer error - Integer warning -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.java new file mode 100644 index 00000000..2ca29452 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.java @@ -0,0 +1,100 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.runner; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public class Expectation { + private String description; + private String message; + private String caller; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append("description", description) + .append("message", message) + .append("caller", caller) + .append("failureText", getFailureText()) + .append("shortFailureText", getShortFailureText()) + .append("callerLine", getCallerLine()) + .toString(); + } + + public String getFailureText() { + final StringBuilder sb = new StringBuilder(); + sb.append(message.trim()); + if (caller != null) { + sb.append('\n'); + sb.append(caller.trim()); + } + return sb.toString(); + } + + public String getShortFailureText() { + final StringBuilder sb = new StringBuilder(); + if (description != null) { + sb.append(description); + sb.append(" (line "); + sb.append(getCallerLine()); + sb.append(")"); + } else { + sb.append("Line "); + sb.append(getCallerLine()); + } + return sb.toString(); + } + + public Integer getCallerLine() { + Integer line = null; + if (caller != null) { + final Pattern p = Pattern.compile("(?i)\"[^\\\"]+\",\\s+line\\s*([0-9]+)"); + final Matcher m = p.matcher(caller); + if (m.find()) { + line = Integer.valueOf(m.group(1)); + } + } + return line; + } + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } + + public String getMessage() { + return message; + } + + public void setMessage(final String message) { + this.message = message; + } + + public String getCaller() { + return caller; + } + + public void setCaller(final String caller) { + this.caller = caller; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.xtend deleted file mode 100644 index 67d7cc19..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Expectation.xtend +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.runner - -import java.util.regex.Pattern -import org.eclipse.xtend.lib.annotations.Accessors -import org.utplsql.sqldev.model.AbstractModel - -@Accessors -class Expectation extends AbstractModel { - String description - String message - String caller - - def getFailureText() { - return ''' - «message.trim» - «caller?.trim» - '''.toString.trim - } - - def getShortFailureText() { - return '''«IF description !== null»«description» (line «callerLine»)«ELSE»Line «callerLine»«ENDIF»'''.toString - } - - def getCallerLine() { - var Integer line = null - if (caller !== null) { - val p = Pattern.compile("(?i)\"[^\\\"]+\",\\s+line\\s*([0-9]+)") - val m = p.matcher(caller) - if (m.find) { - line = Integer.valueOf(m.group(1)) - } - } - return line - } -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.java new file mode 100644 index 00000000..76b859c2 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.java @@ -0,0 +1,193 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.runner; + +import javax.swing.Icon; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; +import org.utplsql.sqldev.resources.UtplsqlResources; + +public abstract class Item { + private String id; + private String name; + private String description; + private String startTime; + private String endTime; + private Double executionTime; + private Counter counter; + private String errorStack; + private String serverOutput; + private String warnings; + + public Item() { + counter = new Counter(); + } + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append("id", id) + .append("name", name) + .append("description", description) + .append("startTime", startTime) + .append("endTime", endTime) + .append("executionTime", executionTime) + .append("counter", counter) + .append("errorStack", errorStack) + .append("serverOutput", serverOutput) + .append("warnings", warnings) + .append("parentId", getParentId()) + .append("statusIcon", getStatusIcon()) + .append("warningIcon", getWarningIcon()) + .append("infoIcon", getInfoIcon()) + .toString(); + } + + public String getParentId() { + // Works only if id (suitepath) is build based on names delimited with a period + // that's expected for real utPLSQL runs, but may fail for artificial runs. + // Returning null is valid, it means this item has no parent and as a + // consequence it will be shown on the top level in the runner. + // A key is required to identify an item since suites can be delivered + // multiple times, e.g. when running a chosen list of tests. This way + // the tests will shown at the right position in the tree, regardless of the call + // parameters. + if (name != null && id != null && name.length() < id.length() && id.endsWith(name)) { + return id.substring(0, id.length() - name.length() - 1); + } + return null; + } + + public Icon getStatusIcon() { + Icon icon = null; + if (getStartTime() != null && getEndTime() == null) { + icon = UtplsqlResources.getIcon("PROGRESS_ICON"); + } else { + if (getCounter() != null) { + // Escalation logic as for the color of the progress bar. + // A suite with errors or failed tests cannot be considered successful, + // even if some tests completed successfully. + if (getCounter().getError() > 0) { + icon = UtplsqlResources.getIcon("ERROR_ICON"); + } else if (getCounter().getFailure() > 0) { + icon = UtplsqlResources.getIcon("FAILURE_ICON"); + } else if (getCounter().getSuccess() > 0) { + icon = UtplsqlResources.getIcon("SUCCESS_ICON"); + } else if (getCounter().getDisabled() > 0) { + icon = UtplsqlResources.getIcon("DISABLED_ICON"); + } + } + } + return icon; + } + + public Icon getWarningIcon() { + Icon icon = null; + if (getCounter() != null && getCounter().getWarning() > 0) { + icon = UtplsqlResources.getIcon("WARNING_ICON"); + } + return icon; + } + + public Icon getInfoIcon() { + Icon icon = null; + if (getServerOutput() != null && getServerOutput().length() > 0) { + icon = UtplsqlResources.getIcon("INFO_ICON"); + } + return icon; + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(final String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(final String endTime) { + this.endTime = endTime; + } + + public Double getExecutionTime() { + return executionTime; + } + + public void setExecutionTime(final Double executionTime) { + this.executionTime = executionTime; + } + + public Counter getCounter() { + return counter; + } + + public void setCounter(final Counter counter) { + this.counter = counter; + } + + public String getErrorStack() { + return errorStack; + } + + public void setErrorStack(final String errorStack) { + this.errorStack = errorStack; + } + + public String getServerOutput() { + return serverOutput; + } + + public void setServerOutput(final String serverOutput) { + this.serverOutput = serverOutput; + } + + public String getWarnings() { + return warnings; + } + + public void setWarnings(final String warnings) { + this.warnings = warnings; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.xtend deleted file mode 100644 index 01db70a6..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Item.xtend +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.runner - -import org.eclipse.xtend.lib.annotations.Accessors -import org.utplsql.sqldev.model.AbstractModel - -@Accessors -abstract class Item extends AbstractModel { - String id - String startTime - String endTime - Double executionTime - Counter counter - String errorStack - String serverOutput - String warnings -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/ItemNode.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/ItemNode.java new file mode 100644 index 00000000..591e1b3f --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/ItemNode.java @@ -0,0 +1,201 @@ +/* + * Copyright 2021 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.runner; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.swing.Icon; +import javax.swing.tree.DefaultMutableTreeNode; + +import org.utplsql.sqldev.resources.UtplsqlResources; + +public class ItemNode extends DefaultMutableTreeNode implements Comparable { + + private static final long serialVersionUID = -4053143673822661743L; + + public ItemNode(Item userObject) { + super(userObject, userObject instanceof Suite); + } + + @Override + public int compareTo(ItemNode other) { + return getId().compareTo(other.getId()); + } + + public String getId() { + return ((Item) getUserObject()).getId(); + } + + public String getName() { + return ((Item) getUserObject()).getName(); + } + + public String getDescription() { + return ((Item) getUserObject()).getDescription(); + } + + public Double getExecutionTime() { + return ((Item) getUserObject()).getExecutionTime(); + } + + public Set getTestPackages() { + HashSet testPackages = new HashSet<>(); + Enumeration orderedNodes = preorderEnumeration(); + while (orderedNodes.hasMoreElements()) { + ItemNode node = (ItemNode) orderedNodes.nextElement(); + if (node.getUserObject() instanceof Test) { + Test test = (Test) node.getUserObject(); + testPackages.add(test.getOwnerName() + "." + test.getObjectName()); + } + } + return testPackages; + } + + public Set getOwners() { + HashSet owners = new HashSet<>(); + Enumeration children = children(); + while (children.hasMoreElements()) { + ItemNode child = (ItemNode) children.nextElement(); + owners.add(child.getOwnerName()); + } + return owners; + } + + public String getOwnerName() { + String ownerName = null; + Enumeration orderedNodes = preorderEnumeration(); + while (orderedNodes.hasMoreElements()) { + ItemNode node = (ItemNode) orderedNodes.nextElement(); + if (node.getUserObject() instanceof Test) { + Test test = (Test) node.getUserObject(); + if (ownerName == null) { + ownerName = test.getOwnerName(); + } else if (!ownerName.equals(test.getOwnerName())) { + ownerName = "***"; + break; + } + } + } + return ownerName; + } + + public String getPackageName() { + String packageName = null; + Enumeration orderedNodes = preorderEnumeration(); + while (orderedNodes.hasMoreElements()) { + ItemNode node = (ItemNode) orderedNodes.nextElement(); + if (node.getUserObject() instanceof Test) { + Test test = (Test) node.getUserObject(); + if (packageName == null) { + packageName = test.getObjectName(); + } else if (!packageName.equals(test.getObjectName())) { + packageName = "***"; + break; + } + } + } + return packageName; + } + + public String getProcedureName() { + String procedureName = null; + Enumeration orderedNodes = preorderEnumeration(); + while (orderedNodes.hasMoreElements()) { + ItemNode node = (ItemNode) orderedNodes.nextElement(); + if (node.getUserObject() instanceof Test) { + Test test = (Test) node.getUserObject(); + if (procedureName == null) { + procedureName = test.getProcedureName(); + } else if (!procedureName.equals(test.getProcedureName())) { + procedureName = "***"; + break; + } + } + } + return procedureName; + } + + public Icon getStatusIcon() { + Item item = (Item) getUserObject(); + Icon icon = item.getStatusIcon(); + if (icon == null) { + if (item.getId() != null) { + if (item instanceof Test) { + icon = UtplsqlResources.getIcon("PROCEDURE_ICON"); + } else if (item.getId().contains("context_#")) { + icon = UtplsqlResources.getIcon("PROCEDURE_FOLDER_ICON"); + } else { + if (item.getName().equals(getPackageName())) { + icon = UtplsqlResources.getIcon("PACKAGE_ICON"); + } else { + icon = UtplsqlResources.getIcon("PACKAGE_FOLDER_ICON"); + } + } + } + } + return icon; + } + + public Icon getWarningIcon() { + return ((Item) getUserObject()).getWarningIcon(); + } + + public Icon getInfoIcon() { + return ((Item) getUserObject()).getInfoIcon(); + } + + /** + * Calculates non-overlapping items. + * + * This can be used to build a list of suites to be started by utPLSQL while ensuring that + * + * - all requested tests are executed, but not more than once + * - the test execution is efficient by ensuring that the list is as short as possible + * + * This means if all tests of a suite shall be executed that the suit should be + * part of the result list and not all of its tests. + * + * In other words, top-level nodes are preferred to produce an optimal result. + * + * @param selectedNodes all selected nodes must be part of the same tree + * @return non-overlapping set of nodes + */ + public static Set createNonOverlappingSet(List selectedNodes) { + HashSet result = new HashSet<>(); + if (selectedNodes != null && selectedNodes.size() > 0) { + HashSet expandedResult = new HashSet<>(); + List sortedNodes = new ArrayList<>(selectedNodes); + Collections.sort(sortedNodes); + for (ItemNode sortedNode : sortedNodes) { + if (!expandedResult.contains(sortedNode)) { + result.add(sortedNode); + Enumeration expandedNodes = sortedNode.preorderEnumeration(); + while (expandedNodes.hasMoreElements()) { + ItemNode expandedNode = (ItemNode) expandedNodes.nextElement(); + expandedResult.add(expandedNode); + } + } + } + } + return result; + } + +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.java new file mode 100644 index 00000000..73b4599a --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.java @@ -0,0 +1,102 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.runner; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public abstract class PostEvent extends RealtimeReporterEvent { + private String startTime; + private String endTime; + private Double executionTime; + private Counter counter; + private String errorStack; + private String serverOutput; + private String warnings; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append("startTime", startTime) + .append("endTime", endTime) + .append("executionTime", executionTime) + .append("counter", counter) + .append("errorStack", errorStack) + .append("serverOutput", serverOutput) + .append("warnings", warnings) + .toString(); + } + + public PostEvent() { + counter = new Counter(); + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(final String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(final String endTime) { + this.endTime = endTime; + } + + public Double getExecutionTime() { + return executionTime; + } + + public void setExecutionTime(final Double executionTime) { + this.executionTime = executionTime; + } + + public Counter getCounter() { + return counter; + } + + public void setCounter(final Counter counter) { + this.counter = counter; + } + + public String getErrorStack() { + return errorStack; + } + + public void setErrorStack(final String errorStack) { + this.errorStack = errorStack; + } + + public String getServerOutput() { + return serverOutput; + } + + public void setServerOutput(final String serverOutput) { + this.serverOutput = serverOutput; + } + + public String getWarnings() { + return warnings; + } + + public void setWarnings(final String warnings) { + this.warnings = warnings; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.xtend deleted file mode 100644 index 67d9c61c..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostEvent.xtend +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.runner - -import org.eclipse.xtend.lib.annotations.Accessors - -@Accessors -abstract class PostEvent extends RealtimeReporterEvent { - String startTime - String endTime - Double executionTime - Counter counter - String errorStack - String serverOutput - String warnings - - new() { - counter = new Counter - } -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.java new file mode 100644 index 00000000..0a167432 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.runner; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public class PostRunEvent extends PostEvent { + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + // ancestor + .append("startTime", getStartTime()) + .append("endTime", getEndTime()) + .append("executionTime", getExecutionTime()) + .append("counter", getCounter()) + .append("errorStack", getErrorStack()) + .append("serverOutput", getServerOutput()) + .append("warnings", getWarnings()) + .toString(); + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.java new file mode 100644 index 00000000..94671c33 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.java @@ -0,0 +1,47 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.runner; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public class PostSuiteEvent extends PostEvent { + private String id; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + // ancestor + .append("startTime", getStartTime()) + .append("endTime", getEndTime()) + .append("executionTime", getExecutionTime()) + .append("counter", getCounter()) + .append("errorStack", getErrorStack()) + .append("serverOutput", getServerOutput()) + .append("warnings", getWarnings()) + // local + .append("id", id) + .toString(); + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.xtend deleted file mode 100644 index 10a674ed..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostSuiteEvent.xtend +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.runner - -import org.eclipse.xtend.lib.annotations.Accessors - -@Accessors -class PostSuiteEvent extends PostEvent { - String id -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.java new file mode 100644 index 00000000..36b99abd --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.java @@ -0,0 +1,84 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.runner; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public class PostTestEvent extends PostEvent { + private String id; + private Integer testNumber; + private Integer totalNumberOfTests; + private List failedExpectations; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + // ancestor + .append("startTime", getStartTime()) + .append("endTime", getEndTime()) + .append("executionTime", getExecutionTime()) + .append("counter", getCounter()) + .append("errorStack", getErrorStack()) + .append("serverOutput", getServerOutput()) + .append("warnings", getWarnings()) + // local + .append("id", id) + .append("testNumber", testNumber) + .append("totalNumberOfTests", totalNumberOfTests) + .append("failedExpectations", failedExpectations) + .toString(); + } + + public PostTestEvent() { + failedExpectations = new ArrayList<>(); + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public Integer getTestNumber() { + return testNumber; + } + + public void setTestNumber(final Integer testNumber) { + this.testNumber = testNumber; + } + + public Integer getTotalNumberOfTests() { + return totalNumberOfTests; + } + + public void setTotalNumberOfTests(final Integer totalNumberOfTests) { + this.totalNumberOfTests = totalNumberOfTests; + } + + public List getFailedExpectations() { + return failedExpectations; + } + + public void setFailedExpectations(final List failedExpectations) { + this.failedExpectations = failedExpectations; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.xtend deleted file mode 100644 index bd28ab6c..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostTestEvent.xtend +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.runner - -import java.util.ArrayList -import java.util.List -import org.eclipse.xtend.lib.annotations.Accessors - -@Accessors -class PostTestEvent extends PostEvent { - String id - Integer testNumber - Integer totalNumberOfTests - List failedExpectations - - new() { - failedExpectations = new ArrayList - } -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.java new file mode 100644 index 00000000..3e150fec --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.java @@ -0,0 +1,55 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.runner; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public class PreRunEvent extends RealtimeReporterEvent { + private List items; + private Integer totalNumberOfTests; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append("items", items) + .append("totalNumberOfTests", totalNumberOfTests) + .toString(); + } + + public PreRunEvent() { + items = new ArrayList<>(); + } + + public List getItems() { + return items; + } + + public void setItems(final List items) { + this.items = items; + } + + public Integer getTotalNumberOfTests() { + return totalNumberOfTests; + } + + public void setTotalNumberOfTests(final Integer totalNumberOfTests) { + this.totalNumberOfTests = totalNumberOfTests; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.xtend deleted file mode 100644 index 5750cd8e..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreRunEvent.xtend +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.runner - -import java.util.ArrayList -import java.util.List -import org.eclipse.xtend.lib.annotations.Accessors - -@Accessors -class PreRunEvent extends RealtimeReporterEvent { - List items - Integer totalNumberOfTests - - new() { - items = new ArrayList - } -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/oddgen/GenContext.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.java similarity index 54% rename from sqldev/src/main/java/org/utplsql/sqldev/model/oddgen/GenContext.xtend rename to sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.java index bb4afc77..7026952a 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/oddgen/GenContext.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.java @@ -13,24 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev.model.oddgen +package org.utplsql.sqldev.model.runner; -import java.sql.Connection -import org.eclipse.xtend.lib.annotations.Accessors -import org.utplsql.sqldev.model.AbstractModel +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; -@Accessors -class GenContext extends AbstractModel { - Connection conn - String objectType - String objectName - String testPackagePrefix - String testPackageSuffix - String testUnitPrefix - String testUnitSuffix - int numberOfTestsPerUnit - boolean generateComments - boolean disableTests - String suitePath - int indentSpaces +public class PreSuiteEvent extends RealtimeReporterEvent { + private String id; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append("id", id) + .toString(); + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } } diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.xtend deleted file mode 100644 index 0304f071..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreSuiteEvent.xtend +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.runner - -import org.eclipse.xtend.lib.annotations.Accessors - -@Accessors -class PreSuiteEvent extends RealtimeReporterEvent { - String id -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.java new file mode 100644 index 00000000..30019335 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.java @@ -0,0 +1,58 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.runner; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public class PreTestEvent extends RealtimeReporterEvent { + private String id; + private Integer testNumber; + private Integer totalNumberOfTests; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append("id", id) + .append("testNumber", testNumber) + .append("totalNumberOfTests", totalNumberOfTests) + .toString(); + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public Integer getTestNumber() { + return testNumber; + } + + public void setTestNumber(final Integer testNumber) { + this.testNumber = testNumber; + } + + public Integer getTotalNumberOfTests() { + return totalNumberOfTests; + } + + public void setTotalNumberOfTests(final Integer totalNumberOfTests) { + this.totalNumberOfTests = totalNumberOfTests; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.xtend deleted file mode 100644 index a54e3c5e..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PreTestEvent.xtend +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.runner - -import org.eclipse.xtend.lib.annotations.Accessors - -@Accessors -class PreTestEvent extends RealtimeReporterEvent { - String id - Integer testNumber - Integer totalNumberOfTests -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RealtimeReporterEvent.java similarity index 81% rename from sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.xtend rename to sqldev/src/main/java/org/utplsql/sqldev/model/runner/RealtimeReporterEvent.java index ee5edc30..3f7d7d1d 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/PostRunEvent.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RealtimeReporterEvent.java @@ -13,10 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev.model.runner +package org.utplsql.sqldev.model.runner; -import org.eclipse.xtend.lib.annotations.Accessors - -@Accessors -class PostRunEvent extends PostEvent { +public abstract class RealtimeReporterEvent { } diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.java new file mode 100644 index 00000000..09d38e2f --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.java @@ -0,0 +1,304 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.runner; + +import java.sql.Connection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; +import org.utplsql.sqldev.model.URLTools; + +public class Run { + private String reporterId; + private String connectionName; + private List pathList; + private Integer currentTestNumber; + private Test currentTest; + private Integer totalNumberOfTests; + private String startTime; + private String endTime; + private Double executionTime; + private Counter counter; + private Integer infoCount; + private String errorStack; + private String serverOutput; + private final Set items; + private Map tests; + private Map itemNodes; + private String status; + private Long start; + // to abort connections, producerConn is handled by UtplsqlRunner + private Connection consumerConn; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append("reporterId", reporterId) + .append("connectionName", connectionName) + .append("pathList", pathList) + .append("currentTestNumber", currentTestNumber) + .append("currentTest", currentTest) + .append("totalNumberOfTests", totalNumberOfTests) + .append("startTime", startTime) + .append("endTime", endTime) + .append("executionTime", executionTime) + .append("counter", counter) + .append("infoCount", infoCount) + .append("errorStack", errorStack) + .append("serverOutput", serverOutput) + .append("tests", tests) + .append("rootNode", itemNodes.get(reporterId)) + .append("status", status) + .append("start", start) + .append("endTime", endTime) + .append("totalNumberOfCompletedTests", getTotalNumberOfCompletedTests()) + .toString(); + } + + public Run(final String reporterId, final String connectionName, final List pathList) { + this.reporterId = reporterId; + this.connectionName = connectionName; + this.pathList = pathList; + counter = new Counter(); + items = new LinkedHashSet<>(); + tests = new LinkedHashMap<>(); + itemNodes = new LinkedHashMap<>(); + createRootNode(); + } + + public void setStartTime(final String startTime) { + this.startTime = startTime; + start = System.currentTimeMillis(); + } + + public String getName() { + final String time = startTime.substring(11, 19); + final String conn = connectionName != null ? URLTools.replaceHexChars(connectionName.substring(15)) : "n/a"; + return time + " (" + conn + ")"; + } + + /** + * Is called after consuming the pre-run event to populate all items of a run. + * It's expected to be called only once. + * + * @param items items of a run, to be shown in the runner right after starting a run. + */ + public void put(final List items) { + populateItems(items); + populateItemNodes(); + populateItemNodeChildren(); + } + + private void createRootNode() { + // Create pseudo root node as suite. + // The TreeTableModel requires a single root node, but it will not be displayed. + final Suite rootSuite = new Suite(); + rootSuite.setId(getReporterId()); + rootSuite.setName(getReporterId()); + ItemNode rootNode = new ItemNode(rootSuite); + itemNodes.put(rootSuite.getId(), rootNode); + } + + private void populateItems(List items) { + for (final Item item : items) { + this.items.add(item); + if (item instanceof Suite) { + populateItems(((Suite) item).getItems()); + } else if (item instanceof Test) { + this.tests.put(item.getId(), (Test) item); + } + } + } + + private void populateItemNodes() { + for (final Item item : items) { + itemNodes.put(item.getId(), new ItemNode(item)); + } + } + + private void populateItemNodeChildren() { + for (Item item : items) { + String parentId = item.getParentId(); + ItemNode node = itemNodes.get(item.getId()); + ItemNode parent = itemNodes.get(parentId == null ? reporterId : parentId); + parent.add(node); + } + } + + public Test getTest(final String id) { + return tests.get(id); + } + + public int getTotalNumberOfCompletedTests() { + if (counter.getDisabled() == null || counter.getSuccess() == null || counter.getFailure() == null + || counter.getError() == null) { + return -1; + } + int total = counter.getDisabled() + counter.getSuccess() + counter.getFailure() + counter.getError(); + if (totalNumberOfTests != null && total > totalNumberOfTests) { + // can happen when run is cancelled and two processes are updating the run in parallel + // not worth to ensure consistency for this case, using synchronized will not be enough + total = totalNumberOfTests; + } + return total; + } + + public String getReporterId() { + return reporterId; + } + + public void setReporterId(final String reporterId) { + this.reporterId = reporterId; + } + + public String getConnectionName() { + return connectionName; + } + + public void setConnectionName(final String connectionName) { + this.connectionName = connectionName; + } + + public List getPathList() { + return pathList; + } + + public void setPathList(final List pathList) { + this.pathList = pathList; + } + + public Integer getCurrentTestNumber() { + return currentTestNumber; + } + + public void setCurrentTestNumber(final Integer currentTestNumber) { + this.currentTestNumber = currentTestNumber; + } + + public Test getCurrentTest() { + return currentTest; + } + + public void setCurrentTest(final Test currentTest) { + this.currentTest = currentTest; + } + + public Integer getTotalNumberOfTests() { + return totalNumberOfTests; + } + + public void setTotalNumberOfTests(final Integer totalNumberOfTests) { + this.totalNumberOfTests = totalNumberOfTests; + } + + public String getStartTime() { + return startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(final String endTime) { + this.endTime = endTime; + } + + public Double getExecutionTime() { + return executionTime; + } + + public void setExecutionTime(final Double executionTime) { + this.executionTime = executionTime; + } + + public Counter getCounter() { + return counter; + } + + public void setCounter(final Counter counter) { + this.counter = counter; + } + + public Integer getInfoCount() { + return infoCount; + } + + public void setInfoCount(final Integer infoCount) { + this.infoCount = infoCount; + } + + public String getErrorStack() { + return errorStack; + } + + public void setErrorStack(final String errorStack) { + this.errorStack = errorStack; + } + + public String getServerOutput() { + return serverOutput; + } + + public void setServerOutput(final String serverOutput) { + this.serverOutput = serverOutput; + } + + public LinkedHashMap getTests() { + return (LinkedHashMap) tests; + } + + public void setTests(final LinkedHashMap tests) { + this.tests = tests; + } + + public LinkedHashMap getItemNodes() { + return (LinkedHashMap) itemNodes; + } + + public void setItemNodes(LinkedHashMap itemNodes) { + this.itemNodes = itemNodes; + } + + public String getStatus() { + return status; + } + + public void setStatus(final String status) { + this.status = status; + } + + public Long getStart() { + return start; + } + + public void setStart(final Long start) { + this.start = start; + } + + public Connection getConsumerConn() { + return consumerConn; + } + + public void setConsumerConn(Connection consumerConn) { + this.consumerConn = consumerConn; + } + +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.xtend deleted file mode 100644 index 37b11926..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Run.xtend +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.runner - -import java.util.LinkedHashMap -import java.util.List -import org.eclipse.xtend.lib.annotations.Accessors -import org.utplsql.sqldev.model.AbstractModel - -@Accessors -class Run extends AbstractModel { - String reporterId - String connectionName - List pathList - Integer currentTestNumber - Test currentTest - Integer totalNumberOfTests - String startTime - String endTime - Double executionTime - Counter counter - Integer infoCount - String errorStack - String serverOutput - LinkedHashMap tests - String status - Long start - - new(String reporterId, String connectionName, List pathList) { - this.reporterId = reporterId - this.connectionName = connectionName - this.pathList = pathList - this.counter = new Counter - this.tests = new LinkedHashMap - } - - def void setStartTime(String startTime) { - this.startTime = startTime - start = System.currentTimeMillis - } - - def getName() { - val time = startTime.substring(11,19) - val conn = connectionName?.substring(15) - return '''«time» («conn»)''' - } - - def void put(List items) { - for (item : items) { - if (item instanceof Test) { - this.tests.put(item.id, item) - } - if (item instanceof Suite) { - item.items.put - } - } - } - - def getTest(String id) { - return tests.get(id) - } - - def getTotalNumberOfCompletedTests() { - return counter.disabled + counter.success + counter.failure + counter.error - } - -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RunnerModel.mgc b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RunnerModel.mgc new file mode 100644 index 00000000..e4ec1ec8 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/RunnerModel.mgc @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.java new file mode 100644 index 00000000..f9f036db --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.java @@ -0,0 +1,61 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.runner; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public class Suite extends Item { + private List items; + + public Suite() { + items = new ArrayList<>(); + } + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + // ancestor + .append("id", getId()) + .append("name", getName()) + .append("description", getDescription()) + .append("startTime", getStartTime()) + .append("endTime", getEndTime()) + .append("executionTime", getExecutionTime()) + .append("counter", getCounter()) + .append("errorStack", getErrorStack()) + .append("serverOutput", getServerOutput()) + .append("warnings", getWarnings()) + .append("parentId", getParentId()) + .append("statusIcon", getStatusIcon()) + .append("warningIcon", getWarningIcon()) + .append("infoIcon", getInfoIcon()) + // local + .append("items", items) + .toString(); + } + + public List getItems() { + return items; + } + + public void setItems(final List items) { + this.items = items; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.xtend deleted file mode 100644 index d75efadc..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Suite.xtend +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.runner - -import java.util.ArrayList -import java.util.List -import org.eclipse.xtend.lib.annotations.Accessors - -@Accessors -class Suite extends Item { - String name - String description - List items - - new() { - items = new ArrayList - } -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.java b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.java new file mode 100644 index 00000000..a0e893f8 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.java @@ -0,0 +1,126 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.runner; + +import java.util.List; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public class Test extends Item { + private String executableType; + private String ownerName; + private String objectName; + private String procedureName; + private Boolean disabled; + private String disabledReason; + private Integer testNumber; + private List failedExpectations; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + // ancestor + .append("id", getId()) + .append("name", getName()) + .append("description", getDescription()) + .append("startTime", getStartTime()) + .append("endTime", getEndTime()) + .append("executionTime", getExecutionTime()) + .append("counter", getCounter()) + .append("errorStack", getErrorStack()) + .append("serverOutput", getServerOutput()) + .append("warnings", getWarnings()) + .append("parentId", getParentId()) + .append("statusIcon", getStatusIcon()) + .append("warningIcon", getWarningIcon()) + .append("infoIcon", getInfoIcon()) + // local + .append("executableType", executableType) + .append("ownerName", ownerName) + .append("objectName", objectName) + .append("procedureName", procedureName) + .append("disabled", disabled) + .append("disabledReason", disabledReason) + .append("testNumber", testNumber) + .append("failedExpectations", failedExpectations) + .toString(); + } + + public String getExecutableType() { + return executableType; + } + + public void setExecutableType(final String executableType) { + this.executableType = executableType; + } + + public String getOwnerName() { + return ownerName; + } + + public void setOwnerName(final String ownerName) { + this.ownerName = ownerName; + } + + public String getObjectName() { + return objectName; + } + + public void setObjectName(final String objectName) { + this.objectName = objectName; + } + + public String getProcedureName() { + return procedureName; + } + + public void setProcedureName(final String procedureName) { + this.procedureName = procedureName; + } + + public Boolean isDisabled() { + return disabled; + } + + public void setDisabled(final Boolean disabled) { + this.disabled = disabled; + } + + public String getDisabledReason() { + return disabledReason; + } + + public void setDisabledReason(final String disabledReason) { + this.disabledReason = disabledReason; + } + + public Integer getTestNumber() { + return testNumber; + } + + public void setTestNumber(final Integer testNumber) { + this.testNumber = testNumber; + } + + public List getFailedExpectations() { + return failedExpectations; + } + + public void setFailedExpectations(final List failedExpectations) { + this.failedExpectations = failedExpectations; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.xtend deleted file mode 100644 index 30070e85..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/runner/Test.xtend +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.runner - -import java.util.List -import javax.swing.Icon -import org.eclipse.xtend.lib.annotations.Accessors -import org.utplsql.sqldev.resources.UtplsqlResources - -@Accessors -class Test extends Item { - String executableType - String ownerName - String objectName - String procedureName - Boolean disabled - String name - String description - Integer testNumber - List failedExpectations - - def getStatusIcon() { - var Icon icon = null - if (startTime !== null && endTime === null ) { - icon = UtplsqlResources.getIcon("PROGRESS_ICON") - } else { - if (counter !== null) { - if (counter.success > 0) { - icon = UtplsqlResources.getIcon("SUCCESS_ICON") - } else if (counter.error > 0) { - icon = UtplsqlResources.getIcon("ERROR_ICON") - } else if (counter.failure > 0) { - icon = UtplsqlResources.getIcon("FAILURE_ICON") - } else if (counter.disabled > 0) { - icon = UtplsqlResources.getIcon("DISABLED_ICON") - } - } - } - return icon - } - - def getWarningIcon() { - var Icon icon = null - if (counter !== null) { - if (counter.warning > 0) { - icon = UtplsqlResources.getIcon("WARNING_ICON") - } - } - return icon - } - - def getInfoIcon() { - var Icon icon = null - if (serverOutput !== null && serverOutput.length > 0) { - icon = UtplsqlResources.getIcon("INFO_ICON") - } - return icon - } -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/ut/Annotation.java b/sqldev/src/main/java/org/utplsql/sqldev/model/ut/Annotation.java new file mode 100644 index 00000000..47e792c7 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/ut/Annotation.java @@ -0,0 +1,78 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.ut; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public class Annotation { + private String objectOwner; + private String objectName; + private String name; + private String text; + private String subobjectName; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append("objectOwner", objectOwner) + .append("objectName", objectName) + .append("name", name) + .append("text", text) + .append("subobjectName", subobjectName) + .toString(); + } + + public String getObjectOwner() { + return objectOwner; + } + + public void setObjectOwner(final String objectOwner) { + this.objectOwner = objectOwner; + } + + public String getObjectName() { + return objectName; + } + + public void setObjectName(final String objectName) { + this.objectName = objectName; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getText() { + return text; + } + + public void setText(final String text) { + this.text = text; + } + + public String getSubobjectName() { + return subobjectName; + } + + public void setSubobjectName(final String subobjectName) { + this.subobjectName = subobjectName; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/ut/Annotation.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/ut/Annotation.xtend deleted file mode 100644 index ee658e91..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/ut/Annotation.xtend +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.ut - -import org.eclipse.xtend.lib.annotations.Accessors -import org.utplsql.sqldev.model.AbstractModel - -@Accessors -class Annotation extends AbstractModel { - String objectOwner - String objectName - String name - String text - String subobjectName -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/ut/OutputLines.java b/sqldev/src/main/java/org/utplsql/sqldev/model/ut/OutputLines.java new file mode 100644 index 00000000..75343d6b --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/model/ut/OutputLines.java @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.model.ut; + +import org.springframework.core.style.ToStringCreator; +import org.utplsql.sqldev.model.JsonToStringStyler; + +public class OutputLines { + private String[] lines; + private Integer numlines; + + @Override + public String toString() { + return new ToStringCreator(this, JsonToStringStyler.getInstance()) + .append("lines", lines) + .append("numlines", numlines) + .toString(); + } + + public String[] getLines() { + return lines; + } + + public void setLines(final String[] lines) { + this.lines = lines; + } + + public Integer getNumlines() { + return numlines; + } + + public void setNumlines(final Integer numlines) { + this.numlines = numlines; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/model/ut/OutputLines.xtend b/sqldev/src/main/java/org/utplsql/sqldev/model/ut/OutputLines.xtend deleted file mode 100644 index 7b1c946c..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/model/ut/OutputLines.xtend +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.model.ut - -import org.eclipse.xtend.lib.annotations.Accessors -import org.utplsql.sqldev.model.AbstractModel - -@Accessors -class OutputLines extends AbstractModel { - String[] lines; - Integer numlines; -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/RunGenerator.java b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/RunGenerator.java new file mode 100644 index 00000000..f88331ba --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/RunGenerator.java @@ -0,0 +1,180 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.oddgen; + +import java.sql.Connection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.stream.Collectors; + +import org.oddgen.sqldev.generators.OddgenGenerator2; +import org.oddgen.sqldev.generators.model.Node; +import org.utplsql.sqldev.dal.UtplsqlDao; +import org.utplsql.sqldev.model.DatabaseTools; +import org.utplsql.sqldev.model.StringTools; +import org.utplsql.sqldev.model.preference.PreferenceModel; +import org.utplsql.sqldev.resources.UtplsqlResources; + +import oracle.ide.config.Preferences; + +public class RunGenerator implements OddgenGenerator2 { + public static final String YES = "Yes"; + public static final String NO = "No"; + public static final String RESET_PACKAGE = UtplsqlResources.getString("PREF_RESET_PACKAGE_LABEL"); + public static final String CLEAR_SCREEN = UtplsqlResources.getString("PREF_CLEAR_SCREEN_LABEL"); + public static final String INDENT_SPACES = UtplsqlResources.getString("PREF_INDENT_SPACES_LABEL"); + + // oddgen node cache + private List runnables = null; + + @Override + public boolean isSupported(final Connection conn) { + return DatabaseTools.isSupported(conn); + } + + @Override + public String getName(final Connection conn) { + return "Run test"; + } + + @Override + public String getDescription(final Connection conn) { + return "Runs utPLSQL test packages in the current user."; + } + + @Override + public List getFolders(final Connection conn) { + final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + final ArrayList folders = new ArrayList<>(); + for (String f : preferences.getRootFolderInOddgenView().split(",")) { + if (f != null) { + folders.add(f.trim()); + } + } + return folders; + } + + @Override + public String getHelp(final Connection conn) { + return "

not yet available

"; + } + + @Override + public List getNodes(final Connection conn, final String parentNodeId) { + // oddgen asks for children for each parent node, regardless of load strategy (eager/lazy) + // oddgen does not know about the load strategy, hence caching is the responsibility of the generator + if (runnables == null) { + final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + final LinkedHashMap params = new LinkedHashMap<>(); + params.put(RESET_PACKAGE, preferences.isResetPackage() ? YES : NO); + params.put(CLEAR_SCREEN, preferences.isClearScreen() ? YES : NO); + params.put(INDENT_SPACES, String.valueOf(preferences.getIndentSpaces())); + final UtplsqlDao dao = new UtplsqlDao(conn); + // load node tree eagerly (all nodes in one go) + runnables = dao.runnables(); + for (final Node node : runnables) { + node.setParams(params); + } + } + return runnables; + } + + @Override + public HashMap> getLov(final Connection conn, final LinkedHashMap params, final List nodes) { + final HashMap> lov = new HashMap<>(); + lov.put(RESET_PACKAGE, Arrays.asList(YES, NO)); + lov.put(CLEAR_SCREEN, Arrays.asList(YES, NO)); + lov.put(INDENT_SPACES, Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8")); + return lov; + } + + @Override + public HashMap getParamStates(final Connection conn, final LinkedHashMap params, final List nodes) { + return new HashMap<>(); + } + + private String getPath(final Node node, final Connection conn) { + if ("SUITE".equals(node.getId()) || "SUITEPATH".equals(node.getId())) { + return DatabaseTools.getUser(conn); + } else { + return node.getId(); + } + } + + public ArrayList dedup(final List nodes) { + final HashSet set = new HashSet<>(); + for (final Node node : nodes) { + set.add(node.getId()); + } + final ArrayList ret = new ArrayList<>(); + for (final Node node : nodes) { + if (!set.contains(node.getParentId())) { + ret.add(node); + } + } + return ret; + } + + @Override + public String generateProlog(final Connection conn, final List nodes) { + final ArrayList dedupNodes = dedup(nodes); + final LinkedHashMap params = dedupNodes.get(0).getParams(); + final StringBuilder sb = new StringBuilder(); + if (YES.equals(params.get(RESET_PACKAGE))) { + sb.append("execute dbms_session.reset_package;\n"); + } + sb.append("set serveroutput on size unlimited\n"); + if (YES.equals(params.get(CLEAR_SCREEN))) { + sb.append("clear screen\n"); + } + if (dedupNodes.size() == 1) { + sb.append("execute ut.run('"); + sb.append(getPath(dedupNodes.get(0), conn)); + sb.append("');\n"); + } else { + final List paths = dedupNodes.stream().map(node -> getPath(node, conn)).collect(Collectors.toList()); + sb.append("begin\n"); + sb.append("\tut.run(\n"); + sb.append("\t\tut_varchar2_list(\n"); + sb.append(StringTools.getCSV(paths, "\t\t\t")); + sb.append("\t\t)\n"); + sb.append("\t);\n"); + sb.append("end;\n"); + sb.append("/\n"); + } + final String ret = sb.toString(); + return StringTools.replaceTabsWithSpaces(ret, Integer.parseInt(params.get(INDENT_SPACES))); + } + + @Override + public String generateSeparator(final Connection conn) { + return ""; + } + + @Override + public String generateEpilog(final Connection conn, final List nodes) { + return ""; + } + + @Override + public String generate(final Connection conn, final Node node) { + return ""; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/RunGenerator.xtend b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/RunGenerator.xtend deleted file mode 100644 index b321b77f..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/RunGenerator.xtend +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.oddgen - -import java.sql.Connection -import java.util.ArrayList -import java.util.HashMap -import java.util.HashSet -import java.util.LinkedHashMap -import java.util.List -import oracle.ide.config.Preferences -import org.oddgen.sqldev.generators.OddgenGenerator2 -import org.oddgen.sqldev.generators.model.Node -import org.utplsql.sqldev.dal.UtplsqlDao -import org.utplsql.sqldev.model.preference.PreferenceModel -import org.utplsql.sqldev.resources.UtplsqlResources - -class RunGenerator implements OddgenGenerator2 { - - public static val YES = "Yes" - public static val NO = "No" - - public static var RESET_PACKAGE = UtplsqlResources.getString("PREF_RESET_PACKAGE_LABEL") - public static var CLEAR_SCREEN = UtplsqlResources.getString("PREF_CLEAR_SCREEN_LABEL") - public static var INDENT_SPACES = UtplsqlResources.getString("PREF_INDENT_SPACES_LABEL") - - // oddgen node cache - var List runnables = null; - - override isSupported(Connection conn) { - var ret = false - if (conn !== null) { - if (conn.metaData.databaseProductName.startsWith("Oracle")) { - if (conn.metaData.databaseMajorVersion == 11) { - if (conn.metaData.databaseMinorVersion >= 2) { - ret = true - } - } else if (conn.metaData.databaseMajorVersion > 11) { - ret = true - } - } - } - return ret - } - - override getName(Connection conn) { - return "Run test" - } - - override getDescription(Connection conn) { - return "Runs utPLSQL test packages in the current user." - } - - override getFolders(Connection conn) { - val preferences = PreferenceModel.getInstance(Preferences.preferences) - val folders = new ArrayList - for (f : preferences.rootFolderInOddgenView.split(",").filter[!it.empty]) { - folders.add(f.trim) - } - return folders - } - - override getHelp(Connection conn) { - return "

not yet available

" - } - - override getNodes(Connection conn, String parentNodeId) { - // oddgen asks for children for each parent node, regardless of load strategy (eager/lazy) - // oddgen does not know about the load strategy, hence caching is the responsibility of the generator - if (runnables === null) { - val preferences = PreferenceModel.getInstance(Preferences.preferences) - val params = new LinkedHashMap() - params.put(RESET_PACKAGE, if (preferences.resetPackage) {YES} else {NO}) - params.put(CLEAR_SCREEN, if (preferences.clearScreen) {YES} else {NO}) - params.put(INDENT_SPACES, String.valueOf(preferences.indentSpaces)) - val UtplsqlDao dao = new UtplsqlDao(conn) - // load node tree eagerly (all nodes in one go) - runnables = dao.runnables - for (node : runnables) { - node.params = params - } - } - return runnables - } - - override getLov(Connection conn, LinkedHashMap params, List nodes) { - val lov = new HashMap>() - lov.put(RESET_PACKAGE, #[YES, NO]) - lov.put(CLEAR_SCREEN, #[YES, NO]) - lov.put(INDENT_SPACES, #["1", "2", "3", "4", "5", "6", "7", "8"]) - return lov - } - - override getParamStates(Connection conn, LinkedHashMap params, List nodes) { - return new HashMap - } - - private def getPath(Node node, Connection conn) { - if (node.id == "SUITE" || node.id == "SUITEPATH") { - return conn.metaData.userName - } else { - return node.id - } - } - - private def replaceTabsWithSpaces(CharSequence input, int indentSpaces) { - val spaces = String.format("%1$"+indentSpaces+"s", "") - return input.toString.replace("\t", spaces) - } - - def dedup(List nodes) { - val set = new HashSet - for (node : nodes) { - set.add(node.id) - } - val ret = new ArrayList - for (node : nodes) { - if (!set.contains(node.parentId)) { - ret.add(node) - } - } - return ret - } - - override generateProlog(Connection conn, List nodes) { - val dedupNodes = nodes.dedup - val params = dedupNodes.get(0).params - val ret = ''' - «IF params.get(RESET_PACKAGE) == YES» - EXECUTE dbms_session.reset_package; - «ENDIF» - SET SERVEROUTPUT ON SIZE UNLIMITED - «IF params.get(CLEAR_SCREEN) == YES» - CLEAR SCREEN - «ENDIF» - «IF dedupNodes.size == 1» - EXECUTE ut.run('«dedupNodes.get(0).getPath(conn)»'); - «ELSE» - BEGIN - ut.run( - ut_varchar2_list( - «FOR node : dedupNodes SEPARATOR ","» - '«node.getPath(conn)»' - «ENDFOR» - ) - ); - END; - / - «ENDIF» - ''' - return ret.replaceTabsWithSpaces(Integer.valueOf(params.get(INDENT_SPACES))) - } - - override generateSeparator(Connection conn) { - return "" - } - - override generateEpilog(Connection conn, List nodes) { - return "" - } - - override generate(Connection conn, Node node) { - return "" - } - -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.java b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.java new file mode 100644 index 00000000..f4602ea6 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.java @@ -0,0 +1,302 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.oddgen; + +import java.io.File; +import java.sql.Connection; +import java.util.*; +import java.util.logging.Logger; + +import org.oddgen.sqldev.generators.OddgenGenerator2; +import org.oddgen.sqldev.generators.model.Node; +import org.oddgen.sqldev.generators.model.NodeTools; +import org.oddgen.sqldev.plugin.templates.TemplateTools; +import org.utplsql.sqldev.dal.UtplsqlDao; +import org.utplsql.sqldev.model.DatabaseTools; +import org.utplsql.sqldev.model.oddgen.GenContext; +import org.utplsql.sqldev.model.preference.PreferenceModel; +import org.utplsql.sqldev.resources.UtplsqlResources; + +import oracle.ide.config.Preferences; + +public class TestGenerator implements OddgenGenerator2 { + private static final Logger logger = Logger.getLogger(TestGenerator.class.getName()); + + public static final String YES = "Yes"; + public static final String NO = "No"; + + public static final String GENERATE_FILES = UtplsqlResources.getString("PREF_GENERATE_FILES_LABEL"); + public static final String OUTPUT_DIRECTORY = UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_LABEL"); + public static final String DELETE_EXISTING_FILES = UtplsqlResources.getString("PREF_DELETE_EXISTING_FILES_LABEL"); + public static final String TEST_PACKAGE_PREFIX = UtplsqlResources.getString("PREF_TEST_PACKAGE_PREFIX_LABEL"); + public static final String TEST_PACKAGE_SUFFIX = UtplsqlResources.getString("PREF_TEST_PACKAGE_SUFFIX_LABEL"); + public static final String TEST_UNIT_PREFIX = UtplsqlResources.getString("PREF_TEST_UNIT_PREFIX_LABEL"); + public static final String TEST_UNIT_SUFFIX = UtplsqlResources.getString("PREF_TEST_UNIT_SUFFIX_LABEL"); + public static final String NUMBER_OF_TESTS_PER_UNIT = UtplsqlResources.getString("PREF_NUMBER_OF_TESTS_PER_UNIT_LABEL"); + public static final String GENERATE_COMMENTS = UtplsqlResources.getString("PREF_GENERATE_COMMENTS_LABEL"); + public static final String DISABLE_TESTS = UtplsqlResources.getString("PREF_DISABLE_TESTS_LABEL"); + public static final String SUITE_PATH = UtplsqlResources.getString("PREF_SUITE_PATH_LABEL"); + public static final String INDENT_SPACES = UtplsqlResources.getString("PREF_INDENT_SPACES_LABEL"); + + private final NodeTools nodeTools = new NodeTools(); + private final TemplateTools templateTools = new TemplateTools(); + private final ArrayList consoleOutput = new ArrayList<>(); + + private GenContext toContext(final Node node) { + final GenContext context = new GenContext(); + context.setObjectType(nodeTools.toObjectType(node)); + context.setObjectName(nodeTools.toObjectName(node)); + context.setTestPackagePrefix(node.getParams().get(TEST_PACKAGE_PREFIX).toLowerCase()); + context.setTestPackageSuffix(node.getParams().get(TEST_PACKAGE_SUFFIX).toLowerCase()); + context.setTestUnitPrefix(node.getParams().get(TEST_UNIT_PREFIX).toLowerCase()); + context.setTestUnitSuffix(node.getParams().get(TEST_UNIT_SUFFIX).toLowerCase()); + context.setNumberOfTestsPerUnit(Integer.parseInt(node.getParams().get(NUMBER_OF_TESTS_PER_UNIT))); + context.setGenerateComments(YES.equals(node.getParams().get(GENERATE_COMMENTS))); + context.setDisableTests(YES.equals(node.getParams().get(DISABLE_TESTS))); + context.setSuitePath(node.getParams().get(SUITE_PATH).toLowerCase()); + context.setIndentSpaces(Integer.parseInt(node.getParams().get(INDENT_SPACES))); + return context; + } + + private void resetConsoleOutput() { + consoleOutput.clear(); + } + + private void saveConsoleOutput(final String s) { + if (s != null) { + consoleOutput.addAll(Arrays.asList(s.split("[\\n\\r]+"))); + } + } + + private void logConsoleOutput() { + for (final String line : consoleOutput) { + if (line.contains("error") || line.startsWith("Cannot")) { + logger.severe(line); + } else { + logger.fine(line); + } + } + } + + private String deleteFile(final File file) { + String ret; + if (file.delete()) { + StringBuilder sb = new StringBuilder(); + sb.append(file.getAbsoluteFile()); + sb.append(" deleted."); + ret = sb.toString(); + } else { + StringBuilder sb = new StringBuilder(); + sb.append("Cannot delete file "); + sb.append(file.getAbsoluteFile()); + sb.append("."); + ret = sb.toString(); + } + return ret; + } + + private CharSequence deleteFiles(final String directory) { + StringBuilder sb = new StringBuilder(); + final File dir = new File(directory); + try { + for (final File file : Objects.requireNonNull(dir.listFiles())) { + if (!file.isDirectory() && (file.getName().endsWith(".pks") || file.getName().endsWith(".pkb"))) { + sb.append(deleteFile(file)); + sb.append('\n'); + } + } + } catch (NullPointerException e) { + // ignore + } + return sb; + } + + @Override + public boolean isSupported(final Connection conn) { + return DatabaseTools.isSupported(conn); + } + + @Override + public String getName(final Connection conn) { + return "Generate test"; + } + + @Override + public String getDescription(final Connection conn) { + return "Generates utPLSQL test packages for public units in packages, types, functions and procedures found in the current schema."; + } + + @Override + public List getFolders(final Connection conn) { + final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + final ArrayList folders = new ArrayList<>(); + for (String f : preferences.getRootFolderInOddgenView().split(",")) { + if (f != null) { + folders.add(f.trim()); + } + } + return folders; + } + + @Override + public String getHelp(final Connection conn) { + return "

not yet available

"; + } + + @Override + public List getNodes(final Connection conn, final String parentNodeId) { + final PreferenceModel preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + final LinkedHashMap params = new LinkedHashMap<>(); + params.put(GENERATE_FILES, preferences.isGenerateFiles() ? YES : NO); + params.put(OUTPUT_DIRECTORY, preferences.getOutputDirectory()); + params.put(DELETE_EXISTING_FILES, preferences.isDeleteExistingFiles() ? YES : NO); + params.put(TEST_PACKAGE_PREFIX, preferences.getTestPackagePrefix()); + params.put(TEST_PACKAGE_SUFFIX, preferences.getTestPackageSuffix()); + params.put(TEST_UNIT_PREFIX, preferences.getTestUnitPrefix()); + params.put(TEST_UNIT_SUFFIX, preferences.getTestUnitSuffix()); + params.put(NUMBER_OF_TESTS_PER_UNIT, String.valueOf(preferences.getNumberOfTestsPerUnit())); + params.put(GENERATE_COMMENTS, preferences.isGenerateComments() ? YES : NO); + params.put(DISABLE_TESTS, preferences.isDisableTests() ? YES : NO); + params.put(SUITE_PATH, preferences.getSuitePath()); + params.put(INDENT_SPACES, String.valueOf(preferences.getIndentSpaces())); + if (parentNodeId == null || parentNodeId.isEmpty()) { + final Node packageNode = new Node(); + packageNode.setId("PACKAGE"); + packageNode.setParams(params); + packageNode.setLeaf(false); + packageNode.setGeneratable(true); + packageNode.setMultiselectable(true); + final Node typeNode = new Node(); + typeNode.setId("TYPE"); + typeNode.setParams(params); + typeNode.setLeaf(false); + typeNode.setGeneratable(true); + typeNode.setMultiselectable(true); + final Node functionNode = new Node(); + functionNode.setId("FUNCTION"); + functionNode.setParams(params); + functionNode.setLeaf(false); + functionNode.setGeneratable(true); + functionNode.setMultiselectable(true); + final Node procedureNode = new Node(); + procedureNode.setId("PROCEDURE"); + procedureNode.setParams(params); + procedureNode.setLeaf(false); + procedureNode.setGeneratable(true); + procedureNode.setMultiselectable(true); + return Arrays.asList(packageNode, typeNode, functionNode, procedureNode); + } else { + final UtplsqlDao dao = new UtplsqlDao(conn); + final List nodes = dao.testables(parentNodeId); + for (final Node node : nodes) { + node.setParams(params); + } + return nodes; + } + } + + @Override + public HashMap> getLov(final Connection conn, final LinkedHashMap params, + final List nodes) { + final HashMap> lov = new HashMap<>(); + lov.put(NUMBER_OF_TESTS_PER_UNIT, Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8", "9", "10")); + lov.put(INDENT_SPACES, Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8")); + lov.put(GENERATE_COMMENTS, Arrays.asList(YES, NO)); + lov.put(DISABLE_TESTS, Arrays.asList(YES, NO)); + lov.put(GENERATE_FILES, Arrays.asList(YES, NO)); + lov.put(DELETE_EXISTING_FILES, Arrays.asList(YES, NO)); + return lov; + } + + @Override + public HashMap getParamStates(final Connection conn, final LinkedHashMap params, + final List nodes) { + final HashMap paramStates = new HashMap<>(); + paramStates.put(OUTPUT_DIRECTORY, YES.equals(params.get(GENERATE_FILES))); + paramStates.put(DELETE_EXISTING_FILES, YES.equals(params.get(GENERATE_FILES))); + return paramStates; + } + + @Override + public String generateProlog(final Connection conn, final List nodes) { + StringBuilder sb = new StringBuilder(); + final boolean generateFiles = YES.equals(nodes.get(0).getParams().get(GENERATE_FILES)); + final String outputDirectory = nodes.get(0).getParams().get(OUTPUT_DIRECTORY); + final boolean deleteExistingfiles = YES.equals(nodes.get(0).getParams().get(DELETE_EXISTING_FILES)); + if (generateFiles) { + resetConsoleOutput(); + saveConsoleOutput(templateTools.mkdirs(outputDirectory)); + if (deleteExistingfiles) { + saveConsoleOutput(deleteFiles(outputDirectory).toString()); + } + sb.append("--\n"); + sb.append("-- install generated utPLSQL test packages\n"); + sb.append("--\n"); + } + for (final Node node : nodes) { + final GenContext context = toContext(node); + context.setConn(conn); + final TestTemplate testTemplate = new TestTemplate(context); + if (generateFiles) { + final String packageName = context.getTestPackagePrefix().toLowerCase() + + nodeTools.toObjectName(node).toLowerCase() + + context.getTestPackageSuffix().toLowerCase(); + final String packagePath = outputDirectory + File.separator + packageName; + saveConsoleOutput(templateTools.writeToFile(packagePath + ".pks", testTemplate.generateSpec())); + saveConsoleOutput(templateTools.writeToFile(packagePath + ".pkb", testTemplate.generateBody())); + sb.append('@'); + sb.append(packagePath); + sb.append(".pks\n"); + sb.append('@'); + sb.append(packagePath); + sb.append(".pkb\n"); + } else { + sb.append(testTemplate.generate()); + sb.append('\n'); + } + } + logConsoleOutput(); + if (generateFiles && consoleOutput.stream().anyMatch(it -> it.contains("error"))) { + sb.append('\n'); + sb.append("--\n"); + sb.append("-- console output produced during the generation of this script (errors found)\n"); + sb.append("--\n"); + sb.append("/*\n\n"); + for (final String line : consoleOutput) { + sb.append(line); + sb.append('\n'); + } + sb.append('\n'); + sb.append("*/\n"); + } + return sb.toString(); + } + + @Override + public String generateSeparator(final Connection conn) { + return ""; + } + + @Override + public String generateEpilog(final Connection conn, final List nodes) { + return ""; + } + + @Override + public String generate(final Connection conn, final Node node) { + return ""; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.xtend b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.xtend deleted file mode 100644 index 1393cc3e..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestGenerator.xtend +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.oddgen - -import java.io.File -import java.sql.Connection -import java.util.ArrayList -import java.util.HashMap -import java.util.LinkedHashMap -import java.util.List -import java.util.logging.Logger -import oracle.ide.config.Preferences -import org.oddgen.sqldev.generators.OddgenGenerator2 -import org.oddgen.sqldev.generators.model.Node -import org.oddgen.sqldev.generators.model.NodeTools -import org.oddgen.sqldev.plugin.templates.TemplateTools -import org.utplsql.sqldev.dal.UtplsqlDao -import org.utplsql.sqldev.model.oddgen.GenContext -import org.utplsql.sqldev.model.preference.PreferenceModel -import org.utplsql.sqldev.resources.UtplsqlResources - -class TestGenerator implements OddgenGenerator2 { - static final Logger logger = Logger.getLogger(TestGenerator.name); - - public static val YES = "Yes" - public static val NO = "No" - - public static var GENERATE_FILES = UtplsqlResources.getString("PREF_GENERATE_FILES_LABEL") - public static var OUTPUT_DIRECTORY = UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_LABEL") - public static var DELETE_EXISTING_FILES = UtplsqlResources.getString("PREF_DELETE_EXISTING_FILES_LABEL") - public static var TEST_PACKAGE_PREFIX = UtplsqlResources.getString("PREF_TEST_PACKAGE_PREFIX_LABEL") - public static var TEST_PACKAGE_SUFFIX = UtplsqlResources.getString("PREF_TEST_PACKAGE_SUFFIX_LABEL") - public static var TEST_UNIT_PREFIX = UtplsqlResources.getString("PREF_TEST_UNIT_PREFIX_LABEL") - public static var TEST_UNIT_SUFFIX = UtplsqlResources.getString("PREF_TEST_UNIT_SUFFIX_LABEL") - public static var NUMBER_OF_TESTS_PER_UNIT = UtplsqlResources.getString("PREF_NUMBER_OF_TESTS_PER_UNIT_LABEL") - public static var GENERATE_COMMENTS = UtplsqlResources.getString("PREF_GENERATE_COMMENTS_LABEL") - public static var DISABLE_TESTS = UtplsqlResources.getString("PREF_DISABLE_TESTS_LABEL") - public static var SUITE_PATH = UtplsqlResources.getString("PREF_SUITE_PATH_LABEL") - public static var INDENT_SPACES = UtplsqlResources.getString("PREF_INDENT_SPACES_LABEL") - - val extension NodeTools nodeTools = new NodeTools - val extension TemplateTools templateTools = new TemplateTools - val consoleOutput = new ArrayList(); - - private def toContext(Node node) { - val context = new GenContext() - context.objectType = node.toObjectType - context.objectName = node.toObjectName - context.testPackagePrefix = node.params.get(TEST_PACKAGE_PREFIX).toLowerCase - context.testPackageSuffix = node.params.get(TEST_PACKAGE_SUFFIX).toLowerCase - context.testUnitPrefix = node.params.get(TEST_UNIT_PREFIX).toLowerCase - context.testUnitSuffix = node.params.get(TEST_UNIT_SUFFIX).toLowerCase - context.numberOfTestsPerUnit = Integer.valueOf(node.params.get(NUMBER_OF_TESTS_PER_UNIT)) - context.generateComments = node.params.get(GENERATE_COMMENTS) == YES - context.disableTests = node.params.get(DISABLE_TESTS) == YES - context.suitePath = node.params.get(SUITE_PATH).toLowerCase - context.indentSpaces = Integer.valueOf(node.params.get(INDENT_SPACES)) - return context - } - - private def void resetConsoleOutput() { - consoleOutput.clear - } - - private def void saveConsoleOutput(String s) { - if (s !== null) { - for (line : s.split("[\\n\\r]+")) { - consoleOutput.add(line) - } - } - } - - private def void logConsoleOutput() { - for (line : consoleOutput) { - if (line.contains("error") || line.startsWith("Cannot")) { - logger.severe(line) - } else { - logger.fine(line) - } - } - } - - private def String deleteFile(File file) { - var String ret - try { - if (file.delete) { - ret = '''«file.absoluteFile» deleted.''' - } else { - ret = '''Cannot delete file «file.absoluteFile».''' - } - } catch (Exception e) { - ret = '''Cannot delete file «file.absoluteFile». Got the following error message: «e.message».''' - } - return ret - } - - private def deleteFiles(String directory) ''' - «val dir = new File(directory)» - «FOR file: dir.listFiles» - «IF !file.directory» - «IF file.name.endsWith(".pks") || file.name.endsWith(".pkb")» - «file.deleteFile» - «ENDIF» - «ENDIF» - «ENDFOR» - ''' - - override isSupported(Connection conn) { - var ret = false - if (conn !== null) { - if (conn.metaData.databaseProductName.startsWith("Oracle")) { - if (conn.metaData.databaseMajorVersion == 11) { - if (conn.metaData.databaseMinorVersion >= 2) { - ret = true - } - } else if (conn.metaData.databaseMajorVersion > 11) { - ret = true - } - } - } - return ret - } - - override getName(Connection conn) { - return "Generate test" - } - - override getDescription(Connection conn) { - return "Generates utPLSQL test packages for public units in packages, types, functions and procedures found in the current schema." - } - - override getFolders(Connection conn) { - val preferences = PreferenceModel.getInstance(Preferences.preferences) - val folders = new ArrayList - for (f : preferences.rootFolderInOddgenView.split(",").filter[!it.empty]) { - folders.add(f.trim) - } - return folders - } - - override getHelp(Connection conn) { - return "

not yet available

" - } - - override getNodes(Connection conn, String parentNodeId) { - val preferences = PreferenceModel.getInstance(Preferences.preferences) - val params = new LinkedHashMap() - params.put(GENERATE_FILES, if (preferences.generateFiles) {YES} else {NO}) - params.put(OUTPUT_DIRECTORY, preferences.outputDirectory) - params.put(DELETE_EXISTING_FILES, if (preferences.deleteExistingFiles) {YES} else {NO}) - params.put(TEST_PACKAGE_PREFIX, preferences.testPackagePrefix) - params.put(TEST_PACKAGE_SUFFIX, preferences.testPackageSuffix) - params.put(TEST_UNIT_PREFIX, preferences.testUnitPrefix) - params.put(TEST_UNIT_SUFFIX, preferences.testUnitSuffix) - params.put(NUMBER_OF_TESTS_PER_UNIT, String.valueOf(preferences.numberOfTestsPerUnit)) - params.put(GENERATE_COMMENTS, if(preferences.generateComments) {YES} else {NO}) - params.put(DISABLE_TESTS, if (preferences.disableTests) {YES} else {NO}) - params.put(SUITE_PATH, preferences.suitePath) - params.put(INDENT_SPACES, String.valueOf(preferences.indentSpaces)) - if (parentNodeId === null || parentNodeId.empty) { - val packageNode = new Node - packageNode.id = "PACKAGE" - packageNode.params = params - packageNode.leaf = false - packageNode.generatable = true - packageNode.multiselectable = true - val typeNode = new Node - typeNode.id = "TYPE" - typeNode.params = params - typeNode.leaf = false - typeNode.generatable = true - typeNode.multiselectable = true - val functionNode = new Node - functionNode.id = "FUNCTION" - functionNode.params = params - functionNode.leaf = false - functionNode.generatable = true - functionNode.multiselectable = true - val procedureNode = new Node - procedureNode.id = "PROCEDURE" - procedureNode.params = params - procedureNode.leaf = false - procedureNode.generatable = true - procedureNode.multiselectable = true - return #[packageNode, typeNode, functionNode, procedureNode] - } else { - val UtplsqlDao dao = new UtplsqlDao(conn) - val nodes = dao.testables(parentNodeId) - for (node : nodes) { - node.params = params - } - return nodes - } - } - - override getLov(Connection conn, LinkedHashMap params, List nodes) { - val lov = new HashMap>() - lov.put(NUMBER_OF_TESTS_PER_UNIT, #["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]) - lov.put(INDENT_SPACES, #["1", "2", "3", "4", "5", "6", "7", "8"]) - lov.put(GENERATE_COMMENTS, #[YES, NO]) - lov.put(DISABLE_TESTS, #[YES, NO]) - lov.put(GENERATE_FILES, #[YES, NO]) - lov.put(DELETE_EXISTING_FILES, #[YES, NO]) - return lov - } - - override getParamStates(Connection conn, LinkedHashMap params, List nodes) { - val paramStates = new HashMap - paramStates.put(OUTPUT_DIRECTORY, params.get(GENERATE_FILES) == YES) - paramStates.put(DELETE_EXISTING_FILES, params.get(GENERATE_FILES) == YES) - return paramStates - } - - override generateProlog(Connection conn, List nodes) ''' - «val generateFiles = nodes.get(0).params.get(GENERATE_FILES) == YES» - «val outputDirectory = nodes.get(0).params.get(OUTPUT_DIRECTORY)» - «val deleteExistingfiles = nodes.get(0).params.get(DELETE_EXISTING_FILES) == YES» - «IF generateFiles» - «resetConsoleOutput» - «outputDirectory.mkdirs.saveConsoleOutput» - «IF deleteExistingfiles» - «deleteFiles(outputDirectory).toString.saveConsoleOutput» - «ENDIF» - -- - -- install generated utPLSQL test packages - -- - «ENDIF» - «FOR node : nodes» - «val context = node.toContext» - «context.conn = conn» - «val testTemplate = new TestTemplate(context)» - «IF generateFiles» - «val packageName = '''«context.testPackagePrefix»«node.toObjectName»«context.testPackageSuffix»'''» - «writeToFile('''«outputDirectory»«File.separator»«packageName».pks'''.toString,testTemplate.generateSpec).saveConsoleOutput» - «writeToFile('''«outputDirectory»«File.separator»«packageName».pkb'''.toString,testTemplate.generateBody).saveConsoleOutput» - @«outputDirectory»«File.separator»«packageName».pks - @«outputDirectory»«File.separator»«packageName».pkb - «ELSE» - «testTemplate.generate» - - «ENDIF» - «ENDFOR» - «logConsoleOutput» - «IF generateFiles && consoleOutput.findFirst[it.contains("error")] !== null» - - -- - -- console output produced during the generation of this script (errors found) - -- - /* - - «FOR line : consoleOutput» - «line» - «ENDFOR» - - */ - «ENDIF» - ''' - - override generateSeparator(Connection conn) { - return "" - } - - override generateEpilog(Connection conn, List nodes) { - return "" - } - - override generate(Connection conn, Node node) { - return "" - } - -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.java b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.java new file mode 100644 index 00000000..7bbdff3f --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.java @@ -0,0 +1,165 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.oddgen; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +import org.utplsql.sqldev.dal.UtplsqlDao; +import org.utplsql.sqldev.model.StringTools; +import org.utplsql.sqldev.model.oddgen.GenContext; + +public class TestTemplate { + private final GenContext context; + private final List units; + private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private final String today = dateTimeFormatter.format(LocalDateTime.now()); + + public TestTemplate(final GenContext context) { + this.context = context; + UtplsqlDao dao = new UtplsqlDao(context.getConn()); + units = dao.units(context.getObjectType(), context.getObjectName()); + } + + public String generateSpec() { + final StringBuilder sb = new StringBuilder(); + final String objectName = context.getObjectName().toLowerCase(); + final String packageName = context.getTestPackagePrefix() + objectName + context.getTestPackageSuffix(); + sb.append("create or replace package "); + sb.append(packageName); + sb.append(" is\n"); + if (context.isGenerateComments()) { + sb.append("\n\t-- generated by utPLSQL for SQL Developer on "); + sb.append(today); + sb.append("\n\n"); + } + sb.append("\t--%suite("); + sb.append(packageName); + sb.append(")\n"); + if (!context.getSuitePath().isEmpty()) { + sb.append("\t--%suitepath("); + sb.append(context.getSuitePath()); + sb.append(")\n"); + } + sb.append("\n"); + for (final String u : units) { + final String unit = u.toLowerCase(); + if (context.getNumberOfTestsPerUnit() > 1 + && ("PACKAGE".equals(context.getObjectType()) || "TYPE".equals(context.getObjectType()))) { + sb.append("\t--%context("); + sb.append(unit); + sb.append(")\n\n"); + } + for (int i=1; i <= context.getNumberOfTestsPerUnit(); i++) { + sb.append("\t--%test\n"); + if (context.isDisableTests()) { + sb.append("\t--%disabled\n"); + } + sb.append("\tprocedure "); + sb.append(context.getTestUnitPrefix()); + sb.append(unit); + sb.append(context.getTestUnitSuffix()); + if (context.getNumberOfTestsPerUnit() > 1) { + sb.append(i); + } + sb.append(";\n\n"); + } + if (context.getNumberOfTestsPerUnit() > 1 + && ("PACKAGE".equals(context.getObjectType()) || "TYPE".equals(context.getObjectType()))) { + sb.append("\t--%endcontext\n\n"); + } + } + sb.append("end "); + sb.append(packageName); + sb.append(";\n"); + sb.append("/"); + final String ret = sb.toString(); + return StringTools.replaceTabsWithSpaces(ret, context.getIndentSpaces()); + } + + public String generateBody() { + StringBuilder sb = new StringBuilder(); + final String objectName = context.getObjectName().toLowerCase(); + sb.append("create or replace package body "); + sb.append(context.getTestPackagePrefix()); + sb.append(objectName); + sb.append(context.getTestPackageSuffix()); + sb.append(" is\n\n"); + if (context.isGenerateComments()) { + sb.append("\t-- generated by utPLSQL for SQL Developer on "); + sb.append(today); + sb.append("\n\n"); + } + for (final String u : units) { + final String unit = u.toLowerCase(); + for (int i=1; i <= context.getNumberOfTestsPerUnit(); i++) { + final String procedureName = context.getTestUnitPrefix() + unit + context.getTestUnitSuffix() + + (context.getNumberOfTestsPerUnit() > 1 ? String.valueOf(i) : ""); + if (context.isGenerateComments()) { + sb.append("\t--\n"); + sb.append("\t-- test "); + sb.append(unit); + if (context.getNumberOfTestsPerUnit() > 1) { + sb.append(" case "); + sb.append(i); + sb.append(": ...\n"); + } else { + sb.append("\n"); + } + sb.append("\t--\n"); + } + sb.append("\tprocedure "); + sb.append(procedureName); + sb.append(" is\n"); + sb.append("\t\tl_actual integer := 0;\n"); + sb.append("\t\tl_expected integer := 1;\n"); + sb.append("\tbegin\n"); + if (context.isGenerateComments()) { + sb.append("\t\t-- arrange\n\n"); + sb.append("\t\t-- act\n"); + sb.append("\t\t-- "); + sb.append(objectName); + sb.append("."); + sb.append(unit); + sb.append(";\n\n"); + sb.append("\t\t-- assert\n"); + } + sb.append("\t\tut.expect(l_actual).to_equal(l_expected);\n"); + sb.append("\tend "); + sb.append(procedureName); + sb.append(";\n\n"); + } + } + sb.append("end "); + sb.append(context.getTestPackagePrefix()); + sb.append(objectName); + sb.append(context.getTestPackageSuffix()); + sb.append(";\n"); + sb.append("/"); + final String ret = sb.toString(); + return StringTools.replaceTabsWithSpaces(ret, context.getIndentSpaces()); + } + + public CharSequence generate() { + StringBuilder sb = new StringBuilder(); + sb.append(generateSpec()); + sb.append("\n\n"); + sb.append(generateBody()); + sb.append('\n'); + return sb; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.xtend b/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.xtend deleted file mode 100644 index 62b79555..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/oddgen/TestTemplate.xtend +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.oddgen - -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter -import java.util.List -import org.utplsql.sqldev.dal.UtplsqlDao -import org.utplsql.sqldev.model.oddgen.GenContext - -class TestTemplate { - var GenContext context - var UtplsqlDao dao - var List units - var dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - var today = dateTimeFormatter.format(LocalDateTime.now()) - - new(GenContext context) { - this.context = context - dao = new UtplsqlDao(context.conn) - units = dao.units(context.objectType, context.objectName) - } - - def replaceTabsWithSpaces(CharSequence input) { - val spaces = String.format("%1$"+context.indentSpaces+"s", "") - return input.toString.replace("\t", spaces) - } - - def generateSpec() { - val ret = ''' - «val objectName = context.objectName.toLowerCase» - «val packageName = '''«context.testPackagePrefix»«objectName»«context.testPackageSuffix»'''» - CREATE OR REPLACE PACKAGE «packageName» IS - - «IF context.generateComments» - /* generated by utPLSQL for SQL Developer on «today» */ - - «ENDIF» - --%suite(«packageName») - «IF !context.suitePath.empty» - --%suitepath(«context.suitePath») - «ENDIF» - - «FOR u : units» - «val unit = u.toLowerCase» - «IF context.numberOfTestsPerUnit > 1 && (context.objectType == "PACKAGE" || context.objectType == "TYPE")» - --%context(«unit») - - «ENDIF» - «FOR i : 1 .. context.numberOfTestsPerUnit» - --%test - «IF context.disableTests» - --%disabled - «ENDIF» - PROCEDURE «context.testUnitPrefix»«unit»«context.testUnitSuffix»«IF context.numberOfTestsPerUnit > 1»«i»«ENDIF»; - - «ENDFOR» - «IF context.numberOfTestsPerUnit > 1 && (context.objectType == "PACKAGE" || context.objectType == "TYPE")» - --%endcontext - - «ENDIF» - «ENDFOR» - END «packageName»; - / - ''' - return ret.replaceTabsWithSpaces - } - - def generateBody() { - val ret = ''' - «val objectName = context.objectName.toLowerCase» - CREATE OR REPLACE PACKAGE BODY «context.testPackagePrefix»«objectName»«context.testPackageSuffix» IS - - «IF context.generateComments» - /* generated by utPLSQL for SQL Developer on «today» */ - - «ENDIF» - «FOR u : units» - «val unit = u.toLowerCase» - «FOR i : 1 .. context.numberOfTestsPerUnit» - «val procedureName = '''«context.testUnitPrefix»«unit»«context.testUnitSuffix»«IF context.numberOfTestsPerUnit > 1»«i»«ENDIF»'''» - «IF context.generateComments» - -- - -- test «unit»«IF context.numberOfTestsPerUnit > 0» case «i»: ...«ENDIF» - -- - «ENDIF» - PROCEDURE «procedureName» IS - l_actual INTEGER := 0; - l_expected INTEGER := 1; - BEGIN - «IF context.generateComments» - -- populate actual - -- «objectName».«unit»; - - -- populate expected - -- ... - - -- assert - «ENDIF» - ut.expect(l_actual).to_equal(l_expected); - END «procedureName»; - - «ENDFOR» - «ENDFOR» - END «context.testPackagePrefix»«objectName»«context.testPackageSuffix»; - / - ''' - return ret.replaceTabsWithSpaces - } - - def generate() ''' - «generateSpec» - - «generateBody» - ''' -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.java b/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.java new file mode 100644 index 00000000..bf7962b1 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.java @@ -0,0 +1,64 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.parser; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import oracle.dbtools.parser.LexerToken; +import oracle.dbtools.raptor.navigator.plsql.Member; +import oracle.dbtools.raptor.navigator.plsql.PlSqlArguments; +import oracle.dbtools.raptor.navigator.plsql.PlsqlStructureParser; + +/* + * Cannot use this class within SQL Developer because the + * package oracle.dbtools.parser is not exported in sqldeveloper OSGI bundle (extension) + * (throws ClassNotFoundException at runtime). + * + * The dbtools-common.jar contains the necessary packages, + * but it cannot be distributed with the utPLSQL extension + * without violating the Oracle license agreement. + */ +public class SqlDevParser { + + @SuppressWarnings("unchecked") + public Set getMembers(final String plsql) { + final List tokens = LexerToken.parse(plsql); + final PlsqlStructureParser parser = new PlsqlStructureParser(); + parser.parse(tokens, PlSqlArguments.getSort()); + return parser.children; + } + + private int getStartLine(final String plsql, final int offset) { + int line = 1; + for (int i = 0; i < plsql.length(); i++) { + final String c = plsql.substring(i, i + 1); + if (i > offset) { + return line; + } else if ("\n".equals(c)) { + line = line + 1; + } + } + return line; + } + + public int getMemberStartLine(final String plsql, final String memberName) { + final Set members = this.getMembers(plsql); + final Optional member = members.stream().filter(it -> it.name.equalsIgnoreCase(memberName)).findFirst(); + return member.map(value -> this.getStartLine(plsql, value.codeOffset)).orElse(1); + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.xtend b/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.xtend deleted file mode 100644 index 3eb38765..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/parser/SqlDevParser.xtend +++ /dev/null @@ -1,60 +0,0 @@ -/* Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.parser - -import oracle.dbtools.parser.LexerToken -import oracle.dbtools.raptor.navigator.plsql.PlSqlArguments -import oracle.dbtools.raptor.navigator.plsql.PlsqlStructureParser - -/* - * Cannot use this class within SQL Developer because the - * package oracle.dbtools.parser is not exported in sqldeveloper OSGI bundle (extension) - * (throws ClassNotFoundException at runtime). - * - * The dbtools-common.jar contains the necessary packages, - * but it cannot be distributed with the utPLSQL extension - * without violating the Oracle license agreement. - */ -class SqlDevParser { - def getMembers(String plsql) { - val tokens = LexerToken.parse(plsql) - val parser = new PlsqlStructureParser - parser.parse(tokens, PlSqlArguments.sort) - return parser.children - } - - private def getStartLine(String plsql, int offset) { - var int line = 1 - for (var i = 0; i < plsql.length; i++) { - val c = plsql.substring(i, i+1) - if (i > offset) { - return line - } else if (c == '\n') { - line = line + 1 - } - } - return line - } - - def getMemberStartLine(String plsql, String memberName) { - val members = plsql.members - val member = members.findFirst[it.name.equalsIgnoreCase(memberName)] - if (member !== null) { - return getStartLine(plsql, member.codeOffset) - } else { - 1 - } - } -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.java b/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.java new file mode 100644 index 00000000..8eb5c9ef --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.java @@ -0,0 +1,280 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.parser; + +import java.sql.Connection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.swing.text.JTextComponent; + +import org.utplsql.sqldev.dal.UtplsqlDao; +import org.utplsql.sqldev.exception.GenericRuntimeException; +import org.utplsql.sqldev.model.DatabaseTools; +import org.utplsql.sqldev.model.parser.PlsqlObject; +import org.utplsql.sqldev.model.parser.Unit; +import org.utplsql.sqldev.model.ut.Annotation; + +public class UtplsqlParser { + private String owner; + private String plsql; + private String plsqlReduced; + private ArrayList objects = new ArrayList<>(); + private ArrayList units = new ArrayList<>(); + + public UtplsqlParser(final String plsql, final Connection conn, final String owner) { + setPlsql(plsql); + setPlsqlReduced(); + populateObjects(); + populateUnits(); + processAnnotations(conn, owner); + } + + public UtplsqlParser(final String plsql) { + this(plsql, null, null); + } + + /** + * JTextComponents uses one position for EOL (end-of-line), + * even on Windows platforms were it is two characters (CR/LF). + * To simplify position calculations and subsequent regular expressions + * all new lines are replaced with LF on Windows platforms. + */ + private void setPlsql(final String plsql) { + final String lineSep = System.getProperty("line.separator"); + if (!"\n".equals(lineSep)) { + // replace CR/LF with LF on Windows platforms + this.plsql = plsql.replace(lineSep, "\n"); + } else { + this.plsql = plsql; + } + } + + /** + * replace the following expressions with space to simplify + * and improve performance of subsequent regular expressions: + * - multi-line PL/SQL comments + * - single-line PL/SQL comments + * - string literals + * the result is not valid PL/SQL anymore, but good enough + * to find PL/SQL objects and units + */ + private void setPlsqlReduced() { + final StringBuilder sb = new StringBuilder(); + final Pattern p = Pattern.compile("(/\\*(.|[\\n])*?\\*/)|(--[^\\n]*\\n)|(\'([^\']|[\\n])*?\')"); + final Matcher m = p.matcher(plsql); + int pos = 0; + while (m.find()) { + if (pos < m.start()) { + sb.append(plsql.substring(pos, m.start())); + } + for (int i = m.start(); i < m.end(); i++) { + final String c = plsql.substring(i, i + 1); + if ("\n".equals(c) || "\r".equals(c)) { + sb.append(c); + } else { + sb.append(' '); + } + } + pos = m.end(); + } + if (plsql.length() > pos) { + sb.append(plsql.substring(pos, plsql.length())); + } + plsqlReduced=sb.toString(); + } + + private void populateObjects() { + final Pattern p = Pattern.compile( + "(?i)(\\s*)(create(\\s+or\\s+replace)?\\s+(package|type|function|procedure)\\s+(body\\s+)?)([^\\s]+)(\\s+)"); + final Matcher m = p.matcher(plsqlReduced); + while (m.find()) { + final PlsqlObject o = new PlsqlObject(); + o.setType(m.group(4).toUpperCase()); + o.setName(m.group(6)); + o.setPosition(m.start()); + objects.add(o); + } + } + + private void populateUnits() { + final Pattern p = Pattern.compile("(?i)(\\s*)(procedure)(\\s+)([^\\s\\(;]+)"); + final Matcher m = p.matcher(plsqlReduced); + while (m.find()) { + final Unit u = new Unit(); + u.setName(m.group(4)); + u.setPosition(m.start()); + u.setPositionOfName(m.start(4)); + units.add(u); + } + } + + private void processAnnotations(final Connection conn, final String owner) { + this.owner = owner; + if (conn != null) { + final UtplsqlDao dao = new UtplsqlDao(conn); + if (dao.isUtAnnotationManagerInstalled()) { + for (final PlsqlObject o : objects) { + final List segments = Arrays.asList(fixName(o.getName()).split("\\.")); + final String schema = owner != null ? owner : DatabaseTools.getSchema(conn); + final List annotations = dao.annotations(schema, + segments.get(segments.size() - 1).toUpperCase()); + if (annotations.stream().anyMatch(it -> it.getName().equals("suite"))) { + o.setAnnotations(annotations); + } + } + final ArrayList fixedUnits = new ArrayList<>(); + for (final Unit u : units) { + final PlsqlObject o = getObjectAt(u.getPosition()); + if (o != null && o.getAnnotations() != null + && o.getAnnotations().stream().anyMatch(it -> "test".equals(it.getName()) + && it.getSubobjectName().equalsIgnoreCase(fixName(u.getName())))) { + fixedUnits.add(u); + } + } + units = fixedUnits; + final ArrayList fixedObjects = new ArrayList<>(); + for (final PlsqlObject o : objects) { + if (o.getAnnotations() != null) { + fixedObjects.add(o); + } + } + objects = fixedObjects; + } + } + } + + /** + * gets the PL/SQL object based on the current editor position + * + * @param position + * the absolute position as used in + * {@link JTextComponent#getCaretPosition()} + * @return the PL/SQL object + */ + public PlsqlObject getObjectAt(final int position) { + PlsqlObject obj = null; + for (final PlsqlObject o : objects) { + if (o.getPosition() <= position) { + obj = o; + } + } + return obj; + } + + /** + * converts a line and column to a postion as used in as used in + * {@link JTextComponent#getCaretPosition()} used for testing purposes only + * + * @param line + * the line as used in SQL Developer, starting with 1 + * @param column + * the column as used in SQL Developer, starting with 1 + * @return the position + */ + public int toPosition(final int line, final int column) { + int lines = 0; + for (int i = 0; i < plsql.length(); i++) { + if ("\n".equals(plsql.substring(i, i + 1))) { + lines++; + if (lines == line - 1) { + return i + column; + } + } + } + throw new GenericRuntimeException("Line " + line + " not found."); + } + + private String getUnitNameAt(final int position) { + String name = ""; + for (final Unit u : units) { + if (u.getPosition() <= position) { + name = u.getName(); + } + } + return name; + } + + private String fixName(final String name) { + return name.replace("\"", ""); + } + + public List getObjects() { + return objects; + } + + public List getUnits() { + return units; + } + + /** + * gets the utPLSQL path based on the current editor position + * + * @param position + * the absolute position as used in + * {@link JTextComponent#getCaretPosition()} + * @return the utPLSQL path + */ + public String getPathAt(final int position) { + final StringBuilder sb = new StringBuilder(); + final PlsqlObject object = getObjectAt(position); + if (object != null && "PACKAGE".equals(object.getType())) { + final String unitName = getUnitNameAt(position); + if (owner != null) { + sb.append(owner); + sb.append("."); + } + sb.append(fixName(object.getName())); + if (!unitName.isEmpty()) { + sb.append("."); + sb.append(fixName(unitName)); + } + } + return sb.toString(); + } + + private int getStartLine(final int position) { + int line = 1; + for (int i = 0; i < plsql.length(); i++) { + final String c = plsql.substring(i, i + 1); + if (i > position) { + return line; + } else if ("\n".equals(c)) { + line++; + } + } + return line; + } + + /** + * get the line of a PL/SQL package unit + * + * @param unitName + * name of the unit. Only procedures are supported + * @return the line where the procedure is defined + */ + public int getLineOf(final String unitName) { + for (final Unit u : units) { + if (u.getName().equalsIgnoreCase(unitName)) { + return getStartLine(u.getPositionOfName()); + } + } + return 1; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.xtend b/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.xtend deleted file mode 100644 index 736be98e..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/parser/UtplsqlParser.xtend +++ /dev/null @@ -1,253 +0,0 @@ -/* Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.parser - -import java.sql.Connection -import java.util.ArrayList -import java.util.Arrays -import java.util.regex.Pattern -import javax.swing.text.JTextComponent -import org.utplsql.sqldev.dal.UtplsqlDao -import org.utplsql.sqldev.model.parser.PlsqlObject -import org.utplsql.sqldev.model.parser.Unit - -class UtplsqlParser { - String owner - String plsql - String plsqlReduced - ArrayList objects = new ArrayList - ArrayList units = new ArrayList - - new(String plsql, Connection conn, String owner) { - setPlsql(plsql) - setPlsqlReduced - populateObjects - populateUnits - processAnnotations(conn, owner) - } - - new(String plsql) { - this(plsql, null, null) - } - - /** - * JTextComponents uses one position for EOL (end-of-line), - * even on Windows platforms were it is two characters (CR/LF). - * To simplify position calculations and subsequent regular expressions - * all new lines are replaced with LF on Windows platforms. - */ - private def setPlsql(String plsql) { - val lineSep = System.getProperty("line.separator") - if (lineSep.length > 0) { - // replace CR/LF with LF on Windows platforms - this.plsql = plsql.replace(lineSep, "\n") - } else { - this.plsql = plsql - } - } - - /** - * replace the following expressions with space to simplify - * and improve performance of subsequent regular expressions: - * - multi-line PL/SQL comments - * - single-line PL/SQL comments - * - string literals - * the result is not valid PL/SQL anymore, but good enough - * to find PL/SQL objects and units - */ - private def setPlsqlReduced() { - val sb = new StringBuffer - val p = Pattern.compile("(/\\*(.|[\\n])*?\\*/)|(--[^\\n]*\\n)|('([^']|[\\n])*?')") - val m = p.matcher(plsql) - var pos = 0 - while (m.find) { - if (pos < m.start) { - sb.append(plsql.substring(pos, m.start)) - } - for (var i=m.start; i pos) { - sb.append(plsql.substring(pos, plsql.length)) - } - plsqlReduced=sb.toString - } - - private def populateObjects() { - val p = Pattern.compile("(?i)(\\s*)(create(\\s+or\\s+replace)?\\s+(package|type|function|procedure)\\s+(body\\s+)?)([^\\s]+)(\\s+)") - val m = p.matcher(plsqlReduced) - while (m.find) { - val o = new PlsqlObject - o.type = m.group(4).toUpperCase - o.name = m.group(6) - o.position = m.start - objects.add(o) - } - } - private def populateUnits() { - val p = Pattern.compile("(?i)(\\s*)(procedure)(\\s+)([^\\s\\(;]+)") - val m = p.matcher(plsqlReduced) - while (m.find) { - val u = new Unit - u.name = m.group(4) - u.position = m.start - u.positionOfName = m.start(4) - units.add(u) - } - } - - private def processAnnotations(Connection conn, String owner) { - this.owner = owner - if (conn !== null) { - val dao = new UtplsqlDao(conn) - if (dao.utAnnotationManagerInstalled) { - for (o : objects) { - val segments = Arrays.asList(o.name.fixName.split("\\.")) - val annotations = dao.annotations(if (owner !== null) {owner} else {conn.schema}, segments.last.toUpperCase) - if (annotations.findFirst[it.name == "suite"] !== null) { - o.annotations = annotations - } - } - val fixedUnits = new ArrayList - for (u : units) { - val o = getObjectAt(u.position) - if (o?.annotations !== null && o.annotations.findFirst [ - it.name == "test" && it.subobjectName.equalsIgnoreCase(u.name.fixName) - ] !== null) { - fixedUnits.add(u) - } - } - units = fixedUnits - val fixedObjects = new ArrayList - for (o : objects) { - if (o.annotations !== null) { - fixedObjects.add(o) - } - } - objects = fixedObjects - } - } - } - - /** - * gets the PL/SQL object based on the current editor position - * - * @param position the absolute position as used in {@link JTextComponent#getCaretPosition()} - * @return the PL/SQL object - */ - def getObjectAt(int position) { - var PlsqlObject obj - for (o : objects) { - if (o.position <= position) { - obj = o - } - } - return obj - } - - /** - * converts a line and column to a postion as used in as used in {@link JTextComponent#getCaretPosition()} - * used for testing purposes only - * - * @param line the line as used in SQL Developer, starting with 1 - * @param column the column as used in SQL Developer, starting with 1 - * @return the position - */ - def toPosition(int line, int column) { - var lines=0 - for (var i=0; i position) { - return line - } else if (c == '\n') { - line = line + 1 - } - } - return line - } - - /** - * get the line of a PL/SQL package unit - * - * @param unitName name of the unit. Only procedures are supported - * @return the line where the procedure is defined - */ - def getLineOf(String unitName) { - for (u : units) { - if (u.name.equalsIgnoreCase(unitName)) { - return u.positionOfName.startLine - } - } - return 1 - } -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/resources/UtplsqlResources.java b/sqldev/src/main/java/org/utplsql/sqldev/resources/UtplsqlResources.java new file mode 100644 index 00000000..1bd9936e --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/resources/UtplsqlResources.java @@ -0,0 +1,56 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.resources; + +import java.awt.Image; + +import javax.swing.Icon; + +import oracle.dbtools.raptor.utils.MessagesBase; + +public class UtplsqlResources extends MessagesBase { + private static final ClassLoader CLASS_LOADER = UtplsqlResources.class.getClassLoader(); + private static final String CLASS_NAME = UtplsqlResources.class.getCanonicalName(); + private static final UtplsqlResources INSTANCE = new UtplsqlResources(); + + private UtplsqlResources() { + super(CLASS_NAME, CLASS_LOADER); + } + + public static String getString(final String paramString) { + return INSTANCE.getStringImpl(paramString); + } + + public static String get(final String paramString) { + return getString(paramString); + } + + public static Image getImage(final String paramString) { + return INSTANCE.getImageImpl(paramString); + } + + public static String format(final String paramString, final Object... paramVarArgs) { + return INSTANCE.formatImpl(paramString, paramVarArgs); + } + + public static Icon getIcon(final String paramString) { + return INSTANCE.getIconImpl(paramString); + } + + public static Integer getInteger(final String paramString) { + return INSTANCE.getIntegerImpl(paramString); + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/resources/UtplsqlResources.xtend b/sqldev/src/main/java/org/utplsql/sqldev/resources/UtplsqlResources.xtend deleted file mode 100644 index 07c2534b..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/resources/UtplsqlResources.xtend +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.resources - -import oracle.dbtools.raptor.utils.MessagesBase - -class UtplsqlResources extends MessagesBase { - static final ClassLoader CLASS_LOADER = UtplsqlResources.classLoader - static final String CLASS_NAME = UtplsqlResources.canonicalName - static final UtplsqlResources INSTANCE = new UtplsqlResources() - - private new() { - super(CLASS_NAME, CLASS_LOADER) - } - - def static getString(String paramString) { - return INSTANCE.getStringImpl(paramString) - } - - def static get(String paramString) { - return getString(paramString) - } - - def static getImage(String paramString) { - return INSTANCE.getImageImpl(paramString) - } - - def static format(String paramString, Object... paramVarArgs) { - return INSTANCE.formatImpl(paramString, paramVarArgs) - } - - def static getIcon(String paramString) { - return INSTANCE.getIconImpl(paramString) - } - - def static getInteger(String paramString) { - return INSTANCE.getIntegerImpl(paramString) - } -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java new file mode 100644 index 00000000..6222c2e0 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.java @@ -0,0 +1,478 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.runner; + +import java.awt.Dimension; +import java.awt.Toolkit; +import java.net.URL; +import java.sql.Connection; +import java.util.List; +import java.util.UUID; +import java.util.function.Supplier; +import java.util.logging.Logger; + +import javax.swing.JFrame; + +import org.utplsql.sqldev.coverage.CodeCoverageReporter; +import org.utplsql.sqldev.dal.RealtimeReporterDao; +import org.utplsql.sqldev.dal.RealtimeReporterEventConsumer; +import org.utplsql.sqldev.exception.GenericRuntimeException; +import org.utplsql.sqldev.model.DatabaseTools; +import org.utplsql.sqldev.model.StringTools; +import org.utplsql.sqldev.model.SystemTools; +import org.utplsql.sqldev.model.runner.ItemNode; +import org.utplsql.sqldev.model.runner.PostRunEvent; +import org.utplsql.sqldev.model.runner.PostSuiteEvent; +import org.utplsql.sqldev.model.runner.PostTestEvent; +import org.utplsql.sqldev.model.runner.PreRunEvent; +import org.utplsql.sqldev.model.runner.PreSuiteEvent; +import org.utplsql.sqldev.model.runner.PreTestEvent; +import org.utplsql.sqldev.model.runner.RealtimeReporterEvent; +import org.utplsql.sqldev.model.runner.Run; +import org.utplsql.sqldev.model.runner.Suite; +import org.utplsql.sqldev.model.runner.Test; +import org.utplsql.sqldev.resources.UtplsqlResources; +import org.utplsql.sqldev.ui.runner.RunnerFactory; +import org.utplsql.sqldev.ui.runner.RunnerPanel; +import org.utplsql.sqldev.ui.runner.RunnerView; + +import oracle.dbtools.raptor.runner.DBStarterFactory; +import oracle.ide.Context; +import oracle.jdevimpl.runner.debug.DebuggingProcess; +import oracle.jdevimpl.runner.run.JRunner; + +public class UtplsqlRunner implements RealtimeReporterEventConsumer { + private static final Logger logger = Logger.getLogger(UtplsqlRunner.class.getName()); + private static final int DEBUG_TIMEOUT_SECONDS = 60*60; + + private final boolean withCodeCoverage; + private final List pathList; + private final List schemaList; + private final List includeObjectList; + private final List excludeObjectList; + private Context context; + private String connectionName; + private Connection producerConn; + private Connection consumerConn; + private final String realtimeReporterId = UUID.randomUUID().toString().replace("-", ""); + private final String coverageReporterId = UUID.randomUUID().toString().replace("-", ""); + private Run run; + private RunnerPanel panel; + private JFrame frame; // for testing purposes only (outside of SQL Developer) + private Thread producerThread; + private Thread consumerThread; + private boolean debug = false; + private final URL htmlReportAssetPath; + + public UtplsqlRunner(final List pathList, final String connectionName) { + this.withCodeCoverage = false; + this.pathList = pathList; + this.schemaList = null; + this.includeObjectList = null; + this.excludeObjectList = null; + this.htmlReportAssetPath = null; + setConnection(connectionName); + this.context = Context.newIdeContext(); + } + + public UtplsqlRunner(final List pathList, final List schemaList, + final List includeObjectList, final List excludeObjectList, + final URL htmlReportAssetPath, final String connectionName) { + this.withCodeCoverage = true; + this.pathList = pathList; + this.schemaList = schemaList; + this.includeObjectList = includeObjectList; + this.excludeObjectList = excludeObjectList; + this.htmlReportAssetPath = htmlReportAssetPath; + setConnection(connectionName); + this.context = Context.newIdeContext(); + } + + /** + * this constructor is intended for tests only (without code coverage) + */ + public UtplsqlRunner(final List pathList, final Connection producerConn, final Connection consumerConn) { + this.withCodeCoverage = false; + this.pathList = pathList; + this.schemaList = null; + this.includeObjectList = null; + this.excludeObjectList = null; + this.htmlReportAssetPath = null; + this.producerConn = producerConn; + this.consumerConn = consumerConn; + } + + /** + * this constructor is intended for tests only (with code coverage and default htmlReportAssetPath) + */ + public UtplsqlRunner(final List pathList, final List schemaList, + final List includeObjectList, final List excludeObjectList, + final Connection producerConn, final Connection consumerConn) { + this.withCodeCoverage = true; + this.pathList = pathList; + this.schemaList = schemaList; + this.includeObjectList = includeObjectList; + this.excludeObjectList = excludeObjectList; + this.htmlReportAssetPath = null; + this.producerConn = producerConn; + this.consumerConn = consumerConn; + } + + private void setConnection(final String connectionName) { + if (connectionName == null) { + throw new NullPointerException("Cannot initialize a RealtimeConsumer without a ConnectionName"); + } else { + producerConn = DatabaseTools.cloneConnection(connectionName); + consumerConn = DatabaseTools.cloneConnection(connectionName); + } + this.connectionName = connectionName; + } + + public void enableDebugging() { + this.debug = true; + } + + public void dispose() { + // running in SQL Developer + DatabaseTools.closeConnection(producerConn); + DatabaseTools.closeConnection(consumerConn); + if (frame != null) { + frame.setVisible(false); + } + run.setConsumerConn(null); + } + + @Override + public void process(final RealtimeReporterEvent event) { + logger.fine(event::toString); + // dynamic dispatching code originally generated by Xtend + if (event instanceof PostRunEvent) { + doProcess((PostRunEvent) event); + } else if (event instanceof PostSuiteEvent) { + doProcess((PostSuiteEvent) event); + } else if (event instanceof PostTestEvent) { + doProcess((PostTestEvent) event); + } else if (event instanceof PreRunEvent) { + doProcess((PreRunEvent) event); + } else if (event instanceof PreSuiteEvent) { + doProcess((PreSuiteEvent) event); + } else if (event instanceof PreTestEvent) { + doProcess((PreTestEvent) event); + } else { + throw new IllegalArgumentException("Unhandled event: " + event.toString()); + } + } + + public boolean isRunning() { + return run != null && run.getEndTime() == null; + } + + private void initRun() { + run = new Run(realtimeReporterId, connectionName, pathList); + run.setStartTime(StringTools.getSysdate()); + run.getCounter().setDisabled(0); + run.getCounter().setSuccess(0); + run.getCounter().setFailure(0); + run.getCounter().setError(0); + run.getCounter().setWarning(0); + run.setInfoCount(0); + run.setTotalNumberOfTests(-1); + run.setCurrentTestNumber(0); + run.setStatus(UtplsqlResources.getString("RUNNER_INITIALIZING_TEXT")); + run.setConsumerConn(consumerConn); + panel.setModel(run); + panel.update(realtimeReporterId); + } + + private boolean logFalseCondition(boolean condition, Supplier msgToLog) { + if (!condition) { + logger.severe(msgToLog); + } + return condition; + } + + private void doProcess(final PreRunEvent event) { + run.setTotalNumberOfTests(event.getTotalNumberOfTests()); + run.put(event.getItems()); + run.setStatus(UtplsqlResources.getString("RUNNER_RUNNING_TEXT")); + panel.update(realtimeReporterId); + } + + private void doProcess(final PostRunEvent event) { + run.setStartTime(event.getStartTime()); + run.setEndTime(event.getEndTime()); + run.setExecutionTime(event.getExecutionTime()); + run.setErrorStack(event.getErrorStack()); + run.setServerOutput(event.getServerOutput()); + run.setStatus(UtplsqlResources.getString("RUNNER_FINISHED_TEXT")); + panel.update(realtimeReporterId); + } + + private void doProcess(final PreSuiteEvent event) { + final ItemNode node = run.getItemNodes().get(event.getId()); + assert logFalseCondition(node != null, () -> "Could not find suite id \"" + event.getId() + + "\" when processing PreSuiteEvent " + event.toString() + "."); + final Suite suite = (Suite) node.getUserObject(); + suite.setStartTime(StringTools.getSysdate()); + panel.update(realtimeReporterId); + } + + private void doProcess(final PostSuiteEvent event) { + final ItemNode node = run.getItemNodes().get(event.getId()); + assert logFalseCondition(node != null, () -> "Could not find suite id \"" + event.getId() + + "\" when processing PostSuiteEvent " + event.toString() + "."); + final Suite suite = (Suite) node.getUserObject(); + if (suite.getEndTime() == null) { + // first occurrence, multiple possible, e.g. ut_tester and ut_user in utPLSQL project + suite.setStartTime(event.getStartTime()); + suite.setEndTime(event.getEndTime()); + suite.setExecutionTime(event.getExecutionTime()); + suite.setCounter(event.getCounter()); + suite.setErrorStack(event.getErrorStack()); + suite.setWarnings(event.getWarnings()); + suite.setServerOutput(event.getServerOutput()); + } else { + // subsequent occurrence, aggregate + suite.setEndTime(event.getEndTime()); + suite.setExecutionTime(suite.getExecutionTime() + event.getExecutionTime()); + suite.getCounter().setWarning(suite.getCounter().getWarning() + event.getCounter().getWarning()); + suite.getCounter().setDisabled(suite.getCounter().getDisabled() + event.getCounter().getDisabled()); + suite.getCounter().setSuccess(suite.getCounter().getSuccess() + event.getCounter().getSuccess()); + suite.getCounter().setFailure(suite.getCounter().getFailure() + event.getCounter().getFailure()); + suite.getCounter().setError(suite.getCounter().getError() + event.getCounter().getError()); + if (event.getWarnings() != null) { + StringBuilder sb = new StringBuilder(); + if (suite.getWarnings() != null) { + sb.append(suite.getWarnings()); + sb.append("\n\n"); + } + sb.append(event.getWarnings()); + suite.setWarnings(sb.toString()); + } + if (event.getServerOutput() != null) { + StringBuilder sb = new StringBuilder(); + if (suite.getServerOutput() != null) { + sb.append(suite.getServerOutput()); + sb.append("\n\n"); + } + sb.append(event.getServerOutput()); + suite.setServerOutput(sb.toString()); + } + } + + final Test test = run.getCurrentTest(); + // Errors on suite levels are reported as warnings by the utPLSQL framework, + // since an error on suite level does not affect a status of a test. + // It is possible that the test is OK, but contains error messages on suite level(s) + // Populating test.errorStack would be a) wrong and b) redundant + if (event.getWarnings() != null) { + if (test.getCounter().getWarning() == 0) { + test.getCounter().setWarning(1); + test.getCounter().setWarning(run.getCounter().getWarning() + 1); + } + StringBuilder sb = new StringBuilder(); + if (test.getWarnings() != null) { + sb.append(test.getWarnings()); + sb.append("\n\n"); + } + sb.append("For suite "); + sb.append(event.getId()); + sb.append(":\n\n"); + sb.append(event.getWarnings()); + test.setWarnings(sb.toString()); + } + if (event.getServerOutput() != null) { + if (test.getServerOutput() == null) { + run.setInfoCount(run.getInfoCount() + 1); + } + StringBuilder sb = new StringBuilder(); + if (test.getServerOutput() != null) { + sb.append(test.getServerOutput()); + sb.append("\n\n"); + } + sb.append("For suite "); + sb.append(event.getId()); + sb.append(":\n\n"); + sb.append(event.getServerOutput()); + test.setServerOutput(sb.toString()); + } + panel.update(realtimeReporterId, suite); + } + + private void doProcess(final PreTestEvent event) { + final Test test = run.getTest(event.getId()); + assert logFalseCondition(test != null, () -> "Could not find test id \"" + event.getId() + + "\" when processing PreTestEvent " + event.toString() + "."); + test.setStartTime(StringTools.getSysdate()); + run.setStatus(event.getId() + "..."); + run.setCurrentTestNumber(event.getTestNumber()); + run.setCurrentTest(test); + panel.update(realtimeReporterId, test); + } + + private void doProcess(final PostTestEvent event) { + final Test test = run.getTest(event.getId()); + assert logFalseCondition(test != null, () -> "Could not find test id \"" + event.getId() + + "\" when processing PostTestEvent " + event.toString() + "."); + test.setStartTime(event.getStartTime()); + test.setEndTime(event.getEndTime()); + test.setExecutionTime(event.getExecutionTime()); + test.setCounter(event.getCounter()); + test.setErrorStack(event.getErrorStack()); + test.setServerOutput(event.getServerOutput()); + if (test.getServerOutput() != null) { + run.setInfoCount(run.getInfoCount() + 1); + } + test.setFailedExpectations(event.getFailedExpectations()); + test.setWarnings(event.getWarnings()); + if (test.getWarnings() != null) { + test.getCounter().setWarning(1); + } else { + test.getCounter().setWarning(0); + } + run.getCounter().setWarning(run.getCounter().getWarning() + test.getCounter().getWarning()); + run.getCounter().setDisabled(run.getCounter().getDisabled() + event.getCounter().getDisabled()); + run.getCounter().setSuccess(run.getCounter().getSuccess() + event.getCounter().getSuccess()); + run.getCounter().setFailure(run.getCounter().getFailure() + event.getCounter().getFailure()); + run.getCounter().setError(run.getCounter().getError() + event.getCounter().getError()); + panel.update(realtimeReporterId, test); + } + + private void produceReportWithDebugger(String anonymousPlsqlBlock) { + try { + Context processContext = JRunner.prepareProcessContext(context, false); + DebuggingProcess process = new DebuggingProcess(processContext); + DBStarterFactory.PlSqlStarter starter = new DBStarterFactory.PlSqlStarter(process, anonymousPlsqlBlock, connectionName, context); + starter.start(); + } catch (Throwable t) { + String msg = t.getClass().getName() + " while debugging utPLSQL test."; + logger.severe(() -> msg); + throw new GenericRuntimeException(msg); + } + } + + private void produce() { + try { + logger.fine(() -> "Running utPLSQL tests and producing events via reporter id " + realtimeReporterId + "..."); + final RealtimeReporterDao dao = new RealtimeReporterDao(producerConn); + if (withCodeCoverage) { + dao.produceReportWithCoverage(realtimeReporterId, coverageReporterId, pathList, schemaList, includeObjectList, excludeObjectList, htmlReportAssetPath); + } else { + if (!debug) { + dao.produceReport(realtimeReporterId, pathList); + } else { + produceReportWithDebugger(dao.getProduceReportPlsql(realtimeReporterId, pathList)); + } + } + logger.fine(() -> "All events produced for reporter id " + realtimeReporterId + "."); + } catch (Exception e) { + logger.severe(() -> "Error while producing events for reporter id " + realtimeReporterId + ": " + e.getMessage() + "."); + } + } + + private void consume() { + try { + try { + logger.fine(() -> "Consuming events from reporter id " + realtimeReporterId + " in realtime..."); + final RealtimeReporterDao dao = new RealtimeReporterDao(consumerConn); + if (!debug) { + dao.consumeReport(realtimeReporterId, this); + } else { + dao.consumeReport(realtimeReporterId, this, DEBUG_TIMEOUT_SECONDS); + } + logger.fine(() -> "All events consumed."); + if (withCodeCoverage) { + String html = dao.getHtmlCoverage(coverageReporterId); + CodeCoverageReporter.openInBrowser(html); + } + } catch (Exception e) { + logger.severe(() -> "Error while consuming events for reporter id " + realtimeReporterId + ": " + e.getMessage() + "."); + } + } finally { + if (run.getTotalNumberOfTests() < 0) { + run.setStatus(UtplsqlResources.getString("RUNNER_NO_TESTS_FOUND_TEXT")); + run.setExecutionTime((System.currentTimeMillis() - Double.valueOf(run.getStart())) / 1000); + run.setEndTime(StringTools.getSysdate()); + run.setTotalNumberOfTests(0); + panel.update(realtimeReporterId); + } + if (isRunningInSqlDeveloper()) { + dispose(); + } + } + } + + private boolean isRunningInSqlDeveloper() { + return (connectionName != null); + } + + private boolean initGUI() { + RunnerView dockable = null; + if (isRunningInSqlDeveloper() && (dockable = RunnerFactory.getDockable()) == null) { + logger.severe(() -> "Error getting utPLSQL dockable. Cannot run utPLSQL test."); + return false; + } else { + if (isRunningInSqlDeveloper()) { + RunnerFactory.showDockable(); + panel = dockable.getRunnerPanel(); + context.setView(dockable); + } else { + frame = new JFrame("utPLSQL Runner Panel"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + panel = new RunnerPanel(); + frame.add(panel.getGUI()); + frame.setPreferredSize(new Dimension(600, 800)); + frame.pack(); + final Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); + frame.setLocation(dim.width / 2 - frame.getSize().width / 2, + dim.height / 2 - frame.getSize().height / 2); + frame.setVisible(true); + } + initRun(); + return true; + } + } + + public void runTestAsync() { + // start tests when the GUI has been successfully initialized. + if (initGUI()) { + // the consumer + consumerThread = new Thread(this::consume); + consumerThread.setName("realtime consumer"); + consumerThread.start(); + // avoid concurrency on output header table to fix issue #80 + SystemTools.sleep(100); + // the producer + producerThread = new Thread(this::produce); + producerThread.setName("realtime producer"); + producerThread.start(); + } + } + + public Thread getProducerThread() { + return producerThread; + } + + public Thread getConsumerThread() { + return consumerThread; + } + + // for testing purposes only + public Run getRun() { + return run; + } + +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.xtend b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.xtend deleted file mode 100644 index 23eec363..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlRunner.xtend +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.runner - -import java.awt.Dimension -import java.awt.Toolkit -import java.sql.Connection -import java.text.SimpleDateFormat -import java.util.Date -import java.util.List -import java.util.UUID -import java.util.logging.Logger -import javax.swing.JFrame -import oracle.dbtools.raptor.utils.Connections -import org.utplsql.sqldev.dal.RealtimeReporterDao -import org.utplsql.sqldev.dal.RealtimeReporterEventConsumer -import org.utplsql.sqldev.model.runner.PostRunEvent -import org.utplsql.sqldev.model.runner.PostSuiteEvent -import org.utplsql.sqldev.model.runner.PostTestEvent -import org.utplsql.sqldev.model.runner.PreRunEvent -import org.utplsql.sqldev.model.runner.PreSuiteEvent -import org.utplsql.sqldev.model.runner.PreTestEvent -import org.utplsql.sqldev.model.runner.RealtimeReporterEvent -import org.utplsql.sqldev.model.runner.Run -import org.utplsql.sqldev.resources.UtplsqlResources -import org.utplsql.sqldev.ui.runner.RunnerFactory -import org.utplsql.sqldev.ui.runner.RunnerPanel -import org.utplsql.sqldev.ui.runner.RunnerView - -class UtplsqlRunner implements RealtimeReporterEventConsumer { - - static val Logger logger = Logger.getLogger(UtplsqlRunner.name); - - var List pathList - var String connectionName - var Connection producerConn - var Connection consumerConn - val String reporterId = UUID.randomUUID().toString.replace("-", "") - var Run run - var RunnerPanel panel - var Thread producerThread - var Thread consumerThread - - new(List pathList, String connectionName) { - this.pathList = pathList - setConnection(connectionName) - } - - /** - * this constructor is intended for tests only - */ - new(List pathList, Connection producerConn, Connection consumerConn) { - this.pathList = pathList - this.producerConn = producerConn - this.consumerConn = consumerConn - } - - private def setConnection(String connectionName) { - if (connectionName === null) { - throw new RuntimeException("Cannot initialize a RealtimeConsumer without a ConnectionName") - } else { - this.producerConn = Connections.instance.cloneConnection(Connections.instance.getConnection(connectionName)) - this.consumerConn = Connections.instance.cloneConnection(Connections.instance.getConnection(connectionName)) - } - this.connectionName = connectionName - } - - def dispose() { - // running in SQL Developer - if (!producerConn.closed) { - producerConn.close; - } - if (!consumerConn.closed) { - consumerConn.close; - } - } - - override void process(RealtimeReporterEvent event) { - logger.fine(event.toString) - event.doProcess - } - - private def getSysdate() { - val dateTime = new Date(System.currentTimeMillis); - val df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'000'"); - return df.format(dateTime) - - } - - private def initRun() { - run = new Run(reporterId, connectionName, pathList) - run.startTime = sysdate - run.counter.disabled = 0 - run.counter.success = 0 - run.counter.failure = 0 - run.counter.error = 0 - run.counter.warning = 0 - run.infoCount = 0 - run.totalNumberOfTests = -1 - run.currentTestNumber = 0 - run.status = UtplsqlResources.getString("RUNNER_INITIALIZING_TEXT") - panel.model = run - panel.update(reporterId) - } - - private def dispatch doProcess(PreRunEvent event) { - run.totalNumberOfTests = event.totalNumberOfTests - run.put(event.items) - run.status = UtplsqlResources.getString("RUNNER_RUNNING_TEXT") - panel.update(reporterId) - } - - private def dispatch doProcess(PostRunEvent event) { - run.startTime = event.startTime - run.endTime = event.endTime - run.executionTime = event.executionTime - run.errorStack = event.errorStack - run.serverOutput = event.serverOutput - run.status = UtplsqlResources.getString("RUNNER_FINNISHED_TEXT") - panel.update(reporterId) - } - - private def dispatch doProcess(PreSuiteEvent event) { - // ignore - } - - private def dispatch doProcess(PostSuiteEvent event) { - val test = run.currentTest - // Errors on suite levels are reported as warnings by the utPLSQL framework, - // since an error on suite level does not affect a status of a test. - // It is possible that the test is OK, but contains error messages on suite level(s) - // Populating test.errorStack would be a) wrong and b) redundant - if (event.warnings !== null) { - if (test.counter.warning == 0) { - test.counter.warning = 1 - run.counter.warning = run.counter.warning + 1 - } - test.warnings = ''' - «IF test.warnings !== null» - «test.warnings» - - «ENDIF» - For suite «event.id»: - - «event.warnings» - '''.toString.trim - } - if (event.serverOutput !== null) { - if (test.serverOutput === null) { - run.infoCount = run.infoCount + 1 - } - test.serverOutput = ''' - «IF test.serverOutput !== null» - «test.serverOutput» - - «ENDIF» - For suite «event.id»: - - «event.serverOutput» - '''.toString.trim - } - panel.update(reporterId) - } - - private def dispatch doProcess(PreTestEvent event) { - val test = run.getTest(event.id) - if (test === null) { - logger.severe('''Could not find test id "«event.id»" when processing PreTestEvent «event.toString».''') - } else { - test.startTime = sysdate - } - run.status = '''«event.id»...''' - run.currentTestNumber = event.testNumber - run.currentTest = test - panel.update(reporterId) - } - - private def dispatch doProcess(PostTestEvent event) { - val test = run.getTest(event.id) - if (test === null) { - logger.severe('''Could not find test id "«event.id»"" when processing PostTestEvent «event.toString».''') - } else { - test.startTime = event.startTime - test.endTime = event.endTime - test.executionTime = event.executionTime - test.counter = event.counter - test.errorStack = event.errorStack - test.serverOutput = event.serverOutput - if (test.serverOutput !== null) { - run.infoCount = run.infoCount + 1 - } - test.failedExpectations = event.failedExpectations - test.warnings = event.warnings - if (test.warnings !== null) { - // it does not matter how many rows are used by utPLSQL to store a warning event - test.counter.warning = 1 - } else { - test.counter.warning = 0 - } - } - run.counter.disabled = run.counter.disabled + event.counter.disabled - run.counter.success = run.counter.success + event.counter.success - run.counter.failure = run.counter.failure + event.counter.failure - run.counter.error = run.counter.error + event.counter.error - run.counter.warning = run.counter.warning + test.counter.warning - panel.update(reporterId) - } - - private def void produce() { - try { - logger.fine('''Running utPLSQL tests and producing events via reporter id «reporterId»...''') - val dao = new RealtimeReporterDao(producerConn) - dao.produceReport(reporterId, pathList) - logger.fine('''All events produced for reporter id «reporterId».''') - } catch (Exception e) { - logger.severe('''Error while producing events for reporter id «reporterId»: «e?.message»''') - } - } - - private def void consume() { - try { - logger.fine('''Consuming events from reporter id «reporterId» in realtime...''') - val dao = new RealtimeReporterDao(consumerConn) - dao.consumeReport(reporterId, this) - logger.fine('''All events consumed.''') - } catch (Exception e) { - logger.severe('''Error while consuming events for reporter id «reporterId»: «e?.message»''') - } - if (run.totalNumberOfTests < 0) { - run.status = UtplsqlResources.getString("RUNNER_NO_TESTS_FOUND_TEXT") - run.executionTime = new Double(System.currentTimeMillis - run.start)/1000 - run.endTime = sysdate - run.totalNumberOfTests = 0 - panel.update(reporterId) - } - if (isRunningInSqlDeveloper) { - dispose - } - } - - private def isRunningInSqlDeveloper() { - return connectionName !== null - } - - private def initGUI() { - var RunnerView dockable = null - if (runningInSqlDeveloper && (dockable = RunnerFactory.dockable) === null) { - logger.severe('''Error getting utPLSQL dockable. Cannot run utPLSQL test.''') - return false - } else { - if (runningInSqlDeveloper) { - RunnerFactory.showDockable; - panel = dockable.runnerPanel - } else { - val frame = new JFrame("utPLSQL Runner Panel") - frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE; - panel = new RunnerPanel - frame.add(panel.getGUI) - frame.preferredSize = new Dimension(600, 800) - frame.pack - val dim = Toolkit.getDefaultToolkit().getScreenSize(); - frame.setLocation(dim.width / 2 - frame.getSize().width / 2, dim.height / 2 - frame.getSize().height / 2); - frame.setVisible(true) - } - initRun - } - return true - } - - def runTestAsync() { - // start tests when the GUI has been successfully initialized. - if (initGUI) { - // the consumer - val Runnable consumer = [|consume] - consumerThread = new Thread(consumer) - consumerThread.name = "realtime consumer" - consumerThread.start - // avoid concurrency on output header table to fix issue #80 - Thread.sleep(100) - // the producer - val Runnable producer = [|produce] - producerThread = new Thread(producer) - producerThread.name = "realtime producer" - producerThread.start - } - } - - def getProducerThread() { - return producerThread - } - - def getConsumerThread() { - return consumerThread - } - -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.java b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.java new file mode 100644 index 00000000..5a0773b2 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.java @@ -0,0 +1,192 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.runner; + +import java.awt.Container; +import java.util.List; +import java.util.logging.Logger; + +import javax.swing.JSplitPane; + +import oracle.dbtools.raptor.runner.DBStarterFactory; +import oracle.ide.Context; +import oracle.jdevimpl.runner.debug.DebuggingProcess; +import oracle.jdevimpl.runner.run.JRunner; +import org.utplsql.sqldev.exception.GenericDatabaseAccessException; +import org.utplsql.sqldev.exception.GenericRuntimeException; +import org.utplsql.sqldev.model.DatabaseTools; +import org.utplsql.sqldev.model.StringTools; +import org.utplsql.sqldev.model.SystemTools; +import org.utplsql.sqldev.model.preference.PreferenceModel; +import org.utplsql.sqldev.resources.UtplsqlResources; + +import oracle.dbtools.worksheet.WorksheetResultPanel; +import oracle.dbtools.worksheet.editor.OpenWorksheetWizard; +import oracle.dbtools.worksheet.editor.Worksheet; +import oracle.dbtools.worksheet.utils.WorksheetUtil; +import oracle.ide.Ide; +import oracle.ide.config.Preferences; +import oracle.ide.controller.IdeAction; + +public class UtplsqlWorksheetRunner { + private static final Logger logger = Logger.getLogger(UtplsqlWorksheetRunner.class.getName()); + + private final PreferenceModel preferences; + private final List pathList; + private String connectionName; + private boolean debug = false; + + public UtplsqlWorksheetRunner(final List pathList, final String connectionName) { + this.pathList = pathList; + preferences = PreferenceModel.getInstance(Preferences.getPreferences()); + setConnection(connectionName); + } + + public void enableDebugging() { + this.debug = true; + } + + private void setConnection(final String connectionName) { + if (connectionName != null && preferences.isUnsharedWorksheet()) { + try { + this.connectionName = DatabaseTools.createTemporaryOrPrivateConnection(connectionName); + } catch (GenericDatabaseAccessException e) { + final String msg = "Failed to create temporary/private connection based on " + connectionName + "."; + logger.severe(() -> msg); + throw new GenericDatabaseAccessException(msg, e); + } + } else { + this.connectionName = connectionName; + } + } + + private CharSequence getCode() { + StringBuilder sb = new StringBuilder(); + if (!debug) { + if (preferences.isResetPackage()) { + sb.append("execute dbms_session.reset_package;\n"); + } + sb.append("set serveroutput on size unlimited\n"); + if (preferences.isClearScreen()) { + sb.append("clear screen\n"); + } + if (pathList.size() == 1) { + sb.append("execute ut.run('"); + sb.append(pathList.get(0)); + sb.append("');\n"); + } else { + // we want a horizontal dense output because we resize the worksheet to fit the command in common cases + sb.append("execute ut.run(ut_varchar2_list("); + sb.append(StringTools.getCSV(pathList, "").replace("\n", "")); + sb.append("));\n"); + } + } else { + sb.append("begin\n"); + sb.append(" ut.run(\n"); + sb.append(" ut_varchar2_list(\n"); + sb.append(StringTools.getCSV(pathList, 9)); + sb.append(" )\n"); + sb.append(" );\n"); + sb.append("end;\n"); + } + return sb; + } + + private Worksheet openWorksheet() { + final Worksheet worksheet = (Worksheet) OpenWorksheetWizard.openNewTempWorksheet(connectionName, + getCode().toString()); + if (connectionName == null) { + worksheet.setComboConnection(null); + } + WorksheetUtil.setWorksheetTabName(worksheet.getContext().getNode().getURL(), + UtplsqlResources.getString("WORKSHEET_TITLE")); + worksheet.getContext().getNode().markDirty(false); + return worksheet; + } + + private void resizeResultPanel(final Worksheet worksheet) { + SystemTools.sleep(200); + Container splitPane = null; + WorksheetResultPanel selectedResultPanel = worksheet.getSelectedResultPanel(); + if (selectedResultPanel != null && selectedResultPanel.getGUI() != null && selectedResultPanel.getGUI().getParent() != null + && selectedResultPanel.getGUI().getParent().getParent() != null && selectedResultPanel.getGUI().getParent().getParent().getParent() != null) { + splitPane = selectedResultPanel.getGUI().getParent().getParent().getParent(); + } + if (splitPane instanceof JSplitPane) { + ((JSplitPane) splitPane).setDividerLocation(0.15); + } else { + final String msg = "Could not adjust size of worksheet. Expected JSplitPane but got " + + (splitPane != null ? splitPane.getClass().getName() : "null") + "."; + logger.severe(msg); + } + } + + // cannot use IdeAction to run debugger, because text has to be set in inaccessible PLSQLController.updateAction + private void runDebugger(final Worksheet worksheet, final String anonymousPlsqlBlock) { + try { + Context processContext = JRunner.prepareProcessContext(worksheet.getContext(), false); + DebuggingProcess process = new DebuggingProcess(processContext); + DBStarterFactory.PlSqlStarter starter = new DBStarterFactory.PlSqlStarter(process, anonymousPlsqlBlock, connectionName, worksheet.getContext()); + starter.start(); + } catch (Throwable t) { + String msg = t.getClass().getName() + " while debugging utPLSQL test."; + logger.severe(() -> msg); + throw new GenericRuntimeException(msg); + } + } + + private void runScript(final Worksheet worksheet) { + if (preferences.isAutoExecute()) { + SystemTools.sleep(100); + if (debug) { + runDebugger(worksheet, worksheet.getFocusedEditorPane().getText()); + } else { + final IdeAction action = ((IdeAction) Ide.getIdeActionMap().get(Ide.findCmdID("Worksheet.RunScript"))); + if (action != null) { + try { + action.performAction(worksheet.getContext()); + } catch (Exception e) { + logger.severe(() -> "Could not run script in worksheet due to " + e.getMessage() + "."); + } + if (!debug) { + resizeResultPanel(worksheet); + } + } + } + } + } + + private void runTest() { + final Worksheet worksheet = openWorksheet(); + runScript(worksheet); + logger.fine(() -> "utPLSQL test called for " + pathList + " in " + connectionName + "."); + } + + public void runTestAsync() { + final Thread thread = new Thread(this::runTest); + thread.setName("utPLSQL run test"); + thread.start(); + } + + public static void openWithCode(final String code, final String connectionName) { + final Worksheet worksheet = (Worksheet) OpenWorksheetWizard.openNewTempWorksheet(connectionName, code); + if (connectionName == null) { + worksheet.setComboConnection(null); + } + WorksheetUtil.setWorksheetTabName(worksheet.getContext().getNode().getURL(), + UtplsqlResources.getString("WORKSHEET_TITLE")); + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.xtend b/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.xtend deleted file mode 100644 index bd03229a..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/runner/UtplsqlWorksheetRunner.xtend +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.runner - -import java.util.List -import java.util.logging.Logger -import javax.swing.JSplitPane -import oracle.dbtools.raptor.utils.Connections -import oracle.dbtools.worksheet.editor.OpenWorksheetWizard -import oracle.dbtools.worksheet.editor.Worksheet -import oracle.dbtools.worksheet.utils.WorksheetUtil -import oracle.ide.Ide -import oracle.ide.config.Preferences -import oracle.ide.controller.IdeAction -import org.utplsql.sqldev.model.preference.PreferenceModel -import org.utplsql.sqldev.resources.UtplsqlResources - -class UtplsqlWorksheetRunner { - static val Logger logger = Logger.getLogger(UtplsqlWorksheetRunner.name); - - var PreferenceModel preferences - var String connectionName - var List pathList - - new(List pathList, String connectionName) { - this.pathList = pathList - this.preferences = PreferenceModel.getInstance(Preferences.preferences); - setConnection(connectionName) - } - - private def setConnection(String connectionName) { - if (connectionName !== null && preferences.unsharedWorksheet) { - // fix for issue #47 - private connections are not closed in SQLDev >= 17.4.0 - try { - // temporary connection is closed when worksheet is closed, but requires SQLDev >= 17.4.0 - this.connectionName = Connections.instance.createTemporaryConnection(connectionName) - } catch (Throwable e) { - // private connection is closed when worksheet is closed in SQLDev < 17.4.0 - this.connectionName = Connections.instance.createPrivateConnection(connectionName) - } - } else { - this.connectionName = connectionName; - } - } - - private def getCode() ''' - «IF preferences.resetPackage» - EXECUTE dbms_session.reset_package; - «ENDIF» - SET SERVEROUTPUT ON SIZE UNLIMITED - «IF preferences.clearScreen» - CLEAR SCREEN - «ENDIF» - «val paths = pathList» - «IF paths.size == 1» - EXECUTE ut.run('«paths.get(0)»'); - «ELSE» - EXECUTE ut.run(ut_varchar2_list(«FOR path : paths SEPARATOR ', '»'«path»'«ENDFOR»)); - «ENDIF» - ''' - - private def openWorksheet() { - val worksheet = OpenWorksheetWizard.openNewTempWorksheet(connectionName, code.toString) as Worksheet - if (connectionName === null) { - worksheet.comboConnection = null - } - WorksheetUtil.setWorksheetTabName(worksheet.context.node.URL, UtplsqlResources.getString("WORKSHEET_TITLE")) - worksheet.context.node.markDirty(false) - return worksheet - } - - private def resizeResultPanel(Worksheet worksheet) { - Thread.sleep(200) // give script runner time to initiate result panel - val splitPane = worksheet.selectedResultPanel?.GUI?.parent?.parent?.parent - if (splitPane instanceof JSplitPane) { - splitPane.dividerLocation = 0.15 // 15% for Worksheet, 85% for Script Output - } else { - logger. - severe('''Could not adjust size of worksheet. Expected JSplitPane but got «splitPane?.class?.name».''') - } - } - - private def runScript(Worksheet worksheet) { - if (preferences.autoExecute) { - Thread.sleep(100) // give worksheet time to initialize - val action = Ide.getIdeActionMap.get(Ide.findCmdID("Worksheet.RunScript")) as IdeAction - if (action !== null) { - action.performAction(worksheet.context) - worksheet.resizeResultPanel - } - } - } - - private def runTest() { - val worksheet = openWorksheet - worksheet.runScript - logger.fine('''utPLSQL test called for «pathList» in «connectionName».''') - } - - def runTestAsync() { - val Runnable runnable = [|runTest] - val thread = new Thread(runnable) - thread.name = "utPLSQL run test" - thread.start - } - - static def void openWithCode(String code, String connectionName) { - val worksheet = OpenWorksheetWizard.openNewTempWorksheet(connectionName, code) as Worksheet - if (connectionName === null) { - worksheet.comboConnection = null - } - WorksheetUtil.setWorksheetTabName(worksheet.context.node.URL, UtplsqlResources.getString("WORKSHEET_TITLE")) - } - -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/snippet/SnippetMerger.java b/sqldev/src/main/java/org/utplsql/sqldev/snippet/SnippetMerger.java new file mode 100644 index 00000000..1a10e5c9 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/snippet/SnippetMerger.java @@ -0,0 +1,99 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.snippet; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.file.Paths; +import java.util.stream.Collectors; + +import javax.xml.parsers.DocumentBuilder; + +import org.utplsql.sqldev.model.FileTools; +import org.utplsql.sqldev.model.XMLTools; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import oracle.dbtools.util.Resource; + +public class SnippetMerger { + private final XMLTools xmlTools = new XMLTools(); + private final File userSnippetsFile; + private final String utplsqlSnippets; + + public String getUtplsqlSnippetsAsString() { + final InputStream stream = getClass() + .getResourceAsStream("/org/utplsql/sqldev/resources/UtplsqlSnippets.xml"); + final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, Charset.defaultCharset())); + return reader.lines().collect(Collectors.joining(System.lineSeparator())); + } + + public SnippetMerger() { + // works in SQL Developer only, otherwise a ExceptionInInitializerError is thrown + this(new File(Resource.RAPTOR_USER.getAbsolutePath() + File.separator + "UserSnippets.xml")); + } + + public SnippetMerger(final File file) { + utplsqlSnippets = getUtplsqlSnippetsAsString(); + userSnippetsFile = file; + } + + public void merge() { + String result; + if (userSnippetsFile.exists()) { + // file exists, proper merge required + final String userSnippets = new String(FileTools.readFile(Paths.get(userSnippetsFile.getAbsolutePath()))); + final DocumentBuilder docBuilder = xmlTools.createDocumentBuilder(); + final Document userSnippetsDoc = xmlTools.parse(docBuilder, new InputSource(new StringReader(userSnippets))); + final NodeList userSnippetsGroups = xmlTools.getNodeList(userSnippetsDoc, + "/snippets/group[not(@category=\"utPLSQL Annotations\" or @category=\"utPLSQL Expectations\")]"); + final Document utplsqlSnippetsDoc = xmlTools.parse(docBuilder, new InputSource(new StringReader(utplsqlSnippets))); + final NodeList utplsqlSnippetsGroups = xmlTools.getNodeList(utplsqlSnippetsDoc, "/snippets/group"); + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("\n"); + for (int i = 0; i < userSnippetsGroups.getLength(); i++) { + sb.append(" "); + sb.append(xmlTools.nodeToString(userSnippetsGroups.item(i), "code")); + sb.append('\n'); + } + for (int i = 0; i < utplsqlSnippetsGroups.getLength(); i ++) { + sb.append(" "); + sb.append(xmlTools.nodeToString(utplsqlSnippetsGroups.item(i), "code")); + sb.append('\n'); + } + sb.append("\n"); + result = sb.toString(); + } else { + // just copy + result = utplsqlSnippets; + } + FileTools.writeFile(Paths.get(userSnippetsFile.getAbsolutePath()), result.getBytes()); + } + + public String getTemplate() { + return utplsqlSnippets; + } + + public File getFile() { + return userSnippetsFile; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/snippet/SnippetMerger.xtend b/sqldev/src/main/java/org/utplsql/sqldev/snippet/SnippetMerger.xtend deleted file mode 100644 index af3a7bb7..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/snippet/SnippetMerger.xtend +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.snippet - -import java.io.BufferedReader -import java.io.File -import java.io.IOException -import java.io.InputStreamReader -import java.io.StringReader -import java.nio.charset.Charset -import java.nio.file.Files -import java.nio.file.Paths -import java.util.stream.Collectors -import javax.xml.parsers.DocumentBuilderFactory -import oracle.dbtools.util.Resource -import org.utplsql.sqldev.model.XMLTools -import org.xml.sax.InputSource - -class SnippetMerger { - val extension XMLTools xmlTools = new XMLTools - File userSnippetsFile - String utplsqlSnippets - - def getUtplsqlSnippetsAsString() throws IOException { - val stream = class.getResourceAsStream("/org/utplsql/sqldev/resources/UtplsqlSnippets.xml") - val reader = new BufferedReader(new InputStreamReader(stream, Charset.defaultCharset)) - return reader.lines.collect(Collectors.joining(System.lineSeparator)) - } - - new() { - // works in SQL Developer only, otherwise a ExceptionInInitializerError is thrown - this (new File(Resource.RAPTOR_USER.absolutePath + File.separator + "UserSnippets.xml")) - } - - new(File file) { - utplsqlSnippets = utplsqlSnippetsAsString - userSnippetsFile = file - } - - def merge() { - var String result - if (userSnippetsFile.exists) { - // file exists, proper merge required - val userSnippets = new String(Files.readAllBytes(Paths.get(userSnippetsFile.absolutePath))) - val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() - val userSnippetsDoc = docBuilder.parse(new InputSource(new StringReader(userSnippets))) - val userSnippetsGroups = userSnippetsDoc.getNodeList('''/snippets/group[not(@category="utPLSQL Annotations" or @category="utPLSQL Expectations")]''') - val utplsqlSnippetsDoc = docBuilder.parse(new InputSource(new StringReader(utplsqlSnippets))) - val utplsqlSnippetsGroups = utplsqlSnippetsDoc.getNodeList('''/snippets/group''') - result = ''' - - - «FOR i : 0 ..< userSnippetsGroups.length» - «userSnippetsGroups.item(i).nodeToString("code")» - «ENDFOR» - «FOR i : 0 ..< utplsqlSnippetsGroups.length» - «utplsqlSnippetsGroups.item(i).nodeToString("code")» - «ENDFOR» - - ''' - } else { - // just copy - result = utplsqlSnippets - - } - Files.write(Paths.get(userSnippetsFile.absolutePath), result.bytes) - } - - def getTemplate() { - return utplsqlSnippets - } - - def getFile() { - return userSnippetsFile - } - -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/common/DirectoryChooser.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/common/DirectoryChooser.java new file mode 100644 index 00000000..2d2fc8e9 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/common/DirectoryChooser.java @@ -0,0 +1,53 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.common; + +import java.io.File; +import java.util.logging.Logger; + +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JTextField; + +public class DirectoryChooser { + private static final Logger logger = Logger.getLogger(DirectoryChooser.class.getName()); + + // do not instantiate this class + private DirectoryChooser() { + super(); + } + + public static String choose(final JFrame parentFrame, final String title, final String initialDirectory) { + logger.finest(() -> "parantFrame: " + parentFrame); + String ret = null; + final JFileChooser chooser = new JFileChooser(); + chooser.setCurrentDirectory(new File(initialDirectory)); + chooser.setDialogTitle(title); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setAcceptAllFileFilterUsed(false); + if (chooser.showOpenDialog(parentFrame) == JFileChooser.APPROVE_OPTION) { + ret = chooser.getSelectedFile().getAbsolutePath(); + } + return ret; + } + + public static void choose(final JFrame parentFrame, final String title, final JTextField textField) { + final String dir = choose(parentFrame, title, textField.getText()); + if (dir != null) { + textField.setText(dir); + } + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/common/DirectoryChooser.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/common/DirectoryChooser.xtend deleted file mode 100644 index adc057a2..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/common/DirectoryChooser.xtend +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.ui.common - -import java.io.File -import java.util.logging.Logger -import javax.swing.JFileChooser -import javax.swing.JFrame -import javax.swing.JTextField - -class DirectoryChooser { - val static Logger logger = Logger.getLogger(DirectoryChooser.name) - - def static choose (JFrame parentFrame, String title, String initialDirectory) { - logger.finest('''parantFrame: «parentFrame»''') - var String ret = null - val chooser = new JFileChooser() - chooser.currentDirectory = new File(initialDirectory) - chooser.dialogTitle = title - chooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY - chooser.acceptAllFileFilterUsed = false - if (chooser.showOpenDialog(parentFrame) == JFileChooser.APPROVE_OPTION) { - ret = chooser.selectedFile.absolutePath - } - return ret - } - - def static void choose (JFrame parentFrame, String title, JTextField textField) { - val dir = choose(parentFrame, title, textField.text) - if (dir !== null) { - textField.text = dir - } - } - -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.java new file mode 100644 index 00000000..cfaebf81 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.java @@ -0,0 +1,223 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.coverage; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.WindowEvent; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingUtilities; + +import org.utplsql.sqldev.coverage.CodeCoverageReporter; +import org.utplsql.sqldev.model.DatabaseTools; +import org.utplsql.sqldev.model.StringTools; +import org.utplsql.sqldev.resources.UtplsqlResources; + +public class CodeCoverageReporterDialog extends JFrame implements ActionListener, FocusListener { + private static final long serialVersionUID = 5503685225300993401L; + + private final CodeCoverageReporter reporter; + private final JButton runButton; + private final JButton cancelButton; + private final JPanel paneParams; + private int paramPos = (-1); + private final JTextField schemasTextField = new JTextField(); + private final JTextArea includeObjectsTextArea = new JTextArea(); + private final JTextArea excludeObjectsTextArea = new JTextArea(); + + public static void createAndShow(final CodeCoverageReporter reporter) { + SwingUtilities.invokeLater(() -> CodeCoverageReporterDialog.createAndShowWithinEventThread(reporter)); + } + + private static void createAndShowWithinEventThread(final CodeCoverageReporter reporter) { + // create and layout the dialog + final CodeCoverageReporterDialog frame = new CodeCoverageReporterDialog(reporter); + reporter.setFrame(frame); + frame.pack(); + // center dialog + final Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); + frame.setLocation(dim.width / 2 - frame.getSize().width / 2, dim.height / 2 - frame.getSize().height / 2); + frame.setAlwaysOnTop(true); + frame.setVisible(true); + } + + public CodeCoverageReporterDialog(final CodeCoverageReporter reporter) { + super(UtplsqlResources.getString("WINDOW_CODE_COVERAGE_REPORT_LABEL")); + this.reporter = reporter; + final Container pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + final GridBagConstraints c = new GridBagConstraints(); + + // parameters pane + paneParams = new JPanel(new GridBagLayout()); + JTextArea pathsTextArea = new JTextArea(); + pathsTextArea.setEditable(false); + pathsTextArea.setEnabled(false); + addParam(UtplsqlResources.getString("WINDOW_PATHS_LABEL"), StringTools.getSimpleCSV(reporter.getPathList()), pathsTextArea, 50, 2); + addParam(UtplsqlResources.getString("WINDOW_SCHEMAS_LABEL"), reporter.getSchemas(), schemasTextField, 0, 0); + addParam(UtplsqlResources.getString("WINDOW_INCLUDE_OBJECS_LABEL"), StringTools.getSimpleCSV(reporter.getIncludeObjectList()), includeObjectsTextArea, 66, 4); + addParam(UtplsqlResources.getString("WINDOW_EXCLUDE_OBJECS_LABEL"), "", excludeObjectsTextArea, 34, 1); + final JScrollPane scrollPane = new JScrollPane(paneParams); + scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 2; + c.insets = new Insets(10, 10, 0, 10); // top, left, bottom, right + c.anchor = GridBagConstraints.NORTH; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 1; + pane.add(scrollPane, c); + + // Buttons pane + final JPanel panelButtons = new JPanel(new GridBagLayout()); + runButton = new JButton(UtplsqlResources.getString("WINDOW_RUN_BUTTON")); + runButton.addActionListener(this); + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 1; + c.insets = new Insets(0, 0, 0, 0); // top, left, bottom, right + c.fill = GridBagConstraints.NONE; + c.weightx = 0; + c.weighty = 0; + panelButtons.add(runButton, c); + cancelButton = new JButton(UtplsqlResources.getString("WINDOW_CANCEL_BUTTON")); + cancelButton.addActionListener(this); + c.gridx = 1; + c.insets = new Insets(0, 10, 0, 0); // top, left, bottom, right + c.fill = GridBagConstraints.NONE; + c.weightx = 0; + c.weighty = 0; + panelButtons.add(cancelButton, c); + c.gridx = 1; + c.gridy = 1; + c.gridwidth = 1; + c.insets = new Insets(30, 10, 10, 10); // top, left, bottom, right + c.anchor = GridBagConstraints.EAST; + c.fill = GridBagConstraints.NONE; + c.weightx = 0; + c.weighty = 0; + pane.add(panelButtons, c); + pane.setPreferredSize(new Dimension(500, 320)); + SwingUtilities.getRootPane(runButton).setDefaultButton(runButton); + } + + private void addParam(final String label, final String text, final Component component, final int height, + final double weighty) { + paramPos++; + final GridBagConstraints c = new GridBagConstraints(); + final JLabel paramLabel = new JLabel(label); + c.gridx = 0; + c.gridy = paramPos; + c.gridwidth = 1; + c.insets = new Insets(10, 10, 0, 0); // top, left, bottom, right + c.anchor = GridBagConstraints.NORTHWEST; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 0; + c.weighty = 0; + paneParams.add(paramLabel, c); + c.gridx = 1; + c.gridwidth = GridBagConstraints.REMAINDER; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.insets = new Insets(10, 10, 0, 10); // top, left, bottom, right + c.weightx = 1; + c.weighty = weighty; + if (component instanceof JTextField) { + ((JTextField) component).setText(text); + paneParams.add(component, c); + } else if (component instanceof JTextArea) { + ((JTextArea) component).setText(text); + ((JTextArea) component).setLineWrap(true); + ((JTextArea) component).setWrapStyleWord(true); + JScrollPane scrollPane = new JScrollPane(component); + scrollPane.getViewport().setPreferredSize(new Dimension(200, height)); + paneParams.add(scrollPane, c); + } + component.addFocusListener(this); + } + + public void exit() { + dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); + } + + @Override + public void actionPerformed(final ActionEvent e) { + if (e.getSource() == runButton) { + reporter.setSchemas(schemasTextField.getText()); + reporter.setIncludeObjects(includeObjectsTextArea.getText()); + reporter.setExcludeObjects(excludeObjectsTextArea.getText()); + schemasTextField.setEnabled(false); + includeObjectsTextArea.setEnabled(false); + excludeObjectsTextArea.setEnabled(false); + runButton.setEnabled(false); + reporter.runAsync(); + } else { + if (e.getSource() == cancelButton) { + if (runButton.isEnabled()) { + // report is not yet started, just close the window + exit(); + } else { + // report is being created... + // frame will close as soon as the connection is technically aborted + // database session is not cancelled. This is not a bug. + // to cancel the session you have to kill it via "ALTER SYSTEM KILL SESSION". + // However, the abort frees all resources on the client side. + DatabaseTools.abortConnection(reporter.getConnection()); + } + } + } + } + + @Override + public void focusGained(final FocusEvent e) { + if (paneParams.isAncestorOf(e.getComponent())) { + // make component at cursor position is visible + final int x = e.getComponent().getLocationOnScreen().x - paneParams.getLocationOnScreen().x; + final int y = e.getComponent().getLocationOnScreen().y - paneParams.getLocationOnScreen().y; + final int width = e.getComponent().getBounds().width; + final int height = e.getComponent().getBounds().height; + final Rectangle rect = new Rectangle(x, y, width, height); + paneParams.scrollRectToVisible(rect); + } + } + + @Override + public void focusLost(final FocusEvent e) { + // ignore + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.xtend deleted file mode 100644 index 1c57eb6e..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/coverage/CodeCoverageReporterDialog.xtend +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.ui.coverage - -import java.awt.Component -import java.awt.Dimension -import java.awt.GridBagConstraints -import java.awt.GridBagLayout -import java.awt.Insets -import java.awt.Rectangle -import java.awt.Toolkit -import java.awt.event.ActionEvent -import java.awt.event.ActionListener -import java.awt.event.FocusEvent -import java.awt.event.FocusListener -import java.awt.event.WindowEvent -import javax.swing.BorderFactory -import javax.swing.JButton -import javax.swing.JFrame -import javax.swing.JLabel -import javax.swing.JPanel -import javax.swing.JScrollPane -import javax.swing.JTextArea -import javax.swing.JTextField -import javax.swing.ScrollPaneConstants -import javax.swing.SwingUtilities -import org.springframework.core.task.SimpleAsyncTaskExecutor -import org.utplsql.sqldev.coverage.CodeCoverageReporter -import org.utplsql.sqldev.resources.UtplsqlResources - -class CodeCoverageReporterDialog extends JFrame implements ActionListener, FocusListener { - - var CodeCoverageReporter reporter - var JButton runButton - var JButton cancelButton - var JPanel paneParams; - var int paramPos = -1; - val pathsTextArea = new JTextArea() - val schemasTextField = new JTextField() - val includeObjectsTextArea = new JTextArea() - val excludeObjectsTextArea = new JTextArea() - - def static createAndShow(CodeCoverageReporter reporter) { - SwingUtilities.invokeLater(new Runnable() { - override run() { - CodeCoverageReporterDialog.createAndShowWithinEventThread(reporter); - } - }); - } - - private def static createAndShowWithinEventThread(CodeCoverageReporter reporter) { - // create and layout the dialog - val frame = new CodeCoverageReporterDialog(reporter) - reporter.frame = frame - frame.pack - // center dialog - val dim = Toolkit.getDefaultToolkit().getScreenSize(); - frame.setLocation(dim.width / 2 - frame.getSize().width / 2, dim.height / 2 - frame.getSize().height / 2); - frame.alwaysOnTop = true - frame.visible = true - } - - new(CodeCoverageReporter reporter) { - super(UtplsqlResources.getString("WINDOW_CODE_COVERAGE_REPORT_LABEL")) - this.reporter = reporter - val pane = getContentPane(); - pane.setLayout(new GridBagLayout()); - val c = new GridBagConstraints(); - - // parameters pane - paneParams = new JPanel(new GridBagLayout()) - pathsTextArea.editable = false - pathsTextArea.enabled = false - addParam(UtplsqlResources.getString("WINDOW_PATHS_LABEL"), '''«FOR path : reporter.pathList SEPARATOR ", "»«path»«ENDFOR»''', pathsTextArea, 50, 2) - addParam(UtplsqlResources.getString("WINDOW_SCHEMAS_LABEL"), "", schemasTextField, 0, 0); - addParam(UtplsqlResources.getString("WINDOW_INCLUDE_OBJECS_LABEL"), '''«FOR i : reporter.includeObjectList SEPARATOR ", "»«i»«ENDFOR»''', includeObjectsTextArea, 66, 4); - addParam(UtplsqlResources.getString("WINDOW_EXCLUDE_OBJECS_LABEL"), "", excludeObjectsTextArea, 34, 1); - val scrollPane = new JScrollPane(paneParams) - scrollPane.verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED - scrollPane.horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER - scrollPane.border = BorderFactory.createEmptyBorder; - c.gridx = 0; - c.gridy = 0; - c.gridwidth = 2; - c.insets = new Insets(10, 10, 0, 10); // top, left, bottom, right - c.anchor = GridBagConstraints.NORTH; - c.fill = GridBagConstraints.BOTH; - c.weightx = 1; - c.weighty = 1; - pane.add(scrollPane, c) - - // Buttons pane - val panelButtons = new JPanel(new GridBagLayout()) - runButton = new JButton(UtplsqlResources.getString("WINDOW_RUN_BUTTON")) - runButton.addActionListener(this); - c.gridx = 0; - c.gridy = 0; - c.gridwidth = 1; - c.insets = new Insets(0, 0, 0, 0); // top, left, bottom, right - c.fill = GridBagConstraints.NONE; - c.weightx = 0; - c.weighty = 0; - panelButtons.add(runButton, c); - cancelButton = new JButton(UtplsqlResources.getString("WINDOW_CANCEL_BUTTON")); - cancelButton.addActionListener(this); - c.gridx = 1; - c.insets = new Insets(0, 10, 0, 0); // top, left, bottom, right - c.fill = GridBagConstraints.NONE; - c.weightx = 0; - c.weighty = 0; - panelButtons.add(cancelButton, c); - c.gridx = 1; - c.gridy = 1; - c.gridwidth = 1; - c.insets = new Insets(30, 10, 10, 10); // top, left, bottom, right - c.anchor = GridBagConstraints.EAST - c.fill = GridBagConstraints.NONE - c.weightx = 0; - c.weighty = 0; - pane.add(panelButtons, c); - pane.setPreferredSize(new Dimension(500, 320)); - SwingUtilities.getRootPane(runButton).defaultButton = runButton - } - - private def addParam(String label, String text, Component component, int height, double weighty) { - paramPos++ - val c = new GridBagConstraints(); - val paramLabel = new JLabel(label) - c.gridx = 0 - c.gridy = paramPos - c.gridwidth = 1 - c.insets = new Insets(10, 10, 0, 0) // top, left, bottom, right - c.anchor = GridBagConstraints.NORTHWEST - c.fill = GridBagConstraints.HORIZONTAL - c.weightx = 0 - c.weighty = 0 - paneParams.add(paramLabel, c); - c.gridx = 1 - c.gridwidth = GridBagConstraints.REMAINDER - c.anchor = GridBagConstraints.WEST - c.fill = GridBagConstraints.BOTH - c.insets = new Insets(10, 10, 0, 10); // top, left, bottom, right - c.weightx = 1 - c.weighty = weighty - if (component instanceof JTextField) { - component.text = text - paneParams.add(component, c) - } else if (component instanceof JTextArea) { - component.text = text - component.lineWrap = true - component.wrapStyleWord = true - var scrollPane = new JScrollPane(component); - scrollPane.viewport.preferredSize = new Dimension(200, height) - paneParams.add(scrollPane, c) - } - component.addFocusListener(this) - } - - def exit() { - dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); - } - - override actionPerformed(ActionEvent e) { - if (e.getSource == runButton) { - reporter.schemas = schemasTextField.text - reporter.includeObjects = includeObjectsTextArea.text - reporter.excludeObjects = excludeObjectsTextArea.text - schemasTextField.setEnabled(false) - includeObjectsTextArea.setEnabled(false) - excludeObjectsTextArea.setEnabled(false) - runButton.setEnabled(false) - reporter.runAsync - } else if (e.getSource == cancelButton) { - if (runButton.enabled) { - // report is not yet started, just close the window - exit - } else { - // report is being created... - // frame will close as soon as the connection is technically aborted - // database session is not cancelled. This is not a bug. - // to cancel the session you have to kill it via "ALTER SYSTEM KILL SESSION". - // However, the abort frees all resources on the client side. - reporter.connection.abort(new SimpleAsyncTaskExecutor) - } - } - } - - override focusGained(FocusEvent e) { - if (paneParams.isAncestorOf(e.component)) { - // make component at cursor position is visible - val x = e.component.getLocationOnScreen.x - paneParams.getLocationOnScreen.x - val y = e.component.getLocationOnScreen.y - paneParams.getLocationOnScreen.y - val width = e.component.getBounds.width - val height = e.component.getBounds.height - val rect = new Rectangle(x, y, width, height) - paneParams.scrollRectToVisible(rect) - } - } - - override focusLost(FocusEvent e) { - // ignore - } - -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.java new file mode 100644 index 00000000..703766ff --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.java @@ -0,0 +1,440 @@ +/* + * Copyright 2018 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.preference; + +import java.util.Map; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.JTabbedPane; +import javax.swing.JTextField; +import javax.swing.SpinnerNumberModel; +import javax.swing.table.DefaultTableModel; + +import org.utplsql.sqldev.model.StringTools; +import org.utplsql.sqldev.model.preference.PreferenceModel; +import org.utplsql.sqldev.resources.UtplsqlResources; +import org.utplsql.sqldev.snippet.SnippetMerger; +import org.utplsql.sqldev.ui.common.DirectoryChooser; + +import oracle.dbtools.raptor.templates.CodeTemplateUtil; +import oracle.ide.panels.DefaultTraversablePanel; +import oracle.ide.panels.TraversableContext; +import oracle.ide.panels.TraversalException; +import oracle.javatools.ui.layout.FieldLayoutBuilder; + +public class PreferencePanel extends DefaultTraversablePanel { + private static final long serialVersionUID = -2583957375062007813L; + private final JPanel runTestPanel = new JPanel(); + private final JCheckBox useRealtimeReporterCheckBox = new JCheckBox(); + private final JCheckBox unsharedWorksheetCheckBox = new JCheckBox(); + private final JCheckBox resetPackageCheckBox = new JCheckBox(); + private final JCheckBox clearScreenCheckBox = new JCheckBox(); + private final JCheckBox autoExecuteCheckBox = new JCheckBox(); + private final JCheckBox checkRunUtplsqlTestCheckBox = new JCheckBox(); + private final JCheckBox useSmartTimesCheckBox = new JCheckBox(); + private final JButton importSnippetsButton = new JButton( + UtplsqlResources.getString("PREF_IMPORT_SNIPPETS_BUTTON_LABEL")); + private final JPanel realtimeReporterPanel = new JPanel(); + private final SpinnerNumberModel numberOfRunsInHistoryModel = new SpinnerNumberModel(1, 1, 100, 1); + private final JSpinner numberOfRunsInHistorySpinner = new JSpinner(numberOfRunsInHistoryModel); + private final JCheckBox showDisabledCounterCheckBox = new JCheckBox(); + private final JCheckBox showWarningsCounterCheckBox = new JCheckBox(); + private final JCheckBox showInfoCounterCheckBox = new JCheckBox(); + private final JCheckBox showWarningIndicatorCheckBox = new JCheckBox(); + private final JCheckBox showInfoIndicatorCheckBox = new JCheckBox(); + private final JCheckBox showSuccessfulTestsCheckBox = new JCheckBox(); + private final JCheckBox showDisabledTestsCheckBox = new JCheckBox(); + private final JCheckBox showTestDescriptionCheckBox = new JCheckBox(); + private final JCheckBox syncDetailTabCheckBox = new JCheckBox(); + private final JCheckBox showSuitesCheckBox = new JCheckBox(); + private final JPanel generateTestPanel = new JPanel(); + private final JTextField testPackagePrefixTextField = new JTextField(); + private final JTextField testPackageSuffixTextField = new JTextField(); + private final JTextField testUnitPrefixTextField = new JTextField(); + private final JTextField testUnitSuffixTextField = new JTextField(); + private final SpinnerNumberModel numberOfTestsPerUnitModel = new SpinnerNumberModel(1, 1, 10, 1); + private final JSpinner numberOfTestsPerUnitSpinner = new JSpinner(numberOfTestsPerUnitModel); + private final JCheckBox checkGenerateUtplsqlTestCheckBox = new JCheckBox(); + private final DefaultTableModel codeTemplatesModel = new DefaultTableModel(new Object[] { "Id", "Template" }, 0); + private final JButton createCodeTemplatesButton = new JButton(); + private final JCheckBox generateCommentsCheckBox = new JCheckBox(); + private final JCheckBox disableTestsCheckBox = new JCheckBox(); + private final JTextField suitePathTextField = new JTextField(); + private final SpinnerNumberModel indentSpacesModel = new SpinnerNumberModel(1, 1, 8, 1); + private final JSpinner indentSpacesSpinner = new JSpinner(indentSpacesModel); + private final JPanel oddgenPanel = new JPanel(); + private final JTextField rootFolderInOddgenViewTextField = new JTextField(); + private final JCheckBox generateFilesCheckBox = new JCheckBox(); + private final JTextField outputDirectoryTextField = new JTextField(); + private final JButton outputDirectoryBrowse = new JButton(); + private final JCheckBox deleteExistingFilesCheckBox = new JCheckBox(); + + public PreferencePanel() { + layoutControls(); + } + + private void layoutControls() { + // run test group + final FieldLayoutBuilder runTab = new FieldLayoutBuilder(runTestPanel); + runTab.setAlignLabelsLeft(true); + runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_USE_REALTIME_REPORTER_LABEL")) + .component(useRealtimeReporterCheckBox) + .withHint(UtplsqlResources.getString("PREF_USE_REALTIME_REPORTER_HINT"))); + runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_UNSHARED_WORKSHEET_LABEL")) + .component(unsharedWorksheetCheckBox)); + runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_RESET_PACKAGE_LABEL")) + .component(resetPackageCheckBox)); + runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_CLEAR_SCREEN_LABEL")) + .component(clearScreenCheckBox)); + runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_AUTO_EXECUTE_LABEL")) + .component(autoExecuteCheckBox)); + runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_CHECK_RUN_UTPLSQL_TEST_LABEL")) + .component(checkRunUtplsqlTestCheckBox)); + runTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_USE_SMART_TIMES_LABEL")) + .component(useSmartTimesCheckBox)); + runTab.addVerticalGap(); + runTab.addRow(importSnippetsButton); + runTab.addVerticalSpring(); + + // realtime reporter group + final FieldLayoutBuilder rrTab = new FieldLayoutBuilder(realtimeReporterPanel); + rrTab.setAlignLabelsLeft(true); + rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_NUMBER_OF_RUNS_IN_HISTORY_LABEL")) + .component(numberOfRunsInHistorySpinner)); + rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_DISABLED_COUNTER_LABEL")) + .component(showDisabledCounterCheckBox)); + rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_WARNINGS_COUNTER_LABEL")) + .component(showWarningsCounterCheckBox)); + rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_INFO_COUNTER_LABEL")) + .component(showInfoCounterCheckBox)); + rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_WARNING_INDICATOR_LABEL")) + .component(showWarningIndicatorCheckBox)); + rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_INFO_INDICATOR_LABEL")) + .component(showInfoIndicatorCheckBox)); + rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_SUCCESSFUL_TESTS_LABEL")) + .component(showSuccessfulTestsCheckBox)); + rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_DISABLED_TESTS_LABEL")) + .component(showDisabledTestsCheckBox)); + rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_TEST_DESCRIPTION_LABEL")) + .component(showTestDescriptionCheckBox)); + rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SYNC_DETAIL_TAB_LABEL")) + .component(syncDetailTabCheckBox)); + rrTab.add(runTab.field().label().withText(UtplsqlResources.getString("PREF_SHOW_SUITES_LABEL")) + .component(showSuitesCheckBox)); + rrTab.addVerticalSpring(); + + // generate test group + final FieldLayoutBuilder generateTab = new FieldLayoutBuilder(generateTestPanel); + generateTab.setAlignLabelsLeft(true); + generateTab.setStretchComponentsWithNoButton(true); + generateTab + .add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_TEST_PACKAGE_PREFIX_LABEL")) + .component(testPackagePrefixTextField)); + generateTab + .add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_TEST_PACKAGE_SUFFIX_LABEL")) + .component(testPackageSuffixTextField)); + generateTab.add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_TEST_UNIT_PREFIX_LABEL")) + .component(testUnitPrefixTextField)); + generateTab.add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_TEST_UNIT_SUFFIX_LABEL")) + .component(testUnitSuffixTextField)); + generateTab.add( + generateTab.field().label().withText(UtplsqlResources.getString("PREF_NUMBER_OF_TESTS_PER_UNIT_LABEL")) + .component(numberOfTestsPerUnitSpinner)); + generateTab.add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_GENERATE_COMMENTS_LABEL")) + .component(generateCommentsCheckBox)); + generateTab.add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_DISABLE_TESTS_LABEL")) + .component(disableTestsCheckBox)); + generateTab.add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_SUITE_PATH_LABEL")) + .component(suitePathTextField)); + generateTab.add(generateTab.field().label().withText(UtplsqlResources.getString("PREF_INDENT_SPACES_LABEL")) + .component(indentSpacesSpinner)); + generateTab.add(generateTab.field().label() + .withText(UtplsqlResources.getString("PREF_CHECK_GENERATE_UTPLSQL_TEST_LABEL")) + .component(checkGenerateUtplsqlTestCheckBox).button(createCodeTemplatesButton) + .withText(UtplsqlResources.getString("PREF_CREATE_CODE_TEMPLATES_BUTTON_LABEL"))); + generateTab.addVerticalSpring(); + + // oddgen group + final FieldLayoutBuilder oddgenTab = new FieldLayoutBuilder(oddgenPanel); + oddgenTab.setAlignLabelsLeft(true); + oddgenTab.setStretchComponentsWithNoButton(true); + oddgenTab.add( + oddgenTab.field().label().withText(UtplsqlResources.getString("PREF_ROOT_FOLDER_IN_ODDGEN_VIEW_LABEL")) + .component(rootFolderInOddgenViewTextField)); + oddgenTab.add(oddgenTab.field().label().withText(UtplsqlResources.getString("PREF_GENERATE_FILES_LABEL")) + .component(generateFilesCheckBox)); + oddgenTab.add(oddgenTab.field().label().withText(UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_LABEL")) + .component(outputDirectoryTextField).button(outputDirectoryBrowse) + .withText(UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_BUTTON_LABEL"))); + oddgenTab.add(oddgenTab.field().label().withText(UtplsqlResources.getString("PREF_DELETE_EXISTING_FILES_LABEL")) + .component(deleteExistingFilesCheckBox)); + oddgenTab.addVerticalSpring(); + + // putting groups into tabbed panes + final JTabbedPane tabbedPane = new JTabbedPane(); + tabbedPane.add(UtplsqlResources.getString("MENU_RUN_TEST_LABEL"), runTestPanel); + tabbedPane.add(UtplsqlResources.getString("MENU_REALTIME_REPORTER_LABEL"), realtimeReporterPanel); + tabbedPane.add(UtplsqlResources.getString("MENU_GENERATE_TEST_LABEL"), generateTestPanel); + tabbedPane.add("oddgen", oddgenPanel); + final FieldLayoutBuilder builder = new FieldLayoutBuilder(this); + builder.setAlignLabelsLeft(true); + builder.addVerticalField("", tabbedPane); + builder.addVerticalSpring(); + + // register action listener for import snippets button + importSnippetsButton.addActionListener(event -> importSnippets()); + + // register action listener for create code template button + createCodeTemplatesButton.addActionListener(event -> saveCodeTemplates()); + + // register action listener for directory chooser + outputDirectoryBrowse.addActionListener(event -> DirectoryChooser.choose(null, + UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_LABEL"), outputDirectoryTextField)); + } + + private void importSnippets() { + final SnippetMerger snippetMerger = new SnippetMerger(); + snippetMerger.merge(); + final String file = snippetMerger.getFile().getAbsolutePath(); + final String message = String.format(UtplsqlResources.getString("PREF_CONFIRM_IMPORT_MESSAGE"), file); + JOptionPane.showMessageDialog(null, message, UtplsqlResources.getString("PREF_CONFIRM_IMPORT_TITLE"), + JOptionPane.INFORMATION_MESSAGE); + } + + private void loadCodeTemplates() { + final Map map = CodeTemplateUtil.loadFiles(); + for (final Map.Entry entry : map.entrySet()) { + codeTemplatesModel.addRow(new Object[] { entry.getKey(), entry.getValue() }); + } + } + + private void saveCodeTemplates() { + Integer indentSpaces = (Integer) indentSpacesSpinner.getValue(); + codeTemplatesModel + .addRow(new Object[] { "ut_spec", StringTools.replaceTabsWithSpaces(utSpecTemplate(), indentSpaces) }); + codeTemplatesModel.addRow(new Object[] { "ut_spec_proc", + trimPlusNewLine(StringTools.replaceTabsWithSpaces(utSpecProcTemplate(), indentSpaces)) }); + codeTemplatesModel + .addRow(new Object[] { "ut_body", StringTools.replaceTabsWithSpaces(utBodyTemplate(), indentSpaces) }); + codeTemplatesModel.addRow(new Object[] { "ut_body_proc", + trimPlusNewLine(StringTools.replaceTabsWithSpaces(utBodyProcTemplate(), indentSpaces)) }); + CodeTemplateUtil.save(codeTemplatesModel); + } + + private String trimPlusNewLine(final String input) { + return input.trim() + System.lineSeparator(); + } + + private CharSequence utSpecTemplate() { + StringBuilder sb = new StringBuilder(); + sb.append("create or replace package "); + sb.append(testPackagePrefixTextField.getText()); + sb.append("[package_name]"); + sb.append(testPackageSuffixTextField.getText()); + sb.append(" is\n\n"); + sb.append("\t--%suite\n"); + if (!suitePathTextField.getText().isEmpty()) { + sb.append("\t--%suitepath("); + sb.append(suitePathTextField.getText()); + sb.append(")\n"); + } + sb.append("\n\t"); + sb.append(utSpecProcTemplate().toString().trim().replace("\n", "\n\t")); + sb.append("\n\n"); + sb.append("end "); + sb.append(testPackagePrefixTextField.getText()); + sb.append("[package_name]"); + sb.append(testPackageSuffixTextField.getText()); + sb.append(";\n"); + sb.append("/\n"); + return sb; + } + + private CharSequence utSpecProcTemplate() { + StringBuilder sb = new StringBuilder(); + final Integer numberOfTestsPerUnit = (Integer) numberOfTestsPerUnitModel.getValue(); + final boolean withContext = numberOfTestsPerUnit > 1; + if (withContext) { + sb.append("--%context([procedure_name])\n\n"); + } + for (int i = 1; i <= numberOfTestsPerUnit; i ++) { + sb.append("--%test\n"); + if (disableTestsCheckBox.isSelected()) { + sb.append("--%disabled\n"); + } + sb.append("procedure "); + sb.append(testUnitPrefixTextField.getText()); + sb.append("[procedure_name]"); + sb.append(testUnitSuffixTextField.getText()); + if (withContext) { + sb.append(i); + } + sb.append(";\n\n"); + } + if (withContext) { + sb.append("--%endcontext\n\n"); + } + return sb; + } + + private CharSequence utBodyTemplate() { + StringBuilder sb = new StringBuilder(); + sb.append("create or replace package body "); + sb.append(testPackagePrefixTextField.getText()); + sb.append("[package_name]"); + sb.append(testPackageSuffixTextField.getText()); + sb.append(" is\n\n\t"); + sb.append(utBodyProcTemplate().toString().trim().replace("\n", "\n\t")); + sb.append("\n\n"); + sb.append("end "); + sb.append(testPackagePrefixTextField.getText()); + sb.append("[package_name]"); + sb.append(testPackageSuffixTextField.getText()); + sb.append(";\n"); + sb.append("/\n"); + return sb; + } + + private CharSequence utBodyProcTemplate() { + StringBuilder sb = new StringBuilder(); + final Integer numberOfTestsPerUnit = (Integer) numberOfTestsPerUnitModel.getValue(); + final boolean withContext = numberOfTestsPerUnit > 1; + for (int i = 1; i <= numberOfTestsPerUnit; i++) { + if (generateCommentsCheckBox.isSelected()) { + sb.append("--\n"); + sb.append("-- test"); + if (withContext) { + sb.append(" [procedure_name] case "); + sb.append(i); + sb.append(": ..."); + } + sb.append('\n'); + sb.append("--\n"); + } + sb.append("procedure "); + sb.append(testUnitPrefixTextField.getText()); + sb.append("[procedure_name]"); + sb.append(testUnitSuffixTextField.getText()); + if (withContext) { + sb.append(i); + } + sb.append(" is\n"); + sb.append("\tl_actual integer := 0;\n"); + sb.append("\tl_expected integer := 1;\n"); + sb.append("begin\n"); + if (generateCommentsCheckBox.isSelected()) { + sb.append("\t-- arrange\n\n"); + sb.append("\t-- act\n\n"); + sb.append("\t-- assert\n"); + } + sb.append("\tut.expect(l_actual).to_equal(l_expected);\n"); + sb.append("end "); + sb.append(testUnitPrefixTextField.getText()); + sb.append("[procedure_name]"); + sb.append(testUnitSuffixTextField.getText()); + if (withContext) { + sb.append(i); + } + sb.append(";\n\n"); + } + return sb; + } + + @Override + public void onEntry(final TraversableContext traversableContext) { + PreferenceModel info = getUserInformation(traversableContext); + useRealtimeReporterCheckBox.setSelected(info.isUseRealtimeReporter()); + unsharedWorksheetCheckBox.setSelected(info.isUnsharedWorksheet()); + resetPackageCheckBox.setSelected(info.isResetPackage()); + clearScreenCheckBox.setSelected(info.isClearScreen()); + autoExecuteCheckBox.setSelected(info.isAutoExecute()); + checkRunUtplsqlTestCheckBox.setSelected(info.isCheckRunUtplsqlTest()); + useSmartTimesCheckBox.setSelected(info.isUseSmartTimes()); + numberOfRunsInHistorySpinner.setValue(info.getNumberOfRunsInHistory()); + showDisabledCounterCheckBox.setSelected(info.isShowDisabledCounter()); + showWarningsCounterCheckBox.setSelected(info.isShowWarningsCounter()); + showInfoCounterCheckBox.setSelected(info.isShowInfoCounter()); + showWarningIndicatorCheckBox.setSelected(info.isShowWarningIndicator()); + showInfoIndicatorCheckBox.setSelected(info.isShowInfoIndicator()); + showSuccessfulTestsCheckBox.setSelected(info.isShowSuccessfulTests()); + showDisabledTestsCheckBox.setSelected(info.isShowDisabledTests()); + showTestDescriptionCheckBox.setSelected(info.isShowTestDescription()); + syncDetailTabCheckBox.setSelected(info.isSyncDetailTab()); + showSuitesCheckBox.setSelected(info.isShowSuites()); + testPackagePrefixTextField.setText(info.getTestPackagePrefix()); + testPackageSuffixTextField.setText(info.getTestPackageSuffix()); + testUnitPrefixTextField.setText(info.getTestUnitPrefix()); + testUnitSuffixTextField.setText(info.getTestUnitSuffix()); + numberOfTestsPerUnitSpinner.setValue(info.getNumberOfTestsPerUnit()); + checkGenerateUtplsqlTestCheckBox.setSelected(info.isCheckGenerateUtplsqlTest()); + loadCodeTemplates(); + generateCommentsCheckBox.setSelected(info.isGenerateComments()); + disableTestsCheckBox.setSelected(info.isDisableTests()); + suitePathTextField.setText(info.getSuitePath()); + indentSpacesSpinner.setValue(info.getIndentSpaces()); + rootFolderInOddgenViewTextField.setText(info.getRootFolderInOddgenView()); + generateFilesCheckBox.setSelected(info.isGenerateFiles()); + outputDirectoryTextField.setText(info.getOutputDirectory()); + deleteExistingFilesCheckBox.setSelected(info.isDeleteExistingFiles()); + super.onEntry(traversableContext); + } + + @Override + public void onExit(final TraversableContext traversableContext) throws TraversalException { + PreferenceModel info = getUserInformation(traversableContext); + info.setUseRealtimeReporter(useRealtimeReporterCheckBox.isSelected()); + info.setUnsharedWorksheet(unsharedWorksheetCheckBox.isSelected()); + info.setResetPackage(resetPackageCheckBox.isSelected()); + info.setClearScreen(clearScreenCheckBox.isSelected()); + info.setAutoExecute(autoExecuteCheckBox.isSelected()); + info.setNumberOfRunsInHistory((Integer) numberOfRunsInHistorySpinner.getValue()); + info.setCheckRunUtplsqlTest(checkRunUtplsqlTestCheckBox.isSelected()); + info.setUseSmartTimes(useSmartTimesCheckBox.isSelected()); + info.setShowDisabledCounter(showDisabledCounterCheckBox.isSelected()); + info.setShowWarningsCounter(showWarningsCounterCheckBox.isSelected()); + info.setShowInfoCounter(showInfoCounterCheckBox.isSelected()); + info.setShowWarningIndicator(showWarningIndicatorCheckBox.isSelected()); + info.setShowInfoIndicator(showInfoIndicatorCheckBox.isSelected()); + info.setShowSuccessfulTests(showSuccessfulTestsCheckBox.isSelected()); + info.setShowDisabledTests(showDisabledTestsCheckBox.isSelected()); + info.setShowTestDescription(showTestDescriptionCheckBox.isSelected()); + info.setSyncDetailTab(syncDetailTabCheckBox.isSelected()); + info.setShowSuites(showSuitesCheckBox.isSelected()); + info.setTestPackagePrefix(testPackagePrefixTextField.getText()); + info.setTestPackageSuffix(testPackageSuffixTextField.getText()); + info.setTestUnitPrefix(testUnitPrefixTextField.getText()); + info.setTestUnitSuffix(testUnitSuffixTextField.getText()); + info.setNumberOfTestsPerUnit((Integer) numberOfTestsPerUnitSpinner.getValue()); + info.setCheckGenerateUtplsqlTest(checkGenerateUtplsqlTestCheckBox.isSelected()); + info.setGenerateComments(generateCommentsCheckBox.isSelected()); + info.setDisableTests(disableTestsCheckBox.isSelected()); + info.setSuitePath(suitePathTextField.getText()); + info.setIndentSpaces((Integer) indentSpacesSpinner.getValue()); + info.setRootFolderInOddgenView(rootFolderInOddgenViewTextField.getText()); + info.setGenerateFiles(generateFilesCheckBox.isSelected()); + info.setOutputDirectory(outputDirectoryTextField.getText()); + info.setDeleteExistingFiles(deleteExistingFilesCheckBox.isSelected()); + super.onExit(traversableContext); + } + + private static PreferenceModel getUserInformation(final TraversableContext tc) { + return PreferenceModel.getInstance(tc.getPropertyStorage()); + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.xtend deleted file mode 100644 index 9abbf9f1..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/preference/PreferencePanel.xtend +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright 2018 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.ui.preference - -import java.awt.event.ActionEvent -import java.awt.event.ActionListener -import java.util.Map -import javax.swing.JButton -import javax.swing.JCheckBox -import javax.swing.JOptionPane -import javax.swing.JPanel -import javax.swing.JSpinner -import javax.swing.JTabbedPane -import javax.swing.JTextField -import javax.swing.SpinnerNumberModel -import javax.swing.table.DefaultTableModel -import oracle.dbtools.raptor.templates.CodeTemplateUtil -import oracle.ide.panels.DefaultTraversablePanel -import oracle.ide.panels.TraversableContext -import oracle.ide.panels.TraversalException -import oracle.javatools.ui.layout.FieldLayoutBuilder -import org.utplsql.sqldev.model.preference.PreferenceModel -import org.utplsql.sqldev.resources.UtplsqlResources -import org.utplsql.sqldev.snippet.SnippetMerger -import org.utplsql.sqldev.ui.common.DirectoryChooser - -class PreferencePanel extends DefaultTraversablePanel { - val JPanel runTestPanel = new JPanel(); - val JCheckBox useRealtimeReporterCheckBox = new JCheckBox - val JCheckBox unsharedWorksheetCheckBox = new JCheckBox - val JCheckBox resetPackageCheckBox = new JCheckBox - val JCheckBox clearScreenCheckBox = new JCheckBox - val JCheckBox autoExecuteCheckBox = new JCheckBox - val JCheckBox checkRunUtplsqlTestCheckBox = new JCheckBox - val JCheckBox useSmartTimesCheckBox = new JCheckBox - val JButton importSnippetsButton = new JButton(UtplsqlResources.getString("PREF_IMPORT_SNIPPETS_BUTTON_LABEL")) - val JPanel realtimeReporterPanel = new JPanel - val SpinnerNumberModel numberOfRunsInHistoryModel = new SpinnerNumberModel(1, 1, 100, 1); - val JSpinner numberOfRunsInHistorySpinner = new JSpinner(numberOfRunsInHistoryModel); - val JCheckBox showDisabledCounterCheckBox = new JCheckBox - val JCheckBox showWarningsCounterCheckBox = new JCheckBox - val JCheckBox showInfoCounterCheckBox = new JCheckBox - val JCheckBox showWarningIndicatorCheckBox = new JCheckBox - val JCheckBox showInfoIndicatorCheckBox = new JCheckBox - val JCheckBox showSuccessfulTestsCheckBox = new JCheckBox - val JCheckBox showDisabledTestsCheckBox = new JCheckBox - val JCheckBox showTestDescriptionCheckBox = new JCheckBox - val JCheckBox syncDetailTabCheckBox = new JCheckBox - val JPanel generateTestPanel = new JPanel(); - val JTextField testPackagePrefixTextField = new JTextField - val JTextField testPackageSuffixTextField = new JTextField - val JTextField testUnitPrefixTextField = new JTextField - val JTextField testUnitSuffixTextField = new JTextField - val SpinnerNumberModel numberOfTestsPerUnitModel = new SpinnerNumberModel(1, 1, 10, 1); - val JSpinner numberOfTestsPerUnitSpinner = new JSpinner(numberOfTestsPerUnitModel); - val JCheckBox checkGenerateUtplsqlTestCheckBox = new JCheckBox - val DefaultTableModel codeTemplatesModel = new DefaultTableModel(#["Id", "Template"], 0); - val JButton createCodeTemplatesButton = new JButton() - val JCheckBox generateCommentsCheckBox = new JCheckBox - val JCheckBox disableTestsCheckBox = new JCheckBox - val JTextField suitePathTextField = new JTextField - val SpinnerNumberModel indentSpacesModel = new SpinnerNumberModel(1, 1, 8, 1); - val JSpinner indentSpacesSpinner = new JSpinner(indentSpacesModel); - val JPanel oddgenPanel = new JPanel(); - val JTextField rootFolderInOddgenViewTextField = new JTextField - val JCheckBox generateFilesCheckBox = new JCheckBox - val JTextField outputDirectoryTextField = new JTextField - val JButton outputDirectoryBrowse = new JButton() - val JCheckBox deleteExistingFilesCheckBox = new JCheckBox - - new() { - layoutControls() - } - - private def layoutControls() { - // run test group - val FieldLayoutBuilder runTab = new FieldLayoutBuilder(runTestPanel) - runTab.alignLabelsLeft = true - runTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_USE_REALTIME_REPORTER_LABEL")).component( - useRealtimeReporterCheckBox).withHint(UtplsqlResources.getString("PREF_USE_REALTIME_REPORTER_HINT"))) - runTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_UNSHARED_WORKSHEET_LABEL")).component( - unsharedWorksheetCheckBox)) - runTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_RESET_PACKAGE_LABEL")).component( - resetPackageCheckBox)) - runTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_CLEAR_SCREEN_LABEL")).component( - clearScreenCheckBox)) - runTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_AUTO_EXECUTE_LABEL")).component( - autoExecuteCheckBox)) - runTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_CHECK_RUN_UTPLSQL_TEST_LABEL")).component( - checkRunUtplsqlTestCheckBox)) - runTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_USE_SMART_TIMES_LABEL")).component( - useSmartTimesCheckBox)) - runTab.addVerticalGap - runTab.addRow(importSnippetsButton) - runTab.addVerticalSpring - - // realtime reporter group - val FieldLayoutBuilder rrTab = new FieldLayoutBuilder(realtimeReporterPanel) - rrTab.alignLabelsLeft = true - - rrTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_NUMBER_OF_RUNS_IN_HISTORY_LABEL")).component( - numberOfRunsInHistorySpinner)) - rrTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_DISABLED_COUNTER_LABEL")).component( - showDisabledCounterCheckBox)) - rrTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_WARNINGS_COUNTER_LABEL")).component( - showWarningsCounterCheckBox)) - rrTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_INFO_COUNTER_LABEL")).component( - showInfoCounterCheckBox)) - rrTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_WARNING_INDICATOR_LABEL")).component( - showWarningIndicatorCheckBox)) - rrTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_INFO_INDICATOR_LABEL")).component( - showInfoIndicatorCheckBox)) - rrTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_SUCCESSFUL_TESTS_LABEL")).component( - showSuccessfulTestsCheckBox)) - rrTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_DISABLED_TESTS_LABEL")).component( - showDisabledTestsCheckBox)) - rrTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_SHOW_TEST_DESCRIPTION_LABEL")).component( - showTestDescriptionCheckBox)) - rrTab.add( - runTab.field.label.withText(UtplsqlResources.getString("PREF_SYNC_DETAIL_TAB_LABEL")).component( - syncDetailTabCheckBox)) - rrTab.addVerticalSpring - - // generate test group - val FieldLayoutBuilder generateTab = new FieldLayoutBuilder(generateTestPanel) - generateTab.alignLabelsLeft = true - generateTab.stretchComponentsWithNoButton = true - generateTab.add( - generateTab.field.label.withText(UtplsqlResources.getString("PREF_TEST_PACKAGE_PREFIX_LABEL")).component( - testPackagePrefixTextField)) - generateTab.add( - generateTab.field.label.withText(UtplsqlResources.getString("PREF_TEST_PACKAGE_SUFFIX_LABEL")).component( - testPackageSuffixTextField)) - generateTab.add( - generateTab.field.label.withText(UtplsqlResources.getString("PREF_TEST_UNIT_PREFIX_LABEL")).component( - testUnitPrefixTextField)) - generateTab.add( - generateTab.field.label.withText(UtplsqlResources.getString("PREF_TEST_UNIT_SUFFIX_LABEL")).component( - testUnitSuffixTextField)) - generateTab.add( - generateTab.field.label.withText(UtplsqlResources.getString("PREF_NUMBER_OF_TESTS_PER_UNIT_LABEL")).component( - numberOfTestsPerUnitSpinner)) - generateTab.add( - generateTab.field.label.withText(UtplsqlResources.getString("PREF_GENERATE_COMMENTS_LABEL")).component( - generateCommentsCheckBox)) - generateTab.add( - generateTab.field.label.withText(UtplsqlResources.getString("PREF_DISABLE_TESTS_LABEL")).component( - disableTestsCheckBox)) - generateTab.add( - generateTab.field.label.withText(UtplsqlResources.getString("PREF_SUITE_PATH_LABEL")).component( - suitePathTextField)) - generateTab.add( - generateTab.field.label.withText(UtplsqlResources.getString("PREF_INDENT_SPACES_LABEL")).component( - indentSpacesSpinner)) - generateTab.add( - generateTab.field.label.withText(UtplsqlResources.getString("PREF_CHECK_GENERATE_UTPLSQL_TEST_LABEL")).component( - checkGenerateUtplsqlTestCheckBox).button(createCodeTemplatesButton).withText( - UtplsqlResources.getString("PREF_CREATE_CODE_TEMPLATES_BUTTON_LABEL"))) - generateTab.addVerticalSpring - - // oddgen group - val FieldLayoutBuilder oddgenTab = new FieldLayoutBuilder(oddgenPanel) - oddgenTab.alignLabelsLeft = true - oddgenTab.stretchComponentsWithNoButton = true - oddgenTab.add( - oddgenTab.field.label.withText(UtplsqlResources.getString("PREF_ROOT_FOLDER_IN_ODDGEN_VIEW_LABEL")).component( - rootFolderInOddgenViewTextField)) - oddgenTab.add( - oddgenTab.field.label.withText(UtplsqlResources.getString("PREF_GENERATE_FILES_LABEL")).component( - generateFilesCheckBox)) - oddgenTab.add( - oddgenTab.field.label.withText(UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_LABEL")).component( - outputDirectoryTextField).button(outputDirectoryBrowse).withText( - UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_BUTTON_LABEL"))) - oddgenTab.add( - oddgenTab.field.label.withText(UtplsqlResources.getString("PREF_DELETE_EXISTING_FILES_LABEL")).component( - deleteExistingFilesCheckBox)) - oddgenTab.addVerticalSpring - - // putting groups into tabbed panes - val tabbedPane = new JTabbedPane() - tabbedPane.add(UtplsqlResources.getString("MENU_RUN_TEST_LABEL"), runTestPanel) - tabbedPane.add(UtplsqlResources.getString("MENU_REALTIME_REPORTER_LABEL"), realtimeReporterPanel) - tabbedPane.add(UtplsqlResources.getString("MENU_GENERATE_TEST_LABEL"), generateTestPanel) - tabbedPane.add("oddgen", oddgenPanel) - val FieldLayoutBuilder builder = new FieldLayoutBuilder(this) - builder.alignLabelsLeft = true - builder.addVerticalField("", tabbedPane) - builder.addVerticalSpring - - // register action listener for import snippets button - importSnippetsButton.addActionListener(new ActionListener() { - override actionPerformed(ActionEvent event) { - importSnippets - } - }) - - // register action listener for create code template button - createCodeTemplatesButton.addActionListener(new ActionListener() { - override actionPerformed(ActionEvent event) { - saveCodeTemplates - } - }) - - // register action listener for directory chooser - outputDirectoryBrowse.addActionListener(new ActionListener() { - override actionPerformed(ActionEvent event) { - DirectoryChooser.choose(null, UtplsqlResources.getString("PREF_OUTPUT_DIRECTORY_LABEL"), - outputDirectoryTextField) - } - }) - } - - private def importSnippets() { - val snippetMerger = new SnippetMerger - snippetMerger.merge - val file = snippetMerger.file.absolutePath - val message = String.format(UtplsqlResources.getString("PREF_CONFIRM_IMPORT_MESSAGE"), file) - JOptionPane.showMessageDialog(null, message, UtplsqlResources.getString("PREF_CONFIRM_IMPORT_TITLE"), - JOptionPane.INFORMATION_MESSAGE); - } - - private def loadCodeTemplates() { - val Map map = CodeTemplateUtil.loadFiles() - for (key : map.keySet) { - codeTemplatesModel.addRow(#[key, map.get(key)]) - } - } - - private def saveCodeTemplates() { - codeTemplatesModel.addRow(#["ut_spec", utSpecTemplate.replaceTabsWithSpaces]) - codeTemplatesModel.addRow(#["ut_spec_proc", utSpecProcTemplate.replaceTabsWithSpaces.trimPlusNewLine]) - codeTemplatesModel.addRow(#["ut_body", utBodyTemplate.replaceTabsWithSpaces]) - codeTemplatesModel.addRow(#["ut_body_proc", utBodyProcTemplate.replaceTabsWithSpaces.trimPlusNewLine]) - CodeTemplateUtil.save(codeTemplatesModel) - } - - private def replaceTabsWithSpaces(CharSequence input) { - val spaces = String.format("%1$"+indentSpacesSpinner.value+"s", "") - return input.toString.replace("\t", spaces) - } - - private def trimPlusNewLine(String input) { - input.trim + System.lineSeparator - } - - private def utSpecTemplate() ''' - CREATE OR REPLACE PACKAGE «testPackagePrefixTextField.text»[package_name]«testPackageSuffixTextField.text» IS - - --%suite - «IF !suitePathTextField.text.empty» - --%suitepath(«suitePathTextField.text») - «ENDIF» - - «utSpecProcTemplate» - END «testPackagePrefixTextField.text»[package_name]«testPackageSuffixTextField.text»; - / - ''' - - private def utSpecProcTemplate() ''' - «val withContext = numberOfTestsPerUnitModel.value as Integer > 1» - «IF withContext» - --%context([procedure_name]) - - «ENDIF» - «FOR i : 1 .. numberOfTestsPerUnitModel.value as Integer» - --%test - «IF disableTestsCheckBox.selected» - --%disabled - «ENDIF» - PROCEDURE «testUnitPrefixTextField.text»[procedure_name]«testUnitSuffixTextField.text»«IF withContext»«i»«ENDIF»; - - «ENDFOR» - «IF withContext» - --%endcontext - - «ENDIF» - ''' - - private def utBodyTemplate() ''' - CREATE OR REPLACE PACKAGE BODY «testPackagePrefixTextField.text»[package_name]«testPackageSuffixTextField.text» IS - - «utBodyProcTemplate» - END «testPackagePrefixTextField.text»[package_name]«testPackageSuffixTextField.text»; - / - ''' - - private def utBodyProcTemplate() ''' - «val withContext = numberOfTestsPerUnitModel.value as Integer > 1» - «FOR i : 1 .. numberOfTestsPerUnitModel.value as Integer» - «IF generateCommentsCheckBox.selected» - -- - -- test«IF withContext» [procedure_name] case «i»: ...«ENDIF» - -- - «ENDIF» - PROCEDURE «testUnitPrefixTextField.text»[procedure_name]«testUnitSuffixTextField.text»«IF withContext»«i»«ENDIF» IS - l_actual INTEGER := 0; - l_expected INTEGER := 1; - BEGIN - «IF generateCommentsCheckBox.selected» - -- populate actual - -- ... - - -- populate expected - -- ... - - -- assert - «ENDIF» - ut.expect(l_actual).to_equal(l_expected); - END «testUnitPrefixTextField.text»[procedure_name]«testUnitSuffixTextField.text»«IF withContext»«i»«ENDIF»; - - «ENDFOR» - ''' - - override onEntry(TraversableContext traversableContext) { - var PreferenceModel info = traversableContext.userInformation - useRealtimeReporterCheckBox.selected = info.useRealtimeReporter - unsharedWorksheetCheckBox.selected = info.unsharedWorksheet - resetPackageCheckBox.selected = info.resetPackage - clearScreenCheckBox.selected = info.clearScreen - autoExecuteCheckBox.selected = info.autoExecute - checkRunUtplsqlTestCheckBox.selected = info.checkRunUtplsqlTest - useSmartTimesCheckBox.selected = info.useSmartTimes - numberOfRunsInHistorySpinner.value = info.numberOfRunsInHistory - showDisabledCounterCheckBox.selected = info.showDisabledCounter - showWarningsCounterCheckBox.selected = info.showWarningsCounter - showInfoCounterCheckBox.selected = info.showInfoCounter - showWarningIndicatorCheckBox.selected = info.showWarningIndicator - showInfoIndicatorCheckBox.selected = info.showInfoIndicator - showSuccessfulTestsCheckBox.selected = info.showSuccessfulTests - showDisabledTestsCheckBox.selected = info.showDisabledTests - showTestDescriptionCheckBox.selected = info.showTestDescription - syncDetailTabCheckBox.selected = info.syncDetailTab - testPackagePrefixTextField.text = info.testPackagePrefix - testPackageSuffixTextField.text = info.testPackageSuffix - testUnitPrefixTextField.text = info.testUnitPrefix - testUnitSuffixTextField.text = info.testUnitSuffix - numberOfTestsPerUnitSpinner.value = info.numberOfTestsPerUnit - checkGenerateUtplsqlTestCheckBox.selected = info.checkGenerateUtplsqlTest - loadCodeTemplates - generateCommentsCheckBox.selected = info.generateComments - disableTestsCheckBox.selected = info.disableTests - suitePathTextField.text = info.suitePath - indentSpacesSpinner.value = info.indentSpaces - rootFolderInOddgenViewTextField.text = info.rootFolderInOddgenView - generateFilesCheckBox.selected = info.generateFiles - outputDirectoryTextField.text = info.outputDirectory - deleteExistingFilesCheckBox.selected = info.deleteExistingFiles - super.onEntry(traversableContext) - } - - override onExit(TraversableContext traversableContext) throws TraversalException { - var PreferenceModel info = traversableContext.userInformation - info.useRealtimeReporter = useRealtimeReporterCheckBox.selected - info.unsharedWorksheet = unsharedWorksheetCheckBox.selected - info.resetPackage = resetPackageCheckBox.selected - info.clearScreen = clearScreenCheckBox.selected - info.autoExecute = autoExecuteCheckBox.selected - info.numberOfRunsInHistory = numberOfRunsInHistorySpinner.value as Integer - info.checkRunUtplsqlTest = checkRunUtplsqlTestCheckBox.selected - info.useSmartTimes = useSmartTimesCheckBox.selected - info.showDisabledCounter = showDisabledCounterCheckBox.selected - info.showWarningsCounter = showWarningsCounterCheckBox.selected - info.showInfoCounter = showInfoCounterCheckBox.selected - info.showWarningIndicator = showWarningIndicatorCheckBox.selected - info.showInfoIndicator = showInfoIndicatorCheckBox.selected - info.showSuccessfulTests = showSuccessfulTestsCheckBox.selected - info.showDisabledTests = showDisabledTestsCheckBox.selected - info.showTestDescription = showTestDescriptionCheckBox.selected - info.syncDetailTab = syncDetailTabCheckBox.selected - info.testPackagePrefix = testPackagePrefixTextField.text - info.testPackageSuffix = testPackageSuffixTextField.text - info.testUnitPrefix = testUnitPrefixTextField.text - info.testUnitSuffix = testUnitSuffixTextField.text - info.numberOfTestsPerUnit = numberOfTestsPerUnitSpinner.value as Integer - info.checkGenerateUtplsqlTest = checkGenerateUtplsqlTestCheckBox.selected - info.generateComments = generateCommentsCheckBox.selected - info.disableTests = disableTestsCheckBox.selected - info.suitePath = suitePathTextField.text - info.indentSpaces = indentSpacesSpinner.value as Integer - info.rootFolderInOddgenView = rootFolderInOddgenViewTextField.text - info.generateFiles = generateFilesCheckBox.selected - info.outputDirectory = outputDirectoryTextField.text - info.deleteExistingFiles = deleteExistingFilesCheckBox.selected - super.onExit(traversableContext) - } - - private def static PreferenceModel getUserInformation(TraversableContext tc) { - return PreferenceModel.getInstance(tc.propertyStorage) - } -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.java similarity index 62% rename from sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.xtend rename to sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.java index f78658a3..77cf6644 100644 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.xtend +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ComboBoxItem.java @@ -13,16 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.utplsql.sqldev.ui.runner +package org.utplsql.sqldev.ui.runner; -import java.util.AbstractMap +import java.util.AbstractMap; -class ComboBoxItem extends AbstractMap.SimpleEntry { - new(K key, V value) { - super(key, value) - } - - override toString() { - return value.toString - } +public class ComboBoxItem extends AbstractMap.SimpleEntry { + private static final long serialVersionUID = 7869442222989031548L; + + public ComboBoxItem(final K key, final V value) { + super(key, value); + } + + @Override + public String toString() { + return this.getValue().toString(); + } } diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.java new file mode 100644 index 00000000..67777bbf --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.java @@ -0,0 +1,89 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.runner; + +import java.util.Arrays; +import java.util.List; + +import javax.swing.table.DefaultTableModel; + +import org.utplsql.sqldev.model.runner.Expectation; +import org.utplsql.sqldev.resources.UtplsqlResources; + +public class FailuresTableModel extends DefaultTableModel { + private static final long serialVersionUID = 8119453059788497567L; + private List failedExpectations; + private final List columnNames = Arrays.asList("#", UtplsqlResources.getString("RUNNER_ASSERT_DESCRIPTION_COLUMN")); + + public FailuresTableModel() { + super(); + } + + public void setModel(final List failedExpectations) { + this.failedExpectations = failedExpectations; + } + + public Expectation getExpectation(final int row) { + return failedExpectations.get(row); + } + + @Override + public int getRowCount() { + if (failedExpectations == null) { + return 0; + } + return failedExpectations.size(); + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public Object getValueAt(final int row, final int col) { + final Expectation expectation = failedExpectations.get(row); + if (expectation == null) { + return null; + } + switch (col) { + case 0: + return row + 1; + case 1: + return expectation.getShortFailureText(); + default: + return null; + } + } + + @Override + public String getColumnName(final int col) { + return columnNames.get(col); + } + + @Override + public boolean isCellEditable(final int row, final int column) { + return false; + } + + @Override + public Class getColumnClass(final int col) { + if (col == 0) { + return Integer.class; + } + return String.class; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.xtend deleted file mode 100644 index acd9418b..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/FailuresTableModel.xtend +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package org.utplsql.sqldev.ui.runner - -import java.util.List -import javax.swing.table.DefaultTableModel -import org.utplsql.sqldev.model.runner.Expectation -import org.utplsql.sqldev.resources.UtplsqlResources - -class FailuresTableModel extends DefaultTableModel { - List failedExpectations - - new() { - super() - } - - def setModel(List failedExpectations) { - this.failedExpectations = failedExpectations - } - - def getExpectation(int row) { - return failedExpectations.get(row) - } - - override getRowCount() { - if (failedExpectations === null) { - return 0 - } - return failedExpectations.size() - } - - override getColumnCount() { - return 2 - } - - override getValueAt(int row, int col) { - val expectation = failedExpectations.get(row) - if (expectation === null) { - return null - } - switch (col) { - case 0: { - return row + 1 - } - case 1: { - return expectation.shortFailureText - } - default: { - return null - } - } - } - - override getColumnName(int col) { - return #["#", UtplsqlResources.getString("RUNNER_ASSERT_DESCRIPTION_COLUMN")].get(col) - } - - override isCellEditable(int row, int column) { - return false - } - - override getColumnClass(int col) { - switch (col) { - case 0: { - return Integer - } - case 1: { - return String - } - default: { - return String - } - } - } -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/GradientToolbar.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/GradientToolbar.java new file mode 100644 index 00000000..6f163124 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/GradientToolbar.java @@ -0,0 +1,85 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.runner; + +import java.awt.Color; +import java.awt.GradientPaint; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; + +import javax.swing.JToolBar; +import javax.swing.LookAndFeel; +import javax.swing.UIManager; +import javax.swing.border.BevelBorder; +import javax.swing.border.EmptyBorder; + +public class GradientToolbar extends JToolBar { + private static final long serialVersionUID = 6128707792081725058L; + + private boolean isOracleLookAndFeel() { + LookAndFeel laf = UIManager.getLookAndFeel(); + final String lafName = laf != null ? laf.getName() : null; + return "Oracle Look and Feel version 2".equals(lafName); + } + + public GradientToolbar() { + super(); + if (isOracleLookAndFeel()) { + setBorder(new EmptyBorder(new Insets(2, 2, 2, 2))); // insets: top, left, bottom, right + } else { + setBorder(new BevelBorder(BevelBorder.RAISED)); + } + } + + @Override + public void paintComponent(final Graphics g) { + if (isOracleLookAndFeel()) { + // emulate Oracle toolbar + // 1. default for non-opaque components + if (!isOpaque()) { + super.paintComponent(g); + return; + } + + // 2. paint gradient background from top to bottom with separator line at the bottom + final Graphics2D g2d = ((Graphics2D) g); + final int w = getWidth(); + final int h = (getHeight() - 1); + final int h2 = (getHeight() / 2); + final Color colorTop = new Color(237, 237, 237); + final Color colorMiddle = new Color(244, 244, 244); + final Color colorBottom = new Color(254, 254, 254); + final Color colorBottomLine = Color.LIGHT_GRAY; + final GradientPaint gp1 = new GradientPaint(0, 0, colorTop, 0, h2, colorMiddle); + g2d.setPaint(gp1); + g2d.fillRect(0, 0, w, h2); + final GradientPaint gp2 = new GradientPaint(0, h2, colorMiddle, 0, h, colorBottom); + g2d.setPaint(gp2); + g2d.fillRect(0, h2, w, h); + g2d.setPaint(colorBottomLine); + g2d.fillRect(0, h, w, (h + 1)); + + // 3. do rest, changing opaque to ensure background is not overwritten + setOpaque(false); + super.paintComponent(g); + setOpaque(true); + } else { + // default logic + super.paintComponent(g); + } + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/GradientToolbar.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/GradientToolbar.xtend deleted file mode 100644 index 8c04e7b5..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/GradientToolbar.xtend +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.ui.runner - -import java.awt.Color -import java.awt.GradientPaint -import java.awt.Graphics -import java.awt.Graphics2D -import java.awt.Insets -import javax.swing.JToolBar -import javax.swing.UIManager -import javax.swing.border.BevelBorder -import javax.swing.border.EmptyBorder - -class GradientToolbar extends JToolBar { - - private def isOracleLookAndFeel() { - val laf = UIManager.lookAndFeel?.name - if (laf == "Oracle Look and Feel version 2") { - return true - } else { - return false - } - } - - new() { - super() - if (oracleLookAndFeel) { - this.border = new EmptyBorder(new Insets(2, 2, 2, 2)) // top, left, bottom, right - } else { - this.border = new BevelBorder(BevelBorder.RAISED) - } - } - - override paintComponent(Graphics g) { - if (oracleLookAndFeel) { - // emulate Oracle toolbar - // 1. default for non-opaque components - if (!opaque) { - super.paintComponent(g) - return - } - - // 2. paint gradient background from top to bottom with separator line at the bottom - val g2d = g as Graphics2D - val w = width - val h = height - 1 - val int h2 = height / 2 - val colorTop = new Color(237, 237, 237) - val colorMiddle = new Color(244, 244, 244) - val colorBottom = new Color(254, 254, 254) - val colorBottomLine = Color.LIGHT_GRAY - val gp1 = new GradientPaint(0, 0, colorTop, 0, h2, colorMiddle) - g2d.paint = gp1 - g2d.fillRect(0, 0, w, h2) - val gp2 = new GradientPaint(0, h2, colorMiddle, 0, h, colorBottom) - g2d.paint = gp2 - g2d.fillRect(0, h2, w, h) - g2d.paint = colorBottomLine - g2d.fillRect(0, h, w, h+1) - - // 3. do rest, changing opaque to ensure background is not overwritten - setOpaque(false) - super.paintComponent(g) - setOpaque(true) - } else { - // default logic - super.paintComponent(g) - } - } - -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerFactory.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerFactory.java new file mode 100644 index 00000000..72483953 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerFactory.java @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.runner; + +import oracle.ide.docking.DockStation; +import oracle.ide.docking.Dockable; +import oracle.ide.docking.DockableFactory; +import oracle.ide.docking.DockingParam; +import oracle.ide.layout.ViewId; + +public class RunnerFactory implements DockableFactory { + public static final String FACTORY_NAME = "UTPLSQL_RUNNER_FACTORY"; + + private RunnerView dockable; + + @Override + public void install() { + final DockStation dockStation = DockStation.getDockStation(); + final DockingParam dp = new DockingParam(); + final ViewId referencedViewId = new ViewId("DatabaseNavigatorWindow", "DatabaseNavigatorWindow"); + final Dockable referencedDockable = dockStation.findDockable(referencedViewId); + dp.setTabbedWith(referencedDockable); + dockStation.dock(getLocalDockable(), dp); + } + + @Override + public Dockable getDockable(final ViewId viewId) { + if (viewId == RunnerView.VIEW_ID) { + return getLocalDockable(); + } + return null; + } + + private RunnerView getLocalDockable() { + if (dockable == null) { + dockable = new RunnerView(); + } + return dockable; + } + + public static RunnerView getDockable() { + final DockStation dockStation = DockStation.getDockStation(); + final Dockable dockable = dockStation.findDockable(RunnerView.VIEW_ID); + return (RunnerView) dockable; + } + + public static void showDockable() { + final DockStation dockStation = DockStation.getDockStation(); + dockStation.setDockableVisible(getDockable(), true); + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerFactory.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerFactory.xtend deleted file mode 100644 index 61915559..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerFactory.xtend +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.ui.runner - -import oracle.ide.docking.DockStation -import oracle.ide.docking.DockableFactory -import oracle.ide.docking.DockingParam -import oracle.ide.layout.ViewId - -class RunnerFactory implements DockableFactory { - public static val FACTORY_NAME = "UTPLSQL_RUNNER_FACTORY" - - var RunnerView dockable - - override install() { - val dockStation = DockStation.getDockStation(); - val dp = new DockingParam(); - val referencedViewId = new ViewId("DatabaseNavigatorWindow", "DatabaseNavigatorWindow") - val referencedDockable = dockStation.findDockable(referencedViewId) - dp.tabbedWith = referencedDockable - dockStation.dock(getLocalDockable(), dp); - } - - override getDockable(ViewId viewId) { - if (viewId === RunnerView.VIEW_ID) { - return localDockable - } - return null - } - - private def getLocalDockable() { - if (dockable === null) { - dockable = new RunnerView - } - return dockable - } - - static def getDockable() { - val dockStation = DockStation.dockStation - val dockable = dockStation.findDockable(RunnerView.VIEW_ID) - return dockable as RunnerView - } - - static def void showDockable() { - val dockStation = DockStation.dockStation - dockStation.setDockableVisible(getDockable(), true) - } -} \ No newline at end of file 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 new file mode 100644 index 00000000..5f225a84 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.java @@ -0,0 +1,1989 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.runner; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.sql.Connection; +import java.util.ArrayList; +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; + +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; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JProgressBar; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +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; +import javax.swing.Timer; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.event.HyperlinkEvent; +import javax.swing.plaf.basic.BasicProgressBarUI; +import javax.swing.table.DefaultTableCellRenderer; +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; +import org.utplsql.sqldev.dal.UtplsqlDao; +import org.utplsql.sqldev.model.DatabaseTools; +import org.utplsql.sqldev.model.LimitedLinkedHashMap; +import org.utplsql.sqldev.model.StringTools; +import org.utplsql.sqldev.model.SystemTools; +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; +import org.utplsql.sqldev.resources.UtplsqlResources; +import org.utplsql.sqldev.runner.UtplsqlRunner; +import org.utplsql.sqldev.runner.UtplsqlWorksheetRunner; + +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; + private static final int OVERVIEW_TABLE_ROW_HEIGHT = 20; + private static final Dimension TEXTPANE_DIM = new Dimension(100, 100); + + private boolean useSmartTimes = false; + private LimitedLinkedHashMap runs = new LimitedLinkedHashMap<>(10); + private Run currentRun; + private JPanel basePanel; + private DefaultComboBoxModel> runComboBoxModel; + private ToolbarButton stopButton; + private JComboBox> runComboBox; + private JLabel statusLabel; + private Timer elapsedTimeTimer; + private JLabel testCounterValueLabel; + private JLabel errorCounterValueLabel; + private JLabel failureCounterValueLabel; + private JLabel disabledCounterValueLabel; + private JLabel warningsCounterValueLabel; + private JLabel infoCounterValueLabel; + private JCheckBoxMenuItem showDisabledCounterCheckBoxMenuItem; + private JCheckBoxMenuItem showWarningsCounterCheckBoxMenuItem; + 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; + private JMenuItem testOverviewCodeCoverageMenuItem; + private JCheckBoxMenuItem showTestDescriptionCheckBoxMenuItem; + private JCheckBoxMenuItem showWarningIndicatorCheckBoxMenuItem; + private JCheckBoxMenuItem showInfoIndicatorCheckBoxMenuItem; + 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; + private JTable failuresTable; + private RunnerTextPane testFailureMessageTextPane; + private RunnerTextPane testErrorStackTextPane; + private RunnerTextPane testWarningsTextPane; + private RunnerTextPane testServerOutputTextPane; + private JTabbedPane testDetailTabbedPane; + + // used in multiple components, therefore an inner class + private static class TestTableHeaderRenderer extends DefaultTableCellRenderer { + private static final long serialVersionUID = 6295858563570577027L; + + @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(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 == 3) { + label.setIcon(UtplsqlResources.getIcon("INFO_ICON")); + label.setHorizontalAlignment(JLabel.CENTER); + } else if (col == 4) { + label.setIcon(null); + label.setHorizontalAlignment(JLabel.RIGHT); + } + return label; + } + } + + // 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; + + @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.setHorizontalAlignment(JLabel.RIGHT); + } else { + label.setHorizontalAlignment(JLabel.LEFT); + } + return label; + } + } + + public Component getGUI() { + if (basePanel == null) { + initializeGUI(); + } + if (!basePanel.isShowing()) { + applyPreferences(); + } + return basePanel; + } + + private void resetDerived() { + testOverviewTable.getRowSorter().setSortKeys(null); + testOverviewRunMenuItem.setEnabled(false); + testOverviewRunWorksheetMenuItem.setEnabled(false); + testOverviewDebugMenuItem.setEnabled(false); + testOverviewCodeCoverageMenuItem.setEnabled(false); + testIdTextArea.setText(null); + testOwnerTextField.setText(null); + testPackageTextField.setText(null); + testProcedureTextField.setText(null); + testDescriptionTextArea.setText(null); + testDisabledReasonTextArea.setText(null); + testStartTextField.setText(null); + failuresTableModel.setModel(null); + failuresTableModel.fireTableDataChanged(); + testFailureMessageTextPane.setText(null); + testErrorStackTextPane.setText(null); + testWarningsTextPane.setText(null); + testServerOutputTextPane.setText(null); + enableOrDisableStopButton(); + } + + private void refreshRunsComboBox() { + if (!runs.isEmpty()) { + for (ActionListener al : runComboBox.getActionListeners()) { + runComboBox.removeActionListener(al); + } + runComboBoxModel.removeAllElements(); + List> entries = new ArrayList<>(runs.entrySet()); + for (int i = runs.size() - 1; i >= 0; i--) { + final Map.Entry entry = entries.get(i); + final ComboBoxItem item = new ComboBoxItem<>(entry.getKey(), entry.getValue().getName()); + runComboBoxModel.addElement(item); + } + runComboBox.setSelectedIndex(0); + runComboBox.addActionListener(event -> comboBoxAction()); + } + } + + private void applyShowNumberOfRunsInHistory(final int maxRuns) { + if (maxRuns != runs.getMaxEntries()) { + final LimitedLinkedHashMap newRuns = new LimitedLinkedHashMap<>(maxRuns); + for (final Map.Entry entry : runs.entrySet()) { + newRuns.put(entry.getKey(), entry.getValue()); + } + runs = newRuns; + } + } + + private void applyShowDisabledCounter() { + disabledCounterValueLabel.getParent().setVisible(showDisabledCounterCheckBoxMenuItem.isSelected()); + } + + private void applyShowWarningsCounter() { + warningsCounterValueLabel.getParent().setVisible(showWarningsCounterCheckBoxMenuItem.isSelected()); + } + + private void applyShowInfoCounter() { + infoCounterValueLabel.getParent().setVisible(showInfoCounterCheckBoxMenuItem.isSelected()); + } + + private void applyShowTestDescription() { + // table + testOverviewTableModel.updateModel(showTestDescriptionCheckBoxMenuItem.isSelected()); + fixColumnHeader(testOverviewTableModel.getTestIdColumnName(), testOverviewTable, 1); + // tree-table + testOverviewTreeTableModel.updateModel(showTestDescriptionCheckBoxMenuItem.isSelected()); + fixColumnHeader(testOverviewTreeTableModel.getTreeColumnName(), testOverviewTreeTable, 0); + } + + private void showColumn(final boolean show, TableColumn col) { + if (show) { + col.setWidth(INDICATOR_WIDTH); + col.setMinWidth(INDICATOR_WIDTH); + col.setMaxWidth(INDICATOR_WIDTH); + col.setPreferredWidth(INDICATOR_WIDTH); + } else { + col.setWidth(0); + col.setMinWidth(0); + col.setMaxWidth(0); + col.setPreferredWidth(0); + } + } + + private void applyShowWarningIndicator(final boolean show) { + showColumn(show, testOverviewTable.getColumnModel().getColumn(2)); + showColumn(show, testOverviewTreeTable.getColumnModel().getColumn(1)); + } + + private void applyShowInfoIndicator(final boolean show) { + 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() { + @Override + public boolean include(final RowFilter.Entry 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; + } + }; + 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()); + } + 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(); + final UtplsqlParser parser = new UtplsqlParser(source); + final int line = parser.getLineOf(test.getProcedureName()); + openEditor(test.getOwnerName(), "PACKAGE", test.getObjectName().toUpperCase(), line, 1); + } + + private void openSelectedTest() { + 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); + } + } + } + + private void openSelectedFailure() { + final int rowIndex = failuresTable.getSelectedRow(); + if (rowIndex != -1) { + final int row = failuresTable.convertRowIndexToModel(rowIndex); + final Expectation expectation = failuresTableModel.getExpectation(row); + Test test = null; + if (!showSuitesCheckBoxMenuItem.isSelected()) { + // table + test = testOverviewTableModel + .getTest(testOverviewTable.convertRowIndexToModel(testOverviewTable.getSelectedRow())); + } else { + // 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); + } + } + } + } + + private String getHtml(final String text) { + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("\t\n"); + sb.append("\t\t\n"); + sb.append("\t\n"); + sb.append("\t\n"); + sb.append("\t\t"); + sb.append(getLinkedAndFormattedText(text)); + sb.append('\n'); + sb.append("\t\n"); + sb.append("\n"); + return sb.toString(); + } + + private void openLink(final String link) { + final String[] parts = link.split("/"); + final String type = parts[0]; + final String ownerName = parts[1]; + final String objectName = parts[2]; + int line = Integer.parseInt(parts[3]); + final UtplsqlDao dao = new UtplsqlDao(DatabaseTools.getConnection(currentRun.getConnectionName())); + final String objectType = "UNKNOWN".equals(type) ? dao.getObjectType(ownerName, objectName) : type; + if (parts.length == 5) { + final String procedureName = parts[4]; + final String source = dao.getSource(ownerName, objectType, objectName).trim(); + final UtplsqlParser parser = new UtplsqlParser(source); + line = parser.getLineOf(procedureName); + } + openEditor(ownerName, objectType, objectName.toUpperCase(), line, 1); + } + + 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()); + // argument order is based on SQLDEV:LINK that can be used in SQL query result tables (editors, reports) + drillLink.setArgs(new String[] { owner, type, name, String.valueOf(line), String.valueOf(col), "OpenEditor", + "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()) { + 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); + } + } + + } + } + + private PreferenceModel getPreferenceModel() { + try { + return PreferenceModel.getInstance(Preferences.getPreferences()); + } catch (NoClassDefFoundError | ExceptionInInitializerError e) { + // running outside of SQL Developer + return PreferenceModel.getInstance(null); + } + } + + private void applyPreferences() { + final PreferenceModel preferences = getPreferenceModel(); + applyShowNumberOfRunsInHistory(preferences.getNumberOfRunsInHistory()); + showDisabledCounterCheckBoxMenuItem.setSelected(preferences.isShowDisabledCounter()); + applyShowDisabledCounter(); + fixCheckBoxMenuItem(showDisabledCounterCheckBoxMenuItem); + showWarningsCounterCheckBoxMenuItem.setSelected(preferences.isShowWarningsCounter()); + applyShowWarningsCounter(); + fixCheckBoxMenuItem(showWarningsCounterCheckBoxMenuItem); + showInfoCounterCheckBoxMenuItem.setSelected(preferences.isShowInfoCounter()); + applyShowInfoCounter(); + fixCheckBoxMenuItem(showInfoCounterCheckBoxMenuItem); + showTestDescriptionCheckBoxMenuItem.setSelected(preferences.isShowTestDescription()); + applyShowTestDescription(); + fixCheckBoxMenuItem(showTestDescriptionCheckBoxMenuItem); + showWarningIndicatorCheckBoxMenuItem.setSelected(preferences.isShowWarningIndicator()); + applyShowWarningIndicator(showWarningIndicatorCheckBoxMenuItem.isSelected()); + fixCheckBoxMenuItem(showWarningIndicatorCheckBoxMenuItem); + showInfoIndicatorCheckBoxMenuItem.setSelected(preferences.isShowInfoIndicator()); + applyShowInfoIndicator(showInfoIndicatorCheckBoxMenuItem.isSelected()); + showSuccessfulTestsCheckBoxMenuItem.setSelected(preferences.isShowSuccessfulTests()); + fixCheckBoxMenuItem(showSuccessfulTestsCheckBoxMenuItem); + 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, 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(); + } + } + + /** + * 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; + } + } + 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); + } + } + + private void enableOrDisableStopButton() { + stopButton.setEnabled(currentRun.getEndTime() == null); + } + + public synchronized void update(final String reporterId) { + 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()); + } + } + } + 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<>(); + 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; + } + + private boolean isWindowsLookAndFeel() { + LookAndFeel laf = UIManager.getLookAndFeel(); + final String lafName = laf != null ? laf.getName() : null; + return "Windows".equals(lafName); + } + + private boolean isMacLookAndFeel() { + LookAndFeel laf = UIManager.getLookAndFeel(); + final String lafName = laf != null ? laf.getName() : null; + return "Mac OS X".equals(lafName); + } + + private void fixCheckBoxMenuItem(final JCheckBoxMenuItem item) { + if (isWindowsLookAndFeel()) { + if (item.isSelected()) { + item.setIcon(UtplsqlResources.getIcon("CHECKMARK_ICON")); + } else { + item.setIcon(null); + } + } + } + + 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") + final ComboBoxItem comboBoxItem = (ComboBoxItem) runComboBox + .getSelectedItem(); + if (currentRun.getReporterId() != null && comboBoxItem != null) { + if (!currentRun.getReporterId().equals(comboBoxItem.getKey())) { + update(comboBoxItem.getKey()); + refreshAction(); + } + } + } + } + + private String getLinkedAndFormattedText(final String text) { + if (text == null) { + return ""; + } + // Patterns (primarily Asserts, Errors, ServerOutput): + // at "OWNER.PACKAGE.PROCEDURE", line 42 + // at "OWNER.PROCEDURE", line 42 + // at "OWNER.PACKAGE", line 42 + // at package "OWNER.PACKAGE", line 42 + final Pattern p1 = Pattern.compile("\\s+(package\\s+)?("(\\S+?)\\.(\\S+?)(?:\\.(\\S+?))?",\\s+line\\s+([0-9]+))"); + String localText = HtmlUtils.htmlEscape(text); + Matcher m = p1.matcher(localText); + while (m.find()) { + final String link = "" + m.group(2) + ""; + final int start = m.start(2); + final int end = m.end(2); + localText = localText.substring(0, start) + link + localText.substring(end); + m = p1.matcher(localText); + } + // Patterns (primarily Warnings, without line reference, calculate when opening link): + // owner.package.procedure + final Pattern p2 = Pattern.compile("^\\s{2}((\\S+?)\\.(\\S+?)\\.(\\S+?))$", Pattern.MULTILINE); + m = p2.matcher(localText); + while (m.find()) { + final String link = "  " + m.group(1) + ""; + final int start = m.start(0); + final int end = m.end(0); + localText = localText.substring(0, start) + link + localText.substring(end); + m = p2.matcher(localText); + } + // Patterns (Title for warning/info on suite level) + // from suite a.junit_utplsql_test1_pkg: + final Pattern p3 = Pattern.compile("^For suite ([^:]+):$", Pattern.MULTILINE); + m = p3.matcher(localText); + while (m.find()) { + final String title = "For suite \"" + m.group(1) + "\""; + final int start = m.start(0); + final int end = m.end(0); + 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("

"); + sb.append(p); + sb.append("

\n"); + } + return sb.toString(); + } + + private JPanel makeLabelledCounterComponent(final JLabel label, final JComponent comp) { + final JPanel groupPanel = new JPanel(); + groupPanel.setLayout(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + // label + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(5, 10, 5, 0); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.NONE; + c.weightx = 0; + c.weighty = 0; + groupPanel.add(label, c); + // component + c.gridx = 1; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(5, 5, 5, 10); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.weighty = 0; + groupPanel.add(comp, c); + final Dimension dim = new Dimension(134, 24); + groupPanel.setMinimumSize(dim); + groupPanel.setPreferredSize(dim); + return groupPanel; + } + + private void runCodeCoverage(boolean selectedOnly) { + final Connection conn = DatabaseTools.getConnection(currentRun.getConnectionName()); + final UtplsqlDao dao = new UtplsqlDao(conn); + final List pathList = new ArrayList<>(); + final HashSet testPackages = new HashSet<>(); + if (selectedOnly) { + // pathList and unique testPackages based on selected tests + 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 + pathList.addAll(currentRun.getPathList()); + for (Test t : currentRun.getTests().values()) { + testPackages.add(t.getOwnerName() + "." + t.getObjectName()); + } + } + // add dependencies of every test package (one DB call per test package) + final HashSet includeObjects = new HashSet<>(); + for (String testPackage : testPackages) { + String[] obj = testPackage.split("\\."); + includeObjects.addAll(dao.includes(obj[0], obj[1])); + } + // remove test packages + for (String testPackage : testPackages) { + includeObjects.remove(testPackage.toUpperCase()); + } + final CodeCoverageReporter reporter = new CodeCoverageReporter(pathList, + includeObjects.stream().sorted().collect(Collectors.toList()), currentRun.getConnectionName()); + reporter.showParameterWindow(); + } + + private void fixCountersAndUpdate() { + // fix incompleteTests + List incompleteTests = currentRun.getTests().values().stream() + .filter(it -> it.getEndTime() == null && !it.isDisabled()).collect(Collectors.toList()); + if (!incompleteTests.isEmpty()) { + 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(StringTools.elapsedTime(test.getStartTime(), test.getEndTime())); + test.setErrorStack(UtplsqlResources.getString("RUNNER_MISSING_TEST_RESULT_MESSAGE")); + test.getCounter().setError(1); + } + } + // recalculate counters and fix inconsistencies + currentRun.getCounter().setSuccess(0); + currentRun.getCounter().setFailure(0); + currentRun.getCounter().setError(0); + currentRun.getCounter().setDisabled(0); + currentRun.getCounter().setWarning(0); + for (Test test : currentRun.getTests().values()) { + if (test.isDisabled() && test.getCounter().getDisabled() == 0) { + test.getCounter().setDisabled(1); + } + if (test.getFailedExpectations() != null && !test.getFailedExpectations().isEmpty() && test.getCounter().getFailure() == 0) { + test.getCounter().setFailure(1); + } + if (test.getErrorStack() != null && test.getCounter().getError() == 0) { + test.getCounter().setError(1); + } + currentRun.getCounter().setSuccess(currentRun.getCounter().getSuccess() + test.getCounter().getSuccess()); + currentRun.getCounter().setFailure(currentRun.getCounter().getFailure() + test.getCounter().getFailure()); + currentRun.getCounter().setError(currentRun.getCounter().getError() + test.getCounter().getError()); + currentRun.getCounter().setDisabled(currentRun.getCounter().getDisabled() + test.getCounter().getDisabled()); + currentRun.getCounter().setWarning(currentRun.getCounter().getWarning() + test.getCounter().getWarning()); + } + // terminate run + currentRun.setEndTime(StringTools.getSysdate()); + currentRun.setExecutionTime(StringTools.elapsedTime(currentRun.getStartTime(), currentRun.getEndTime())); + currentRun.setCurrentTestNumber(0); + // update run in GUI + update(currentRun.getReporterId()); + } + + private void initializeGUI() { + // Base panel containing all components + basePanel = new JPanel(); + basePanel.setLayout(new GridBagLayout()); + final GridBagConstraints c = new GridBagConstraints(); + + // Toolbar + 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 -> refreshAction()); + toolbar.add(refreshButton); + final ToolbarButton rerunButton = new ToolbarButton(UtplsqlResources.getIcon("RUN_ICON")); + rerunButton.setToolTipText(UtplsqlResources.getString("RUNNER_RERUN_TOOLTIP")); + rerunButton.setBorder(buttonBorder); + rerunButton.addActionListener(event -> { + final UtplsqlRunner runner = new UtplsqlRunner(currentRun.getPathList(), currentRun.getConnectionName()); + runner.runTestAsync(); + }); + toolbar.add(rerunButton); + final ToolbarButton rerunWorksheetButton = new ToolbarButton(UtplsqlResources.getIcon("RUN_WORKSHEET_ICON")); + rerunWorksheetButton.setToolTipText(UtplsqlResources.getString("RUNNER_RERUN_WORKSHEET_TOOLTIP")); + rerunWorksheetButton.setBorder(buttonBorder); + rerunWorksheetButton.addActionListener(event -> { + final UtplsqlWorksheetRunner worksheet = new UtplsqlWorksheetRunner(currentRun.getPathList(), + currentRun.getConnectionName()); + worksheet.runTestAsync(); + }); + toolbar.add(rerunWorksheetButton); + final ToolbarButton debugButton = new ToolbarButton(UtplsqlResources.getIcon("DEBUG_ICON")); + debugButton.setToolTipText(UtplsqlResources.getString("RUNNER_DEBUG_TOOLTIP")); + debugButton.setBorder(buttonBorder); + debugButton.addActionListener(event -> { + final UtplsqlRunner runner = new UtplsqlRunner(currentRun.getPathList(), currentRun.getConnectionName()); + runner.enableDebugging(); + runner.runTestAsync(); + }); + toolbar.add(debugButton); + final ToolbarButton codeCoverageButton = new ToolbarButton(UtplsqlResources.getIcon("CODE_COVERAGE_ICON")); + codeCoverageButton.setToolTipText(UtplsqlResources.getString("RUNNER_CODE_COVERAGE_TOOLTIP")); + codeCoverageButton.setBorder(buttonBorder); + codeCoverageButton.addActionListener(event -> runCodeCoverage(false)); + toolbar.add(codeCoverageButton); + stopButton = new ToolbarButton(UtplsqlResources.getIcon("STOP_ICON")); + stopButton.setToolTipText(UtplsqlResources.getString("RUNNER_STOP_TOOLTIP")); + stopButton.setBorder(buttonBorder); + stopButton.addActionListener(event -> { + if (currentRun.getConsumerConn() != null) { + // Aborts JDBC Connection. Connection might still run in the background. That's expected. + DatabaseTools.abortConnection(currentRun.getConsumerConn()); + 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()); + 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(); + } + }); + stopButton.setEnabled(false); + toolbar.add(stopButton); + toolbar.add(Box.createHorizontalGlue()); + runComboBoxModel = new DefaultComboBoxModel<>(); + runComboBox = new JComboBox<>(runComboBoxModel); + runComboBox.setEditable(false); + final Dimension comboBoxDim = new Dimension(500, 50); + runComboBox.setMaximumSize(comboBoxDim); + runComboBox.addActionListener(event -> comboBoxAction()); + toolbar.add(runComboBox); + final ToolbarButton clearButton = new ToolbarButton(UtplsqlResources.getIcon("CLEAR_ICON")); + clearButton.setToolTipText(UtplsqlResources.getString("RUNNER_CLEAR_BUTTON")); + clearButton.setBorder(buttonBorder); + clearButton.addActionListener(event -> { + final Run run = currentRun; + runs.clear(); + currentRun = null; + setModel(run, true); + update(run.getReporterId()); + }); + toolbar.add(clearButton); + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 2; + c.gridheight = 1; + c.insets = new Insets(0, 0, 0, 0); // top, left, bottom, right + c.anchor = GridBagConstraints.NORTH; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.weighty = 0; + basePanel.add(toolbar, c); + + // Status line + statusLabel = new JLabel(); + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(10, 10, 10, 0); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.weighty = 0; + basePanel.add(statusLabel, c); + JLabel elapsedTimeLabel = new JLabel(); + elapsedTimeLabel.setPreferredSize(new Dimension(60, 0)); + c.gridx = 1; + c.gridy = 1; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(10, 10, 10, 10); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.NONE; + c.weightx = 0; + c.weighty = 0; + basePanel.add(elapsedTimeLabel, c); + elapsedTimeTimer = new Timer(100, event -> { + if (currentRun != null && currentRun.getStart() != null) { + final SmartTime time = new SmartTime(); + time.setSmart(useSmartTimes); + if (currentRun.getExecutionTime() != null) { + time.setSeconds(currentRun.getExecutionTime()); + elapsedTimeTimer.stop(); + if (!currentRun.getTotalNumberOfTests().equals(currentRun.getTotalNumberOfCompletedTests())) { + fixCountersAndUpdate(); + } + } else { + final Double now = (double) System.currentTimeMillis(); + time.setSeconds((now - currentRun.getStart()) / 1000); + } + elapsedTimeLabel.setText(time.toString() + (!useSmartTimes ? " s" : "")); + } else { + elapsedTimeLabel.setText(null); + } + }); + + // Counters + // - Test counter + final JPanel counterPanel = new JPanel(); + counterPanel.setLayout(new WrapLayout(FlowLayout.LEFT, 0, 0)); + final JLabel testCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_TESTS_LABEL") + ":", JLabel.LEADING); + testCounterValueLabel = new JLabel(); + counterPanel.add(makeLabelledCounterComponent(testCounterLabel, testCounterValueLabel)); + // - Failure counter + final JLabel failureCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_FAILURES_LABEL") + ":", + UtplsqlResources.getIcon("FAILURE_ICON"), JLabel.LEADING); + failureCounterValueLabel = new JLabel(); + counterPanel.add(makeLabelledCounterComponent(failureCounterLabel, failureCounterValueLabel)); + // - Error counter + final JLabel errorCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_ERRORS_LABEL") + ":", + UtplsqlResources.getIcon("ERROR_ICON"), JLabel.LEADING); + errorCounterValueLabel = new JLabel(); + counterPanel.add(makeLabelledCounterComponent(errorCounterLabel, errorCounterValueLabel)); + // - Disabled counter + final JLabel disabledCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_DISABLED_LABEL") + ":", + UtplsqlResources.getIcon("DISABLED_ICON"), JLabel.LEADING); + disabledCounterValueLabel = new JLabel(); + counterPanel.add(makeLabelledCounterComponent(disabledCounterLabel, disabledCounterValueLabel)); + // - Warnings counter + final JLabel warningsCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_WARNINGS_LABEL") + ":", + UtplsqlResources.getIcon("WARNING_ICON"), JLabel.LEADING); + warningsCounterValueLabel = new JLabel(); + counterPanel.add(makeLabelledCounterComponent(warningsCounterLabel, warningsCounterValueLabel)); + // - Info counter + final JLabel infoCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_INFO_LABEL") + ":", + UtplsqlResources.getIcon("INFO_ICON"), JLabel.LEADING); + infoCounterValueLabel = new JLabel(); + counterPanel.add(makeLabelledCounterComponent(infoCounterLabel, infoCounterValueLabel)); + // - add everything to basePanel + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 2; + c.gridheight = 1; + c.insets = new Insets(5, 0, 5, 0); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.weighty = 0; + basePanel.add(counterPanel, c); + + // Context menu for counters panel + final JPopupMenu countersPopupMenu = new JPopupMenu(); + showDisabledCounterCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_DISABLED_COUNTER_LABEL").replace("?", ""), true); + showDisabledCounterCheckBoxMenuItem.addActionListener(event -> { + applyShowDisabledCounter(); + fixCheckBoxMenuItem(showDisabledCounterCheckBoxMenuItem); + }); + countersPopupMenu.add(showDisabledCounterCheckBoxMenuItem); + showWarningsCounterCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_WARNINGS_COUNTER_LABEL").replace("?", ""), true); + showWarningsCounterCheckBoxMenuItem.addActionListener(event -> { + applyShowWarningsCounter(); + fixCheckBoxMenuItem(showWarningsCounterCheckBoxMenuItem); + }); + countersPopupMenu.add(showWarningsCounterCheckBoxMenuItem); + showInfoCounterCheckBoxMenuItem = new JCheckBoxMenuItem( UtplsqlResources.getString("PREF_SHOW_INFO_COUNTER_LABEL").replace("?", ""), true); + showInfoCounterCheckBoxMenuItem.addActionListener(event -> { + applyShowInfoCounter(); + fixCheckBoxMenuItem(showInfoCounterCheckBoxMenuItem); + }); + countersPopupMenu.add(showInfoCounterCheckBoxMenuItem); + basePanel.setComponentPopupMenu(countersPopupMenu); + + // Progress bar + progressBar = new JProgressBar(); + final Dimension progressBarDim = new Dimension(10, 20); + progressBar.setPreferredSize(progressBarDim); + progressBar.setMinimumSize(progressBarDim); + progressBar.setStringPainted(false); + progressBar.setForeground(GREEN); + progressBar.setUI(new BasicProgressBarUI()); + c.gridx = 0; + c.gridy = 3; + c.gridwidth = 2; + c.gridheight = 1; + c.insets = new Insets(10, 10, 10, 10); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.weighty = 0; + basePanel.add(progressBar, c); + + // Test overview (table variant) + testOverviewTableModel = new TestOverviewTableModel(); + testOverviewTable = new JTable(testOverviewTableModel); + testOverviewTable.getTableHeader().setReorderingAllowed(false); + testOverviewTable.setAutoCreateRowSorter(true); + testOverviewTable.setRowHeight(OVERVIEW_TABLE_ROW_HEIGHT); + testOverviewTable.getTableHeader().setPreferredSize( + new Dimension(testOverviewTable.getTableHeader().getPreferredSize().width, OVERVIEW_TABLE_ROW_HEIGHT)); + testOverviewTable.getSelectionModel().addListSelectionListener(event -> { + final int rowIndex = testOverviewTable.getSelectedRow(); + if (rowIndex != -1) { + final int row = testOverviewTable.convertRowIndexToModel(rowIndex); + final Test test = testOverviewTableModel.getTest(row); + testOwnerTextField.setText(test.getOwnerName()); + testPackageTextField.setText(test.getObjectName()); + testProcedureTextField.setText(test.getProcedureName()); + 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()); + failuresTableModel.fireTableDataChanged(); + testFailureMessageTextPane.setText(null); + if (test.getFailedExpectations() != null && !test.getFailedExpectations().isEmpty()) { + failuresTable.setRowSelectionInterval(0, 0); + } + 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() { + @Override + public void mouseClicked(final MouseEvent e) { + if (e.getClickCount() == 2) { + if (failuresTable.getSelectedRowCount() == 1) { + openSelectedFailure(); + } else { + openSelectedTest(); + } + } + } + }); + RepaintManager.currentManager(testOverviewTable).setDoubleBufferingEnabled(true); + final TestTableHeaderRenderer testTableHeaderRenderer = new TestTableHeaderRenderer(); + final TableColumn overviewTableStatus = testOverviewTable.getColumnModel().getColumn(0); + overviewTableStatus.setMinWidth(INDICATOR_WIDTH); + overviewTableStatus.setPreferredWidth(INDICATOR_WIDTH); + overviewTableStatus.setMaxWidth(INDICATOR_WIDTH); + overviewTableStatus.setHeaderRenderer(testTableHeaderRenderer); + 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(3); + overviewTableInfo.setMinWidth(INDICATOR_WIDTH); + overviewTableInfo.setPreferredWidth(INDICATOR_WIDTH); + overviewTableInfo.setMaxWidth(INDICATOR_WIDTH); + overviewTableInfo.setHeaderRenderer(testTableHeaderRenderer); + final TableColumn overviewTableTime = testOverviewTable.getColumnModel().getColumn(4); + overviewTableTime.setPreferredWidth(60); + overviewTableTime.setMaxWidth(100); + overviewTableTime.setHeaderRenderer(testTableHeaderRenderer); + final DefaultTableCellRenderer timeColumnRenderer = new DefaultTableCellRenderer() { + private static final long serialVersionUID = 7720067427609773267L; + { + setHorizontalAlignment(JLabel.RIGHT); + } + + @Override + public Component getTableCellRendererComponent(final JTable table, final Object value, + final boolean isSelected, final boolean hasFocus, final int row, final int col) { + 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 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(); + testOverviewRunMenuItem = new JMenuItem(UtplsqlResources.getString("RUNNER_RUN_MENUITEM"), UtplsqlResources.getIcon("RUN_ICON")); + testOverviewRunMenuItem.addActionListener(event -> { + final UtplsqlRunner runner = new UtplsqlRunner(getPathListFromSelectedTests(), currentRun.getConnectionName()); + runner.runTestAsync(); + }); + testOverviewPopupMenu.add(testOverviewRunMenuItem); + testOverviewRunWorksheetMenuItem = new JMenuItem(UtplsqlResources.getString("RUNNER_RUN_WORKSHEET_MENUITEM"), UtplsqlResources.getIcon("RUN_WORKSHEET_ICON")); + testOverviewRunWorksheetMenuItem.addActionListener(event -> { + final UtplsqlWorksheetRunner worksheet = new UtplsqlWorksheetRunner(this.getPathListFromSelectedTests(), + currentRun.getConnectionName()); + worksheet.runTestAsync(); + }); + testOverviewPopupMenu.add(testOverviewRunWorksheetMenuItem); + testOverviewDebugMenuItem = new JMenuItem(UtplsqlResources.getString("MENU_DEBUG_TEST_LABEL"), UtplsqlResources.getIcon("DEBUG_ICON")); + testOverviewDebugMenuItem.addActionListener(event -> { + final UtplsqlRunner runner = new UtplsqlRunner(getPathListFromSelectedTests(), currentRun.getConnectionName()); + runner.enableDebugging(); + runner.runTestAsync(); + }); + testOverviewPopupMenu.add(testOverviewDebugMenuItem); + testOverviewCodeCoverageMenuItem = new JMenuItem(UtplsqlResources.getString("MENU_CODE_COVERAGE_LABEL"), UtplsqlResources.getIcon("CODE_COVERAGE_ICON")); + testOverviewCodeCoverageMenuItem.addActionListener(event -> runCodeCoverage(true)); + testOverviewPopupMenu.add(testOverviewCodeCoverageMenuItem); + testOverviewPopupMenu.add(new JSeparator()); + showSuccessfulTestsCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_SUCCESSFUL_TESTS_LABEL").replace("?", ""), true); + showSuccessfulTestsCheckBoxMenuItem.addActionListener(event -> { + applyFilter(showSuccessfulTestsCheckBoxMenuItem.isSelected(), + showDisabledTestsCheckBoxMenuItem.isSelected()); + fixCheckBoxMenuItem(showSuccessfulTestsCheckBoxMenuItem); + }); + testOverviewPopupMenu.add(showSuccessfulTestsCheckBoxMenuItem); + showDisabledTestsCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_DISABLED_TESTS_LABEL").replace("?", ""), true); + showDisabledTestsCheckBoxMenuItem.addActionListener(event -> { + applyFilter(showSuccessfulTestsCheckBoxMenuItem.isSelected(), + showDisabledTestsCheckBoxMenuItem.isSelected()); + fixCheckBoxMenuItem(showDisabledTestsCheckBoxMenuItem); + }); + testOverviewPopupMenu.add(showDisabledTestsCheckBoxMenuItem); + testOverviewPopupMenu.add(new JSeparator()); + showTestDescriptionCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_TEST_DESCRIPTION_LABEL").replace("?", ""), true); + 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); + showWarningIndicatorCheckBoxMenuItem.addActionListener(event -> { + applyShowWarningIndicator(showWarningIndicatorCheckBoxMenuItem.isSelected()); + fixCheckBoxMenuItem(showWarningIndicatorCheckBoxMenuItem); + }); + testOverviewPopupMenu.add(showWarningIndicatorCheckBoxMenuItem); + showInfoIndicatorCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_INFO_INDICATOR_LABEL").replace("?", ""), true); + showInfoIndicatorCheckBoxMenuItem.addActionListener(event -> { + applyShowInfoIndicator(showInfoIndicatorCheckBoxMenuItem.isSelected()); + fixCheckBoxMenuItem(showInfoIndicatorCheckBoxMenuItem); + }); + testOverviewPopupMenu.add(showInfoIndicatorCheckBoxMenuItem); + syncDetailTabCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SYNC_DETAIL_TAB_LABEL").replace("?", ""), true); + syncDetailTabCheckBoxMenuItem.addActionListener(event -> { + 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(); + testInfoPanel.setLayout(new GridBagLayout()); + // - Owner + final JLabel testOwnerLabel = new JLabel(UtplsqlResources.getString("RUNNER_OWNER_LABEL")); + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(10, 10, 0, 0); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.NONE; + c.weightx = 0; + c.weighty = 0; + testInfoPanel.add(testOwnerLabel, c); + testOwnerTextField = new RunnerTextField(); + testOwnerTextField.setEditable(false); + c.gridx = 1; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(10, 5, 0, 10); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.weighty = 0; + testInfoPanel.add(testOwnerTextField, c); + // - Package + final JLabel testPackageLabel = new JLabel(UtplsqlResources.getString("RUNNER_PACKAGE_LABEL")); + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(5, 10, 0, 0); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.NONE; + c.weightx = 0; + c.weighty = 0; + testInfoPanel.add(testPackageLabel, c); + testPackageTextField = new RunnerTextField(); + testPackageTextField.setEditable(false); + c.gridx = 1; + c.gridy = 1; + 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(testPackageTextField, c); + // - Procedure + final JLabel testProcedureLabel = new JLabel(UtplsqlResources.getString("RUNNER_PROCEDURE_LABEL")); + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(5, 10, 0, 0); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.NONE; + c.weightx = 0; + c.weighty = 0; + testInfoPanel.add(testProcedureLabel, c); + testProcedureTextField = new RunnerTextField(); + testProcedureTextField.setEditable(false); + c.gridx = 1; + c.gridy = 2; + 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(testProcedureTextField, c); + // - Description + final JLabel testDescriptionLabel = new JLabel(UtplsqlResources.getString("RUNNER_DESCRIPTION_LABEL")); + testDescriptionLabel.setBorder(BorderFactory.createEmptyBorder(isMacLookAndFeel() ? 5 : 3, 0, 0, 0)); + c.gridx = 0; + c.gridy = 3; + 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(testDescriptionLabel, c); + testDescriptionTextArea = new RunnerTextArea(); + testDescriptionTextArea.setEditable(false); + testDescriptionTextArea.setEnabled(true); + testDescriptionTextArea.setLineWrap(true); + testDescriptionTextArea.setWrapStyleWord(true); + c.gridx = 1; + c.gridy = 3; + 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(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 = 5; + 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(testIdLabel, c); + testIdTextArea = new RunnerTextArea(); + testIdTextArea.setEditable(false); + testIdTextArea.setEnabled(true); + testIdTextArea.setLineWrap(true); + testIdTextArea.setWrapStyleWord(false); + c.gridx = 1; + c.gridy = 5; + 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(testIdTextArea, c); + // - Start + final JLabel testStartLabel = new JLabel(UtplsqlResources.getString("RUNNER_START_LABEL")); + c.gridx = 0; + c.gridy = 6; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(5, 10, 10, 0); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.NONE; + c.weightx = 0; + c.weighty = 0; + testInfoPanel.add(testStartLabel, c); + testStartTextField = new RunnerTextField(); + testStartTextField.setEditable(false); + c.gridx = 1; + c.gridy = 6; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(5, 5, 10, 10); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.weighty = 0; + testInfoPanel.add(testStartTextField, c); + // - Vertical filler + c.gridx = 0; + c.gridy = 7; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(0, 0, 0, 0); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 0; + c.weighty = 1; + testInfoPanel.add(Box.createVerticalGlue(), c); + final JScrollPane testPropertiesScrollPane = new JScrollPane(testInfoPanel); + + // Failures tabbed pane (failed expectations) + // - failures table (number and description) + failuresTableModel = new FailuresTableModel(); + failuresTable = new JTable(failuresTableModel); + failuresTable.getTableHeader().setReorderingAllowed(false); + failuresTable.getSelectionModel().addListSelectionListener(event -> { + final int rowIndex = failuresTable.getSelectedRow(); + if (rowIndex != -1) { + final int row = failuresTable.convertRowIndexToModel(rowIndex); + final Expectation expectation = failuresTableModel.getExpectation(row); + final String html = getHtml(expectation.getFailureText()); + testFailureMessageTextPane.setText(html); + } + }); + failuresTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(final MouseEvent e) { + if (e.getClickCount() == 2 && failuresTable.getSelectedRowCount() == 1) { + openSelectedFailure(); + } + } + }); + final FailuresTableHeaderRenderer failuresTableHeaderRenderer = new FailuresTableHeaderRenderer(); + final TableColumn failuresTableNumber = failuresTable.getColumnModel().getColumn(0); + failuresTableNumber.setHeaderRenderer(failuresTableHeaderRenderer); + failuresTableNumber.setPreferredWidth(30); + failuresTableNumber.setMaxWidth(30); + final TableColumn failuresDescription = failuresTable.getColumnModel().getColumn(1); + failuresDescription.setHeaderRenderer(failuresTableHeaderRenderer); + final JScrollPane failuresTableScrollPane = new JScrollPane(failuresTable); + // - failures details + testFailureMessageTextPane = new RunnerTextPane(); + testFailureMessageTextPane.setEditable(false); + testFailureMessageTextPane.setEnabled(true); + testFailureMessageTextPane.setContentType("text/html"); + testFailureMessageTextPane.setMinimumSize(TEXTPANE_DIM); + testFailureMessageTextPane.setPreferredSize(TEXTPANE_DIM); + testFailureMessageTextPane.addHyperlinkListener(event -> { + if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + final String link = event.getDescription(); + openLink(link); + } + }); + final JScrollPane testFailureMessageScrollPane = new JScrollPane(testFailureMessageTextPane); + c.gridx = 1; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(10, 5, 0, 10); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 6; + + // - split pane + final JSplitPane failuresSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, failuresTableScrollPane, + testFailureMessageScrollPane); + failuresSplitPane.setResizeWeight(0.2); + + // Errors tabbed pane (Error Stack) + final JPanel testErrorStackPanel = new JPanel(); + testErrorStackPanel.setLayout(new GridBagLayout()); + testErrorStackTextPane = new RunnerTextPane(); + testErrorStackTextPane.setEditable(false); + testErrorStackTextPane.setEnabled(true); + testErrorStackTextPane.setContentType("text/html"); + testErrorStackTextPane.setMinimumSize(TEXTPANE_DIM); + testErrorStackTextPane.setPreferredSize(TEXTPANE_DIM); + testErrorStackTextPane.addHyperlinkListener(event -> { + if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + final String link = event.getDescription(); + openLink(link); + } + }); + final JScrollPane testErrorStackScrollPane = new JScrollPane(testErrorStackTextPane); + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(0, 0, 0, 0); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 1; + testErrorStackPanel.add(testErrorStackScrollPane, c); + + // Warnings tabbed pane + final JPanel testWarningsPanel = new JPanel(); + testWarningsPanel.setLayout(new GridBagLayout()); + testWarningsTextPane = new RunnerTextPane(); + testWarningsTextPane.setEditable(false); + testWarningsTextPane.setEnabled(true); + testWarningsTextPane.setContentType("text/html"); + testWarningsTextPane.setMinimumSize(TEXTPANE_DIM); + testWarningsTextPane.setPreferredSize(TEXTPANE_DIM); + 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; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(0, 0, 0, 0); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 1; + testWarningsPanel.add(testWarningsScrollPane, c); + + // Info tabbed pane (Server Output) + final JPanel testServerOutputPanel = new JPanel(); + testServerOutputPanel.setLayout(new GridBagLayout()); + testServerOutputTextPane = new RunnerTextPane(); + testServerOutputTextPane.setEditable(false); + testServerOutputTextPane.setEnabled(true); + testServerOutputTextPane.setContentType("text/html"); + testServerOutputTextPane.setMinimumSize(TEXTPANE_DIM); + testServerOutputTextPane.setPreferredSize(TEXTPANE_DIM); + 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; + c.gridwidth = 1; + c.gridheight = 1; + c.insets = new Insets(0, 0, 0, 0); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 1; + testServerOutputPanel.add(testServerOutputScrollPane, c); + + // split pane with all tabs + testDetailTabbedPane = new JTabbedPane(); + testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_TEST_TAB_LABEL"), testPropertiesScrollPane); + testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_FAILURES_TAB_LABEL"), failuresSplitPane); + testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_ERRORS_TAB_LABEL"), testErrorStackPanel); + testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_WARNINGS_TAB_LABEL"), testWarningsPanel); + testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_INFO_TAB_LABEL"), testServerOutputPanel); + final JSplitPane horizontalSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, testOverviewScrollPane, + testDetailTabbedPane); + horizontalSplitPane.setResizeWeight(0.5); + c.gridx = 0; + c.gridy = 4; + c.gridwidth = 2; + c.gridheight = 1; + c.insets = new Insets(10, 10, 10, 10); // top, left, bottom, right + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 1; + basePanel.add(horizontalSplitPane, c); + if (isMacLookAndFeel()) { + final CompoundBorder border = BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(3, 3, 3, 3), + 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/RunnerPanel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.xtend deleted file mode 100644 index 37a4f699..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerPanel.xtend +++ /dev/null @@ -1,1330 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.ui.runner - -import java.awt.Color -import java.awt.Component -import java.awt.Dimension -import java.awt.FlowLayout -import java.awt.GridBagConstraints -import java.awt.GridBagLayout -import java.awt.Insets -import java.awt.event.ActionEvent -import java.awt.event.ActionListener -import java.awt.event.MouseEvent -import java.awt.event.MouseListener -import java.util.ArrayList -import java.util.regex.Pattern -import javax.swing.BorderFactory -import javax.swing.Box -import javax.swing.DefaultComboBoxModel -import javax.swing.JCheckBoxMenuItem -import javax.swing.JComboBox -import javax.swing.JComponent -import javax.swing.JLabel -import javax.swing.JMenuItem -import javax.swing.JPanel -import javax.swing.JPopupMenu -import javax.swing.JProgressBar -import javax.swing.JScrollPane -import javax.swing.JSeparator -import javax.swing.JSplitPane -import javax.swing.JTabbedPane -import javax.swing.JTable -import javax.swing.RepaintManager -import javax.swing.RowFilter -import javax.swing.SwingConstants -import javax.swing.Timer -import javax.swing.UIManager -import javax.swing.border.EmptyBorder -import javax.swing.event.HyperlinkEvent -import javax.swing.event.HyperlinkListener -import javax.swing.event.ListSelectionEvent -import javax.swing.event.ListSelectionListener -import javax.swing.plaf.basic.BasicProgressBarUI -import javax.swing.table.DefaultTableCellRenderer -import javax.swing.table.TableRowSorter -import oracle.dbtools.raptor.controls.grid.DefaultDrillLink -import oracle.dbtools.raptor.utils.Connections -import oracle.ide.config.Preferences -import oracle.javatools.ui.table.ToolbarButton -import org.springframework.web.util.HtmlUtils -import org.utplsql.sqldev.dal.UtplsqlDao -import org.utplsql.sqldev.model.LimitedLinkedHashMap -import org.utplsql.sqldev.model.preference.PreferenceModel -import org.utplsql.sqldev.model.runner.Run -import org.utplsql.sqldev.model.runner.Test -import org.utplsql.sqldev.parser.UtplsqlParser -import org.utplsql.sqldev.resources.UtplsqlResources -import org.utplsql.sqldev.runner.UtplsqlRunner -import org.utplsql.sqldev.runner.UtplsqlWorksheetRunner - -class RunnerPanel implements ActionListener, MouseListener, HyperlinkListener { - static val GREEN = new Color(0, 153, 0) - static val RED = new Color(153, 0, 0) - static val INDICATOR_WIDTH = 20 - static val OVERVIEW_TABLE_ROW_HEIGHT = 20 - static val TEXTPANE_DIM = new Dimension(100, 100) - static var boolean useSmartTimes - LimitedLinkedHashMap runs = new LimitedLinkedHashMap(10) - Run currentRun - JPanel basePanel - ToolbarButton refreshButton - ToolbarButton rerunButton - ToolbarButton rerunWorksheetButton - DefaultComboBoxModel> runComboBoxModel - ToolbarButton clearButton - JComboBox> runComboBox - JLabel statusLabel - JLabel elapsedTimeLabel - Timer elapsedTimeTimer - JLabel testCounterValueLabel - JLabel errorCounterValueLabel - JLabel failureCounterValueLabel - JLabel disabledCounterValueLabel - JLabel warningsCounterValueLabel - JLabel infoCounterValueLabel - JCheckBoxMenuItem showDisabledCounterCheckBoxMenuItem - JCheckBoxMenuItem showWarningsCounterCheckBoxMenuItem - JCheckBoxMenuItem showInfoCounterCheckBoxMenuItem - JProgressBar progressBar; - TestOverviewTableModel testOverviewTableModel - JTable testOverviewTable - JMenuItem testOverviewRunMenuItem - JMenuItem testOverviewRunWorksheetMenuItem - JCheckBoxMenuItem showTestDescriptionCheckBoxMenuItem - JCheckBoxMenuItem showWarningIndicatorCheckBoxMenuItem - JCheckBoxMenuItem showInfoIndicatorCheckBoxMenuItem - JCheckBoxMenuItem showSuccessfulTestsCheckBoxMenuItem - JCheckBoxMenuItem showDisabledTestsCheckBoxMenuItem - JCheckBoxMenuItem syncDetailTabCheckBoxMenuItem - RunnerTextField testOwnerTextField - RunnerTextField testPackageTextField - RunnerTextField testProcedureTextField - RunnerTextArea testDescriptionTextArea - RunnerTextArea testIdTextArea - RunnerTextField testStartTextField - FailuresTableModel failuresTableModel - JTable failuresTable - RunnerTextPane testFailureMessageTextPane - RunnerTextPane testErrorStackTextPane - RunnerTextPane testWarningsTextPane - RunnerTextPane testServerOutputTextPane - JTabbedPane testDetailTabbedPane - - def Component getGUI() { - if (basePanel === null) { - initializeGUI() - } - if (!basePanel.showing) { - applyPreferences - } - return basePanel - } - - private def resetDerived() { - testOverviewTable.rowSorter.sortKeys = null - testOverviewRunMenuItem.enabled = false - testOverviewRunWorksheetMenuItem.enabled = false - testIdTextArea.text = null - testOwnerTextField.text = null - testPackageTextField.text = null - testProcedureTextField.text = null - testDescriptionTextArea.text = null - testStartTextField.text = null - failuresTableModel.model = null - failuresTableModel.fireTableDataChanged - testFailureMessageTextPane.text = null - testErrorStackTextPane.text = null - testWarningsTextPane.text = null - testServerOutputTextPane.text = null - } - - private def refreshRunsComboBox() { - if (runs.size > 0) { - runComboBox.removeActionListener(this) - runComboBoxModel.removeAllElements - for (var i = runs.size - 1 ; i >= 0; i--) { - val entry = runs.entrySet.get(i) - val item = new ComboBoxItem(entry.key, entry.value.name) - runComboBoxModel.addElement(item) - } - runComboBox.selectedIndex = 0 - runComboBox.addActionListener(this) - } - } - - private def applyShowNumberOfRunsInHistory(int maxRuns) { - if (maxRuns != runs.maxEntries) { - val newRuns = new LimitedLinkedHashMap(maxRuns) - for (entry : runs.entrySet) { - newRuns.put(entry.key, entry.value) - } - runs = newRuns - } - } - - private def applyShowDisabledCounter(boolean show) { - disabledCounterValueLabel.parent.visible = showDisabledCounterCheckBoxMenuItem.selected - } - - private def applyShowWarningsCounter(boolean show) { - warningsCounterValueLabel.parent.visible = showWarningsCounterCheckBoxMenuItem.selected - } - - private def applyShowInfoCounter(boolean show) { - infoCounterValueLabel.parent.visible = showInfoCounterCheckBoxMenuItem.selected - } - - private def applyShowTestDescription(boolean show) { - testOverviewTableModel.updateModel(showTestDescriptionCheckBoxMenuItem.selected) - val idColumn = testOverviewTable.columnModel.getColumn(3) - idColumn.headerValue = testOverviewTableModel.testIdColumnName - testOverviewTable.tableHeader.repaint - } - - private def applyShowWarningIndicator(boolean show) { - val col = testOverviewTable.columnModel.getColumn(1) - if (show) { - col.width = INDICATOR_WIDTH - col.minWidth = INDICATOR_WIDTH - col.maxWidth = INDICATOR_WIDTH - col.preferredWidth = INDICATOR_WIDTH - } else { - col.width = 0 - col.minWidth = 0 - col.maxWidth = 0 - col.preferredWidth = 0 - } - } - - private def applyShowInfoIndicator(boolean show) { - val col = testOverviewTable.columnModel.getColumn(2) - if (show) { - col.width = INDICATOR_WIDTH - col.minWidth = INDICATOR_WIDTH - col.maxWidth = INDICATOR_WIDTH - col.preferredWidth = INDICATOR_WIDTH - } else { - col.width = 0 - col.minWidth = 0 - col.maxWidth = 0 - col.preferredWidth = 0 - } - } - - private def applyFilter(boolean showSuccessfulTests, boolean showDisabledTests) { - val sorter = testOverviewTable.rowSorter as TableRowSorter - val filter = new RowFilter() { - override include(Entry entry) { - val test = entry.model.getTest(entry.identifier) - val counter = test.counter - if (counter !== null) { - if (counter.success > 0) { - if (!showSuccessfulTests) { - return false - } - } - if (counter.disabled > 0) { - if (!showDisabledTests) { - return false - } - } - } - return true - } - } - sorter.rowFilter = filter - } - - private def openTest(Test test) { - val dao = new UtplsqlDao(Connections.instance.getConnection(currentRun.connectionName)) - val source = dao.getSource(test.ownerName, "PACKAGE", test.objectName.toUpperCase).trim - val parser = new UtplsqlParser(source) - val line = parser.getLineOf(test.procedureName) - openEditor(test.ownerName, "PACKAGE", test.objectName.toUpperCase, line, 1) - } - - private def openSelectedTest() { - val rowIndex = testOverviewTable.selectedRow - if (rowIndex != -1) { - val row = testOverviewTable.convertRowIndexToModel(rowIndex) - val test = testOverviewTableModel.getTest(row) - openTest(test) - } - } - - private def openSelectedFailure() { - val rowIndex = failuresTable.selectedRow - if (rowIndex != -1) { - val row = failuresTable.convertRowIndexToModel(rowIndex) - val expectation = failuresTableModel.getExpectation(row) - val test = testOverviewTableModel.getTest(testOverviewTable.convertRowIndexToModel(testOverviewTable.selectedRow)) - val callerLine = expectation.callerLine - if (callerLine !== null) { - openEditor(test.ownerName, "PACKAGE BODY", test.objectName.toUpperCase, expectation.callerLine, 1) - } else { - openTest(test) - } - } - } - - private def getHtml(String text) { - val html = ''' - - - - - - «getLinkedAndFormattedText(text)» - - - ''' - return html - } - - private def openLink(String link) { - val parts = link.split("/") - val type = parts.get(0) - val ownerName = parts.get(1) - val objectName = parts.get(2) - var line = Integer.parseInt(parts.get(3)) - val dao = new UtplsqlDao(Connections.instance.getConnection(currentRun.connectionName)) - val objectType = if (type=="UNKNOWN") {dao.getObjectType(ownerName, objectName)} else {type} - if (parts.size == 5) { - val procedureName = parts.get(4) - val source = dao.getSource(ownerName, objectType, objectName).trim - val parser = new UtplsqlParser(source) - line = parser.getLineOf(procedureName) - } - openEditor(ownerName, objectType, objectName.toUpperCase, line, 1) - } - - private def openEditor(String owner, String type, String name, int line, int col) { - var drillLink = new DefaultDrillLink - drillLink.connName = currentRun.connectionName - // argument order is based on SQLDEV:LINK that can be used in SQL query result tables (editors, reports) - drillLink.args = #[owner, type, name, String.valueOf(line), String.valueOf(col), "OpenEditor", "oracle.dbtools.raptor.controls.grid.DefaultDrillLink"] - drillLink.performDrill - } - - private def syncDetailTab() { - if (syncDetailTabCheckBoxMenuItem.selected) { - val rowIndex = testOverviewTable.selectedRow - if (rowIndex != -1) { - val row = testOverviewTable.convertRowIndexToModel(rowIndex) - val test = testOverviewTableModel.getTest(row) - var int tabIndex - if (test.counter?.failure !== null && test.counter.failure > 0) { - tabIndex = 1 - } else if (test.counter?.error !== null && test.counter.error > 0) { - tabIndex = 2 - } else if (test.counter?.warning !== null && test.counter.warning > 0) { - tabIndex = 3 - } else if (test.serverOutput !== null && test.serverOutput.length > 0) { - tabIndex = 4 - } else { - tabIndex = 0 - } - testDetailTabbedPane.selectedIndex = tabIndex - } - } - } - - private def getPreferenceModel() { - var PreferenceModel preferences - try { - preferences = PreferenceModel.getInstance(Preferences.preferences) - } catch (NoClassDefFoundError e) { - preferences = PreferenceModel.getInstance(null) - } - return preferences - } - - private def applyPreferences() { - val PreferenceModel preferences = preferenceModel - applyShowNumberOfRunsInHistory(preferences.numberOfRunsInHistory) - showDisabledCounterCheckBoxMenuItem.selected = preferences.showDisabledCounter - applyShowDisabledCounter(showDisabledCounterCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showDisabledCounterCheckBoxMenuItem) - showWarningsCounterCheckBoxMenuItem.selected = preferences.showWarningsCounter - applyShowWarningsCounter(showWarningsCounterCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showWarningsCounterCheckBoxMenuItem) - showInfoCounterCheckBoxMenuItem.selected = preferences.showInfoCounter - applyShowInfoCounter(showInfoCounterCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showInfoCounterCheckBoxMenuItem) - showTestDescriptionCheckBoxMenuItem.selected = preferences.showTestDescription - applyShowTestDescription(showTestDescriptionCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showTestDescriptionCheckBoxMenuItem) - showWarningIndicatorCheckBoxMenuItem.selected = preferences.showWarningIndicator - applyShowWarningIndicator(showWarningIndicatorCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showWarningIndicatorCheckBoxMenuItem) - showInfoIndicatorCheckBoxMenuItem.selected = preferences.showInfoIndicator - applyShowInfoIndicator(showInfoIndicatorCheckBoxMenuItem.selected) - showSuccessfulTestsCheckBoxMenuItem.selected = preferences.showSuccessfulTests - fixCheckBoxMenuItem(showSuccessfulTestsCheckBoxMenuItem) - showDisabledTestsCheckBoxMenuItem.selected = preferences.showDisabledTests - fixCheckBoxMenuItem(showDisabledTestsCheckBoxMenuItem) - applyFilter(showSuccessfulTestsCheckBoxMenuItem.selected, showDisabledTestsCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showInfoIndicatorCheckBoxMenuItem) - syncDetailTabCheckBoxMenuItem.selected = preferences.syncDetailTab - fixCheckBoxMenuItem(syncDetailTabCheckBoxMenuItem) - useSmartTimes = preferences.useSmartTimes - } - - def setModel(Run run) { - runs.put(run.reporterId, run) - refreshRunsComboBox - setCurrentRun(run) - } - - private def setCurrentRun(Run run) { - if (run !== currentRun) { - currentRun = run - testOverviewTableModel.setModel(run.tests, showTestDescriptionCheckBoxMenuItem.selected, useSmartTimes) - val header = testOverviewTableModel.timeColumnName - val timeColumn = testOverviewTable.columnModel.getColumn(4) - if (timeColumn.headerValue != header) { - timeColumn.headerValue = header - testOverviewTable.tableHeader.repaint - } - resetDerived - val item = new ComboBoxItem(currentRun.reporterId, currentRun.name) - runComboBox.selectedItem = item - elapsedTimeTimer.start - } - } - - def synchronized update(String reporterId) { - setCurrentRun(runs.get(reporterId)) - val row = currentRun.currentTestNumber - 1 - val header = testOverviewTableModel.testIdColumnName - val idColumn = testOverviewTable.columnModel.getColumn(3) - if (idColumn.headerValue != header) { - idColumn.headerValue = header - testOverviewTable.tableHeader.repaint - } - if (row < 0) { - testOverviewTableModel.fireTableDataChanged - } else { - if (testOverviewTableModel.rowCount > row) { - val positionOfCurrentTest = testOverviewTable.getCellRect(testOverviewTable.convertRowIndexToView(row), 0, true); - testOverviewTable.scrollRectToVisible = positionOfCurrentTest - testOverviewTableModel.fireTableRowsUpdated(row, row) - Thread.sleep(5) // reduce flickering - if (!showSuccessfulTestsCheckBoxMenuItem.selected || !showDisabledTestsCheckBoxMenuItem.selected) { - applyFilter(showSuccessfulTestsCheckBoxMenuItem.selected, showDisabledTestsCheckBoxMenuItem.selected) - } - testOverviewTable.scrollRectToVisible = positionOfCurrentTest - } - } - statusLabel.text = currentRun.status - testCounterValueLabel.text = '''«currentRun.totalNumberOfCompletedTests»«IF currentRun.totalNumberOfTests >= 0»/«currentRun.totalNumberOfTests»«ENDIF»''' - errorCounterValueLabel.text = '''«currentRun.counter.error»''' - failureCounterValueLabel.text = '''«currentRun.counter.failure»''' - disabledCounterValueLabel.text = '''«currentRun.counter.disabled»''' - warningsCounterValueLabel.text = '''«currentRun.counter.warning»''' - infoCounterValueLabel.text = '''«currentRun.infoCount»''' - if (currentRun.totalNumberOfTests == 0) { - progressBar.value = 100 - } else { - progressBar.value = Math.round(100 * currentRun.totalNumberOfCompletedTests / currentRun.totalNumberOfTests) - } - if (currentRun.counter.error > 0 || currentRun.counter.failure > 0) { - progressBar.foreground = RED - } else { - progressBar.foreground = GREEN - } - } - - private def getPathListFromSelectedTests() { - val pathList = new ArrayList - for (rowIndex : testOverviewTable.selectedRows) { - val row = testOverviewTable.convertRowIndexToModel(rowIndex) - val test = testOverviewTableModel.getTest(row) - val path = '''«test.ownerName».«test.objectName».«test.procedureName»''' - pathList.add(path) - } - return pathList - } - - private def isWindowsLookAndFeel() { - val laf = UIManager.lookAndFeel?.name - if (laf == "Windows") { - return true - } else { - return false - } - } - - private def isMacLookAndFeel() { - val laf = UIManager.lookAndFeel?.name - if (laf == "Mac OS X") { - return true - } else { - return false - } - } - - private def void fixCheckBoxMenuItem(JCheckBoxMenuItem item) { - if (windowsLookAndFeel) { - if (item.selected) { - item.icon = UtplsqlResources.getIcon("CHECKMARK_ICON") - } else { - item.icon = null - } - } - } - - override actionPerformed(ActionEvent e) { - if (e.source == refreshButton) { - resetDerived - testDetailTabbedPane.selectedIndex = 0 - testOverviewTableModel.fireTableDataChanged - } else if (e.source == rerunButton) { - val runner = new UtplsqlRunner(currentRun.pathList, currentRun.connectionName) - runner.runTestAsync - } else if (e.source == rerunWorksheetButton) { - val worksheet = new UtplsqlWorksheetRunner(currentRun.pathList, currentRun.connectionName) - worksheet.runTestAsync - } else if (e.source == runComboBox) { - if (currentRun !== null) { - val comboBoxItem = runComboBox.selectedItem as ComboBoxItem - if (currentRun.reporterId != comboBoxItem.key) { - update(comboBoxItem.key) - testDetailTabbedPane.selectedIndex = 0 - } - } - } else if (e.source == clearButton) { - val run = currentRun - runs.clear - currentRun = null - setModel(run) - update(run.reporterId) - } else if (e.source == testOverviewRunMenuItem) { - val runner = new UtplsqlRunner(pathListFromSelectedTests, currentRun.connectionName) - runner.runTestAsync - } else if (e.source == testOverviewRunWorksheetMenuItem) { - val worksheet = new UtplsqlWorksheetRunner(pathListFromSelectedTests, currentRun.connectionName) - worksheet.runTestAsync - } else if (e.source == showDisabledCounterCheckBoxMenuItem) { - applyShowDisabledCounter(showDisabledCounterCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showDisabledCounterCheckBoxMenuItem) - } else if (e.source == showWarningsCounterCheckBoxMenuItem) { - applyShowWarningsCounter( showWarningsCounterCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showWarningsCounterCheckBoxMenuItem) - } else if (e.source == showInfoCounterCheckBoxMenuItem) { - applyShowInfoCounter(showInfoCounterCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showInfoCounterCheckBoxMenuItem) - } else if (e.source == showSuccessfulTestsCheckBoxMenuItem) { - applyFilter(showSuccessfulTestsCheckBoxMenuItem.selected, showDisabledTestsCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showSuccessfulTestsCheckBoxMenuItem) - } else if (e.source == showDisabledTestsCheckBoxMenuItem) { - applyFilter(showSuccessfulTestsCheckBoxMenuItem.selected, showDisabledTestsCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showDisabledTestsCheckBoxMenuItem) - } else if (e.source == showTestDescriptionCheckBoxMenuItem) { - applyShowTestDescription(showTestDescriptionCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showTestDescriptionCheckBoxMenuItem) - } else if (e.source == showWarningIndicatorCheckBoxMenuItem) { - applyShowWarningIndicator(showWarningIndicatorCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showWarningIndicatorCheckBoxMenuItem) - } else if (e.source == showInfoIndicatorCheckBoxMenuItem) { - applyShowInfoIndicator(showInfoIndicatorCheckBoxMenuItem.selected) - fixCheckBoxMenuItem(showInfoIndicatorCheckBoxMenuItem) - } else if (e.source == syncDetailTabCheckBoxMenuItem) { - syncDetailTab - fixCheckBoxMenuItem(syncDetailTabCheckBoxMenuItem) - } - } - - override mouseClicked(MouseEvent e) { - if (e.clickCount == 2) { - if (e.source == testOverviewTable) { - if (failuresTable.selectedRowCount == 1) { - openSelectedFailure - } else { - openSelectedTest - } - - } else if (e.source == failuresTable) { - if (failuresTable.selectedRowCount == 1) { - openSelectedFailure - } - } - } - } - - override mouseEntered(MouseEvent e) { - } - - override mouseExited(MouseEvent e) { - } - - override mousePressed(MouseEvent e) { - } - - override mouseReleased(MouseEvent e) { - } - - override hyperlinkUpdate(HyperlinkEvent e) { - if (e.eventType == HyperlinkEvent.EventType.ACTIVATED) { - val link = e.description - openLink(link) - } - } - - private static def formatDateTime(String dateTime) { - if (dateTime === null) { - return null - } else { - if (dateTime.length == 26) { - return dateTime.replace("T", " ").substring(0, 23) - } else { - return dateTime - } - } - } - - static class TestOverviewRowListener implements ListSelectionListener { - RunnerPanel p - - new (RunnerPanel p) { - this.p = p - } - - override void valueChanged(ListSelectionEvent event) { - val rowIndex = p.testOverviewTable.selectedRow - if (rowIndex != -1) { - val row = p.testOverviewTable.convertRowIndexToModel(rowIndex) - val test = p.testOverviewTableModel.getTest(row) - p.testOwnerTextField.text = test.ownerName - p.testPackageTextField.text = test.objectName - p.testProcedureTextField.text = test.procedureName - p.testDescriptionTextArea.text = test.description?.trim - p.testIdTextArea.text = test.id - p.testStartTextField.text = formatDateTime(test.startTime) - p.failuresTableModel.model = test.failedExpectations - p.failuresTableModel.fireTableDataChanged - p.testFailureMessageTextPane.text = null - if (test.failedExpectations !== null && test.failedExpectations.size > 0) { - p.failuresTable.setRowSelectionInterval(0, 0) - } - p.testErrorStackTextPane.text = p.getHtml(test.errorStack?.trim) - p.testWarningsTextPane.text = p.getHtml(test.warnings?.trim) - p.testServerOutputTextPane.text = p.getHtml(test.serverOutput?.trim) - p.syncDetailTab - p.testOverviewRunMenuItem.enabled = true - p.testOverviewRunWorksheetMenuItem.enabled = true - } - } - } - - private def getLinkedAndFormattedText(String text) { - if (text === null) { - return "" - } - // Patterns (primarily Asserts, Errors, ServerOutput): - // at "OWNER.PACKAGE.PROCEDURE", line 42 - // at "OWNER.PROCEDURE", line 42 - // at "OWNER.PACKAGE", line 42 - // at package "OWNER.PACKAGE", line 42 - val p1 = Pattern.compile('''\s+(package\s+)?("(\S+?)\.(\S+?)(?:\.(\S+?))?",\s+line\s+([0-9]+))''') - var localText = HtmlUtils.htmlEscape(text) - var m = p1.matcher(localText) - while(m.find) { - val link = '''«m.group(2)»''' - val start = m.start(2) - val end = m.end(2) - localText = '''«localText.substring(0, start)»«link»«localText.substring(end)»''' - m = p1.matcher(localText) - } - // Patterns (primarily Warnings, without line reference, calculate when opening link): - // owner.package.procedure - val p2 = Pattern.compile('''^\s{2}((\S+?)\.(\S+?)\.(\S+?))$''', Pattern.MULTILINE) - m = p2.matcher(localText) - while(m.find) { - val link = '''  «m.group(1)»''' - val start = m.start(0) - val end = m.end(0) - localText = '''«localText.substring(0, start)»«link»«localText.substring(end)»''' - m = p2.matcher(localText) - } - // Patterns (Title for warning/info on suite level) - // from suite a.junit_utplsql_test1_pkg: - val p3 = Pattern.compile('''^For suite ([^:]+):$''', Pattern.MULTILINE) - m = p3.matcher(localText) - while(m.find) { - val title = '''For suite "«m.group(1)»"''' - val start = m.start(0) - val end = m.end(0) - localText = '''«localText.substring(0, start)»«title»«localText.substring(end)»''' - m = p3.matcher(localText) - } - val result = ''' - «FOR p : localText.split("\n")» -

«p»

- «ENDFOR» - ''' - return result - } - - static class FailuresRowListener implements ListSelectionListener { - RunnerPanel p - - new (RunnerPanel p) { - this.p = p - } - - override void valueChanged(ListSelectionEvent event) { - val rowIndex = p.failuresTable.selectedRow - if (rowIndex != -1) { - val row = p.failuresTable.convertRowIndexToModel(rowIndex) - val expectation = p.failuresTableModel.getExpectation(row) - val html = p.getHtml(expectation.failureText) - p.testFailureMessageTextPane.text = html - - } - } - } - - static class TimeFormatRenderer extends DefaultTableCellRenderer { - override getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, - int row, int col) { - val smartTime = new SmartTime(value as Double, useSmartTimes) - return super.getTableCellRendererComponent(table, smartTime.toString, isSelected, hasFocus, row, col) - } - } - - static class TestTableHeaderRenderer extends DefaultTableCellRenderer { - override getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, - int row, int col) { - val renderer = table.tableHeader.defaultRenderer - val label = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col) as JLabel - if (col === 0) { - label.icon = UtplsqlResources.getIcon("STATUS_ICON") - label.horizontalAlignment = JLabel.CENTER - } else if (col === 1) { - label.icon = UtplsqlResources.getIcon("WARNING_ICON") - label.horizontalAlignment = JLabel.CENTER - } else if (col === 2) { - label.icon = UtplsqlResources.getIcon("INFO_ICON") - label.horizontalAlignment = JLabel.CENTER - } else if (col === 3) { - label.icon = null - label.horizontalAlignment = JLabel.LEFT - } else if (col === 4) { - label.icon = null - label.horizontalAlignment = JLabel.RIGHT - } - return label - } - } - - static class FailuresTableHeaderRenderer extends DefaultTableCellRenderer { - override getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, - int row, int col) { - val renderer = table.tableHeader.defaultRenderer - val label = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col) as JLabel - if (col === 0) { - label.horizontalAlignment = JLabel.RIGHT - } else { - label.horizontalAlignment = JLabel.LEFT - } - return label - } - } - - private def makeLabelledCounterComponent (JLabel label, JComponent comp) { - val groupPanel = new JPanel - groupPanel.layout = new GridBagLayout - var GridBagConstraints c = new GridBagConstraints - // label - c.gridx = 0 - c.gridy = 0 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(5, 10, 5, 0) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::NONE - c.weightx = 0 - c.weighty = 0 - groupPanel.add(label, c) - // component - c.gridx = 1 - c.gridy = 0 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(5, 5, 5, 10) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::HORIZONTAL - c.weightx = 1 - c.weighty = 0 - groupPanel.add(comp, c) - val dim = new Dimension(134, 24) - groupPanel.minimumSize = dim - groupPanel.preferredSize = dim - return groupPanel - } - - private def initializeGUI() { - // Base panel containing all components - basePanel = new JPanel() - basePanel.setLayout(new GridBagLayout()) - var GridBagConstraints c = new GridBagConstraints() - - // Toolbar - var toolbar = new GradientToolbar - toolbar.floatable = false - val buttonBorder = new EmptyBorder(new Insets(2, 4, 2, 4)) // top, left, bottom, right - refreshButton = new ToolbarButton(UtplsqlResources.getIcon("REFRESH_ICON")) - refreshButton.toolTipText = UtplsqlResources.getString("RUNNER_REFRESH_TOOLTIP") - refreshButton.border = buttonBorder - refreshButton.addActionListener(this) - toolbar.add(refreshButton) - rerunButton = new ToolbarButton(UtplsqlResources.getIcon("RUN_ICON")) - rerunButton.toolTipText = UtplsqlResources.getString("RUNNER_RERUN_TOOLTIP") - rerunButton.border = buttonBorder - rerunButton.addActionListener(this) - toolbar.add(rerunButton) - rerunWorksheetButton = new ToolbarButton(UtplsqlResources.getIcon("RUN_WORKSHEET_ICON")) - rerunWorksheetButton.toolTipText = UtplsqlResources.getString("RUNNER_RERUN_WORKSHEET_TOOLTIP") - rerunWorksheetButton.border = buttonBorder - rerunWorksheetButton.addActionListener(this) - toolbar.add(rerunWorksheetButton) - toolbar.add(Box.createHorizontalGlue()) - runComboBoxModel = new DefaultComboBoxModel>; - runComboBox = new JComboBox>(runComboBoxModel); - runComboBox.editable = false - val comboBoxDim = new Dimension(500, 50) - runComboBox.maximumSize = comboBoxDim - runComboBox.addActionListener(this) - toolbar.add(runComboBox) - clearButton = new ToolbarButton(UtplsqlResources.getIcon("CLEAR_ICON")) - clearButton.toolTipText = UtplsqlResources.getString("RUNNER_CLEAR_BUTTON") - clearButton.border = buttonBorder - clearButton.addActionListener(this) - toolbar.add(clearButton) - c.gridx = 0 - c.gridy = 0 - c.gridwidth = 2 - c.gridheight = 1 - c.insets = new Insets(0, 0, 0, 0) // top, left, bottom, right - c.anchor = GridBagConstraints::NORTH - c.fill = GridBagConstraints::HORIZONTAL - c.weightx = 1 - c.weighty = 0 - basePanel.add(toolbar, c) - - // Status line - statusLabel = new JLabel - c.gridx = 0 - c.gridy = 1 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(10, 10, 10, 0) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::HORIZONTAL - c.weightx = 1 - c.weighty = 0 - basePanel.add(statusLabel, c) - elapsedTimeLabel = new JLabel - elapsedTimeLabel.preferredSize = new Dimension(60, 0) - c.gridx = 1 - c.gridy = 1 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(10, 10, 10, 10) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::NONE - c.weightx = 0 - c.weighty = 0 - basePanel.add(elapsedTimeLabel, c) - elapsedTimeTimer = new Timer(100, new ActionListener() { - override actionPerformed(ActionEvent e) { - if (currentRun !== null && currentRun.start !== null) { - val time = new SmartTime - time.smart = useSmartTimes - if (currentRun.executionTime !== null) { - time.seconds = currentRun.executionTime - elapsedTimeTimer.stop - } else { - val long now = System.currentTimeMillis - time.seconds = new Double(now - currentRun.start) / 1000 - } - elapsedTimeLabel.text = '''«time.toString»«IF !useSmartTimes» s«ENDIF»''' - } else { - elapsedTimeLabel.text = null - } - } - }) - - // Counters - // - Test counter - val counterPanel = new JPanel - counterPanel.layout = new WrapLayout(FlowLayout.LEFT, 0, 0) - val testCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_TESTS_LABEL") + ":", JLabel::LEADING) - testCounterValueLabel = new JLabel - counterPanel.add(makeLabelledCounterComponent(testCounterLabel, testCounterValueLabel)) - // - Failure counter - val failureCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_FAILURES_LABEL") + ":", - UtplsqlResources.getIcon("FAILURE_ICON"), JLabel::LEADING) - failureCounterValueLabel = new JLabel - counterPanel.add(makeLabelledCounterComponent(failureCounterLabel,failureCounterValueLabel)) - // - Error counter - val errorCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_ERRORS_LABEL") + ":", - UtplsqlResources.getIcon("ERROR_ICON"), JLabel::LEADING) - errorCounterValueLabel = new JLabel - counterPanel.add(makeLabelledCounterComponent(errorCounterLabel, errorCounterValueLabel)) - // - Disabled counter - val disabledCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_DISABLED_LABEL") + ":", - UtplsqlResources.getIcon("DISABLED_ICON"), JLabel::LEADING) - disabledCounterValueLabel = new JLabel - counterPanel.add(makeLabelledCounterComponent(disabledCounterLabel, disabledCounterValueLabel)) - // - Warnings counter - val warningsCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_WARNINGS_LABEL") + ":", - UtplsqlResources.getIcon("WARNING_ICON"), JLabel::LEADING) - warningsCounterValueLabel = new JLabel - counterPanel.add(makeLabelledCounterComponent(warningsCounterLabel, warningsCounterValueLabel)) - // - Info counter - val infoCounterLabel = new JLabel(UtplsqlResources.getString("RUNNER_INFO_LABEL") + ":", - UtplsqlResources.getIcon("INFO_ICON"), JLabel::LEADING) - infoCounterValueLabel = new JLabel - counterPanel.add(makeLabelledCounterComponent(infoCounterLabel, infoCounterValueLabel)) - // - add everything to basePanel - c.gridx = 0 - c.gridy = 2 - c.gridwidth = 2 - c.gridheight = 1 - c.insets = new Insets(5, 0, 5, 0) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::HORIZONTAL - c.weightx = 1 - c.weighty = 0 - basePanel.add(counterPanel,c) - - // Context menu for counters panel - val countersPopupMenu = new JPopupMenu - showDisabledCounterCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_DISABLED_COUNTER_LABEL").replace("?",""), true) - showDisabledCounterCheckBoxMenuItem.addActionListener(this) - countersPopupMenu.add(showDisabledCounterCheckBoxMenuItem) - showWarningsCounterCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_WARNINGS_COUNTER_LABEL").replace("?",""), true) - showWarningsCounterCheckBoxMenuItem.addActionListener(this) - countersPopupMenu.add(showWarningsCounterCheckBoxMenuItem) - showInfoCounterCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_INFO_COUNTER_LABEL").replace("?",""), true) - showInfoCounterCheckBoxMenuItem.addActionListener(this) - countersPopupMenu.add(showInfoCounterCheckBoxMenuItem) - counterPanel.componentPopupMenu = countersPopupMenu - - // Progress bar - progressBar = new JProgressBar - val progressBarDim = new Dimension(10, 20) - progressBar.preferredSize = progressBarDim - progressBar.minimumSize = progressBarDim - progressBar.stringPainted = false - progressBar.foreground = GREEN - progressBar.UI = new BasicProgressBarUI - c.gridx = 0 - c.gridy = 3 - c.gridwidth = 2 - c.gridheight = 1 - c.insets = new Insets(10, 10, 10, 10) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::HORIZONTAL - c.weightx = 1 - c.weighty = 0 - basePanel.add(progressBar, c) - - // Test overview - testOverviewTableModel = new TestOverviewTableModel - testOverviewTable = new JTable(testOverviewTableModel) - testOverviewTable.tableHeader.reorderingAllowed = false - testOverviewTable.autoCreateRowSorter = true - testOverviewTable.rowHeight = OVERVIEW_TABLE_ROW_HEIGHT - testOverviewTable.tableHeader.preferredSize = new Dimension(testOverviewTable.tableHeader.getPreferredSize.width, OVERVIEW_TABLE_ROW_HEIGHT) - testOverviewTable.selectionModel.addListSelectionListener(new TestOverviewRowListener(this)) - testOverviewTable.addMouseListener(this) - RepaintManager.currentManager(testOverviewTable).doubleBufferingEnabled = true // reduce flickering - val testTableHeaderRenderer = new TestTableHeaderRenderer - val overviewTableStatus = testOverviewTable.columnModel.getColumn(0) - overviewTableStatus.minWidth = INDICATOR_WIDTH - overviewTableStatus.preferredWidth = INDICATOR_WIDTH - overviewTableStatus.maxWidth = INDICATOR_WIDTH - overviewTableStatus.headerRenderer = testTableHeaderRenderer - val overviewTableWarning = testOverviewTable.columnModel.getColumn(1) - overviewTableWarning.minWidth = INDICATOR_WIDTH - overviewTableWarning.preferredWidth = INDICATOR_WIDTH - overviewTableWarning.maxWidth = INDICATOR_WIDTH - overviewTableWarning.headerRenderer = testTableHeaderRenderer - val overviewTableInfo = testOverviewTable.columnModel.getColumn(2) - overviewTableInfo.minWidth = INDICATOR_WIDTH - overviewTableInfo.preferredWidth = INDICATOR_WIDTH - overviewTableInfo.maxWidth = INDICATOR_WIDTH - overviewTableInfo.headerRenderer = testTableHeaderRenderer - val overviewTableId = testOverviewTable.columnModel.getColumn(3) - overviewTableId.headerRenderer = testTableHeaderRenderer - val overviewTableTime = testOverviewTable.columnModel.getColumn(4) - overviewTableTime.preferredWidth = 60 - overviewTableTime.maxWidth = 100 - overviewTableTime.headerRenderer = testTableHeaderRenderer - val timeFormatRenderer = new TimeFormatRenderer - timeFormatRenderer.horizontalAlignment = JLabel.RIGHT - overviewTableTime.cellRenderer = timeFormatRenderer - val testOverviewScrollPane = new JScrollPane(testOverviewTable) - - // Context menu for test overview - val testOverviewPopupMenu = new JPopupMenu - testOverviewRunMenuItem = new JMenuItem(UtplsqlResources.getString("RUNNER_RUN_MENUITEM"), UtplsqlResources.getIcon("RUN_ICON")); - testOverviewRunMenuItem.addActionListener(this) - testOverviewPopupMenu.add(testOverviewRunMenuItem) - testOverviewRunWorksheetMenuItem = new JMenuItem(UtplsqlResources.getString("RUNNER_RUN_WORKSHEET_MENUITEM"), UtplsqlResources.getIcon("RUN_WORKSHEET_ICON")); - testOverviewRunWorksheetMenuItem.addActionListener(this) - testOverviewPopupMenu.add(testOverviewRunWorksheetMenuItem) - testOverviewPopupMenu.add(new JSeparator) - showSuccessfulTestsCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_SUCCESSFUL_TESTS_LABEL").replace("?",""), true) - showSuccessfulTestsCheckBoxMenuItem.addActionListener(this) - testOverviewPopupMenu.add(showSuccessfulTestsCheckBoxMenuItem) - showDisabledTestsCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_DISABLED_TESTS_LABEL").replace("?",""), true) - showDisabledTestsCheckBoxMenuItem.addActionListener(this) - testOverviewPopupMenu.add(showDisabledTestsCheckBoxMenuItem) - testOverviewPopupMenu.add(new JSeparator) - showTestDescriptionCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_TEST_DESCRIPTION_LABEL").replace("?",""), true) - showTestDescriptionCheckBoxMenuItem.addActionListener(this) - testOverviewPopupMenu.add(showTestDescriptionCheckBoxMenuItem) - showWarningIndicatorCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_WARNING_INDICATOR_LABEL").replace("?",""), true) - showWarningIndicatorCheckBoxMenuItem.addActionListener(this) - testOverviewPopupMenu.add(showWarningIndicatorCheckBoxMenuItem) - showInfoIndicatorCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SHOW_INFO_INDICATOR_LABEL").replace("?",""), true) - showInfoIndicatorCheckBoxMenuItem.addActionListener(this) - testOverviewPopupMenu.add(showInfoIndicatorCheckBoxMenuItem) - syncDetailTabCheckBoxMenuItem = new JCheckBoxMenuItem(UtplsqlResources.getString("PREF_SYNC_DETAIL_TAB_LABEL").replace("?",""), true) - syncDetailTabCheckBoxMenuItem.addActionListener(this) - testOverviewPopupMenu.add(syncDetailTabCheckBoxMenuItem) - testOverviewTable.componentPopupMenu = testOverviewPopupMenu - testOverviewTable.tableHeader.componentPopupMenu = testOverviewPopupMenu - - // Test tabbed pane (Test Properties) - val testInfoPanel = new ScrollablePanel - testInfoPanel.setLayout(new GridBagLayout()) - // - Owner - val testOwnerLabel = new JLabel(UtplsqlResources.getString("RUNNER_OWNER_LABEL")) - c.gridx = 0 - c.gridy = 0 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(10, 10, 0, 0) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::NONE - c.weightx = 0 - c.weighty = 0 - testInfoPanel.add(testOwnerLabel, c) - testOwnerTextField = new RunnerTextField - testOwnerTextField.editable = false - c.gridx = 1 - c.gridy = 0 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(10, 5, 0, 10) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::HORIZONTAL - c.weightx = 1 - c.weighty = 0 - testInfoPanel.add(testOwnerTextField, c) - // - Package - val testPackageLabel = new JLabel(UtplsqlResources.getString("RUNNER_PACKAGE_LABEL")) - c.gridx = 0 - c.gridy = 1 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(5, 10, 0, 0) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::NONE - c.weightx = 0 - c.weighty = 0 - testInfoPanel.add(testPackageLabel, c) - testPackageTextField = new RunnerTextField - testPackageTextField.editable = false - c.gridx = 1 - c.gridy = 1 - 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(testPackageTextField, c) - // - Procedure - val testProcedureLabel = new JLabel(UtplsqlResources.getString("RUNNER_PROCEDURE_LABEL")) - c.gridx = 0 - c.gridy = 2 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(5, 10, 0, 0) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::NONE - c.weightx = 0 - c.weighty = 0 - testInfoPanel.add(testProcedureLabel, c) - testProcedureTextField = new RunnerTextField - testProcedureTextField.editable = false - c.gridx = 1 - c.gridy = 2 - 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(testProcedureTextField, c) - // - Description - val testDescriptionLabel = new JLabel(UtplsqlResources.getString("RUNNER_DESCRIPTION_LABEL")) - testDescriptionLabel.border = BorderFactory.createEmptyBorder(if (macLookAndFeel) {5} else {3}, 0, 0, 0) - c.gridx = 0 - c.gridy = 3 - 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(testDescriptionLabel, c) - testDescriptionTextArea = new RunnerTextArea - testDescriptionTextArea.editable = false - testDescriptionTextArea.enabled = true - testDescriptionTextArea.lineWrap = true - testDescriptionTextArea.wrapStyleWord = true - c.gridx = 1 - c.gridy = 3 - 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(testDescriptionTextArea, c) - // - Suitepath (id) - val testIdLabel = new JLabel(UtplsqlResources.getString("RUNNER_TEST_ID_COLUMN")) - testIdLabel.border = BorderFactory.createEmptyBorder(if (macLookAndFeel) {5} else {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(testIdLabel, c) - testIdTextArea = new RunnerTextArea - testIdTextArea.editable = false - testIdTextArea.enabled = true - testIdTextArea.lineWrap = true - testIdTextArea.wrapStyleWord = false - 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(testIdTextArea, c) - // - Start - val testStartLabel = new JLabel(UtplsqlResources.getString("RUNNER_START_LABEL")) - c.gridx = 0 - c.gridy = 5 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(5, 10, 10, 0) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::NONE - c.weightx = 0 - c.weighty = 0 - testInfoPanel.add(testStartLabel, c) - testStartTextField = new RunnerTextField - testStartTextField.editable = false - c.gridx = 1 - c.gridy = 5 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(5, 5, 10, 10) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::HORIZONTAL - c.weightx = 1 - c.weighty = 0 - testInfoPanel.add(testStartTextField, c) - // - Vertical spring and scrollbar for info panel - c.gridx = 0 - c.gridy = 6 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(0, 0, 0, 0) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::BOTH - c.weightx = 0 - c.weighty = 1 - testInfoPanel.add(Box.createVerticalGlue(), c) - val testPropertiesScrollPane = new JScrollPane(testInfoPanel) - - // Failures tabbed pane (failed expectations) - // - failures table (number and description) - failuresTableModel = new FailuresTableModel - failuresTable = new JTable(failuresTableModel) - failuresTable.tableHeader.reorderingAllowed = false - failuresTable.selectionModel.addListSelectionListener(new FailuresRowListener(this)) - failuresTable.addMouseListener(this) - val failuresTableHeaderRenderer = new FailuresTableHeaderRenderer - val failuresTableNumber = failuresTable.columnModel.getColumn(0) - failuresTableNumber.headerRenderer = failuresTableHeaderRenderer - failuresTableNumber.preferredWidth = 30 - failuresTableNumber.maxWidth = 30 - val failuresDescription = failuresTable.columnModel.getColumn(1) - failuresDescription.headerRenderer = failuresTableHeaderRenderer - val failuresTableScrollPane = new JScrollPane(failuresTable) - // - failures details - testFailureMessageTextPane = new RunnerTextPane - testFailureMessageTextPane.editable = false - testFailureMessageTextPane.enabled = true - testFailureMessageTextPane.contentType = "text/html" - testFailureMessageTextPane.minimumSize = TEXTPANE_DIM - testFailureMessageTextPane.preferredSize = TEXTPANE_DIM - testFailureMessageTextPane.addHyperlinkListener(this) - val testFailureMessageScrollPane = new JScrollPane(testFailureMessageTextPane) - c.gridx = 1 - c.gridy = 0 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(10, 5, 0, 10) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::BOTH - c.weightx = 1 - c.weighty = 6 - - // - split pane - val failuresSplitPane = new JSplitPane(SwingConstants.HORIZONTAL, failuresTableScrollPane, testFailureMessageScrollPane) - failuresSplitPane.resizeWeight = 0.2 - - // Errors tabbed pane (Error Stack) - val testErrorStackPanel = new JPanel - testErrorStackPanel.setLayout(new GridBagLayout()) - testErrorStackTextPane = new RunnerTextPane - testErrorStackTextPane.editable = false - testErrorStackTextPane.enabled = true - testErrorStackTextPane.contentType = "text/html" - testErrorStackTextPane.minimumSize = TEXTPANE_DIM - testErrorStackTextPane.preferredSize = TEXTPANE_DIM - testErrorStackTextPane.addHyperlinkListener(this) - val testErrorStackScrollPane = new JScrollPane(testErrorStackTextPane) - c.gridx = 0 - c.gridy = 0 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(0, 0, 0, 0) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::BOTH - c.weightx = 1 - c.weighty = 1 - testErrorStackPanel.add(testErrorStackScrollPane, c) - - // Warnings tabbed pane - val testWarningsPanel = new JPanel - testWarningsPanel.setLayout(new GridBagLayout()) - testWarningsTextPane = new RunnerTextPane - testWarningsTextPane.editable = false - testWarningsTextPane.enabled = true - testWarningsTextPane.contentType = "text/html" - testWarningsTextPane.minimumSize = TEXTPANE_DIM - testWarningsTextPane.preferredSize = TEXTPANE_DIM - testWarningsTextPane.addHyperlinkListener(this) - val testWarningsScrollPane = new JScrollPane(testWarningsTextPane) - c.gridx = 0 - c.gridy = 0 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(0, 0, 0, 0) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::BOTH - c.weightx = 1 - c.weighty = 1 - testWarningsPanel.add(testWarningsScrollPane, c) - - // Info tabbed pane (Server Output) - val testServerOutputPanel = new JPanel - testServerOutputPanel.setLayout(new GridBagLayout()) - testServerOutputTextPane = new RunnerTextPane - testServerOutputTextPane.editable = false - testServerOutputTextPane.enabled = true - testServerOutputTextPane.contentType = "text/html" - testServerOutputTextPane.minimumSize = TEXTPANE_DIM - testServerOutputTextPane.preferredSize = TEXTPANE_DIM - testServerOutputTextPane.addHyperlinkListener(this) - val testServerOutputScrollPane = new JScrollPane(testServerOutputTextPane) - c.gridx = 0 - c.gridy = 0 - c.gridwidth = 1 - c.gridheight = 1 - c.insets = new Insets(0, 0, 0, 0) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::BOTH - c.weightx = 1 - c.weighty = 1 - testServerOutputPanel.add(testServerOutputScrollPane, c) - - // split pane with all tabs - testDetailTabbedPane = new JTabbedPane() - testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_TEST_TAB_LABEL"), testPropertiesScrollPane) - testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_FAILURES_TAB_LABEL"), failuresSplitPane) - testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_ERRORS_TAB_LABEL"), testErrorStackPanel) - testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_WARNINGS_TAB_LABEL"), testWarningsPanel) - testDetailTabbedPane.add(UtplsqlResources.getString("RUNNER_INFO_TAB_LABEL"), testServerOutputPanel) - val horizontalSplitPane = new JSplitPane(SwingConstants.HORIZONTAL, testOverviewScrollPane, testDetailTabbedPane) - horizontalSplitPane.resizeWeight = 0.5 - c.gridx = 0 - c.gridy = 4 - c.gridwidth = 2 - c.gridheight = 1 - c.insets = new Insets(10, 10, 10, 10) // top, left, bottom, right - c.anchor = GridBagConstraints::WEST - c.fill = GridBagConstraints::BOTH - c.weightx = 1 - c.weighty = 1 - basePanel.add(horizontalSplitPane, c) - - // fix borders (colors, margins) - if (macLookAndFeel) { - val border = BorderFactory.createCompoundBorder( - BorderFactory.createEmptyBorder(3, 3, 3, 3), - BorderFactory.createCompoundBorder( - BorderFactory.createLineBorder(new Color(219, 219, 219)), - BorderFactory.createEmptyBorder(1, 1, 1, 1) - ) - ) - testDescriptionTextArea.border = border - testIdTextArea.border = border - } else { - val referenceBorder = testOwnerTextField.border - testDescriptionTextArea.border = referenceBorder - testIdTextArea.border = referenceBorder - } - } - -} \ No newline at end of file diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextArea.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextArea.java new file mode 100644 index 00000000..25377d1d --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextArea.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.runner; + +import java.awt.Graphics; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +import javax.swing.JTextArea; +import javax.swing.UIManager; + +public class RunnerTextArea extends JTextArea implements FocusListener { + private static final long serialVersionUID = -1536393556223117580L; + + public RunnerTextArea() { + super(); + addFocusListener(this); + } + + @Override + public void paintComponent(final Graphics g) { + // default for non-opaque components + if (!isOpaque()) { + super.paintComponent(g); + return; + } + + // use value of JTextField for consistency + g.setColor(UIManager.getColor("TextField.inactiveBackground")); + g.fillRect(3, 3, getWidth() - 6, getHeight() - 6); + + // do rest, changing opaque to ensure background is not overwritten + setOpaque(false); + super.paintComponent(g); + setOpaque(true); + } + + @Override + public void focusGained(final FocusEvent e) { + getCaret().setVisible(true); + } + + @Override + public void focusLost(final FocusEvent e) { + getCaret().setVisible(false); + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextArea.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextArea.xtend deleted file mode 100644 index be9520c7..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextArea.xtend +++ /dev/null @@ -1,40 +0,0 @@ -package org.utplsql.sqldev.ui.runner - -import java.awt.Graphics -import java.awt.event.FocusEvent -import java.awt.event.FocusListener -import javax.swing.JTextArea -import javax.swing.UIManager - -class RunnerTextArea extends JTextArea implements FocusListener{ - - new() { - super() - this.addFocusListener = this - } - - override paintComponent(Graphics g) { - // default for non-opaque components - if (!opaque) { - super.paintComponent(g) - return - } - - // use value of JTextField for consistency - g.color = UIManager.getColor("TextField.inactiveBackground") - g.fillRect(3, 3, width - 6, height - 6) - - // do rest, changing opaque to ensure background is not overwritten - setOpaque(false) - super.paintComponent(g) - setOpaque(true) - } - - override void focusGained(FocusEvent e) { - this.caret.visible = true - } - - override focusLost(FocusEvent e) { - this.caret.visible = false - } -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextField.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextField.java new file mode 100644 index 00000000..0553ab5e --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextField.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.runner; + +import java.awt.Graphics; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +import javax.swing.JTextField; +import javax.swing.UIManager; + +public class RunnerTextField extends JTextField implements FocusListener { + private static final long serialVersionUID = 4527406698634871523L; + + public RunnerTextField() { + super(); + addFocusListener(this); + } + + @Override + public void paintComponent(final Graphics g) { + // default for non-opaque components + if (!isOpaque()) { + super.paintComponent(g); + return; + } + + // use value of JTextField for consistency + g.setColor(UIManager.getColor("TextField.inactiveBackground")); + g.fillRect(0, 0, getWidth(), getHeight()); + + // do rest, changing opaque to ensure background is not overwritten + setOpaque(false); + super.paintComponent(g); + setOpaque(true); + } + + @Override + public void focusGained(final FocusEvent e) { + getCaret().setVisible(true); + } + + @Override + public void focusLost(final FocusEvent e) { + getCaret().setVisible(false); + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextField.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextField.xtend deleted file mode 100644 index 65e326ea..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextField.xtend +++ /dev/null @@ -1,39 +0,0 @@ -package org.utplsql.sqldev.ui.runner - -import java.awt.Graphics -import java.awt.event.FocusEvent -import java.awt.event.FocusListener -import javax.swing.JTextField -import javax.swing.UIManager - -class RunnerTextField extends JTextField implements FocusListener { - - new() { - super() - this.addFocusListener = this - } - - override paintComponent(Graphics g) { - // default for non-opaque components - if (!opaque) { - super.paintComponent(g) - return - } - - g.color = UIManager.getColor("TextField.inactiveBackground") - g.fillRect(0, 0, width, height) - - // do rest, changing opaque to ensure background is not overwritten - setOpaque(false) - super.paintComponent(g) - setOpaque(true) - } - - override void focusGained(FocusEvent e) { - this.caret.visible = true - } - - override focusLost(FocusEvent e) { - this.caret.visible = false - } -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextPane.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextPane.java new file mode 100644 index 00000000..cd1fb700 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextPane.java @@ -0,0 +1,67 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.runner; + +import java.awt.Graphics; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +import javax.swing.JTextPane; +import javax.swing.UIManager; + +public class RunnerTextPane extends JTextPane implements FocusListener { + private static final long serialVersionUID = 1089473481444949272L; + + public RunnerTextPane() { + super(); + addFocusListener(this); + } + + @Override + public void paintComponent(final Graphics g) { + // default for non-opaque components + if (isOpaque()) { + super.paintComponent(g); + return; + } + + // use value of JTextField for consistency + g.setColor(UIManager.getColor("TextField.inactiveBackground")); + g.fillRect(0, 0, getWidth(), getHeight()); + setOpaque(false); + + // do rest, changing opaque to ensure background is not overwritten + super.paintComponent(g); + setOpaque(true); + } + + @Override + public void focusGained(final FocusEvent e) { + getCaret().setVisible(true); + } + + @Override + public void focusLost(final FocusEvent e) { + getCaret().setVisible(false); + } + + @Override + public void setText(final String t) { + super.setText(t); + // ensure left parts of long lines are always visible + setCaretPosition(0); + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextPane.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextPane.xtend deleted file mode 100644 index e3e4c663..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerTextPane.xtend +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.utplsql.sqldev.ui.runner - -import java.awt.Graphics -import java.awt.event.FocusEvent -import java.awt.event.FocusListener -import javax.swing.JTextPane -import javax.swing.UIManager - -class RunnerTextPane extends JTextPane implements FocusListener{ - - new() { - super() - this.addFocusListener = this - } - - override paintComponent(Graphics g) { - // default for non-opaque components - if (!opaque) { - super.paintComponent(g) - return - } - - // use value of JTextField for consistency - g.color = UIManager.getColor("TextField.inactiveBackground") - g.fillRect(0, 0, width, height) - - // do rest, changing opaque to ensure background is not overwritten - setOpaque(false) - super.paintComponent(g) - setOpaque(true) - } - - override void focusGained(FocusEvent e) { - this.caret.visible = true - } - - override focusLost(FocusEvent e) { - this.caret.visible = false - } - - override setText(String t) { - super.setText(t) - // ensure left parts of long lines are always visible - caretPosition = 0 - } -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerView.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerView.java new file mode 100644 index 00000000..e97d7ba0 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerView.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.runner; + +import java.awt.Component; + +import javax.swing.Icon; + +import org.utplsql.sqldev.resources.UtplsqlResources; + +import oracle.ide.docking.DockableWindow; +import oracle.ide.layout.ViewId; + +public class RunnerView extends DockableWindow { + private static final String VIEW_NAME = "UTPLSQL_RUNNER_VIEW"; + public static final ViewId VIEW_ID = new ViewId(RunnerFactory.FACTORY_NAME, VIEW_NAME); + private RunnerPanel panel; + + @Override + public String getTitleName() { + return UtplsqlResources.getString("RUNNER_VIEW_TITLE"); + } + + @Override + public Component getGUI() { + if (panel == null) { + panel = new RunnerPanel(); + } + return panel.getGUI(); + } + + @Override + public Icon getTabIcon() { + return UtplsqlResources.getIcon("UTPLSQL_ICON"); + } + + public RunnerPanel getRunnerPanel() { + getGUI(); + return panel; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerView.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerView.xtend deleted file mode 100644 index 49050d4a..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/RunnerView.xtend +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.ui.runner - -import oracle.ide.docking.DockableWindow -import oracle.ide.layout.ViewId -import org.utplsql.sqldev.resources.UtplsqlResources - -class RunnerView extends DockableWindow { - static val VIEW_NAME = "UTPLSQL_RUNNER_VIEW" - public static val ViewId VIEW_ID = new ViewId(RunnerFactory.FACTORY_NAME, VIEW_NAME) - var RunnerPanel panel - - override getTitleName() { - return UtplsqlResources.getString("RUNNER_VIEW_TITLE") - } - - override getGUI() { - if (panel === null) { - panel = new RunnerPanel - } - return panel.getGUI() - } - - override getTabIcon() { - return UtplsqlResources.getIcon("UTPLSQL_ICON") - } - - def getRunnerPanel() { - getGUI() - return panel - } -} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ScrollablePanel.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ScrollablePanel.java new file mode 100644 index 00000000..b2398d6d --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ScrollablePanel.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.runner; + +import java.awt.Dimension; +import java.awt.Rectangle; + +import javax.swing.JPanel; +import javax.swing.Scrollable; + +/** + * Fixes resizing issues of JTextArea when put into JPanel and JPanel into JScrollPane + * Solution is based on https://stackoverflow.com/questions/15783014/jtextarea-on-jpanel-inside-jscrollpane-does-not-resize-properly/15786939 + */ +public class ScrollablePanel extends JPanel implements Scrollable { + private static final long serialVersionUID = -8074226692678606351L; + + @Override + public Dimension getPreferredScrollableViewportSize() { + return super.getPreferredSize(); + } + + @Override + public int getScrollableUnitIncrement(final Rectangle visibleRect, final int orientation, final int direction) { + return 0; + } + + @Override + public int getScrollableBlockIncrement(final Rectangle visibleRect, final int orientation, final int direction) { + return 0; + } + + @Override + public boolean getScrollableTracksViewportWidth() { + return true; + } + + @Override + public boolean getScrollableTracksViewportHeight() { + return false; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ScrollablePanel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ScrollablePanel.xtend deleted file mode 100644 index fb834008..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/ScrollablePanel.xtend +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.ui.runner - -import java.awt.Rectangle -import javax.swing.JPanel -import javax.swing.Scrollable - -/* - * Fixes resizing issues of JTextArea when put into JPanel and JPanel into JScrollPane - * Solution is based on https://stackoverflow.com/questions/15783014/jtextarea-on-jpanel-inside-jscrollpane-does-not-resize-properly/15786939 - */ -class ScrollablePanel extends JPanel implements Scrollable { - - override getPreferredScrollableViewportSize() { - return super.getPreferredSize() - } - - override getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { - return 0 - } - - override getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { - return 0 - } - - override getScrollableTracksViewportWidth() { - return true - } - - override getScrollableTracksViewportHeight() { - return false - } -} 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 new file mode 100644 index 00000000..69ea1500 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/SmartTime.java @@ -0,0 +1,71 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.runner; + +import java.text.DecimalFormat; + +public class SmartTime { + private Double seconds; + private boolean smart = false; + + public SmartTime() { + super(); + } + + public SmartTime(final Double seconds, final boolean smart) { + super(); + this.seconds = seconds; + this.smart = smart; + } + + public void setSeconds(final Double seconds) { + this.seconds = seconds; + } + + public void setSmart(final boolean smart) { + this.smart = smart; + } + + public Double getSeconds() { + return seconds; + } + + @Override + public String toString() { + String ret; + if (seconds == null) { + ret = null; + } else if (smart) { + if (seconds >= 60 * 60) { + final DecimalFormat formatter = new DecimalFormat("#0.00"); + ret = formatter.format(seconds / 60 / 60) + " h"; + } else if (seconds >= 60) { + final DecimalFormat formatter = new DecimalFormat("#0.00"); + ret = formatter.format(seconds / 60) + " min"; + } else if (seconds >= 1) { + final DecimalFormat formatter = new DecimalFormat("#0.000"); + ret = formatter.format(seconds) + " s"; + } else { + final DecimalFormat formatter = new DecimalFormat("##0"); + ret = formatter.format(seconds * 1000) + " ms"; + } + } else { + final DecimalFormat formatter = new DecimalFormat("##,##0.000"); + ret = formatter.format(seconds); + } + return ret; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/SmartTime.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/SmartTime.xtend deleted file mode 100644 index 6dc7246a..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/SmartTime.xtend +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.ui.runner - -import java.text.DecimalFormat - -class SmartTime { - var Double seconds - var boolean smart = false - - new() { - super() - } - - new(Double seconds, boolean smart) { - super() - this.seconds = seconds - this.smart = smart - } - - def setSeconds(Double seconds) { - this.seconds = seconds - } - - def setSmart(boolean smart) { - this.smart = smart - } - - def getSeconds() { - return seconds - } - - override toString() { - var String ret; - if (seconds === null) { - ret = null - } else if (smart) { - if (seconds >= 60*60) { - val DecimalFormat formatter = new DecimalFormat("#0.00") - ret = formatter.format(seconds / 60 / 60) + " h" - } else if (seconds >= 60) { - val DecimalFormat formatter = new DecimalFormat("#0.00") - ret = formatter.format(seconds / 60) + " min" - } else if (seconds >= 1) { - val DecimalFormat formatter = new DecimalFormat("#0.000") - ret = formatter.format(seconds) + " s" - } else { - val DecimalFormat formatter = new DecimalFormat("##0") - ret = formatter.format(seconds * 1000) + " ms" - } - - } else { - val DecimalFormat formatter = new DecimalFormat("##,##0.000") - ret = formatter.format(seconds) - } - return ret - } - -} \ No newline at end of file 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 new file mode 100644 index 00000000..ea2729e5 --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/TestOverviewTableModel.java @@ -0,0 +1,165 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.runner; + +import java.util.ArrayList; +import java.util.LinkedHashMap; + +import javax.swing.Icon; +import javax.swing.table.DefaultTableModel; + +import org.utplsql.sqldev.model.PrefixTools; +import org.utplsql.sqldev.model.runner.Test; +import org.utplsql.sqldev.resources.UtplsqlResources; + +public class TestOverviewTableModel extends DefaultTableModel { + private static final long serialVersionUID = -4087082648970132657L; + + private LinkedHashMap tests; + private String commonPrefix; + private boolean commonPrefixCalculated; + private boolean showDescription; + private boolean useSmartTimes; + + public TestOverviewTableModel() { + super(); + } + + private void calcCommonPrefix() { + if (!commonPrefixCalculated && tests != null && tests.size() > 0) { + commonPrefix = PrefixTools.commonPrefix(new ArrayList<>(tests.keySet())); + fireTableDataChanged(); + commonPrefixCalculated = true; + } + } + + public void setModel(final LinkedHashMap tests, final boolean showDescription, + final boolean useSmartTimes) { + commonPrefixCalculated = false; + this.tests = tests; + this.showDescription = showDescription; + this.useSmartTimes = useSmartTimes; + calcCommonPrefix(); + fireTableDataChanged(); + } + + public void updateModel(final boolean showDescription) { + this.showDescription = showDescription; + fireTableDataChanged(); + } + + public String getTestIdColumnName() { + StringBuilder sb = new StringBuilder(); + calcCommonPrefix(); + if (commonPrefix == null || commonPrefix.isEmpty()) { + if (showDescription) { + sb.append(UtplsqlResources.getString("RUNNER_DESCRIPTION_LABEL")); + } else { + sb.append(UtplsqlResources.getString("RUNNER_TEST_ID_COLUMN")); + } + } else { + if (showDescription) { + sb.append(UtplsqlResources.getString("RUNNER_DESCRIPTION_LABEL")); + sb.append(" ("); + sb.append(commonPrefix); + sb.append(")"); + } else { + sb.append(commonPrefix); + } + } + return sb.toString(); + } + + public String getTimeColumnName() { + return UtplsqlResources.getString("RUNNER_TEST_EXECUTION_TIME_COLUMN") + (!useSmartTimes ? " [s]" : ""); + } + + public Test getTest(final int row) { + return new ArrayList<>(tests.entrySet()).get(row).getValue(); + } + + @Override + public int getRowCount() { + if (tests == null) { + return 0; + } + return tests.size(); + } + + @Override + public int getColumnCount() { + return 5; + } + + @Override + public Object getValueAt(final int row, final int col) { + final Test test = getTest(row); + switch (col) { + 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 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; + } + + } + + @Override + public boolean isCellEditable(final int row, final int column) { + return false; + } + + @Override + public Class getColumnClass(final int col) { + switch (col) { + 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/TestOverviewTableModel.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/TestOverviewTableModel.xtend deleted file mode 100644 index c0f021c2..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/TestOverviewTableModel.xtend +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package org.utplsql.sqldev.ui.runner - -import java.util.LinkedHashMap -import javax.swing.Icon -import javax.swing.table.DefaultTableModel -import org.utplsql.sqldev.model.PrefixTools -import org.utplsql.sqldev.model.runner.Test -import org.utplsql.sqldev.resources.UtplsqlResources - -class TestOverviewTableModel extends DefaultTableModel { - LinkedHashMap tests - String commonPrefix - boolean commonPrefixCalculated - boolean showDescription - boolean useSmartTimes - - new() { - super() - } - - private def calcCommonPrefix() { - if (!commonPrefixCalculated && tests !== null && tests.size > 0) { - this.commonPrefix = PrefixTools.commonPrefix(tests.keySet.toList) - fireTableDataChanged() - commonPrefixCalculated = true - } - } - - def setModel(LinkedHashMap tests, boolean showDescription, boolean useSmartTimes) { - commonPrefixCalculated = false - this.tests = tests - this.showDescription = showDescription - this.useSmartTimes = useSmartTimes - calcCommonPrefix - fireTableDataChanged() - } - - def updateModel(boolean showDescription) { - this.showDescription = showDescription - fireTableDataChanged() - } - - def getTestIdColumnName() { - calcCommonPrefix - if (commonPrefix === null || commonPrefix == "") { - if (showDescription) { - UtplsqlResources.getString("RUNNER_DESCRIPTION_LABEL") - } else { - UtplsqlResources.getString("RUNNER_TEST_ID_COLUMN") - } - } else { - if (showDescription) { - '''«UtplsqlResources.getString("RUNNER_DESCRIPTION_LABEL")» («commonPrefix»)''' - } else { - commonPrefix - } - } - } - - def getTimeColumnName() { - val timeColumnName = '''«UtplsqlResources.getString("RUNNER_TEST_EXECUTION_TIME_COLUMN")»«IF !useSmartTimes» [s]«ENDIF»''' - return timeColumnName - } - - def getTest(int row) { - val entry = tests.entrySet.get(row) - val test = tests.get(entry.key) - return test - } - - override getRowCount() { - if (tests === null) { - return 0 - } - return tests.size() - } - - override getColumnCount() { - return 5 - } - - override getValueAt(int row, int col) { - val test = tests.entrySet.get(row).value - if (test === null) { - return null - } - switch (col) { - case 0: { - return test.statusIcon - } - case 1: { - return test.warningIcon - } - case 2: { - return test.infoIcon - } - case 3: { - return if(showDescription && test.description !== null) { - test.description - } else { - test.id.substring(if(commonPrefix === null) {0} else {commonPrefix.length}) - } - } - case 4: { - return test.executionTime - } - default: { - return null - } - } - } - - override getColumnName(int col) { - return #["", "", "", UtplsqlResources.getString(if (showDescription) {"RUNNER_DESCRIPTION_LABEL"} else {"RUNNER_TEST_ID_COLUMN"}), - timeColumnName].get(col) - } - - override isCellEditable(int row, int column) { - return false - } - - override getColumnClass(int col) { - switch (col) { - case 0: { - return Icon - } - case 1: { - return Icon - } - case 2: { - return Icon - } - case 3: { - return String - } - case 4: { - return Double - } - default: { - return String - } - } - } -} 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/java/org/utplsql/sqldev/ui/runner/WrapLayout.java b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/WrapLayout.java new file mode 100644 index 00000000..1ccf2f2c --- /dev/null +++ b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/WrapLayout.java @@ -0,0 +1,188 @@ +/* + * Copyright 2019 Philipp Salvisberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.utplsql.sqldev.ui.runner; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Insets; + +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; + +/** + * FlowLayout subclass that fully supports wrapping of components. + * Converted to Xtend based on http://www.camick.com/java/source/WrapLayout.java + * Converted back to Java with small amendments to original code + */ +public class WrapLayout extends FlowLayout { + private static final long serialVersionUID = -4576022991725404247L; + + /** + * Constructs a new WrapLayout with a left alignment and a default + * 5-unit horizontal and vertical gap. + */ + public WrapLayout() { + super(); + } + + /** + * Constructs a new FlowLayout with the specified alignment and a + * default 5-unit horizontal and vertical gap. The value of the alignment + * argument must be one of WrapLayout, WrapLayout, or + * WrapLayout. + * + * @param align + * the alignment value + */ + public WrapLayout(final int align) { + super(align); + } + + /** + * Creates a new flow layout manager with the indicated alignment and the + * indicated horizontal and vertical gaps. + *

+ * The value of the alignment argument must be one of WrapLayout, + * WrapLayout, or WrapLayout. + * + * @param align + * the alignment value + * @param hgap + * the horizontal gap between components + * @param vgap + * the vertical gap between components + */ + public WrapLayout(final int align, final int hgap, final int vgap) { + super(align, hgap, vgap); + } + + /** + * Returns the preferred dimensions for this layout given the visible + * components in the specified target container. + * + * @param target + * the component which needs to be laid out + * @return the preferred dimensions to lay out the subcomponents of the + * specified container + */ + @Override + public Dimension preferredLayoutSize(final Container target) { + return layoutSize(target, true); + } + + /** + * Returns the minimum dimensions needed to layout the visible components + * contained in the specified target container. + * + * @param target + * the component which needs to be laid out + * @return the minimum dimensions to lay out the subcomponents of the specified + * container + */ + @Override + public Dimension minimumLayoutSize(final Container target) { + Dimension minimum = layoutSize(target, false); + minimum.width -= (getHgap() + 1); + return minimum; + } + + /** + * Returns the minimum or preferred dimension needed to layout the target + * container. + * + * @param target + * target to get layout size for + * @param preferred + * should preferred size be calculated + * @return the dimension to layout the target container + */ + private Dimension layoutSize(final Container target, final boolean preferred) { + synchronized (target.getTreeLock()) { + // Each row must fit with the width allocated to the container. + // When the container width = 0, the preferred width of the container + // has not yet been calculated so lets ask for the maximum. + Container container = target; + while (container.getSize().width == 0 && container.getParent() != null) { + container = container.getParent(); + } + int targetWidth = container.getSize().width; + if (targetWidth == 0) { + targetWidth = Integer.MAX_VALUE; + } + int hgap = getHgap(); + int vgap = getVgap(); + Insets insets = target.getInsets(); + int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2); + int maxWidth = targetWidth - horizontalInsetsAndGap; + // Fit components into the allowed width + Dimension dim = new Dimension(0, 0); + int rowWidth = 0; + int rowHeight = 0; + int nmembers = target.getComponentCount(); + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize(); + // Can't add the component to current row. Start a new row. + if (rowWidth + d.width > maxWidth) { + addRow(dim, rowWidth, rowHeight); + rowWidth = 0; + rowHeight = 0; + } + // Add a horizontal gap for all components after the first + if (rowWidth != 0) { + rowWidth += hgap; + } + rowWidth += d.width; + rowHeight = Math.max(rowHeight, d.height); + } + } + addRow(dim, rowWidth, rowHeight); + dim.width += horizontalInsetsAndGap; + dim.height += insets.top + insets.bottom + vgap * 2; + // When using a scroll pane or the DecoratedLookAndFeel we need to + // make sure the preferred size is less than the size of the + // target container so shrinking the container size works + // correctly. Removing the horizontal gap is an easy way to do this. + Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target); + if (scrollPane != null && target.isValid()) { + dim.width -= (hgap + 1); + } + return dim; + } + } + + /** + * A new row has been completed. Use the dimensions of this row to update the + * preferred size for the container. + * + * @param dim + * update the width and height when appropriate + * @param rowWidth + * the width of the row to add + * @param rowHeight + * the height of the row to add + */ + private void addRow(final Dimension dim, final int rowWidth, final int rowHeight) { + dim.width = Math.max(dim.width, rowWidth); + if (dim.height > 0) { + dim.height += getVgap(); + } + dim.height += rowHeight; + } +} diff --git a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/WrapLayout.xtend b/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/WrapLayout.xtend deleted file mode 100644 index dce4695c..00000000 --- a/sqldev/src/main/java/org/utplsql/sqldev/ui/runner/WrapLayout.xtend +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2019 Philipp Salvisberg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.utplsql.sqldev.ui.runner - -import java.awt.Component -import java.awt.Container -import java.awt.Dimension -import java.awt.FlowLayout -import java.awt.Insets -import javax.swing.JScrollPane -import javax.swing.SwingUtilities - -/** - * FlowLayout subclass that fully supports wrapping of components. - * Converted to Xtend based on http://www.camick.com/java/source/WrapLayout.java - */ -class WrapLayout extends FlowLayout { - - /** - * Constructs a new WrapLayout with a left - * alignment and a default 5-unit horizontal and vertical gap. - */ - new() { - super() - } - - /** - * Constructs a new FlowLayout with the specified - * alignment and a default 5-unit horizontal and vertical gap. - * The value of the alignment argument must be one of - * WrapLayout, WrapLayout, - * or WrapLayout. - * @param align the alignment value - */ - new(int align) { - super(align) - } - - /** - * Creates a new flow layout manager with the indicated alignment - * and the indicated horizontal and vertical gaps. - *

- * The value of the alignment argument must be one of - * WrapLayout, WrapLayout, - * or WrapLayout. - * @param align the alignment value - * @param hgap the horizontal gap between components - * @param vgap the vertical gap between components - */ - new(int align, int hgap, int vgap) { - super(align, hgap, vgap) - } - - /** - * Returns the preferred dimensions for this layout given the - * visible components in the specified target container. - * @param target the component which needs to be laid out - * @return the preferred dimensions to lay out the - * subcomponents of the specified container - */ - override Dimension preferredLayoutSize(Container target) { - return layoutSize(target, true) - } - - /** - * Returns the minimum dimensions needed to layout the visible - * components contained in the specified target container. - * @param target the component which needs to be laid out - * @return the minimum dimensions to lay out the - * subcomponents of the specified container - */ - override Dimension minimumLayoutSize(Container target) { - var Dimension minimum = layoutSize(target, false) - minimum.width -= (getHgap() + 1) - return minimum - } - - /** - * Returns the minimum or preferred dimension needed to layout the target - * container. - * @param target target to get layout size for - * @param preferred should preferred size be calculated - * @return the dimension to layout the target container - */ - def private Dimension layoutSize(Container target, boolean preferred) { - synchronized (target.getTreeLock()) { - // Each row must fit with the width allocated to the containter. - // When the container width = 0, the preferred width of the container - // has not yet been calculated so lets ask for the maximum. - var int targetWidth = target.getSize().width - var Container container = target - while (container.getSize().width === 0 && container.getParent() !== null) { - container = container.getParent() - } - targetWidth = container.getSize().width - if(targetWidth === 0) targetWidth = Integer.MAX_VALUE - var int hgap = getHgap() - var int vgap = getVgap() - var Insets insets = target.getInsets() - var int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2) - var int maxWidth = targetWidth - horizontalInsetsAndGap - // Fit components into the allowed width - var Dimension dim = new Dimension(0, 0) - var int rowWidth = 0 - var int rowHeight = 0 - var int nmembers = target.getComponentCount() - for (var int i = 0; i < nmembers; i++) { - var Component m = target.getComponent(i) - if (m.isVisible()) { - var Dimension d = if(preferred) m.getPreferredSize() else m.getMinimumSize() - // Can't add the component to current row. Start a new row. - if (rowWidth + d.width > maxWidth) { - addRow(dim, rowWidth, rowHeight) - rowWidth = 0 - rowHeight = 0 - } - // Add a horizontal gap for all components after the first - if (rowWidth !== 0) { - rowWidth += hgap - } - rowWidth += d.width - rowHeight = Math.max(rowHeight, d.height) - } - } - addRow(dim, rowWidth, rowHeight) - dim.width += horizontalInsetsAndGap - dim.height += insets.top + insets.bottom + vgap * 2 - // When using a scroll pane or the DecoratedLookAndFeel we need to - // make sure the preferred size is less than the size of the - // target containter so shrinking the container size works - // correctly. Removing the horizontal gap is an easy way to do this. - var Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane, target) - if (scrollPane !== null && target.isValid()) { - dim.width -= (hgap + 1) - } - return dim - } - } - - /* - * A new row has been completed. Use the dimensions of this row - * to update the preferred size for the container. - * - * @param dim update the width and height when appropriate - * @param rowWidth the width of the row to add - * @param rowHeight the height of the row to add - */ - def private void addRow(Dimension dim, int rowWidth, int rowHeight) { - dim.width = Math.max(dim.width, rowWidth) - if (dim.height > 0) { - dim.height += getVgap() - } - dim.height += rowHeight - } -} 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="
t
",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]||i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=w:v&&c!=="className"&&(f.nodeName(a,"form")||u.test(c))&&(i=v)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}},value:{get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return f.prop(a,c)?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.attrHooks.title=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i. +shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function(c){var d=c.target,e,g;if(!!y.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=I(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(h=g;h0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};bf.optgroup=bf.option,bf.tbody=bf.tfoot=bf.colgroup=bf.caption=bf.thead,bf.th=bf.td,f.support.htmlSerialize||(bf._default=[1,"div
","
"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!bf[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j +)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bi(a,d),e=bj(a),g=bj(d);for(h=0;e[h];++h)bi(e[h],g[h])}if(b){bh(a,d);if(c){e=bj(a),g=bj(d);for(h=0;e[h];++h)bh(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!ba.test(k))k=b.createTextNode(k);else{k=k.replace(Z,"<$1>");var l=($.exec(k)||["",""])[1].toLowerCase(),m=bf[l]||bf._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=_.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Y.test(k)&&o.insertBefore(b.createTextNode(Y.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bo.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bn.test(g)?g.replace(bn,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bB=/%20/g,bC=/\[\]$/,bD=/\r?\n/g,bE=/#.*$/,bF=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bG=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bH=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bI=/^(?:GET|HEAD)$/,bJ=/^\/\//,bK=/\?/,bL=/)<[^<]*)*<\/script>/gi,bM=/^(?:select|textarea)/i,bN=/\s+/,bO=/([?&])_=[^&]*/,bP=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bQ=f.fn.load,bR={},bS={},bT,bU;try{bT=e.href}catch(bV){bT=c.createElement("a"),bT.href="",bT=bT.href}bU=bP.exec(bT.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bQ)return bQ.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bL,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bM.test(this.nodeName)||bG.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bD,"\r\n")}}):{name:b.name,value:c.replace(bD,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bT,isLocal:bH.test(bU[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bW(bR),ajaxTransport:bW(bS),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?bZ(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b$(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bF.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bE,"").replace(bJ,bU[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bN),d.crossDomain==null&&(r=bP.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bU[1]&&r[2]==bU[2]&&(r[3]||(r[1]==="http:"?80:443))==(bU[3]||(bU[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bX(bR,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bI.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bK.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bO,"$1_="+x);d.url=y+(y===d.url?(bK.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bX(bS,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bB,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn,co=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cr("show",3),a,b,c);for(var g=0,h=this.length;g=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b
";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cu.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cu.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cv(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cv(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); +var hljs=new function(){function l(o){return o.replace(/&/gm,"&").replace(/"}while(x.length||y.length){var u=t().splice(0,1)[0];v+=l(w.substr(q,u.offset-q));q=u.offset;if(u.event=="start"){v+=r(u.node);s.push(u.node)}else{if(u.event=="stop"){var p=s.length;do{p--;var o=s[p];v+=("")}while(o!=u.node);s.splice(p,1);while(p'+l(K[0])+""}else{M+=l(K[0])}O=N.lR.lastIndex;K=N.lR.exec(L)}M+=l(L.substr(O,L.length-O));return M}function J(r,L){if(L.sL&&d[L.sL]){var K=f(L.sL,r);s+=K.keyword_count;return K.value}else{return E(r,L)}}function H(L,r){var K=L.cN?'':"";if(L.rB){p+=K;L.buffer=""}else{if(L.eB){p+=l(r)+K;L.buffer=""}else{p+=K;L.buffer=r}}B.push(L);A+=L.r}function D(N,K,P){var Q=B[B.length-1];if(P){p+=J(Q.buffer+N,Q);return false}var L=y(K,Q);if(L){p+=J(Q.buffer+N,Q);H(L,K);return L.rB}var r=v(B.length-1,K);if(r){var M=Q.cN?"":"";if(Q.rE){p+=J(Q.buffer+N,Q)+M}else{if(Q.eE){p+=J(Q.buffer+N,Q)+M+l(K)}else{p+=J(Q.buffer+N+K,Q)+M}}while(r>1){M=B[B.length-2].cN?"":"";p+=M;r--;B.length--}var O=B[B.length-1];B.length--;B[B.length-1].buffer="";if(O.starts){H(O.starts,"")}return Q.rE}if(w(K,Q)){throw"Illegal"}}var G=d[I];var B=[G.dM];var A=0;var s=0;var p="";try{var u=0;G.dM.buffer="";do{var x=q(C,u);var t=D(x[0],x[1],x[2]);u+=x[0].length;if(!t){u+=x[1].length}}while(!x[2]);if(B.length>1){throw"Illegal"}return{language:I,r:A,keyword_count:s,value:p}}catch(F){if(F=="Illegal"){return{language:null,r:0,keyword_count:0,value:l(C)}}else{throw F}}}function h(){function o(t,s,u){if(t.compiled){return}if(!u){t.bR=c(s,t.b?t.b:"\\B|\\b");if(!t.e&&!t.eW){t.e="\\B|\\b"}if(t.e){t.eR=c(s,t.e)}}if(t.i){t.iR=c(s,t.i)}if(t.r==undefined){t.r=1}if(t.k){t.lR=c(s,t.l||hljs.IR,true)}for(var r in t.k){if(!t.k.hasOwnProperty(r)){continue}if(t.k[r] instanceof Object){t.kG=t.k}else{t.kG={keyword:t.k}}break}if(!t.c){t.c=[]}t.compiled=true;for(var q=0;qx.keyword_count+x.r){x=u}if(u.keyword_count+u.r>w.keyword_count+w.r){x=w;w=u}}}var s=t.className;if(!s.match(w.language)){s=s?(s+" "+w.language):w.language}var o=b(t);if(o.length){var q=document.createElement("pre");q.innerHTML=w.value;w.value=k(o,b(q),A)}if(y){w.value=w.value.replace(/^((<[^>]+>|\t)+)/gm,function(B,E,D,C){return E.replace(/\t/g,y)})}if(p){w.value=w.value.replace(/\n/g,"
")}if(/MSIE [678]/.test(navigator.userAgent)&&t.tagName=="CODE"&&t.parentNode.tagName=="PRE"){var q=t.parentNode;var v=document.createElement("div");v.innerHTML="
"+w.value+"
";t=v.firstChild.firstChild;v.firstChild.cN=q.cN;q.parentNode.replaceChild(v.firstChild,q)}else{t.innerHTML=w.value}t.className=s;t.dataset={};t.dataset.result={language:w.language,kw:w.keyword_count,re:w.r};if(x&&x.language){t.dataset.second_best={language:x.language,kw:x.keyword_count,re:x.r}}}function j(){if(j.called){return}j.called=true;e();var q=document.getElementsByTagName("pre");for(var o=0;o|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\.",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.inherit=function(o,r){var q={};for(var p in o){q[p]=o[p]}if(r){for(var p in r){q[p]=r[p]}}return q}}();hljs.LANGUAGES.ruby=function(){var g="[a-zA-Z_][a-zA-Z0-9_]*(\\!|\\?)?";var a="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?";var n={keyword:{and:1,"false":1,then:1,defined:1,module:1,"in":1,"return":1,redo:1,"if":1,BEGIN:1,retry:1,end:1,"for":1,"true":1,self:1,when:1,next:1,until:1,"do":1,begin:1,unless:1,END:1,rescue:1,nil:1,"else":1,"break":1,undef:1,not:1,"super":1,"class":1,"case":1,require:1,yield:1,alias:1,"while":1,ensure:1,elsif:1,or:1,def:1},keymethods:{__id__:1,__send__:1,abort:1,abs:1,"all?":1,allocate:1,ancestors:1,"any?":1,arity:1,assoc:1,at:1,at_exit:1,autoload:1,"autoload?":1,"between?":1,binding:1,binmode:1,"block_given?":1,call:1,callcc:1,caller:1,capitalize:1,"capitalize!":1,casecmp:1,"catch":1,ceil:1,center:1,chomp:1,"chomp!":1,chop:1,"chop!":1,chr:1,"class":1,class_eval:1,"class_variable_defined?":1,class_variables:1,clear:1,clone:1,close:1,close_read:1,close_write:1,"closed?":1,coerce:1,collect:1,"collect!":1,compact:1,"compact!":1,concat:1,"const_defined?":1,const_get:1,const_missing:1,const_set:1,constants:1,count:1,crypt:1,"default":1,default_proc:1,"delete":1,"delete!":1,delete_at:1,delete_if:1,detect:1,display:1,div:1,divmod:1,downcase:1,"downcase!":1,downto:1,dump:1,dup:1,each:1,each_byte:1,each_index:1,each_key:1,each_line:1,each_pair:1,each_value:1,each_with_index:1,"empty?":1,entries:1,eof:1,"eof?":1,"eql?":1,"equal?":1,"eval":1,exec:1,exit:1,"exit!":1,extend:1,fail:1,fcntl:1,fetch:1,fileno:1,fill:1,find:1,find_all:1,first:1,flatten:1,"flatten!":1,floor:1,flush:1,for_fd:1,foreach:1,fork:1,format:1,freeze:1,"frozen?":1,fsync:1,getc:1,gets:1,global_variables:1,grep:1,gsub:1,"gsub!":1,"has_key?":1,"has_value?":1,hash:1,hex:1,id:1,include:1,"include?":1,included_modules:1,index:1,indexes:1,indices:1,induced_from:1,inject:1,insert:1,inspect:1,instance_eval:1,instance_method:1,instance_methods:1,"instance_of?":1,"instance_variable_defined?":1,instance_variable_get:1,instance_variable_set:1,instance_variables:1,"integer?":1,intern:1,invert:1,ioctl:1,"is_a?":1,isatty:1,"iterator?":1,join:1,"key?":1,keys:1,"kind_of?":1,lambda:1,last:1,length:1,lineno:1,ljust:1,load:1,local_variables:1,loop:1,lstrip:1,"lstrip!":1,map:1,"map!":1,match:1,max:1,"member?":1,merge:1,"merge!":1,method:1,"method_defined?":1,method_missing:1,methods:1,min:1,module_eval:1,modulo:1,name:1,nesting:1,"new":1,next:1,"next!":1,"nil?":1,nitems:1,"nonzero?":1,object_id:1,oct:1,open:1,pack:1,partition:1,pid:1,pipe:1,pop:1,popen:1,pos:1,prec:1,prec_f:1,prec_i:1,print:1,printf:1,private_class_method:1,private_instance_methods:1,"private_method_defined?":1,private_methods:1,proc:1,protected_instance_methods:1,"protected_method_defined?":1,protected_methods:1,public_class_method:1,public_instance_methods:1,"public_method_defined?":1,public_methods:1,push:1,putc:1,puts:1,quo:1,raise:1,rand:1,rassoc:1,read:1,read_nonblock:1,readchar:1,readline:1,readlines:1,readpartial:1,rehash:1,reject:1,"reject!":1,remainder:1,reopen:1,replace:1,require:1,"respond_to?":1,reverse:1,"reverse!":1,reverse_each:1,rewind:1,rindex:1,rjust:1,round:1,rstrip:1,"rstrip!":1,scan:1,seek:1,select:1,send:1,set_trace_func:1,shift:1,singleton_method_added:1,singleton_methods:1,size:1,sleep:1,slice:1,"slice!":1,sort:1,"sort!":1,sort_by:1,split:1,sprintf:1,squeeze:1,"squeeze!":1,srand:1,stat:1,step:1,store:1,strip:1,"strip!":1,sub:1,"sub!":1,succ:1,"succ!":1,sum:1,superclass:1,swapcase:1,"swapcase!":1,sync:1,syscall:1,sysopen:1,sysread:1,sysseek:1,system:1,syswrite:1,taint:1,"tainted?":1,tell:1,test:1,"throw":1,times:1,to_a:1,to_ary:1,to_f:1,to_hash:1,to_i:1,to_int:1,to_io:1,to_proc:1,to_s:1,to_str:1,to_sym:1,tr:1,"tr!":1,tr_s:1,"tr_s!":1,trace_var:1,transpose:1,trap:1,truncate:1,"tty?":1,type:1,ungetc:1,uniq:1,"uniq!":1,unpack:1,unshift:1,untaint:1,untrace_var:1,upcase:1,"upcase!":1,update:1,upto:1,"value?":1,values:1,values_at:1,warn:1,write:1,write_nonblock:1,"zero?":1,zip:1}};var h={cN:"yardoctag",b:"@[A-Za-z]+"};var d={cN:"comment",b:"#",e:"$",c:[h]};var c={cN:"comment",b:"^\\=begin",e:"^\\=end",c:[h],r:10};var b={cN:"comment",b:"^__END__",e:"\\n$"};var u={cN:"subst",b:"#\\{",e:"}",l:g,k:n};var p=[hljs.BE,u];var s={cN:"string",b:"'",e:"'",c:p,r:0};var r={cN:"string",b:'"',e:'"',c:p,r:0};var q={cN:"string",b:"%[qw]?\\(",e:"\\)",c:p,r:10};var o={cN:"string",b:"%[qw]?\\[",e:"\\]",c:p,r:10};var m={cN:"string",b:"%[qw]?{",e:"}",c:p,r:10};var l={cN:"string",b:"%[qw]?<",e:">",c:p,r:10};var k={cN:"string",b:"%[qw]?/",e:"/",c:p,r:10};var j={cN:"string",b:"%[qw]?%",e:"%",c:p,r:10};var i={cN:"string",b:"%[qw]?-",e:"-",c:p,r:10};var t={cN:"string",b:"%[qw]?\\|",e:"\\|",c:p,r:10};var e={cN:"function",b:"\\bdef\\s+",e:" |$|;",l:g,k:n,c:[{cN:"title",b:a,l:g,k:n},{cN:"params",b:"\\(",e:"\\)",l:g,k:n},d,c,b]};var f={cN:"identifier",b:g,l:g,k:n,r:0};var v=[d,c,b,s,r,q,o,m,l,k,j,i,t,{cN:"class",b:"\\b(class|module)\\b",e:"$|;",k:{"class":1,module:1},c:[{cN:"title",b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?",r:0},{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+hljs.IR+"::)?"+hljs.IR}]},d,c,b]},e,{cN:"constant",b:"(::)?([A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:":",c:[s,r,q,o,m,l,k,j,i,t,f],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"number",b:"\\?\\w"},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},f,{b:"("+hljs.RSR+")\\s*",c:[d,c,b,{cN:"regexp",b:"/",e:"/[a-z]*",i:"\\n",c:[hljs.BE]}],r:0}];u.c=v;e.c[1].c=v;return{dM:{l:g,k:n,c:v}}}(); +/*! + Colorbox v1.5.13 - 2014-08-04 + jQuery lightbox and modal window plugin + (c) 2014 Jack Moore - http://www.jacklmoore.com/colorbox + license: http://www.opensource.org/licenses/mit-license.php + */ + +(function ($, document, window) { + var + // Default settings object. + // See http://jacklmoore.com/colorbox for details. + defaults = { + // data sources + html: false, + photo: false, + iframe: false, + inline: false, + + // behavior and appearance + transition: "elastic", + speed: 300, + fadeOut: 300, + width: false, + initialWidth: "600", + innerWidth: false, + maxWidth: false, + height: false, + initialHeight: "450", + innerHeight: false, + maxHeight: false, + scalePhotos: true, + scrolling: true, + opacity: 0.9, + preloading: true, + className: false, + overlayClose: true, + escKey: true, + arrowKey: true, + top: false, + bottom: false, + left: false, + right: false, + fixed: false, + data: undefined, + closeButton: true, + fastIframe: true, + open: false, + reposition: true, + loop: true, + slideshow: false, + slideshowAuto: true, + slideshowSpeed: 2500, + slideshowStart: "start slideshow", + slideshowStop: "stop slideshow", + photoRegex: /\.(gif|png|jp(e|g|eg)|bmp|ico|webp|jxr|svg)((#|\?).*)?$/i, + + // alternate image paths for high-res displays + retinaImage: false, + retinaUrl: false, + retinaSuffix: '@2x.$1', + + // internationalization + current: "image {current} of {total}", + previous: "previous", + next: "next", + close: "close", + xhrError: "This content failed to load.", + imgError: "This image failed to load.", + + // accessbility + returnFocus: true, + trapFocus: true, + + // callbacks + onOpen: false, + onLoad: false, + onComplete: false, + onCleanup: false, + onClosed: false, + + rel: function() { + return this.rel; + }, + href: function() { + // using this.href would give the absolute url, when the href may have been inteded as a selector (e.g. '#container') + return $(this).attr('href'); + }, + title: function() { + return this.title; + } + }, + + // Abstracting the HTML and event identifiers for easy rebranding + colorbox = 'colorbox', + prefix = 'cbox', + boxElement = prefix + 'Element', + + // Events + event_open = prefix + '_open', + event_load = prefix + '_load', + event_complete = prefix + '_complete', + event_cleanup = prefix + '_cleanup', + event_closed = prefix + '_closed', + event_purge = prefix + '_purge', + + // Cached jQuery Object Variables + $overlay, + $box, + $wrap, + $content, + $topBorder, + $leftBorder, + $rightBorder, + $bottomBorder, + $related, + $window, + $loaded, + $loadingBay, + $loadingOverlay, + $title, + $current, + $slideshow, + $next, + $prev, + $close, + $groupControls, + $events = $(''), // $({}) would be prefered, but there is an issue with jQuery 1.4.2 + + // Variables for cached values or use across multiple functions + settings, + interfaceHeight, + interfaceWidth, + loadedHeight, + loadedWidth, + index, + photo, + open, + active, + closing, + loadingTimer, + publicMethod, + div = "div", + requests = 0, + previousCSS = {}, + init; + + // **************** + // HELPER FUNCTIONS + // **************** + + // Convenience function for creating new jQuery objects + function $tag(tag, id, css) { + var element = document.createElement(tag); + + if (id) { + element.id = prefix + id; + } + + if (css) { + element.style.cssText = css; + } + + return $(element); + } + + // Get the window height using innerHeight when available to avoid an issue with iOS + // http://bugs.jquery.com/ticket/6724 + function winheight() { + return window.innerHeight ? window.innerHeight : $(window).height(); + } + + function Settings(element, options) { + if (options !== Object(options)) { + options = {}; + } + + this.cache = {}; + this.el = element; + + this.value = function(key) { + var dataAttr; + + if (this.cache[key] === undefined) { + dataAttr = $(this.el).attr('data-cbox-'+key); + + if (dataAttr !== undefined) { + this.cache[key] = dataAttr; + } else if (options[key] !== undefined) { + this.cache[key] = options[key]; + } else if (defaults[key] !== undefined) { + this.cache[key] = defaults[key]; + } + } + + return this.cache[key]; + }; + + this.get = function(key) { + var value = this.value(key); + return $.isFunction(value) ? value.call(this.el, this) : value; + }; + } + + // Determine the next and previous members in a group. + function getIndex(increment) { + var + max = $related.length, + newIndex = (index + increment) % max; + + return (newIndex < 0) ? max + newIndex : newIndex; + } + + // Convert '%' and 'px' values to integers + function setSize(size, dimension) { + return Math.round((/%/.test(size) ? ((dimension === 'x' ? $window.width() : winheight()) / 100) : 1) * parseInt(size, 10)); + } + + // Checks an href to see if it is a photo. + // There is a force photo option (photo: true) for hrefs that cannot be matched by the regex. + function isImage(settings, url) { + return settings.get('photo') || settings.get('photoRegex').test(url); + } + + function retinaUrl(settings, url) { + return settings.get('retinaUrl') && window.devicePixelRatio > 1 ? url.replace(settings.get('photoRegex'), settings.get('retinaSuffix')) : url; + } + + function trapFocus(e) { + if ('contains' in $box[0] && !$box[0].contains(e.target) && e.target !== $overlay[0]) { + e.stopPropagation(); + $box.focus(); + } + } + + function setClass(str) { + if (setClass.str !== str) { + $box.add($overlay).removeClass(setClass.str).addClass(str); + setClass.str = str; + } + } + + function getRelated(rel) { + index = 0; + + if (rel && rel !== false && rel !== 'nofollow') { + $related = $('.' + boxElement).filter(function () { + var options = $.data(this, colorbox); + var settings = new Settings(this, options); + return (settings.get('rel') === rel); + }); + index = $related.index(settings.el); + + // Check direct calls to Colorbox. + if (index === -1) { + $related = $related.add(settings.el); + index = $related.length - 1; + } + } else { + $related = $(settings.el); + } + } + + function trigger(event) { + // for external use + $(document).trigger(event); + // for internal use + $events.triggerHandler(event); + } + + var slideshow = (function(){ + var active, + className = prefix + "Slideshow_", + click = "click." + prefix, + timeOut; + + function clear () { + clearTimeout(timeOut); + } + + function set() { + if (settings.get('loop') || $related[index + 1]) { + clear(); + timeOut = setTimeout(publicMethod.next, settings.get('slideshowSpeed')); + } + } + + function start() { + $slideshow + .html(settings.get('slideshowStop')) + .unbind(click) + .one(click, stop); + + $events + .bind(event_complete, set) + .bind(event_load, clear); + + $box.removeClass(className + "off").addClass(className + "on"); + } + + function stop() { + clear(); + + $events + .unbind(event_complete, set) + .unbind(event_load, clear); + + $slideshow + .html(settings.get('slideshowStart')) + .unbind(click) + .one(click, function () { + publicMethod.next(); + start(); + }); + + $box.removeClass(className + "on").addClass(className + "off"); + } + + function reset() { + active = false; + $slideshow.hide(); + clear(); + $events + .unbind(event_complete, set) + .unbind(event_load, clear); + $box.removeClass(className + "off " + className + "on"); + } + + return function(){ + if (active) { + if (!settings.get('slideshow')) { + $events.unbind(event_cleanup, reset); + reset(); + } + } else { + if (settings.get('slideshow') && $related[1]) { + active = true; + $events.one(event_cleanup, reset); + if (settings.get('slideshowAuto')) { + start(); + } else { + stop(); + } + $slideshow.show(); + } + } + }; + + }()); + + + function launch(element) { + var options; + + if (!closing) { + + options = $(element).data(colorbox); + + settings = new Settings(element, options); + + getRelated(settings.get('rel')); + + if (!open) { + open = active = true; // Prevents the page-change action from queuing up if the visitor holds down the left or right keys. + + setClass(settings.get('className')); + + // Show colorbox so the sizes can be calculated in older versions of jQuery + $box.css({visibility:'hidden', display:'block', opacity:''}); + + $loaded = $tag(div, 'LoadedContent', 'width:0; height:0; overflow:hidden; visibility:hidden'); + $content.css({width:'', height:''}).append($loaded); + + // Cache values needed for size calculations + interfaceHeight = $topBorder.height() + $bottomBorder.height() + $content.outerHeight(true) - $content.height(); + interfaceWidth = $leftBorder.width() + $rightBorder.width() + $content.outerWidth(true) - $content.width(); + loadedHeight = $loaded.outerHeight(true); + loadedWidth = $loaded.outerWidth(true); + + // Opens inital empty Colorbox prior to content being loaded. + var initialWidth = setSize(settings.get('initialWidth'), 'x'); + var initialHeight = setSize(settings.get('initialHeight'), 'y'); + var maxWidth = settings.get('maxWidth'); + var maxHeight = settings.get('maxHeight'); + + settings.w = (maxWidth !== false ? Math.min(initialWidth, setSize(maxWidth, 'x')) : initialWidth) - loadedWidth - interfaceWidth; + settings.h = (maxHeight !== false ? Math.min(initialHeight, setSize(maxHeight, 'y')) : initialHeight) - loadedHeight - interfaceHeight; + + $loaded.css({width:'', height:settings.h}); + publicMethod.position(); + + trigger(event_open); + settings.get('onOpen'); + + $groupControls.add($title).hide(); + + $box.focus(); + + if (settings.get('trapFocus')) { + // Confine focus to the modal + // Uses event capturing that is not supported in IE8- + if (document.addEventListener) { + + document.addEventListener('focus', trapFocus, true); + + $events.one(event_closed, function () { + document.removeEventListener('focus', trapFocus, true); + }); + } + } + + // Return focus on closing + if (settings.get('returnFocus')) { + $events.one(event_closed, function () { + $(settings.el).focus(); + }); + } + } + + var opacity = parseFloat(settings.get('opacity')); + $overlay.css({ + opacity: opacity === opacity ? opacity : '', + cursor: settings.get('overlayClose') ? 'pointer' : '', + visibility: 'visible' + }).show(); + + if (settings.get('closeButton')) { + $close.html(settings.get('close')).appendTo($content); + } else { + $close.appendTo('
'); // replace with .detach() when dropping jQuery < 1.4 + } + + load(); + } + } + + // Colorbox's markup needs to be added to the DOM prior to being called + // so that the browser will go ahead and load the CSS background images. + function appendHTML() { + if (!$box && document.body) { + init = false; + $window = $(window); + $box = $tag(div).attr({ + id: colorbox, + 'class': $.support.opacity === false ? prefix + 'IE' : '', // class for optional IE8 & lower targeted CSS. + role: 'dialog', + tabindex: '-1' + }).hide(); + $overlay = $tag(div, "Overlay").hide(); + $loadingOverlay = $([$tag(div, "LoadingOverlay")[0],$tag(div, "LoadingGraphic")[0]]); + $wrap = $tag(div, "Wrapper"); + $content = $tag(div, "Content").append( + $title = $tag(div, "Title"), + $current = $tag(div, "Current"), + $prev = $('