mavenized: sql-dk v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Mon, 04 Mar 2019 20:15:24 +0100
branchv_0
changeset 2384a1864c3e867
parent 237 7e08730da258
child 239 39e6c2ad3571
mavenized: sql-dk
.hgignore
distributions/debian/build.sh
distributions/fedora/sql-dk.spec
java/sql-dk/bash-completion.sh
java/sql-dk/build.xml
java/sql-dk/data/info/globalcode/sql/dk/example-config.xml
java/sql-dk/data/info/globalcode/sql/dk/formatter/XhtmlFormatter.css
java/sql-dk/data/info/globalcode/sql/dk/license.txt
java/sql-dk/help-generator.sh
java/sql-dk/manifest.mf
java/sql-dk/nbproject/build-impl.xml
java/sql-dk/nbproject/genfiles.properties
java/sql-dk/nbproject/project.properties
java/sql-dk/nbproject/project.xml
java/sql-dk/pom.xml
java/sql-dk/src/info/globalcode/sql/dk/CLIOptions.java
java/sql-dk/src/info/globalcode/sql/dk/CLIParser.java
java/sql-dk/src/info/globalcode/sql/dk/CLIParserException.java
java/sql-dk/src/info/globalcode/sql/dk/CLIStarter.java
java/sql-dk/src/info/globalcode/sql/dk/ColorfulPrintWriter.java
java/sql-dk/src/info/globalcode/sql/dk/Constants.java
java/sql-dk/src/info/globalcode/sql/dk/DKException.java
java/sql-dk/src/info/globalcode/sql/dk/DatabaseConnection.java
java/sql-dk/src/info/globalcode/sql/dk/Functions.java
java/sql-dk/src/info/globalcode/sql/dk/InfoLister.java
java/sql-dk/src/info/globalcode/sql/dk/InvalidOptionsException.java
java/sql-dk/src/info/globalcode/sql/dk/NamedParameter.java
java/sql-dk/src/info/globalcode/sql/dk/Parameter.java
java/sql-dk/src/info/globalcode/sql/dk/SQLCommand.java
java/sql-dk/src/info/globalcode/sql/dk/SQLCommandNamed.java
java/sql-dk/src/info/globalcode/sql/dk/SQLCommandNumbered.java
java/sql-dk/src/info/globalcode/sql/dk/SQLType.java
java/sql-dk/src/info/globalcode/sql/dk/Xmlns.java
java/sql-dk/src/info/globalcode/sql/dk/batch/Batch.java
java/sql-dk/src/info/globalcode/sql/dk/batch/BatchConstants.java
java/sql-dk/src/info/globalcode/sql/dk/batch/BatchDecoder.java
java/sql-dk/src/info/globalcode/sql/dk/batch/BatchEncoder.java
java/sql-dk/src/info/globalcode/sql/dk/batch/BatchException.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/CommandArgument.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/ConfigurationException.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/ConfigurationProvider.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/DatabaseDefinition.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/FormatterDefinition.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/Loader.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/NameIdentified.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/Properties.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/Property.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/PropertyDeclaration.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/PropertyDeclarations.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/TunnelDefinition.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/jaxb.index
java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/BarChartFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/ColumnDescriptor.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/ColumnsHeader.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/CommonProperties.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/FakeSqlArray.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/Formatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/FormatterContext.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/FormatterException.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/SilentFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/SingleRecordFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/SingleValueFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularWrappingFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/TeXFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/XhtmlFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/XmlFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagement.java
java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagementMBean.java
java/sql-dk/src/info/globalcode/sql/dk/jmx/ManagementUtils.java
java/sql-dk/src/info/globalcode/sql/dk/logging/ColorfulConsoleFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/logging/LoggerInitializer.java
java/sql-dk/src/info/globalcode/sql/dk/logging/LoggerProducer.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIOptions.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIParser.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIParserException.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIStarter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/ColorfulPrintWriter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/Constants.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/DKException.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/DatabaseConnection.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/Functions.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/InfoLister.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/InvalidOptionsException.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/NamedParameter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/Parameter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommand.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommandNamed.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommandNumbered.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLType.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/Xmlns.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/Batch.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchConstants.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchDecoder.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchEncoder.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchException.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/CommandArgument.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Configuration.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/ConfigurationException.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/ConfigurationProvider.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/DatabaseDefinition.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/FormatterDefinition.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Loader.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/NameIdentified.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Properties.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Property.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/PropertyDeclaration.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/PropertyDeclarations.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/TunnelDefinition.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/BarChartFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnDescriptor.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnsHeader.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/CommonProperties.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FakeSqlArray.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/Formatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FormatterContext.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FormatterException.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SilentFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SingleRecordFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SingleValueFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularWrappingFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TeXFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XhtmlFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XmlFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ConnectionManagement.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ConnectionManagementMBean.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ManagementUtils.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/ColorfulConsoleFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/LoggerInitializer.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/LoggerProducer.java
java/sql-dk/src/main/resources/info/globalcode/sql/dk/configuration/jaxb.index
java/sql-dk/src/main/resources/info/globalcode/sql/dk/example-config.xml
java/sql-dk/src/main/resources/info/globalcode/sql/dk/formatter/XhtmlFormatter.css
java/sql-dk/src/main/resources/info/globalcode/sql/dk/license.txt
java/sql-dk/src/test/java/info/globalcode/sql/dk/CLIParserTest.java
java/sql-dk/src/test/java/info/globalcode/sql/dk/FunctionsTest.java
java/sql-dk/test/info/globalcode/sql/dk/CLIParserTest.java
java/sql-dk/test/info/globalcode/sql/dk/FunctionsTest.java
     1.1 --- a/.hgignore	Mon Mar 04 17:06:42 2019 +0100
     1.2 +++ b/.hgignore	Mon Mar 04 20:15:24 2019 +0100
     1.3 @@ -3,8 +3,8 @@
     1.4  *~
     1.5  temp/*
     1.6  
     1.7 -java/sql-dk/data/info/globalcode/sql/dk/version.txt
     1.8 -java/sql-dk/data/info/globalcode/sql/dk/help.txt
     1.9 +java/sql-dk/src/main/resources/info/globalcode/sql/dk/version.txt
    1.10 +java/sql-dk/src/main/resources/info/globalcode/sql/dk/help.txt
    1.11  
    1.12  syntax: regexp
    1.13  
     2.1 --- a/distributions/debian/build.sh	Mon Mar 04 17:06:42 2019 +0100
     2.2 +++ b/distributions/debian/build.sh	Mon Mar 04 20:15:24 2019 +0100
     2.3 @@ -29,12 +29,12 @@
     2.4  cp ../../../xml/config.xsd                                                      config.xsd &&
     2.5  cp ../../../xml/config.rnc                                                      config.rnc &&
     2.6  cp ../../../xml/config.xsl                                                      config.xsl &&
     2.7 -cp ../../../java/sql-dk/dist/sql-dk.jar                                         sql-dk.jar &&
     2.8 +cp ../../../java/sql-dk/target/sql-dk-*.jar                                     sql-dk.jar &&
     2.9  cp ../../../java/jdbc-loopback-driver/target/jdbc-loopback-driver-*.jar         jdbc-loopback-driver.jar &&
    2.10 -cp ../../../java/sql-dk/dist/bash-completion.sh     SQL-DK && # TODO: should be sql-dk – name conflict with sql-dk in /usr/bin/ (equivs bug)
    2.11 +cp ../../../java/sql-dk/target/bash-completion.sh                               SQL-DK && # TODO: should be sql-dk – name conflict with sql-dk in /usr/bin/ (equivs bug)
    2.12  
    2.13  chmod 755 sql-dk &&
    2.14 -chmod 755 SQL-DK &&
    2.15 +chmod 644 SQL-DK &&
    2.16  
    2.17  EMAIL=`echo c3FsLWRrLmRlYmlhbkBwdWIuZnJhbnRvdm8uY3oK | base64 -d` &&
    2.18  NAME="Ing. František Kučera <$EMAIL>" &&
     3.1 --- a/distributions/fedora/sql-dk.spec	Mon Mar 04 17:06:42 2019 +0100
     3.2 +++ b/distributions/fedora/sql-dk.spec	Mon Mar 04 20:15:24 2019 +0100
     3.3 @@ -71,9 +71,9 @@
     3.4  cp ../../../../xml/config.xsd                                                      ${RPM_BUILD_ROOT}/usr/share/doc/sql-dk/
     3.5  cp ../../../../xml/config.rnc                                                      ${RPM_BUILD_ROOT}/usr/share/doc/sql-dk/
     3.6  cp ../../../../xml/config.xsl                                                      ${RPM_BUILD_ROOT}/usr/share/doc/sql-dk/
     3.7 -cp ../../../../java/sql-dk/dist/sql-dk.jar                                         ${RPM_BUILD_ROOT}/usr/share/sql-dk/
     3.8 -cp ../../../../java/jdbc-loopback-driver/dist/jdbc-loopback-driver.jar             ${RPM_BUILD_ROOT}/usr/share/sql-dk/
     3.9 -cp ../../../../java/sql-dk/dist/bash-completion.sh                                 ${RPM_BUILD_ROOT}/etc/bash_completion.d/sql-dk
    3.10 +cp ../../../../java/sql-dk/target/sql-dk-*.jar                                     ${RPM_BUILD_ROOT}/usr/share/sql-dk/
    3.11 +cp ../../../../java/jdbc-loopback-driver/target/jdbc-loopback-driver-*.jar         ${RPM_BUILD_ROOT}/usr/share/sql-dk/
    3.12 +cp ../../../../java/sql-dk/target/bash-completion.sh                               ${RPM_BUILD_ROOT}/etc/bash_completion.d/sql-dk
    3.13  
    3.14  %files
    3.15  %defattr(-,root,root)
    3.16 @@ -81,4 +81,3 @@
    3.17  /usr/share/sql-dk/*
    3.18  /usr/share/doc/sql-dk/*
    3.19  /etc/bash_completion.d/*
    3.20 -
     4.1 --- a/java/sql-dk/bash-completion.sh	Mon Mar 04 17:06:42 2019 +0100
     4.2 +++ b/java/sql-dk/bash-completion.sh	Mon Mar 04 20:15:24 2019 +0100
     4.3 @@ -1,8 +1,8 @@
     4.4  #!/bin/bash
     4.5  
     4.6  cat \
     4.7 -	src/info/globalcode/sql/dk/Constants.java \
     4.8 -	src/info/globalcode/sql/dk/formatting/* \
     4.9 -	src/info/globalcode/sql/dk/CLIParser.java \
    4.10 +	src/main/java/info/globalcode/sql/dk/Constants.java \
    4.11 +	src/main/java/info/globalcode/sql/dk/formatting/* \
    4.12 +	src/main/java/info/globalcode/sql/dk/CLIParser.java \
    4.13  	| ../../scripts/bash_completion.pl
    4.14  
     5.1 --- a/java/sql-dk/build.xml	Mon Mar 04 17:06:42 2019 +0100
     5.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.3 @@ -1,102 +0,0 @@
     5.4 -<?xml version="1.0" encoding="UTF-8"?>
     5.5 -<!--
     5.6 - SQL-DK
     5.7 - Copyright © 2013 František Kučera (frantovo.cz)
     5.8 -
     5.9 - This program is free software: you can redistribute it and/or modify
    5.10 - it under the terms of the GNU General Public License as published by
    5.11 - the Free Software Foundation, either version 3 of the License, or
    5.12 - (at your option) any later version.
    5.13 -
    5.14 - This program is distributed in the hope that it will be useful,
    5.15 - but WITHOUT ANY WARRANTY; without even the implied warranty of
    5.16 - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    5.17 - GNU General Public License for more details.
    5.18 -
    5.19 - You should have received a copy of the GNU General Public License
    5.20 - along with this program. If not, see <http://www.gnu.org/licenses/>.
    5.21 --->
    5.22 -
    5.23 -<!-- You may freely edit this file. See commented blocks below for -->
    5.24 -<!-- some examples of how to customize the build. -->
    5.25 -<!-- (If you delete it and reopen the project it will be recreated.) -->
    5.26 -<!-- By default, only the Clean and Build commands use this build script. -->
    5.27 -<!-- Commands such as Run, Debug, and Test only use this build script if -->
    5.28 -<!-- the Compile on Save feature is turned off for the project. -->
    5.29 -<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
    5.30 -<!-- in the project's Project Properties dialog box.-->
    5.31 -<project name="sql-dk" default="default" basedir=".">
    5.32 -    <description>Builds, tests, and runs the project sql-dk.</description>
    5.33 -    <import file="nbproject/build-impl.xml"/>
    5.34 -    <!--
    5.35 -
    5.36 -    There exist several targets which are by default empty and which can be 
    5.37 -    used for execution of your tasks. These targets are usually executed 
    5.38 -    before and after some main targets. They are: 
    5.39 -
    5.40 -      -pre-init:                 called before initialization of project properties
    5.41 -      -post-init:                called after initialization of project properties
    5.42 -      -pre-compile:              called before javac compilation
    5.43 -      -post-compile:             called after javac compilation
    5.44 -      -pre-compile-single:       called before javac compilation of single file
    5.45 -      -post-compile-single:      called after javac compilation of single file
    5.46 -      -pre-compile-test:         called before javac compilation of JUnit tests
    5.47 -      -post-compile-test:        called after javac compilation of JUnit tests
    5.48 -      -pre-compile-test-single:  called before javac compilation of single JUnit test
    5.49 -      -post-compile-test-single: called after javac compilation of single JUunit test
    5.50 -      -pre-jar:                  called before JAR building
    5.51 -      -post-jar:                 called after JAR building
    5.52 -      -post-clean:               called after cleaning build products
    5.53 -
    5.54 -    (Targets beginning with '-' are not intended to be called on their own.)
    5.55 -
    5.56 -    Example of inserting an obfuscator after compilation could look like this:
    5.57 -
    5.58 -        <target name="-post-compile">
    5.59 -            <obfuscate>
    5.60 -                <fileset dir="${build.classes.dir}"/>
    5.61 -            </obfuscate>
    5.62 -        </target>
    5.63 -
    5.64 -    For list of available properties check the imported 
    5.65 -    nbproject/build-impl.xml file. 
    5.66 -
    5.67 -
    5.68 -    Another way to customize the build is by overriding existing main targets.
    5.69 -    The targets of interest are: 
    5.70 -
    5.71 -      -init-macrodef-javac:     defines macro for javac compilation
    5.72 -      -init-macrodef-junit:     defines macro for junit execution
    5.73 -      -init-macrodef-debug:     defines macro for class debugging
    5.74 -      -init-macrodef-java:      defines macro for class execution
    5.75 -      -do-jar-with-manifest:    JAR building (if you are using a manifest)
    5.76 -      -do-jar-without-manifest: JAR building (if you are not using a manifest)
    5.77 -      run:                      execution of project 
    5.78 -      -javadoc-build:           Javadoc generation
    5.79 -      test-report:              JUnit report generation
    5.80 -
    5.81 -    An example of overriding the target for project execution could look like this:
    5.82 -
    5.83 -        <target name="run" depends="sql-dk-impl.jar">
    5.84 -            <exec dir="bin" executable="launcher.exe">
    5.85 -                <arg file="${dist.jar}"/>
    5.86 -            </exec>
    5.87 -        </target>
    5.88 -
    5.89 -    Notice that the overridden target depends on the jar target and not only on 
    5.90 -    the compile target as the regular run target does. Again, for a list of available 
    5.91 -    properties which you can use, check the target you are overriding in the
    5.92 -    nbproject/build-impl.xml file. 
    5.93 -
    5.94 -	-->
    5.95 -	
    5.96 -	<target name="-pre-compile">
    5.97 -		<exec executable="./version-info.sh" output="data/info/globalcode/sql/dk/version.txt"/>
    5.98 -		<exec executable="./help-generator.sh" output="data/info/globalcode/sql/dk/help.txt"/>
    5.99 -	</target>
   5.100 -	
   5.101 -	<target name="-post-jar">
   5.102 -		<exec executable="./bash-completion.sh" output="dist/bash-completion.sh"/>
   5.103 -	</target>
   5.104 -	
   5.105 -</project>
     6.1 --- a/java/sql-dk/data/info/globalcode/sql/dk/example-config.xml	Mon Mar 04 17:06:42 2019 +0100
     6.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.3 @@ -1,1 +0,0 @@
     6.4 -../../../../../../../xml/config.xml
     6.5 \ No newline at end of file
     7.1 --- a/java/sql-dk/data/info/globalcode/sql/dk/formatter/XhtmlFormatter.css	Mon Mar 04 17:06:42 2019 +0100
     7.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.3 @@ -1,54 +0,0 @@
     7.4 -body {
     7.5 -	font-family: sans-serif;
     7.6 -	font-size: 16px;
     7.7 -	padding-left: 16px;
     7.8 -	padding-right: 16px;
     7.9 -}
    7.10 -
    7.11 -pre {
    7.12 -	background-color: #ddd;
    7.13 -	padding: 6px;
    7.14 -	border-radius: 4px;
    7.15 -	overflow: auto;
    7.16 -
    7.17 -	-moz-tab-size: 4;
    7.18 -	-o-tab-size: 4;
    7.19 -	tab-size: 4;
    7.20 -}
    7.21 -
    7.22 -table {
    7.23 -	border-collapse:collapse;
    7.24 -	box-shadow: 3px 3px 3px grey;
    7.25 -	margin-top: 10px;
    7.26 -	margin-bottom: 20px;
    7.27 -}
    7.28 -td, th {
    7.29 -	border: 1px solid black;
    7.30 -	padding-top: 4px;
    7.31 -	padding-bottom: 4px;
    7.32 -	padding-left: 6px;
    7.33 -	padding-right: 6px;
    7.34 -	font-weight: normal;
    7.35 -}
    7.36 -td.number {
    7.37 -	text-align: right;
    7.38 -}
    7.39 -td.boolean {
    7.40 -	text-align: right;
    7.41 -}
    7.42 -thead tr {
    7.43 -	background: #ddd;
    7.44 -	color:black;
    7.45 -}
    7.46 -tbody tr:hover {
    7.47 -	background-color: #eee;
    7.48 -	color:black;
    7.49 -}
    7.50 -
    7.51 -table ul {
    7.52 -	margin: 0px;
    7.53 -}
    7.54 -
    7.55 -table li {
    7.56 -	padding-right: 10px;
    7.57 -}
     8.1 --- a/java/sql-dk/data/info/globalcode/sql/dk/license.txt	Mon Mar 04 17:06:42 2019 +0100
     8.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.3 @@ -1,1 +0,0 @@
     8.4 -../../../../../../../license/gpl.txt
     8.5 \ No newline at end of file
     9.1 --- a/java/sql-dk/help-generator.sh	Mon Mar 04 17:06:42 2019 +0100
     9.2 +++ b/java/sql-dk/help-generator.sh	Mon Mar 04 20:15:24 2019 +0100
     9.3 @@ -1,7 +1,7 @@
     9.4  #!/bin/bash
     9.5  
     9.6  cat \
     9.7 -	src/info/globalcode/sql/dk/CLIParser.java \
     9.8 -	src/info/globalcode/sql/dk/CLIStarter.java \
     9.9 +	src/main/java/info/globalcode/sql/dk/CLIParser.java \
    9.10 +	src/main/java/info/globalcode/sql/dk/CLIStarter.java \
    9.11  	| ../../scripts/help_generator.pl
    9.12  
    10.1 --- a/java/sql-dk/manifest.mf	Mon Mar 04 17:06:42 2019 +0100
    10.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.3 @@ -1,3 +0,0 @@
    10.4 -Manifest-Version: 1.0
    10.5 -X-COMMENT: Main-Class will be added automatically by build
    10.6 -
    11.1 --- a/java/sql-dk/nbproject/build-impl.xml	Mon Mar 04 17:06:42 2019 +0100
    11.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.3 @@ -1,1429 +0,0 @@
    11.4 -<?xml version="1.0" encoding="UTF-8"?>
    11.5 -<!--
    11.6 -*** GENERATED FROM project.xml - DO NOT EDIT  ***
    11.7 -***         EDIT ../build.xml INSTEAD         ***
    11.8 -
    11.9 -For the purpose of easier reading the script
   11.10 -is divided into following sections:
   11.11 -
   11.12 -  - initialization
   11.13 -  - compilation
   11.14 -  - jar
   11.15 -  - execution
   11.16 -  - debugging
   11.17 -  - javadoc
   11.18 -  - test compilation
   11.19 -  - test execution
   11.20 -  - test debugging
   11.21 -  - applet
   11.22 -  - cleanup
   11.23 -
   11.24 -        -->
   11.25 -<project xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc" basedir=".." default="default" name="sql-dk-impl">
   11.26 -    <fail message="Please build using Ant 1.8.0 or higher.">
   11.27 -        <condition>
   11.28 -            <not>
   11.29 -                <antversion atleast="1.8.0"/>
   11.30 -            </not>
   11.31 -        </condition>
   11.32 -    </fail>
   11.33 -    <target depends="test,jar,javadoc" description="Build and test whole project." name="default"/>
   11.34 -    <!-- 
   11.35 -                ======================
   11.36 -                INITIALIZATION SECTION 
   11.37 -                ======================
   11.38 -            -->
   11.39 -    <target name="-pre-init">
   11.40 -        <!-- Empty placeholder for easier customization. -->
   11.41 -        <!-- You can override this target in the ../build.xml file. -->
   11.42 -    </target>
   11.43 -    <target depends="-pre-init" name="-init-private">
   11.44 -        <property file="nbproject/private/config.properties"/>
   11.45 -        <property file="nbproject/private/configs/${config}.properties"/>
   11.46 -        <property file="nbproject/private/private.properties"/>
   11.47 -    </target>
   11.48 -    <target depends="-pre-init,-init-private" name="-init-user">
   11.49 -        <property file="${user.properties.file}"/>
   11.50 -        <!-- The two properties below are usually overridden -->
   11.51 -        <!-- by the active platform. Just a fallback. -->
   11.52 -        <property name="default.javac.source" value="1.6"/>
   11.53 -        <property name="default.javac.target" value="1.6"/>
   11.54 -    </target>
   11.55 -    <target depends="-pre-init,-init-private,-init-user" name="-init-project">
   11.56 -        <property file="nbproject/configs/${config}.properties"/>
   11.57 -        <property file="nbproject/project.properties"/>
   11.58 -    </target>
   11.59 -    <target depends="-pre-init,-init-private,-init-user,-init-project,-init-macrodef-property" name="-do-init">
   11.60 -        <property name="platform.java" value="${java.home}/bin/java"/>
   11.61 -        <available file="${manifest.file}" property="manifest.available"/>
   11.62 -        <condition property="splashscreen.available">
   11.63 -            <and>
   11.64 -                <not>
   11.65 -                    <equals arg1="${application.splash}" arg2="" trim="true"/>
   11.66 -                </not>
   11.67 -                <available file="${application.splash}"/>
   11.68 -            </and>
   11.69 -        </condition>
   11.70 -        <condition property="main.class.available">
   11.71 -            <and>
   11.72 -                <isset property="main.class"/>
   11.73 -                <not>
   11.74 -                    <equals arg1="${main.class}" arg2="" trim="true"/>
   11.75 -                </not>
   11.76 -            </and>
   11.77 -        </condition>
   11.78 -        <condition property="profile.available">
   11.79 -            <and>
   11.80 -                <isset property="javac.profile"/>
   11.81 -                <length length="0" string="${javac.profile}" when="greater"/>
   11.82 -                <matches pattern="((1\.[89])|9)(\..*)?" string="${javac.source}"/>
   11.83 -            </and>
   11.84 -        </condition>
   11.85 -        <condition property="do.archive">
   11.86 -            <or>
   11.87 -                <not>
   11.88 -                    <istrue value="${jar.archive.disabled}"/>
   11.89 -                </not>
   11.90 -                <istrue value="${not.archive.disabled}"/>
   11.91 -            </or>
   11.92 -        </condition>
   11.93 -        <condition property="do.mkdist">
   11.94 -            <and>
   11.95 -                <isset property="do.archive"/>
   11.96 -                <isset property="libs.CopyLibs.classpath"/>
   11.97 -                <not>
   11.98 -                    <istrue value="${mkdist.disabled}"/>
   11.99 -                </not>
  11.100 -            </and>
  11.101 -        </condition>
  11.102 -        <condition property="do.archive+manifest.available">
  11.103 -            <and>
  11.104 -                <isset property="manifest.available"/>
  11.105 -                <istrue value="${do.archive}"/>
  11.106 -            </and>
  11.107 -        </condition>
  11.108 -        <condition property="do.archive+main.class.available">
  11.109 -            <and>
  11.110 -                <isset property="main.class.available"/>
  11.111 -                <istrue value="${do.archive}"/>
  11.112 -            </and>
  11.113 -        </condition>
  11.114 -        <condition property="do.archive+splashscreen.available">
  11.115 -            <and>
  11.116 -                <isset property="splashscreen.available"/>
  11.117 -                <istrue value="${do.archive}"/>
  11.118 -            </and>
  11.119 -        </condition>
  11.120 -        <condition property="do.archive+profile.available">
  11.121 -            <and>
  11.122 -                <isset property="profile.available"/>
  11.123 -                <istrue value="${do.archive}"/>
  11.124 -            </and>
  11.125 -        </condition>
  11.126 -        <condition property="have.tests">
  11.127 -            <or>
  11.128 -                <available file="${test.src.dir}"/>
  11.129 -            </or>
  11.130 -        </condition>
  11.131 -        <condition property="have.sources">
  11.132 -            <or>
  11.133 -                <available file="${src.dir}"/>
  11.134 -                <available file="${src.data.dir}"/>
  11.135 -            </or>
  11.136 -        </condition>
  11.137 -        <condition property="netbeans.home+have.tests">
  11.138 -            <and>
  11.139 -                <isset property="netbeans.home"/>
  11.140 -                <isset property="have.tests"/>
  11.141 -            </and>
  11.142 -        </condition>
  11.143 -        <condition property="no.javadoc.preview">
  11.144 -            <and>
  11.145 -                <isset property="javadoc.preview"/>
  11.146 -                <isfalse value="${javadoc.preview}"/>
  11.147 -            </and>
  11.148 -        </condition>
  11.149 -        <property name="run.jvmargs" value=""/>
  11.150 -        <property name="run.jvmargs.ide" value=""/>
  11.151 -        <property name="javac.compilerargs" value=""/>
  11.152 -        <property name="work.dir" value="${basedir}"/>
  11.153 -        <condition property="no.deps">
  11.154 -            <and>
  11.155 -                <istrue value="${no.dependencies}"/>
  11.156 -            </and>
  11.157 -        </condition>
  11.158 -        <property name="javac.debug" value="true"/>
  11.159 -        <property name="javadoc.preview" value="true"/>
  11.160 -        <property name="application.args" value=""/>
  11.161 -        <property name="source.encoding" value="${file.encoding}"/>
  11.162 -        <property name="runtime.encoding" value="${source.encoding}"/>
  11.163 -        <property name="manifest.encoding" value="${source.encoding}"/>
  11.164 -        <condition property="javadoc.encoding.used" value="${javadoc.encoding}">
  11.165 -            <and>
  11.166 -                <isset property="javadoc.encoding"/>
  11.167 -                <not>
  11.168 -                    <equals arg1="${javadoc.encoding}" arg2=""/>
  11.169 -                </not>
  11.170 -            </and>
  11.171 -        </condition>
  11.172 -        <property name="javadoc.encoding.used" value="${source.encoding}"/>
  11.173 -        <property name="includes" value="**"/>
  11.174 -        <property name="excludes" value=""/>
  11.175 -        <property name="do.depend" value="false"/>
  11.176 -        <condition property="do.depend.true">
  11.177 -            <istrue value="${do.depend}"/>
  11.178 -        </condition>
  11.179 -        <path id="endorsed.classpath.path" path="${endorsed.classpath}"/>
  11.180 -        <condition else="" property="endorsed.classpath.cmd.line.arg" value="-Xbootclasspath/p:'${toString:endorsed.classpath.path}'">
  11.181 -            <and>
  11.182 -                <isset property="endorsed.classpath"/>
  11.183 -                <not>
  11.184 -                    <equals arg1="${endorsed.classpath}" arg2="" trim="true"/>
  11.185 -                </not>
  11.186 -            </and>
  11.187 -        </condition>
  11.188 -        <condition else="" property="javac.profile.cmd.line.arg" value="-profile ${javac.profile}">
  11.189 -            <isset property="profile.available"/>
  11.190 -        </condition>
  11.191 -        <condition else="false" property="jdkBug6558476">
  11.192 -            <and>
  11.193 -                <matches pattern="1\.[56]" string="${java.specification.version}"/>
  11.194 -                <not>
  11.195 -                    <os family="unix"/>
  11.196 -                </not>
  11.197 -            </and>
  11.198 -        </condition>
  11.199 -        <condition else="false" property="javac.fork">
  11.200 -            <or>
  11.201 -                <istrue value="${jdkBug6558476}"/>
  11.202 -                <istrue value="${javac.external.vm}"/>
  11.203 -            </or>
  11.204 -        </condition>
  11.205 -        <property name="jar.index" value="false"/>
  11.206 -        <property name="jar.index.metainf" value="${jar.index}"/>
  11.207 -        <property name="copylibs.rebase" value="true"/>
  11.208 -        <available file="${meta.inf.dir}/persistence.xml" property="has.persistence.xml"/>
  11.209 -        <condition property="junit.available">
  11.210 -            <or>
  11.211 -                <available classname="org.junit.Test" classpath="${run.test.classpath}"/>
  11.212 -                <available classname="junit.framework.Test" classpath="${run.test.classpath}"/>
  11.213 -            </or>
  11.214 -        </condition>
  11.215 -        <condition property="testng.available">
  11.216 -            <available classname="org.testng.annotations.Test" classpath="${run.test.classpath}"/>
  11.217 -        </condition>
  11.218 -        <condition property="junit+testng.available">
  11.219 -            <and>
  11.220 -                <istrue value="${junit.available}"/>
  11.221 -                <istrue value="${testng.available}"/>
  11.222 -            </and>
  11.223 -        </condition>
  11.224 -        <condition else="testng" property="testng.mode" value="mixed">
  11.225 -            <istrue value="${junit+testng.available}"/>
  11.226 -        </condition>
  11.227 -        <condition else="" property="testng.debug.mode" value="-mixed">
  11.228 -            <istrue value="${junit+testng.available}"/>
  11.229 -        </condition>
  11.230 -        <property name="java.failonerror" value="true"/>
  11.231 -    </target>
  11.232 -    <target name="-post-init">
  11.233 -        <!-- Empty placeholder for easier customization. -->
  11.234 -        <!-- You can override this target in the ../build.xml file. -->
  11.235 -    </target>
  11.236 -    <target depends="-pre-init,-init-private,-init-user,-init-project,-do-init" name="-init-check">
  11.237 -        <fail unless="src.dir">Must set src.dir</fail>
  11.238 -        <fail unless="src.data.dir">Must set src.data.dir</fail>
  11.239 -        <fail unless="test.src.dir">Must set test.src.dir</fail>
  11.240 -        <fail unless="build.dir">Must set build.dir</fail>
  11.241 -        <fail unless="dist.dir">Must set dist.dir</fail>
  11.242 -        <fail unless="build.classes.dir">Must set build.classes.dir</fail>
  11.243 -        <fail unless="dist.javadoc.dir">Must set dist.javadoc.dir</fail>
  11.244 -        <fail unless="build.test.classes.dir">Must set build.test.classes.dir</fail>
  11.245 -        <fail unless="build.test.results.dir">Must set build.test.results.dir</fail>
  11.246 -        <fail unless="build.classes.excludes">Must set build.classes.excludes</fail>
  11.247 -        <fail unless="dist.jar">Must set dist.jar</fail>
  11.248 -    </target>
  11.249 -    <target name="-init-macrodef-property">
  11.250 -        <macrodef name="property" uri="http://www.netbeans.org/ns/j2se-project/1">
  11.251 -            <attribute name="name"/>
  11.252 -            <attribute name="value"/>
  11.253 -            <sequential>
  11.254 -                <property name="@{name}" value="${@{value}}"/>
  11.255 -            </sequential>
  11.256 -        </macrodef>
  11.257 -    </target>
  11.258 -    <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-macrodef-javac-with-processors">
  11.259 -        <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.260 -            <attribute default="${src.dir}:${src.data.dir}" name="srcdir"/>
  11.261 -            <attribute default="${build.classes.dir}" name="destdir"/>
  11.262 -            <attribute default="${javac.classpath}" name="classpath"/>
  11.263 -            <attribute default="${javac.processorpath}" name="processorpath"/>
  11.264 -            <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/>
  11.265 -            <attribute default="${includes}" name="includes"/>
  11.266 -            <attribute default="${excludes}" name="excludes"/>
  11.267 -            <attribute default="${javac.debug}" name="debug"/>
  11.268 -            <attribute default="${empty.dir}" name="sourcepath"/>
  11.269 -            <attribute default="${empty.dir}" name="gensrcdir"/>
  11.270 -            <element name="customize" optional="true"/>
  11.271 -            <sequential>
  11.272 -                <property location="${build.dir}/empty" name="empty.dir"/>
  11.273 -                <mkdir dir="${empty.dir}"/>
  11.274 -                <mkdir dir="@{apgeneratedsrcdir}"/>
  11.275 -                <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}">
  11.276 -                    <src>
  11.277 -                        <dirset dir="@{gensrcdir}" erroronmissingdir="false">
  11.278 -                            <include name="*"/>
  11.279 -                        </dirset>
  11.280 -                    </src>
  11.281 -                    <classpath>
  11.282 -                        <path path="@{classpath}"/>
  11.283 -                    </classpath>
  11.284 -                    <compilerarg line="${endorsed.classpath.cmd.line.arg}"/>
  11.285 -                    <compilerarg line="${javac.profile.cmd.line.arg}"/>
  11.286 -                    <compilerarg line="${javac.compilerargs}"/>
  11.287 -                    <compilerarg value="-processorpath"/>
  11.288 -                    <compilerarg path="@{processorpath}:${empty.dir}"/>
  11.289 -                    <compilerarg line="${ap.processors.internal}"/>
  11.290 -                    <compilerarg line="${annotation.processing.processor.options}"/>
  11.291 -                    <compilerarg value="-s"/>
  11.292 -                    <compilerarg path="@{apgeneratedsrcdir}"/>
  11.293 -                    <compilerarg line="${ap.proc.none.internal}"/>
  11.294 -                    <customize/>
  11.295 -                </javac>
  11.296 -            </sequential>
  11.297 -        </macrodef>
  11.298 -    </target>
  11.299 -    <target depends="-init-ap-cmdline-properties" name="-init-macrodef-javac-without-processors" unless="ap.supported.internal">
  11.300 -        <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.301 -            <attribute default="${src.dir}:${src.data.dir}" name="srcdir"/>
  11.302 -            <attribute default="${build.classes.dir}" name="destdir"/>
  11.303 -            <attribute default="${javac.classpath}" name="classpath"/>
  11.304 -            <attribute default="${javac.processorpath}" name="processorpath"/>
  11.305 -            <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/>
  11.306 -            <attribute default="${includes}" name="includes"/>
  11.307 -            <attribute default="${excludes}" name="excludes"/>
  11.308 -            <attribute default="${javac.debug}" name="debug"/>
  11.309 -            <attribute default="${empty.dir}" name="sourcepath"/>
  11.310 -            <attribute default="${empty.dir}" name="gensrcdir"/>
  11.311 -            <element name="customize" optional="true"/>
  11.312 -            <sequential>
  11.313 -                <property location="${build.dir}/empty" name="empty.dir"/>
  11.314 -                <mkdir dir="${empty.dir}"/>
  11.315 -                <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}">
  11.316 -                    <src>
  11.317 -                        <dirset dir="@{gensrcdir}" erroronmissingdir="false">
  11.318 -                            <include name="*"/>
  11.319 -                        </dirset>
  11.320 -                    </src>
  11.321 -                    <classpath>
  11.322 -                        <path path="@{classpath}"/>
  11.323 -                    </classpath>
  11.324 -                    <compilerarg line="${endorsed.classpath.cmd.line.arg}"/>
  11.325 -                    <compilerarg line="${javac.profile.cmd.line.arg}"/>
  11.326 -                    <compilerarg line="${javac.compilerargs}"/>
  11.327 -                    <customize/>
  11.328 -                </javac>
  11.329 -            </sequential>
  11.330 -        </macrodef>
  11.331 -    </target>
  11.332 -    <target depends="-init-macrodef-javac-with-processors,-init-macrodef-javac-without-processors" name="-init-macrodef-javac">
  11.333 -        <macrodef name="depend" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.334 -            <attribute default="${src.dir}:${src.data.dir}" name="srcdir"/>
  11.335 -            <attribute default="${build.classes.dir}" name="destdir"/>
  11.336 -            <attribute default="${javac.classpath}" name="classpath"/>
  11.337 -            <sequential>
  11.338 -                <depend cache="${build.dir}/depcache" destdir="@{destdir}" excludes="${excludes}" includes="${includes}" srcdir="@{srcdir}">
  11.339 -                    <classpath>
  11.340 -                        <path path="@{classpath}"/>
  11.341 -                    </classpath>
  11.342 -                </depend>
  11.343 -            </sequential>
  11.344 -        </macrodef>
  11.345 -        <macrodef name="force-recompile" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.346 -            <attribute default="${build.classes.dir}" name="destdir"/>
  11.347 -            <sequential>
  11.348 -                <fail unless="javac.includes">Must set javac.includes</fail>
  11.349 -                <pathconvert pathsep="${line.separator}" property="javac.includes.binary">
  11.350 -                    <path>
  11.351 -                        <filelist dir="@{destdir}" files="${javac.includes}"/>
  11.352 -                    </path>
  11.353 -                    <globmapper from="*.java" to="*.class"/>
  11.354 -                </pathconvert>
  11.355 -                <tempfile deleteonexit="true" property="javac.includesfile.binary"/>
  11.356 -                <echo file="${javac.includesfile.binary}" message="${javac.includes.binary}"/>
  11.357 -                <delete>
  11.358 -                    <files includesfile="${javac.includesfile.binary}"/>
  11.359 -                </delete>
  11.360 -                <delete>
  11.361 -                    <fileset file="${javac.includesfile.binary}"/>
  11.362 -                </delete>
  11.363 -            </sequential>
  11.364 -        </macrodef>
  11.365 -    </target>
  11.366 -    <target if="${junit.available}" name="-init-macrodef-junit-init">
  11.367 -        <condition else="false" property="nb.junit.batch" value="true">
  11.368 -            <and>
  11.369 -                <istrue value="${junit.available}"/>
  11.370 -                <not>
  11.371 -                    <isset property="test.method"/>
  11.372 -                </not>
  11.373 -            </and>
  11.374 -        </condition>
  11.375 -        <condition else="false" property="nb.junit.single" value="true">
  11.376 -            <and>
  11.377 -                <istrue value="${junit.available}"/>
  11.378 -                <isset property="test.method"/>
  11.379 -            </and>
  11.380 -        </condition>
  11.381 -    </target>
  11.382 -    <target name="-init-test-properties">
  11.383 -        <property name="test.binaryincludes" value="&lt;nothing&gt;"/>
  11.384 -        <property name="test.binarytestincludes" value=""/>
  11.385 -        <property name="test.binaryexcludes" value=""/>
  11.386 -    </target>
  11.387 -    <target if="${nb.junit.single}" name="-init-macrodef-junit-single" unless="${nb.junit.batch}">
  11.388 -        <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.389 -            <attribute default="${includes}" name="includes"/>
  11.390 -            <attribute default="${excludes}" name="excludes"/>
  11.391 -            <attribute default="**" name="testincludes"/>
  11.392 -            <attribute default="" name="testmethods"/>
  11.393 -            <element name="customize" optional="true"/>
  11.394 -            <sequential>
  11.395 -                <property name="junit.forkmode" value="perTest"/>
  11.396 -                <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
  11.397 -                    <test methods="@{testmethods}" name="@{testincludes}" todir="${build.test.results.dir}"/>
  11.398 -                    <syspropertyset>
  11.399 -                        <propertyref prefix="test-sys-prop."/>
  11.400 -                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
  11.401 -                    </syspropertyset>
  11.402 -                    <formatter type="brief" usefile="false"/>
  11.403 -                    <formatter type="xml"/>
  11.404 -                    <jvmarg value="-ea"/>
  11.405 -                    <customize/>
  11.406 -                </junit>
  11.407 -            </sequential>
  11.408 -        </macrodef>
  11.409 -    </target>
  11.410 -    <target depends="-init-test-properties" if="${nb.junit.batch}" name="-init-macrodef-junit-batch" unless="${nb.junit.single}">
  11.411 -        <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.412 -            <attribute default="${includes}" name="includes"/>
  11.413 -            <attribute default="${excludes}" name="excludes"/>
  11.414 -            <attribute default="**" name="testincludes"/>
  11.415 -            <attribute default="" name="testmethods"/>
  11.416 -            <element name="customize" optional="true"/>
  11.417 -            <sequential>
  11.418 -                <property name="junit.forkmode" value="perTest"/>
  11.419 -                <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
  11.420 -                    <batchtest todir="${build.test.results.dir}">
  11.421 -                        <fileset dir="${test.src.dir}" excludes="@{excludes},${excludes}" includes="@{includes}">
  11.422 -                            <filename name="@{testincludes}"/>
  11.423 -                        </fileset>
  11.424 -                        <fileset dir="${build.test.classes.dir}" excludes="@{excludes},${excludes},${test.binaryexcludes}" includes="${test.binaryincludes}">
  11.425 -                            <filename name="${test.binarytestincludes}"/>
  11.426 -                        </fileset>
  11.427 -                    </batchtest>
  11.428 -                    <syspropertyset>
  11.429 -                        <propertyref prefix="test-sys-prop."/>
  11.430 -                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
  11.431 -                    </syspropertyset>
  11.432 -                    <formatter type="brief" usefile="false"/>
  11.433 -                    <formatter type="xml"/>
  11.434 -                    <jvmarg value="-ea"/>
  11.435 -                    <customize/>
  11.436 -                </junit>
  11.437 -            </sequential>
  11.438 -        </macrodef>
  11.439 -    </target>
  11.440 -    <target depends="-init-macrodef-junit-init,-init-macrodef-junit-single, -init-macrodef-junit-batch" if="${junit.available}" name="-init-macrodef-junit"/>
  11.441 -    <target if="${testng.available}" name="-init-macrodef-testng">
  11.442 -        <macrodef name="testng" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.443 -            <attribute default="${includes}" name="includes"/>
  11.444 -            <attribute default="${excludes}" name="excludes"/>
  11.445 -            <attribute default="**" name="testincludes"/>
  11.446 -            <attribute default="" name="testmethods"/>
  11.447 -            <element name="customize" optional="true"/>
  11.448 -            <sequential>
  11.449 -                <condition else="" property="testng.methods.arg" value="@{testincludes}.@{testmethods}">
  11.450 -                    <isset property="test.method"/>
  11.451 -                </condition>
  11.452 -                <union id="test.set">
  11.453 -                    <fileset dir="${test.src.dir}" excludes="@{excludes},**/*.xml,${excludes}" includes="@{includes}">
  11.454 -                        <filename name="@{testincludes}"/>
  11.455 -                    </fileset>
  11.456 -                </union>
  11.457 -                <taskdef classname="org.testng.TestNGAntTask" classpath="${run.test.classpath}" name="testng"/>
  11.458 -                <testng classfilesetref="test.set" failureProperty="tests.failed" listeners="org.testng.reporters.VerboseReporter" methods="${testng.methods.arg}" mode="${testng.mode}" outputdir="${build.test.results.dir}" suitename="sql-dk" testname="TestNG tests" workingDir="${work.dir}">
  11.459 -                    <xmlfileset dir="${build.test.classes.dir}" includes="@{testincludes}"/>
  11.460 -                    <propertyset>
  11.461 -                        <propertyref prefix="test-sys-prop."/>
  11.462 -                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
  11.463 -                    </propertyset>
  11.464 -                    <customize/>
  11.465 -                </testng>
  11.466 -            </sequential>
  11.467 -        </macrodef>
  11.468 -    </target>
  11.469 -    <target name="-init-macrodef-test-impl">
  11.470 -        <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.471 -            <attribute default="${includes}" name="includes"/>
  11.472 -            <attribute default="${excludes}" name="excludes"/>
  11.473 -            <attribute default="**" name="testincludes"/>
  11.474 -            <attribute default="" name="testmethods"/>
  11.475 -            <element implicit="true" name="customize" optional="true"/>
  11.476 -            <sequential>
  11.477 -                <echo>No tests executed.</echo>
  11.478 -            </sequential>
  11.479 -        </macrodef>
  11.480 -    </target>
  11.481 -    <target depends="-init-macrodef-junit" if="${junit.available}" name="-init-macrodef-junit-impl">
  11.482 -        <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.483 -            <attribute default="${includes}" name="includes"/>
  11.484 -            <attribute default="${excludes}" name="excludes"/>
  11.485 -            <attribute default="**" name="testincludes"/>
  11.486 -            <attribute default="" name="testmethods"/>
  11.487 -            <element implicit="true" name="customize" optional="true"/>
  11.488 -            <sequential>
  11.489 -                <j2seproject3:junit excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
  11.490 -                    <customize/>
  11.491 -                </j2seproject3:junit>
  11.492 -            </sequential>
  11.493 -        </macrodef>
  11.494 -    </target>
  11.495 -    <target depends="-init-macrodef-testng" if="${testng.available}" name="-init-macrodef-testng-impl">
  11.496 -        <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.497 -            <attribute default="${includes}" name="includes"/>
  11.498 -            <attribute default="${excludes}" name="excludes"/>
  11.499 -            <attribute default="**" name="testincludes"/>
  11.500 -            <attribute default="" name="testmethods"/>
  11.501 -            <element implicit="true" name="customize" optional="true"/>
  11.502 -            <sequential>
  11.503 -                <j2seproject3:testng excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
  11.504 -                    <customize/>
  11.505 -                </j2seproject3:testng>
  11.506 -            </sequential>
  11.507 -        </macrodef>
  11.508 -    </target>
  11.509 -    <target depends="-init-macrodef-test-impl,-init-macrodef-junit-impl,-init-macrodef-testng-impl" name="-init-macrodef-test">
  11.510 -        <macrodef name="test" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.511 -            <attribute default="${includes}" name="includes"/>
  11.512 -            <attribute default="${excludes}" name="excludes"/>
  11.513 -            <attribute default="**" name="testincludes"/>
  11.514 -            <attribute default="" name="testmethods"/>
  11.515 -            <sequential>
  11.516 -                <j2seproject3:test-impl excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
  11.517 -                    <customize>
  11.518 -                        <classpath>
  11.519 -                            <path path="${run.test.classpath}"/>
  11.520 -                        </classpath>
  11.521 -                        <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
  11.522 -                        <jvmarg line="${run.jvmargs}"/>
  11.523 -                        <jvmarg line="${run.jvmargs.ide}"/>
  11.524 -                    </customize>
  11.525 -                </j2seproject3:test-impl>
  11.526 -            </sequential>
  11.527 -        </macrodef>
  11.528 -    </target>
  11.529 -    <target if="${junit.available}" name="-init-macrodef-junit-debug" unless="${nb.junit.batch}">
  11.530 -        <macrodef name="junit-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.531 -            <attribute default="${includes}" name="includes"/>
  11.532 -            <attribute default="${excludes}" name="excludes"/>
  11.533 -            <attribute default="**" name="testincludes"/>
  11.534 -            <attribute default="" name="testmethods"/>
  11.535 -            <element name="customize" optional="true"/>
  11.536 -            <sequential>
  11.537 -                <property name="junit.forkmode" value="perTest"/>
  11.538 -                <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
  11.539 -                    <test methods="@{testmethods}" name="@{testincludes}" todir="${build.test.results.dir}"/>
  11.540 -                    <syspropertyset>
  11.541 -                        <propertyref prefix="test-sys-prop."/>
  11.542 -                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
  11.543 -                    </syspropertyset>
  11.544 -                    <formatter type="brief" usefile="false"/>
  11.545 -                    <formatter type="xml"/>
  11.546 -                    <jvmarg value="-ea"/>
  11.547 -                    <jvmarg line="${debug-args-line}"/>
  11.548 -                    <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
  11.549 -                    <customize/>
  11.550 -                </junit>
  11.551 -            </sequential>
  11.552 -        </macrodef>
  11.553 -    </target>
  11.554 -    <target depends="-init-test-properties" if="${nb.junit.batch}" name="-init-macrodef-junit-debug-batch">
  11.555 -        <macrodef name="junit-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.556 -            <attribute default="${includes}" name="includes"/>
  11.557 -            <attribute default="${excludes}" name="excludes"/>
  11.558 -            <attribute default="**" name="testincludes"/>
  11.559 -            <attribute default="" name="testmethods"/>
  11.560 -            <element name="customize" optional="true"/>
  11.561 -            <sequential>
  11.562 -                <property name="junit.forkmode" value="perTest"/>
  11.563 -                <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
  11.564 -                    <batchtest todir="${build.test.results.dir}">
  11.565 -                        <fileset dir="${test.src.dir}" excludes="@{excludes},${excludes}" includes="@{includes}">
  11.566 -                            <filename name="@{testincludes}"/>
  11.567 -                        </fileset>
  11.568 -                        <fileset dir="${build.test.classes.dir}" excludes="@{excludes},${excludes},${test.binaryexcludes}" includes="${test.binaryincludes}">
  11.569 -                            <filename name="${test.binarytestincludes}"/>
  11.570 -                        </fileset>
  11.571 -                    </batchtest>
  11.572 -                    <syspropertyset>
  11.573 -                        <propertyref prefix="test-sys-prop."/>
  11.574 -                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
  11.575 -                    </syspropertyset>
  11.576 -                    <formatter type="brief" usefile="false"/>
  11.577 -                    <formatter type="xml"/>
  11.578 -                    <jvmarg value="-ea"/>
  11.579 -                    <jvmarg line="${debug-args-line}"/>
  11.580 -                    <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
  11.581 -                    <customize/>
  11.582 -                </junit>
  11.583 -            </sequential>
  11.584 -        </macrodef>
  11.585 -    </target>
  11.586 -    <target depends="-init-macrodef-junit-debug,-init-macrodef-junit-debug-batch" if="${junit.available}" name="-init-macrodef-junit-debug-impl">
  11.587 -        <macrodef name="test-debug-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.588 -            <attribute default="${includes}" name="includes"/>
  11.589 -            <attribute default="${excludes}" name="excludes"/>
  11.590 -            <attribute default="**" name="testincludes"/>
  11.591 -            <attribute default="" name="testmethods"/>
  11.592 -            <element implicit="true" name="customize" optional="true"/>
  11.593 -            <sequential>
  11.594 -                <j2seproject3:junit-debug excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
  11.595 -                    <customize/>
  11.596 -                </j2seproject3:junit-debug>
  11.597 -            </sequential>
  11.598 -        </macrodef>
  11.599 -    </target>
  11.600 -    <target if="${testng.available}" name="-init-macrodef-testng-debug">
  11.601 -        <macrodef name="testng-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.602 -            <attribute default="${main.class}" name="testClass"/>
  11.603 -            <attribute default="" name="testMethod"/>
  11.604 -            <element name="customize2" optional="true"/>
  11.605 -            <sequential>
  11.606 -                <condition else="-testclass @{testClass}" property="test.class.or.method" value="-methods @{testClass}.@{testMethod}">
  11.607 -                    <isset property="test.method"/>
  11.608 -                </condition>
  11.609 -                <condition else="-suitename sql-dk -testname @{testClass} ${test.class.or.method}" property="testng.cmd.args" value="@{testClass}">
  11.610 -                    <matches pattern=".*\.xml" string="@{testClass}"/>
  11.611 -                </condition>
  11.612 -                <delete dir="${build.test.results.dir}" quiet="true"/>
  11.613 -                <mkdir dir="${build.test.results.dir}"/>
  11.614 -                <j2seproject3:debug classname="org.testng.TestNG" classpath="${debug.test.classpath}">
  11.615 -                    <customize>
  11.616 -                        <customize2/>
  11.617 -                        <jvmarg value="-ea"/>
  11.618 -                        <arg line="${testng.debug.mode}"/>
  11.619 -                        <arg line="-d ${build.test.results.dir}"/>
  11.620 -                        <arg line="-listener org.testng.reporters.VerboseReporter"/>
  11.621 -                        <arg line="${testng.cmd.args}"/>
  11.622 -                    </customize>
  11.623 -                </j2seproject3:debug>
  11.624 -            </sequential>
  11.625 -        </macrodef>
  11.626 -    </target>
  11.627 -    <target depends="-init-macrodef-testng-debug" if="${testng.available}" name="-init-macrodef-testng-debug-impl">
  11.628 -        <macrodef name="testng-debug-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.629 -            <attribute default="${main.class}" name="testClass"/>
  11.630 -            <attribute default="" name="testMethod"/>
  11.631 -            <element implicit="true" name="customize2" optional="true"/>
  11.632 -            <sequential>
  11.633 -                <j2seproject3:testng-debug testClass="@{testClass}" testMethod="@{testMethod}">
  11.634 -                    <customize2/>
  11.635 -                </j2seproject3:testng-debug>
  11.636 -            </sequential>
  11.637 -        </macrodef>
  11.638 -    </target>
  11.639 -    <target depends="-init-macrodef-junit-debug-impl" if="${junit.available}" name="-init-macrodef-test-debug-junit">
  11.640 -        <macrodef name="test-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.641 -            <attribute default="${includes}" name="includes"/>
  11.642 -            <attribute default="${excludes}" name="excludes"/>
  11.643 -            <attribute default="**" name="testincludes"/>
  11.644 -            <attribute default="" name="testmethods"/>
  11.645 -            <attribute default="${main.class}" name="testClass"/>
  11.646 -            <attribute default="" name="testMethod"/>
  11.647 -            <sequential>
  11.648 -                <j2seproject3:test-debug-impl excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
  11.649 -                    <customize>
  11.650 -                        <classpath>
  11.651 -                            <path path="${run.test.classpath}"/>
  11.652 -                        </classpath>
  11.653 -                        <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
  11.654 -                        <jvmarg line="${run.jvmargs}"/>
  11.655 -                        <jvmarg line="${run.jvmargs.ide}"/>
  11.656 -                    </customize>
  11.657 -                </j2seproject3:test-debug-impl>
  11.658 -            </sequential>
  11.659 -        </macrodef>
  11.660 -    </target>
  11.661 -    <target depends="-init-macrodef-testng-debug-impl" if="${testng.available}" name="-init-macrodef-test-debug-testng">
  11.662 -        <macrodef name="test-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.663 -            <attribute default="${includes}" name="includes"/>
  11.664 -            <attribute default="${excludes}" name="excludes"/>
  11.665 -            <attribute default="**" name="testincludes"/>
  11.666 -            <attribute default="" name="testmethods"/>
  11.667 -            <attribute default="${main.class}" name="testClass"/>
  11.668 -            <attribute default="" name="testMethod"/>
  11.669 -            <sequential>
  11.670 -                <j2seproject3:testng-debug-impl testClass="@{testClass}" testMethod="@{testMethod}">
  11.671 -                    <customize2>
  11.672 -                        <syspropertyset>
  11.673 -                            <propertyref prefix="test-sys-prop."/>
  11.674 -                            <mapper from="test-sys-prop.*" to="*" type="glob"/>
  11.675 -                        </syspropertyset>
  11.676 -                    </customize2>
  11.677 -                </j2seproject3:testng-debug-impl>
  11.678 -            </sequential>
  11.679 -        </macrodef>
  11.680 -    </target>
  11.681 -    <target depends="-init-macrodef-test-debug-junit,-init-macrodef-test-debug-testng" name="-init-macrodef-test-debug"/>
  11.682 -    <!--
  11.683 -                pre NB7.2 profiling section; consider it deprecated
  11.684 -            -->
  11.685 -    <target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile, -profile-init-check" if="profiler.info.jvmargs.agent" name="profile-init"/>
  11.686 -    <target if="profiler.info.jvmargs.agent" name="-profile-pre-init">
  11.687 -        <!-- Empty placeholder for easier customization. -->
  11.688 -        <!-- You can override this target in the ../build.xml file. -->
  11.689 -    </target>
  11.690 -    <target if="profiler.info.jvmargs.agent" name="-profile-post-init">
  11.691 -        <!-- Empty placeholder for easier customization. -->
  11.692 -        <!-- You can override this target in the ../build.xml file. -->
  11.693 -    </target>
  11.694 -    <target if="profiler.info.jvmargs.agent" name="-profile-init-macrodef-profile">
  11.695 -        <macrodef name="resolve">
  11.696 -            <attribute name="name"/>
  11.697 -            <attribute name="value"/>
  11.698 -            <sequential>
  11.699 -                <property name="@{name}" value="${env.@{value}}"/>
  11.700 -            </sequential>
  11.701 -        </macrodef>
  11.702 -        <macrodef name="profile">
  11.703 -            <attribute default="${main.class}" name="classname"/>
  11.704 -            <element name="customize" optional="true"/>
  11.705 -            <sequential>
  11.706 -                <property environment="env"/>
  11.707 -                <resolve name="profiler.current.path" value="${profiler.info.pathvar}"/>
  11.708 -                <java classname="@{classname}" dir="${profiler.info.dir}" failonerror="${java.failonerror}" fork="true" jvm="${profiler.info.jvm}">
  11.709 -                    <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
  11.710 -                    <jvmarg value="${profiler.info.jvmargs.agent}"/>
  11.711 -                    <jvmarg line="${profiler.info.jvmargs}"/>
  11.712 -                    <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
  11.713 -                    <arg line="${application.args}"/>
  11.714 -                    <classpath>
  11.715 -                        <path path="${run.classpath}"/>
  11.716 -                    </classpath>
  11.717 -                    <syspropertyset>
  11.718 -                        <propertyref prefix="run-sys-prop."/>
  11.719 -                        <mapper from="run-sys-prop.*" to="*" type="glob"/>
  11.720 -                    </syspropertyset>
  11.721 -                    <customize/>
  11.722 -                </java>
  11.723 -            </sequential>
  11.724 -        </macrodef>
  11.725 -    </target>
  11.726 -    <target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile" if="profiler.info.jvmargs.agent" name="-profile-init-check">
  11.727 -        <fail unless="profiler.info.jvm">Must set JVM to use for profiling in profiler.info.jvm</fail>
  11.728 -        <fail unless="profiler.info.jvmargs.agent">Must set profiler agent JVM arguments in profiler.info.jvmargs.agent</fail>
  11.729 -    </target>
  11.730 -    <!--
  11.731 -                end of pre NB7.2 profiling section
  11.732 -            -->
  11.733 -    <target depends="-init-debug-args" name="-init-macrodef-nbjpda">
  11.734 -        <macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1">
  11.735 -            <attribute default="${main.class}" name="name"/>
  11.736 -            <attribute default="${debug.classpath}" name="classpath"/>
  11.737 -            <attribute default="" name="stopclassname"/>
  11.738 -            <sequential>
  11.739 -                <nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
  11.740 -                    <classpath>
  11.741 -                        <path path="@{classpath}"/>
  11.742 -                    </classpath>
  11.743 -                </nbjpdastart>
  11.744 -            </sequential>
  11.745 -        </macrodef>
  11.746 -        <macrodef name="nbjpdareload" uri="http://www.netbeans.org/ns/j2se-project/1">
  11.747 -            <attribute default="${build.classes.dir}" name="dir"/>
  11.748 -            <sequential>
  11.749 -                <nbjpdareload>
  11.750 -                    <fileset dir="@{dir}" includes="${fix.classes}">
  11.751 -                        <include name="${fix.includes}*.class"/>
  11.752 -                    </fileset>
  11.753 -                </nbjpdareload>
  11.754 -            </sequential>
  11.755 -        </macrodef>
  11.756 -    </target>
  11.757 -    <target name="-init-debug-args">
  11.758 -        <property name="version-output" value="java version &quot;${ant.java.version}"/>
  11.759 -        <condition property="have-jdk-older-than-1.4">
  11.760 -            <or>
  11.761 -                <contains string="${version-output}" substring="java version &quot;1.0"/>
  11.762 -                <contains string="${version-output}" substring="java version &quot;1.1"/>
  11.763 -                <contains string="${version-output}" substring="java version &quot;1.2"/>
  11.764 -                <contains string="${version-output}" substring="java version &quot;1.3"/>
  11.765 -            </or>
  11.766 -        </condition>
  11.767 -        <condition else="-Xdebug" property="debug-args-line" value="-Xdebug -Xnoagent -Djava.compiler=none">
  11.768 -            <istrue value="${have-jdk-older-than-1.4}"/>
  11.769 -        </condition>
  11.770 -        <condition else="dt_socket" property="debug-transport-by-os" value="dt_shmem">
  11.771 -            <os family="windows"/>
  11.772 -        </condition>
  11.773 -        <condition else="${debug-transport-by-os}" property="debug-transport" value="${debug.transport}">
  11.774 -            <isset property="debug.transport"/>
  11.775 -        </condition>
  11.776 -    </target>
  11.777 -    <target depends="-init-debug-args" name="-init-macrodef-debug">
  11.778 -        <macrodef name="debug" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.779 -            <attribute default="${main.class}" name="classname"/>
  11.780 -            <attribute default="${debug.classpath}" name="classpath"/>
  11.781 -            <element name="customize" optional="true"/>
  11.782 -            <sequential>
  11.783 -                <java classname="@{classname}" dir="${work.dir}" failonerror="${java.failonerror}" fork="true">
  11.784 -                    <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
  11.785 -                    <jvmarg line="${debug-args-line}"/>
  11.786 -                    <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
  11.787 -                    <jvmarg value="-Dfile.encoding=${runtime.encoding}"/>
  11.788 -                    <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/>
  11.789 -                    <jvmarg line="${run.jvmargs}"/>
  11.790 -                    <jvmarg line="${run.jvmargs.ide}"/>
  11.791 -                    <classpath>
  11.792 -                        <path path="@{classpath}"/>
  11.793 -                    </classpath>
  11.794 -                    <syspropertyset>
  11.795 -                        <propertyref prefix="run-sys-prop."/>
  11.796 -                        <mapper from="run-sys-prop.*" to="*" type="glob"/>
  11.797 -                    </syspropertyset>
  11.798 -                    <customize/>
  11.799 -                </java>
  11.800 -            </sequential>
  11.801 -        </macrodef>
  11.802 -    </target>
  11.803 -    <target name="-init-macrodef-java">
  11.804 -        <macrodef name="java" uri="http://www.netbeans.org/ns/j2se-project/1">
  11.805 -            <attribute default="${main.class}" name="classname"/>
  11.806 -            <attribute default="${run.classpath}" name="classpath"/>
  11.807 -            <attribute default="jvm" name="jvm"/>
  11.808 -            <element name="customize" optional="true"/>
  11.809 -            <sequential>
  11.810 -                <java classname="@{classname}" dir="${work.dir}" failonerror="${java.failonerror}" fork="true">
  11.811 -                    <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
  11.812 -                    <jvmarg value="-Dfile.encoding=${runtime.encoding}"/>
  11.813 -                    <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/>
  11.814 -                    <jvmarg line="${run.jvmargs}"/>
  11.815 -                    <jvmarg line="${run.jvmargs.ide}"/>
  11.816 -                    <classpath>
  11.817 -                        <path path="@{classpath}"/>
  11.818 -                    </classpath>
  11.819 -                    <syspropertyset>
  11.820 -                        <propertyref prefix="run-sys-prop."/>
  11.821 -                        <mapper from="run-sys-prop.*" to="*" type="glob"/>
  11.822 -                    </syspropertyset>
  11.823 -                    <customize/>
  11.824 -                </java>
  11.825 -            </sequential>
  11.826 -        </macrodef>
  11.827 -    </target>
  11.828 -    <target name="-init-macrodef-copylibs">
  11.829 -        <macrodef name="copylibs" uri="http://www.netbeans.org/ns/j2se-project/3">
  11.830 -            <attribute default="${manifest.file}" name="manifest"/>
  11.831 -            <element name="customize" optional="true"/>
  11.832 -            <sequential>
  11.833 -                <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
  11.834 -                <pathconvert property="run.classpath.without.build.classes.dir">
  11.835 -                    <path path="${run.classpath}"/>
  11.836 -                    <map from="${build.classes.dir.resolved}" to=""/>
  11.837 -                </pathconvert>
  11.838 -                <pathconvert pathsep=" " property="jar.classpath">
  11.839 -                    <path path="${run.classpath.without.build.classes.dir}"/>
  11.840 -                    <chainedmapper>
  11.841 -                        <flattenmapper/>
  11.842 -                        <filtermapper>
  11.843 -                            <replacestring from=" " to="%20"/>
  11.844 -                        </filtermapper>
  11.845 -                        <globmapper from="*" to="lib/*"/>
  11.846 -                    </chainedmapper>
  11.847 -                </pathconvert>
  11.848 -                <taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" classpath="${libs.CopyLibs.classpath}" name="copylibs"/>
  11.849 -                <copylibs compress="${jar.compress}" excludeFromCopy="${copylibs.excludes}" index="${jar.index}" indexMetaInf="${jar.index.metainf}" jarfile="${dist.jar}" manifest="@{manifest}" manifestencoding="UTF-8" rebase="${copylibs.rebase}" runtimeclasspath="${run.classpath.without.build.classes.dir}">
  11.850 -                    <fileset dir="${build.classes.dir}" excludes="${dist.archive.excludes}"/>
  11.851 -                    <manifest>
  11.852 -                        <attribute name="Class-Path" value="${jar.classpath}"/>
  11.853 -                        <customize/>
  11.854 -                    </manifest>
  11.855 -                </copylibs>
  11.856 -            </sequential>
  11.857 -        </macrodef>
  11.858 -    </target>
  11.859 -    <target name="-init-presetdef-jar">
  11.860 -        <presetdef name="jar" uri="http://www.netbeans.org/ns/j2se-project/1">
  11.861 -            <jar compress="${jar.compress}" index="${jar.index}" jarfile="${dist.jar}" manifestencoding="UTF-8">
  11.862 -                <j2seproject1:fileset dir="${build.classes.dir}" excludes="${dist.archive.excludes}"/>
  11.863 -            </jar>
  11.864 -        </presetdef>
  11.865 -    </target>
  11.866 -    <target name="-init-ap-cmdline-properties">
  11.867 -        <property name="annotation.processing.enabled" value="true"/>
  11.868 -        <property name="annotation.processing.processors.list" value=""/>
  11.869 -        <property name="annotation.processing.processor.options" value=""/>
  11.870 -        <property name="annotation.processing.run.all.processors" value="true"/>
  11.871 -        <property name="javac.processorpath" value="${javac.classpath}"/>
  11.872 -        <property name="javac.test.processorpath" value="${javac.test.classpath}"/>
  11.873 -        <condition property="ap.supported.internal" value="true">
  11.874 -            <not>
  11.875 -                <matches pattern="1\.[0-5](\..*)?" string="${javac.source}"/>
  11.876 -            </not>
  11.877 -        </condition>
  11.878 -    </target>
  11.879 -    <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-ap-cmdline-supported">
  11.880 -        <condition else="" property="ap.processors.internal" value="-processor ${annotation.processing.processors.list}">
  11.881 -            <isfalse value="${annotation.processing.run.all.processors}"/>
  11.882 -        </condition>
  11.883 -        <condition else="" property="ap.proc.none.internal" value="-proc:none">
  11.884 -            <isfalse value="${annotation.processing.enabled}"/>
  11.885 -        </condition>
  11.886 -    </target>
  11.887 -    <target depends="-init-ap-cmdline-properties,-init-ap-cmdline-supported" name="-init-ap-cmdline">
  11.888 -        <property name="ap.cmd.line.internal" value=""/>
  11.889 -    </target>
  11.890 -    <target depends="-pre-init,-init-private,-init-user,-init-project,-do-init,-post-init,-init-check,-init-macrodef-property,-init-macrodef-javac,-init-macrodef-test,-init-macrodef-test-debug,-init-macrodef-nbjpda,-init-macrodef-debug,-init-macrodef-java,-init-presetdef-jar,-init-ap-cmdline" name="init"/>
  11.891 -    <!--
  11.892 -                ===================
  11.893 -                COMPILATION SECTION
  11.894 -                ===================
  11.895 -            -->
  11.896 -    <target name="-deps-jar-init" unless="built-jar.properties">
  11.897 -        <property location="${build.dir}/built-jar.properties" name="built-jar.properties"/>
  11.898 -        <delete file="${built-jar.properties}" quiet="true"/>
  11.899 -    </target>
  11.900 -    <target if="already.built.jar.${basedir}" name="-warn-already-built-jar">
  11.901 -        <echo level="warn" message="Cycle detected: sql-dk was already built"/>
  11.902 -    </target>
  11.903 -    <target depends="init,-deps-jar-init" name="deps-jar" unless="no.deps">
  11.904 -        <mkdir dir="${build.dir}"/>
  11.905 -        <touch file="${built-jar.properties}" verbose="false"/>
  11.906 -        <property file="${built-jar.properties}" prefix="already.built.jar."/>
  11.907 -        <antcall target="-warn-already-built-jar"/>
  11.908 -        <propertyfile file="${built-jar.properties}">
  11.909 -            <entry key="${basedir}" value=""/>
  11.910 -        </propertyfile>
  11.911 -    </target>
  11.912 -    <target depends="init,-check-automatic-build,-clean-after-automatic-build" name="-verify-automatic-build"/>
  11.913 -    <target depends="init" name="-check-automatic-build">
  11.914 -        <available file="${build.classes.dir}/.netbeans_automatic_build" property="netbeans.automatic.build"/>
  11.915 -    </target>
  11.916 -    <target depends="init" if="netbeans.automatic.build" name="-clean-after-automatic-build">
  11.917 -        <antcall target="clean"/>
  11.918 -    </target>
  11.919 -    <target depends="init,deps-jar" name="-pre-pre-compile">
  11.920 -        <mkdir dir="${build.classes.dir}"/>
  11.921 -    </target>
  11.922 -    <target name="-pre-compile">
  11.923 -        <!-- Empty placeholder for easier customization. -->
  11.924 -        <!-- You can override this target in the ../build.xml file. -->
  11.925 -    </target>
  11.926 -    <target if="do.depend.true" name="-compile-depend">
  11.927 -        <pathconvert property="build.generated.subdirs">
  11.928 -            <dirset dir="${build.generated.sources.dir}" erroronmissingdir="false">
  11.929 -                <include name="*"/>
  11.930 -            </dirset>
  11.931 -        </pathconvert>
  11.932 -        <j2seproject3:depend srcdir="${src.dir}:${src.data.dir}:${build.generated.subdirs}"/>
  11.933 -    </target>
  11.934 -    <target depends="init,deps-jar,-pre-pre-compile,-pre-compile, -copy-persistence-xml,-compile-depend" if="have.sources" name="-do-compile">
  11.935 -        <j2seproject3:javac gensrcdir="${build.generated.sources.dir}"/>
  11.936 -        <copy todir="${build.classes.dir}">
  11.937 -            <fileset dir="${src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
  11.938 -            <fileset dir="${src.data.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
  11.939 -        </copy>
  11.940 -    </target>
  11.941 -    <target if="has.persistence.xml" name="-copy-persistence-xml">
  11.942 -        <mkdir dir="${build.classes.dir}/META-INF"/>
  11.943 -        <copy todir="${build.classes.dir}/META-INF">
  11.944 -            <fileset dir="${meta.inf.dir}" includes="persistence.xml orm.xml"/>
  11.945 -        </copy>
  11.946 -    </target>
  11.947 -    <target name="-post-compile">
  11.948 -        <!-- Empty placeholder for easier customization. -->
  11.949 -        <!-- You can override this target in the ../build.xml file. -->
  11.950 -    </target>
  11.951 -    <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project." name="compile"/>
  11.952 -    <target name="-pre-compile-single">
  11.953 -        <!-- Empty placeholder for easier customization. -->
  11.954 -        <!-- You can override this target in the ../build.xml file. -->
  11.955 -    </target>
  11.956 -    <target depends="init,deps-jar,-pre-pre-compile" name="-do-compile-single">
  11.957 -        <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
  11.958 -        <j2seproject3:force-recompile/>
  11.959 -        <j2seproject3:javac excludes="" gensrcdir="${build.generated.sources.dir}" includes="${javac.includes}" sourcepath="${src.dir}:${src.data.dir}"/>
  11.960 -    </target>
  11.961 -    <target name="-post-compile-single">
  11.962 -        <!-- Empty placeholder for easier customization. -->
  11.963 -        <!-- You can override this target in the ../build.xml file. -->
  11.964 -    </target>
  11.965 -    <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single" name="compile-single"/>
  11.966 -    <!--
  11.967 -                ====================
  11.968 -                JAR BUILDING SECTION
  11.969 -                ====================
  11.970 -            -->
  11.971 -    <target depends="init" name="-pre-pre-jar">
  11.972 -        <dirname file="${dist.jar}" property="dist.jar.dir"/>
  11.973 -        <mkdir dir="${dist.jar.dir}"/>
  11.974 -    </target>
  11.975 -    <target name="-pre-jar">
  11.976 -        <!-- Empty placeholder for easier customization. -->
  11.977 -        <!-- You can override this target in the ../build.xml file. -->
  11.978 -    </target>
  11.979 -    <target depends="init" if="do.archive" name="-do-jar-create-manifest" unless="manifest.available">
  11.980 -        <tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/>
  11.981 -        <touch file="${tmp.manifest.file}" verbose="false"/>
  11.982 -    </target>
  11.983 -    <target depends="init" if="do.archive+manifest.available" name="-do-jar-copy-manifest">
  11.984 -        <tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/>
  11.985 -        <copy encoding="${manifest.encoding}" file="${manifest.file}" outputencoding="UTF-8" tofile="${tmp.manifest.file}"/>
  11.986 -    </target>
  11.987 -    <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+main.class.available" name="-do-jar-set-mainclass">
  11.988 -        <manifest encoding="UTF-8" file="${tmp.manifest.file}" mode="update">
  11.989 -            <attribute name="Main-Class" value="${main.class}"/>
  11.990 -        </manifest>
  11.991 -    </target>
  11.992 -    <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+profile.available" name="-do-jar-set-profile">
  11.993 -        <manifest encoding="UTF-8" file="${tmp.manifest.file}" mode="update">
  11.994 -            <attribute name="Profile" value="${javac.profile}"/>
  11.995 -        </manifest>
  11.996 -    </target>
  11.997 -    <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+splashscreen.available" name="-do-jar-set-splashscreen">
  11.998 -        <basename file="${application.splash}" property="splashscreen.basename"/>
  11.999 -        <mkdir dir="${build.classes.dir}/META-INF"/>
 11.1000 -        <copy failonerror="false" file="${application.splash}" todir="${build.classes.dir}/META-INF"/>
 11.1001 -        <manifest encoding="UTF-8" file="${tmp.manifest.file}" mode="update">
 11.1002 -            <attribute name="SplashScreen-Image" value="META-INF/${splashscreen.basename}"/>
 11.1003 -        </manifest>
 11.1004 -    </target>
 11.1005 -    <target depends="init,-init-macrodef-copylibs,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen" if="do.mkdist" name="-do-jar-copylibs">
 11.1006 -        <j2seproject3:copylibs manifest="${tmp.manifest.file}"/>
 11.1007 -        <echo level="info">To run this application from the command line without Ant, try:</echo>
 11.1008 -        <property location="${dist.jar}" name="dist.jar.resolved"/>
 11.1009 -        <echo level="info">java -jar "${dist.jar.resolved}"</echo>
 11.1010 -    </target>
 11.1011 -    <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen" if="do.archive" name="-do-jar-jar" unless="do.mkdist">
 11.1012 -        <j2seproject1:jar manifest="${tmp.manifest.file}"/>
 11.1013 -        <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
 11.1014 -        <property location="${dist.jar}" name="dist.jar.resolved"/>
 11.1015 -        <pathconvert property="run.classpath.with.dist.jar">
 11.1016 -            <path path="${run.classpath}"/>
 11.1017 -            <map from="${build.classes.dir.resolved}" to="${dist.jar.resolved}"/>
 11.1018 -        </pathconvert>
 11.1019 -        <condition else="" property="jar.usage.message" value="To run this application from the command line without Ant, try:${line.separator}${platform.java} -cp ${run.classpath.with.dist.jar} ${main.class}">
 11.1020 -            <isset property="main.class.available"/>
 11.1021 -        </condition>
 11.1022 -        <condition else="debug" property="jar.usage.level" value="info">
 11.1023 -            <isset property="main.class.available"/>
 11.1024 -        </condition>
 11.1025 -        <echo level="${jar.usage.level}" message="${jar.usage.message}"/>
 11.1026 -    </target>
 11.1027 -    <target depends="-do-jar-copylibs" if="do.archive" name="-do-jar-delete-manifest">
 11.1028 -        <delete>
 11.1029 -            <fileset file="${tmp.manifest.file}"/>
 11.1030 -        </delete>
 11.1031 -    </target>
 11.1032 -    <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen,-do-jar-jar,-do-jar-delete-manifest" name="-do-jar-without-libraries"/>
 11.1033 -    <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen,-do-jar-copylibs,-do-jar-delete-manifest" name="-do-jar-with-libraries"/>
 11.1034 -    <target name="-post-jar">
 11.1035 -        <!-- Empty placeholder for easier customization. -->
 11.1036 -        <!-- You can override this target in the ../build.xml file. -->
 11.1037 -    </target>
 11.1038 -    <target depends="init,compile,-pre-jar,-do-jar-without-libraries,-do-jar-with-libraries,-post-jar" name="-do-jar"/>
 11.1039 -    <target depends="init,compile,-pre-jar,-do-jar,-post-jar" description="Build JAR." name="jar"/>
 11.1040 -    <!--
 11.1041 -                =================
 11.1042 -                EXECUTION SECTION
 11.1043 -                =================
 11.1044 -            -->
 11.1045 -    <target depends="init,compile" description="Run a main class." name="run">
 11.1046 -        <j2seproject1:java>
 11.1047 -            <customize>
 11.1048 -                <arg line="${application.args}"/>
 11.1049 -            </customize>
 11.1050 -        </j2seproject1:java>
 11.1051 -    </target>
 11.1052 -    <target name="-do-not-recompile">
 11.1053 -        <property name="javac.includes.binary" value=""/>
 11.1054 -    </target>
 11.1055 -    <target depends="init,compile-single" name="run-single">
 11.1056 -        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
 11.1057 -        <j2seproject1:java classname="${run.class}"/>
 11.1058 -    </target>
 11.1059 -    <target depends="init,compile-test-single" name="run-test-with-main">
 11.1060 -        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
 11.1061 -        <j2seproject1:java classname="${run.class}" classpath="${run.test.classpath}"/>
 11.1062 -    </target>
 11.1063 -    <!--
 11.1064 -                =================
 11.1065 -                DEBUGGING SECTION
 11.1066 -                =================
 11.1067 -            -->
 11.1068 -    <target depends="init" if="netbeans.home" name="-debug-start-debugger">
 11.1069 -        <j2seproject1:nbjpdastart name="${debug.class}"/>
 11.1070 -    </target>
 11.1071 -    <target depends="init" if="netbeans.home" name="-debug-start-debugger-main-test">
 11.1072 -        <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${debug.class}"/>
 11.1073 -    </target>
 11.1074 -    <target depends="init,compile" name="-debug-start-debuggee">
 11.1075 -        <j2seproject3:debug>
 11.1076 -            <customize>
 11.1077 -                <arg line="${application.args}"/>
 11.1078 -            </customize>
 11.1079 -        </j2seproject3:debug>
 11.1080 -    </target>
 11.1081 -    <target depends="init,compile,-debug-start-debugger,-debug-start-debuggee" description="Debug project in IDE." if="netbeans.home" name="debug"/>
 11.1082 -    <target depends="init" if="netbeans.home" name="-debug-start-debugger-stepinto">
 11.1083 -        <j2seproject1:nbjpdastart stopclassname="${main.class}"/>
 11.1084 -    </target>
 11.1085 -    <target depends="init,compile,-debug-start-debugger-stepinto,-debug-start-debuggee" if="netbeans.home" name="debug-stepinto"/>
 11.1086 -    <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-single">
 11.1087 -        <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
 11.1088 -        <j2seproject3:debug classname="${debug.class}"/>
 11.1089 -    </target>
 11.1090 -    <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-single" if="netbeans.home" name="debug-single"/>
 11.1091 -    <target depends="init,compile-test-single" if="netbeans.home" name="-debug-start-debuggee-main-test">
 11.1092 -        <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
 11.1093 -        <j2seproject3:debug classname="${debug.class}" classpath="${debug.test.classpath}"/>
 11.1094 -    </target>
 11.1095 -    <target depends="init,compile-test-single,-debug-start-debugger-main-test,-debug-start-debuggee-main-test" if="netbeans.home" name="debug-test-with-main"/>
 11.1096 -    <target depends="init" name="-pre-debug-fix">
 11.1097 -        <fail unless="fix.includes">Must set fix.includes</fail>
 11.1098 -        <property name="javac.includes" value="${fix.includes}.java"/>
 11.1099 -    </target>
 11.1100 -    <target depends="init,-pre-debug-fix,compile-single" if="netbeans.home" name="-do-debug-fix">
 11.1101 -        <j2seproject1:nbjpdareload/>
 11.1102 -    </target>
 11.1103 -    <target depends="init,-pre-debug-fix,-do-debug-fix" if="netbeans.home" name="debug-fix"/>
 11.1104 -    <!--
 11.1105 -                =================
 11.1106 -                PROFILING SECTION
 11.1107 -                =================
 11.1108 -            -->
 11.1109 -    <!--
 11.1110 -                pre NB7.2 profiler integration
 11.1111 -            -->
 11.1112 -    <target depends="profile-init,compile" description="Profile a project in the IDE." if="profiler.info.jvmargs.agent" name="-profile-pre72">
 11.1113 -        <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
 11.1114 -        <nbprofiledirect>
 11.1115 -            <classpath>
 11.1116 -                <path path="${run.classpath}"/>
 11.1117 -            </classpath>
 11.1118 -        </nbprofiledirect>
 11.1119 -        <profile/>
 11.1120 -    </target>
 11.1121 -    <target depends="profile-init,compile-single" description="Profile a selected class in the IDE." if="profiler.info.jvmargs.agent" name="-profile-single-pre72">
 11.1122 -        <fail unless="profile.class">Must select one file in the IDE or set profile.class</fail>
 11.1123 -        <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
 11.1124 -        <nbprofiledirect>
 11.1125 -            <classpath>
 11.1126 -                <path path="${run.classpath}"/>
 11.1127 -            </classpath>
 11.1128 -        </nbprofiledirect>
 11.1129 -        <profile classname="${profile.class}"/>
 11.1130 -    </target>
 11.1131 -    <target depends="profile-init,compile-single" if="profiler.info.jvmargs.agent" name="-profile-applet-pre72">
 11.1132 -        <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
 11.1133 -        <nbprofiledirect>
 11.1134 -            <classpath>
 11.1135 -                <path path="${run.classpath}"/>
 11.1136 -            </classpath>
 11.1137 -        </nbprofiledirect>
 11.1138 -        <profile classname="sun.applet.AppletViewer">
 11.1139 -            <customize>
 11.1140 -                <arg value="${applet.url}"/>
 11.1141 -            </customize>
 11.1142 -        </profile>
 11.1143 -    </target>
 11.1144 -    <target depends="profile-init,compile-test-single" if="profiler.info.jvmargs.agent" name="-profile-test-single-pre72">
 11.1145 -        <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
 11.1146 -        <nbprofiledirect>
 11.1147 -            <classpath>
 11.1148 -                <path path="${run.test.classpath}"/>
 11.1149 -            </classpath>
 11.1150 -        </nbprofiledirect>
 11.1151 -        <junit dir="${profiler.info.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" jvm="${profiler.info.jvm}" showoutput="true">
 11.1152 -            <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
 11.1153 -            <jvmarg value="${profiler.info.jvmargs.agent}"/>
 11.1154 -            <jvmarg line="${profiler.info.jvmargs}"/>
 11.1155 -            <test name="${profile.class}"/>
 11.1156 -            <classpath>
 11.1157 -                <path path="${run.test.classpath}"/>
 11.1158 -            </classpath>
 11.1159 -            <syspropertyset>
 11.1160 -                <propertyref prefix="test-sys-prop."/>
 11.1161 -                <mapper from="test-sys-prop.*" to="*" type="glob"/>
 11.1162 -            </syspropertyset>
 11.1163 -            <formatter type="brief" usefile="false"/>
 11.1164 -            <formatter type="xml"/>
 11.1165 -        </junit>
 11.1166 -    </target>
 11.1167 -    <!--
 11.1168 -                end of pre NB72 profiling section
 11.1169 -            -->
 11.1170 -    <target if="netbeans.home" name="-profile-check">
 11.1171 -        <condition property="profiler.configured">
 11.1172 -            <or>
 11.1173 -                <contains casesensitive="true" string="${run.jvmargs.ide}" substring="-agentpath:"/>
 11.1174 -                <contains casesensitive="true" string="${run.jvmargs.ide}" substring="-javaagent:"/>
 11.1175 -            </or>
 11.1176 -        </condition>
 11.1177 -    </target>
 11.1178 -    <target depends="-profile-check,-profile-pre72" description="Profile a project in the IDE." if="profiler.configured" name="profile" unless="profiler.info.jvmargs.agent">
 11.1179 -        <startprofiler/>
 11.1180 -        <antcall target="run"/>
 11.1181 -    </target>
 11.1182 -    <target depends="-profile-check,-profile-single-pre72" description="Profile a selected class in the IDE." if="profiler.configured" name="profile-single" unless="profiler.info.jvmargs.agent">
 11.1183 -        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
 11.1184 -        <startprofiler/>
 11.1185 -        <antcall target="run-single"/>
 11.1186 -    </target>
 11.1187 -    <target depends="-profile-test-single-pre72" description="Profile a selected test in the IDE." name="profile-test-single"/>
 11.1188 -    <target depends="-profile-check" description="Profile a selected test in the IDE." if="profiler.configured" name="profile-test" unless="profiler.info.jvmargs">
 11.1189 -        <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
 11.1190 -        <startprofiler/>
 11.1191 -        <antcall target="test-single"/>
 11.1192 -    </target>
 11.1193 -    <target depends="-profile-check" description="Profile a selected class in the IDE." if="profiler.configured" name="profile-test-with-main">
 11.1194 -        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
 11.1195 -        <startprofiler/>
 11.1196 -        <antcall target="run-test-with-main"/>
 11.1197 -    </target>
 11.1198 -    <target depends="-profile-check,-profile-applet-pre72" if="profiler.configured" name="profile-applet" unless="profiler.info.jvmargs.agent">
 11.1199 -        <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
 11.1200 -        <startprofiler/>
 11.1201 -        <antcall target="run-applet"/>
 11.1202 -    </target>
 11.1203 -    <!--
 11.1204 -                ===============
 11.1205 -                JAVADOC SECTION
 11.1206 -                ===============
 11.1207 -            -->
 11.1208 -    <target depends="init" if="have.sources" name="-javadoc-build">
 11.1209 -        <mkdir dir="${dist.javadoc.dir}"/>
 11.1210 -        <condition else="" property="javadoc.endorsed.classpath.cmd.line.arg" value="-J${endorsed.classpath.cmd.line.arg}">
 11.1211 -            <and>
 11.1212 -                <isset property="endorsed.classpath.cmd.line.arg"/>
 11.1213 -                <not>
 11.1214 -                    <equals arg1="${endorsed.classpath.cmd.line.arg}" arg2=""/>
 11.1215 -                </not>
 11.1216 -            </and>
 11.1217 -        </condition>
 11.1218 -        <condition else="" property="bug5101868workaround" value="*.java">
 11.1219 -            <matches pattern="1\.[56](\..*)?" string="${java.version}"/>
 11.1220 -        </condition>
 11.1221 -        <javadoc additionalparam="-J-Dfile.encoding=${file.encoding} ${javadoc.additionalparam}" author="${javadoc.author}" charset="UTF-8" destdir="${dist.javadoc.dir}" docencoding="UTF-8" encoding="${javadoc.encoding.used}" failonerror="true" noindex="${javadoc.noindex}" nonavbar="${javadoc.nonavbar}" notree="${javadoc.notree}" private="${javadoc.private}" source="${javac.source}" splitindex="${javadoc.splitindex}" use="${javadoc.use}" useexternalfile="true" version="${javadoc.version}" windowtitle="${javadoc.windowtitle}">
 11.1222 -            <classpath>
 11.1223 -                <path path="${javac.classpath}"/>
 11.1224 -            </classpath>
 11.1225 -            <fileset dir="${src.dir}" excludes="${bug5101868workaround},${excludes}" includes="${includes}">
 11.1226 -                <filename name="**/*.java"/>
 11.1227 -            </fileset>
 11.1228 -            <fileset dir="${src.data.dir}" excludes="${bug5101868workaround},${excludes}" includes="${includes}">
 11.1229 -                <filename name="**/*.java"/>
 11.1230 -            </fileset>
 11.1231 -            <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
 11.1232 -                <include name="**/*.java"/>
 11.1233 -                <exclude name="*.java"/>
 11.1234 -            </fileset>
 11.1235 -            <arg line="${javadoc.endorsed.classpath.cmd.line.arg}"/>
 11.1236 -        </javadoc>
 11.1237 -        <copy todir="${dist.javadoc.dir}">
 11.1238 -            <fileset dir="${src.dir}" excludes="${excludes}" includes="${includes}">
 11.1239 -                <filename name="**/doc-files/**"/>
 11.1240 -            </fileset>
 11.1241 -            <fileset dir="${src.data.dir}" excludes="${excludes}" includes="${includes}">
 11.1242 -                <filename name="**/doc-files/**"/>
 11.1243 -            </fileset>
 11.1244 -            <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
 11.1245 -                <include name="**/doc-files/**"/>
 11.1246 -            </fileset>
 11.1247 -        </copy>
 11.1248 -    </target>
 11.1249 -    <target depends="init,-javadoc-build" if="netbeans.home" name="-javadoc-browse" unless="no.javadoc.preview">
 11.1250 -        <nbbrowse file="${dist.javadoc.dir}/index.html"/>
 11.1251 -    </target>
 11.1252 -    <target depends="init,-javadoc-build,-javadoc-browse" description="Build Javadoc." name="javadoc"/>
 11.1253 -    <!--
 11.1254 -                =========================
 11.1255 -                TEST COMPILATION SECTION
 11.1256 -                =========================
 11.1257 -            -->
 11.1258 -    <target depends="init,compile" if="have.tests" name="-pre-pre-compile-test">
 11.1259 -        <mkdir dir="${build.test.classes.dir}"/>
 11.1260 -    </target>
 11.1261 -    <target name="-pre-compile-test">
 11.1262 -        <!-- Empty placeholder for easier customization. -->
 11.1263 -        <!-- You can override this target in the ../build.xml file. -->
 11.1264 -    </target>
 11.1265 -    <target if="do.depend.true" name="-compile-test-depend">
 11.1266 -        <j2seproject3:depend classpath="${javac.test.classpath}" destdir="${build.test.classes.dir}" srcdir="${test.src.dir}"/>
 11.1267 -    </target>
 11.1268 -    <target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test,-compile-test-depend" if="have.tests" name="-do-compile-test">
 11.1269 -        <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" processorpath="${javac.test.processorpath}" srcdir="${test.src.dir}"/>
 11.1270 -        <copy todir="${build.test.classes.dir}">
 11.1271 -            <fileset dir="${test.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
 11.1272 -        </copy>
 11.1273 -    </target>
 11.1274 -    <target name="-post-compile-test">
 11.1275 -        <!-- Empty placeholder for easier customization. -->
 11.1276 -        <!-- You can override this target in the ../build.xml file. -->
 11.1277 -    </target>
 11.1278 -    <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test,-do-compile-test,-post-compile-test" name="compile-test"/>
 11.1279 -    <target name="-pre-compile-test-single">
 11.1280 -        <!-- Empty placeholder for easier customization. -->
 11.1281 -        <!-- You can override this target in the ../build.xml file. -->
 11.1282 -    </target>
 11.1283 -    <target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test-single" if="have.tests" name="-do-compile-test-single">
 11.1284 -        <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
 11.1285 -        <j2seproject3:force-recompile destdir="${build.test.classes.dir}"/>
 11.1286 -        <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" excludes="" includes="${javac.includes}" processorpath="${javac.test.processorpath}" sourcepath="${test.src.dir}" srcdir="${test.src.dir}"/>
 11.1287 -        <copy todir="${build.test.classes.dir}">
 11.1288 -            <fileset dir="${test.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
 11.1289 -        </copy>
 11.1290 -    </target>
 11.1291 -    <target name="-post-compile-test-single">
 11.1292 -        <!-- Empty placeholder for easier customization. -->
 11.1293 -        <!-- You can override this target in the ../build.xml file. -->
 11.1294 -    </target>
 11.1295 -    <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single,-do-compile-test-single,-post-compile-test-single" name="compile-test-single"/>
 11.1296 -    <!--
 11.1297 -                =======================
 11.1298 -                TEST EXECUTION SECTION
 11.1299 -                =======================
 11.1300 -            -->
 11.1301 -    <target depends="init" if="have.tests" name="-pre-test-run">
 11.1302 -        <mkdir dir="${build.test.results.dir}"/>
 11.1303 -    </target>
 11.1304 -    <target depends="init,compile-test,-pre-test-run" if="have.tests" name="-do-test-run">
 11.1305 -        <j2seproject3:test includes="${includes}" testincludes="**/*Test.java"/>
 11.1306 -    </target>
 11.1307 -    <target depends="init,compile-test,-pre-test-run,-do-test-run" if="have.tests" name="-post-test-run">
 11.1308 -        <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
 11.1309 -    </target>
 11.1310 -    <target depends="init" if="have.tests" name="test-report"/>
 11.1311 -    <target depends="init" if="netbeans.home+have.tests" name="-test-browse"/>
 11.1312 -    <target depends="init,compile-test,-pre-test-run,-do-test-run,test-report,-post-test-run,-test-browse" description="Run unit tests." name="test"/>
 11.1313 -    <target depends="init" if="have.tests" name="-pre-test-run-single">
 11.1314 -        <mkdir dir="${build.test.results.dir}"/>
 11.1315 -    </target>
 11.1316 -    <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single">
 11.1317 -        <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
 11.1318 -        <j2seproject3:test excludes="" includes="${test.includes}" testincludes="${test.includes}"/>
 11.1319 -    </target>
 11.1320 -    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single" if="have.tests" name="-post-test-run-single">
 11.1321 -        <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
 11.1322 -    </target>
 11.1323 -    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single,-post-test-run-single" description="Run single unit test." name="test-single"/>
 11.1324 -    <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single-method">
 11.1325 -        <fail unless="test.class">Must select some files in the IDE or set test.class</fail>
 11.1326 -        <fail unless="test.method">Must select some method in the IDE or set test.method</fail>
 11.1327 -        <j2seproject3:test excludes="" includes="${javac.includes}" testincludes="${test.class}" testmethods="${test.method}"/>
 11.1328 -    </target>
 11.1329 -    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single-method" if="have.tests" name="-post-test-run-single-method">
 11.1330 -        <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
 11.1331 -    </target>
 11.1332 -    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single-method,-post-test-run-single-method" description="Run single unit test." name="test-single-method"/>
 11.1333 -    <!--
 11.1334 -                =======================
 11.1335 -                TEST DEBUGGING SECTION
 11.1336 -                =======================
 11.1337 -            -->
 11.1338 -    <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-debug-start-debuggee-test">
 11.1339 -        <fail unless="test.class">Must select one file in the IDE or set test.class</fail>
 11.1340 -        <j2seproject3:test-debug excludes="" includes="${javac.includes}" testClass="${test.class}" testincludes="${javac.includes}"/>
 11.1341 -    </target>
 11.1342 -    <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-debug-start-debuggee-test-method">
 11.1343 -        <fail unless="test.class">Must select one file in the IDE or set test.class</fail>
 11.1344 -        <fail unless="test.method">Must select some method in the IDE or set test.method</fail>
 11.1345 -        <j2seproject3:test-debug excludes="" includes="${javac.includes}" testClass="${test.class}" testMethod="${test.method}" testincludes="${test.class}" testmethods="${test.method}"/>
 11.1346 -    </target>
 11.1347 -    <target depends="init,compile-test" if="netbeans.home+have.tests" name="-debug-start-debugger-test">
 11.1348 -        <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${test.class}"/>
 11.1349 -    </target>
 11.1350 -    <target depends="init,compile-test-single,-debug-start-debugger-test,-debug-start-debuggee-test" name="debug-test"/>
 11.1351 -    <target depends="init,compile-test-single,-debug-start-debugger-test,-debug-start-debuggee-test-method" name="debug-test-method"/>
 11.1352 -    <target depends="init,-pre-debug-fix,compile-test-single" if="netbeans.home" name="-do-debug-fix-test">
 11.1353 -        <j2seproject1:nbjpdareload dir="${build.test.classes.dir}"/>
 11.1354 -    </target>
 11.1355 -    <target depends="init,-pre-debug-fix,-do-debug-fix-test" if="netbeans.home" name="debug-fix-test"/>
 11.1356 -    <!--
 11.1357 -                =========================
 11.1358 -                APPLET EXECUTION SECTION
 11.1359 -                =========================
 11.1360 -            -->
 11.1361 -    <target depends="init,compile-single" name="run-applet">
 11.1362 -        <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
 11.1363 -        <j2seproject1:java classname="sun.applet.AppletViewer">
 11.1364 -            <customize>
 11.1365 -                <arg value="${applet.url}"/>
 11.1366 -            </customize>
 11.1367 -        </j2seproject1:java>
 11.1368 -    </target>
 11.1369 -    <!--
 11.1370 -                =========================
 11.1371 -                APPLET DEBUGGING  SECTION
 11.1372 -                =========================
 11.1373 -            -->
 11.1374 -    <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-applet">
 11.1375 -        <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
 11.1376 -        <j2seproject3:debug classname="sun.applet.AppletViewer">
 11.1377 -            <customize>
 11.1378 -                <arg value="${applet.url}"/>
 11.1379 -            </customize>
 11.1380 -        </j2seproject3:debug>
 11.1381 -    </target>
 11.1382 -    <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-applet" if="netbeans.home" name="debug-applet"/>
 11.1383 -    <!--
 11.1384 -                ===============
 11.1385 -                CLEANUP SECTION
 11.1386 -                ===============
 11.1387 -            -->
 11.1388 -    <target name="-deps-clean-init" unless="built-clean.properties">
 11.1389 -        <property location="${build.dir}/built-clean.properties" name="built-clean.properties"/>
 11.1390 -        <delete file="${built-clean.properties}" quiet="true"/>
 11.1391 -    </target>
 11.1392 -    <target if="already.built.clean.${basedir}" name="-warn-already-built-clean">
 11.1393 -        <echo level="warn" message="Cycle detected: sql-dk was already built"/>
 11.1394 -    </target>
 11.1395 -    <target depends="init,-deps-clean-init" name="deps-clean" unless="no.deps">
 11.1396 -        <mkdir dir="${build.dir}"/>
 11.1397 -        <touch file="${built-clean.properties}" verbose="false"/>
 11.1398 -        <property file="${built-clean.properties}" prefix="already.built.clean."/>
 11.1399 -        <antcall target="-warn-already-built-clean"/>
 11.1400 -        <propertyfile file="${built-clean.properties}">
 11.1401 -            <entry key="${basedir}" value=""/>
 11.1402 -        </propertyfile>
 11.1403 -    </target>
 11.1404 -    <target depends="init" name="-do-clean">
 11.1405 -        <delete dir="${build.dir}"/>
 11.1406 -        <delete dir="${dist.dir}" followsymlinks="false" includeemptydirs="true"/>
 11.1407 -    </target>
 11.1408 -    <target name="-post-clean">
 11.1409 -        <!-- Empty placeholder for easier customization. -->
 11.1410 -        <!-- You can override this target in the ../build.xml file. -->
 11.1411 -    </target>
 11.1412 -    <target depends="init,deps-clean,-do-clean,-post-clean" description="Clean build products." name="clean"/>
 11.1413 -    <target name="-check-call-dep">
 11.1414 -        <property file="${call.built.properties}" prefix="already.built."/>
 11.1415 -        <condition property="should.call.dep">
 11.1416 -            <and>
 11.1417 -                <not>
 11.1418 -                    <isset property="already.built.${call.subproject}"/>
 11.1419 -                </not>
 11.1420 -                <available file="${call.script}"/>
 11.1421 -            </and>
 11.1422 -        </condition>
 11.1423 -    </target>
 11.1424 -    <target depends="-check-call-dep" if="should.call.dep" name="-maybe-call-dep">
 11.1425 -        <ant antfile="${call.script}" inheritall="false" target="${call.target}">
 11.1426 -            <propertyset>
 11.1427 -                <propertyref prefix="transfer."/>
 11.1428 -                <mapper from="transfer.*" to="*" type="glob"/>
 11.1429 -            </propertyset>
 11.1430 -        </ant>
 11.1431 -    </target>
 11.1432 -</project>
    12.1 --- a/java/sql-dk/nbproject/genfiles.properties	Mon Mar 04 17:06:42 2019 +0100
    12.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.3 @@ -1,8 +0,0 @@
    12.4 -build.xml.data.CRC32=b51b939b
    12.5 -build.xml.script.CRC32=f55b3340
    12.6 -build.xml.stylesheet.CRC32=28e38971@1.56.1.46
    12.7 -# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
    12.8 -# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
    12.9 -nbproject/build-impl.xml.data.CRC32=b51b939b
   12.10 -nbproject/build-impl.xml.script.CRC32=6a0815e1
   12.11 -nbproject/build-impl.xml.stylesheet.CRC32=830a3534@1.80.1.48
    13.1 --- a/java/sql-dk/nbproject/project.properties	Mon Mar 04 17:06:42 2019 +0100
    13.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.3 @@ -1,75 +0,0 @@
    13.4 -annotation.processing.enabled=true
    13.5 -annotation.processing.enabled.in.editor=false
    13.6 -annotation.processing.processors.list=
    13.7 -annotation.processing.run.all.processors=true
    13.8 -annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
    13.9 -application.title=sql-dk
   13.10 -application.vendor=fiki
   13.11 -build.classes.dir=${build.dir}/classes
   13.12 -build.classes.excludes=**/*.java,**/*.form
   13.13 -# This directory is removed when the project is cleaned:
   13.14 -build.dir=build
   13.15 -build.generated.dir=${build.dir}/generated
   13.16 -build.generated.sources.dir=${build.dir}/generated-sources
   13.17 -# Only compile against the classpath explicitly listed here:
   13.18 -build.sysclasspath=ignore
   13.19 -build.test.classes.dir=${build.dir}/test/classes
   13.20 -build.test.results.dir=${build.dir}/test/results
   13.21 -# Uncomment to specify the preferred debugger connection transport:
   13.22 -#debug.transport=dt_socket
   13.23 -debug.classpath=\
   13.24 -    ${run.classpath}
   13.25 -debug.test.classpath=\
   13.26 -    ${run.test.classpath}
   13.27 -# This directory is removed when the project is cleaned:
   13.28 -dist.dir=dist
   13.29 -dist.jar=${dist.dir}/sql-dk.jar
   13.30 -dist.javadoc.dir=${dist.dir}/javadoc
   13.31 -endorsed.classpath=
   13.32 -excludes=
   13.33 -includes=**
   13.34 -jar.compress=false
   13.35 -javac.classpath=
   13.36 -# Space-separated list of extra javac options
   13.37 -javac.compilerargs=
   13.38 -javac.deprecation=false
   13.39 -javac.processorpath=\
   13.40 -    ${javac.classpath}
   13.41 -javac.source=1.8
   13.42 -javac.target=1.8
   13.43 -javac.test.classpath=\
   13.44 -    ${javac.classpath}:\
   13.45 -    ${build.classes.dir}:\
   13.46 -    ${libs.testng.classpath}
   13.47 -javac.test.processorpath=\
   13.48 -    ${javac.test.classpath}
   13.49 -javadoc.additionalparam=
   13.50 -javadoc.author=false
   13.51 -javadoc.encoding=${source.encoding}
   13.52 -javadoc.noindex=false
   13.53 -javadoc.nonavbar=false
   13.54 -javadoc.notree=false
   13.55 -javadoc.private=false
   13.56 -javadoc.splitindex=true
   13.57 -javadoc.use=true
   13.58 -javadoc.version=false
   13.59 -javadoc.windowtitle=
   13.60 -main.class=info.globalcode.sql.dk.CLIStarter
   13.61 -manifest.file=manifest.mf
   13.62 -meta.inf.dir=${src.dir}/META-INF
   13.63 -mkdist.disabled=false
   13.64 -platform.active=default_platform
   13.65 -run.classpath=\
   13.66 -    ${javac.classpath}:\
   13.67 -    ${build.classes.dir}
   13.68 -# Space-separated list of JVM arguments used when running the project.
   13.69 -# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
   13.70 -# To set system properties for unit tests define test-sys-prop.name=value:
   13.71 -run.jvmargs=
   13.72 -run.test.classpath=\
   13.73 -    ${javac.test.classpath}:\
   13.74 -    ${build.test.classes.dir}
   13.75 -source.encoding=UTF-8
   13.76 -src.data.dir=data
   13.77 -src.dir=src
   13.78 -test.src.dir=test
    14.1 --- a/java/sql-dk/nbproject/project.xml	Mon Mar 04 17:06:42 2019 +0100
    14.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.3 @@ -1,16 +0,0 @@
    14.4 -<?xml version="1.0" encoding="UTF-8"?>
    14.5 -<project xmlns="http://www.netbeans.org/ns/project/1">
    14.6 -    <type>org.netbeans.modules.java.j2seproject</type>
    14.7 -    <configuration>
    14.8 -        <data xmlns="http://www.netbeans.org/ns/j2se-project/3">
    14.9 -            <name>sql-dk</name>
   14.10 -            <source-roots>
   14.11 -                <root id="src.dir"/>
   14.12 -                <root id="src.data.dir"/>
   14.13 -            </source-roots>
   14.14 -            <test-roots>
   14.15 -                <root id="test.src.dir"/>
   14.16 -            </test-roots>
   14.17 -        </data>
   14.18 -    </configuration>
   14.19 -</project>
    15.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.2 +++ b/java/sql-dk/pom.xml	Mon Mar 04 20:15:24 2019 +0100
    15.3 @@ -0,0 +1,69 @@
    15.4 +<?xml version="1.0" encoding="UTF-8"?>
    15.5 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    15.6 +	<modelVersion>4.0.0</modelVersion>
    15.7 +	<groupId>info.globalcode.sql.dk</groupId>
    15.8 +	<artifactId>sql-dk</artifactId>
    15.9 +	<version>0.10-SNAPSHOT</version>
   15.10 +	<packaging>jar</packaging>
   15.11 +	
   15.12 +	<dependencies>
   15.13 +		<dependency>
   15.14 +			<groupId>org.testng</groupId>
   15.15 +			<artifactId>testng</artifactId>
   15.16 +			<version>6.9.9</version>
   15.17 +			<scope>test</scope>
   15.18 +		</dependency>
   15.19 +	</dependencies>
   15.20 +	
   15.21 +	<properties>
   15.22 +		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   15.23 +		<maven.compiler.source>1.8</maven.compiler.source>
   15.24 +		<maven.compiler.target>1.8</maven.compiler.target>
   15.25 +	</properties>
   15.26 +	
   15.27 +	<build>
   15.28 +		<plugins>
   15.29 +			<plugin>
   15.30 +				<groupId>org.codehaus.mojo</groupId>
   15.31 +				<artifactId>exec-maven-plugin</artifactId>
   15.32 +				<version>1.6.0</version>
   15.33 +				<executions>
   15.34 +					<execution>
   15.35 +						<id>version-info</id>
   15.36 +						<phase>generate-sources</phase>
   15.37 +						<goals>
   15.38 +							<goal>exec</goal>
   15.39 +						</goals>
   15.40 +						<configuration>
   15.41 +							<executable>./version-info.sh</executable>
   15.42 +							<outputFile>src/main/resources/info/globalcode/sql/dk/version.txt</outputFile><!-- TODO: move to target/generated-sources/ -->
   15.43 +						</configuration>
   15.44 +					</execution>
   15.45 +					<execution>
   15.46 +						<id>help-generator</id>
   15.47 +						<phase>generate-sources</phase>
   15.48 +						<goals>
   15.49 +							<goal>exec</goal>
   15.50 +						</goals>
   15.51 +						<configuration>
   15.52 +							<executable>./help-generator.sh</executable>
   15.53 +							<outputFile>src/main/resources/info/globalcode/sql/dk/help.txt</outputFile><!-- TODO: move to target/generated-sources/ -->
   15.54 +						</configuration>
   15.55 +					</execution>
   15.56 +					<execution>
   15.57 +						<id>bash-completion</id>
   15.58 +						<phase>generate-sources</phase>
   15.59 +						<goals>
   15.60 +							<goal>exec</goal>
   15.61 +						</goals>
   15.62 +						<configuration>
   15.63 +							<executable>./bash-completion.sh</executable>
   15.64 +							<outputFile>target/bash-completion.sh</outputFile>
   15.65 +						</configuration>
   15.66 +					</execution>
   15.67 +				</executions>
   15.68 +			</plugin>
   15.69 +		</plugins>
   15.70 +	</build>
   15.71 +	
   15.72 +</project>
    16.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/CLIOptions.java	Mon Mar 04 17:06:42 2019 +0100
    16.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.3 @@ -1,283 +0,0 @@
    16.4 -/**
    16.5 - * SQL-DK
    16.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    16.7 - *
    16.8 - * This program is free software: you can redistribute it and/or modify
    16.9 - * it under the terms of the GNU General Public License as published by
   16.10 - * the Free Software Foundation, either version 3 of the License, or
   16.11 - * (at your option) any later version.
   16.12 - *
   16.13 - * This program is distributed in the hope that it will be useful,
   16.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   16.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   16.16 - * GNU General Public License for more details.
   16.17 - *
   16.18 - * You should have received a copy of the GNU General Public License
   16.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   16.20 - */
   16.21 -package info.globalcode.sql.dk;
   16.22 -
   16.23 -import static info.globalcode.sql.dk.Functions.isNotEmpty;
   16.24 -import static info.globalcode.sql.dk.Functions.equalz;
   16.25 -import info.globalcode.sql.dk.InfoLister.InfoType;
   16.26 -import info.globalcode.sql.dk.configuration.Properties;
   16.27 -import info.globalcode.sql.dk.configuration.Property;
   16.28 -import java.io.InputStream;
   16.29 -import java.io.OutputStream;
   16.30 -import java.util.ArrayList;
   16.31 -import java.util.Collection;
   16.32 -import java.util.EnumSet;
   16.33 -import java.util.LinkedHashSet;
   16.34 -import java.util.List;
   16.35 -import java.util.Set;
   16.36 -import java.util.regex.Pattern;
   16.37 -import java.util.regex.PatternSyntaxException;
   16.38 -
   16.39 -/**
   16.40 - * Holds options from command line, validates them, combines with configuration and provides derived
   16.41 - * objects.
   16.42 - *
   16.43 - * @author Ing. František Kučera (frantovo.cz)
   16.44 - */
   16.45 -public class CLIOptions {
   16.46 -
   16.47 -	public static final String DEFAULT_NAME_PREFIX = ":";
   16.48 -	public static final String DEFAULT_NAME_SUFFIX = "(?=([^\\w]|$))";
   16.49 -	private String sql;
   16.50 -	private String databaseName;
   16.51 -	private final Set<String> databaseNamesToTest = new LinkedHashSet<>();
   16.52 -	private final Set<String> databaseNamesToListProperties = new LinkedHashSet<>();
   16.53 -	private final Set<String> formatterNamesToListProperties = new LinkedHashSet<>();
   16.54 -	private String namePrefix = DEFAULT_NAME_PREFIX;
   16.55 -	private String nameSuffix = DEFAULT_NAME_SUFFIX;
   16.56 -	private String formatterName;
   16.57 -	private boolean batch;
   16.58 -	private final Properties formatterProperties = new Properties();
   16.59 -	private final Properties databaseProperties = new Properties();
   16.60 -
   16.61 -	public enum MODE {
   16.62 -
   16.63 -		QUERY_NOW,
   16.64 -		PREPARE_BATCH,
   16.65 -		EXECUTE_BATCH,
   16.66 -		JUST_SHOW_INFO
   16.67 -	}
   16.68 -	private final List<NamedParameter> namedParameters = new ArrayList<>();
   16.69 -	private final List<Parameter> numberedParameters = new ArrayList<>();
   16.70 -	private final EnumSet<InfoType> showInfo = EnumSet.noneOf(InfoType.class);
   16.71 -
   16.72 -	public void validate() throws InvalidOptionsException {
   16.73 -		InvalidOptionsException e = new InvalidOptionsException();
   16.74 -
   16.75 -		MODE mode = getMode();
   16.76 -		if (mode == null) {
   16.77 -			e.addProblem(new InvalidOptionsException.OptionProblem("Invalid combination of DB, SQL and BATCH – please specify just 2 of this 3 options"));
   16.78 -		} else if (mode == MODE.JUST_SHOW_INFO) {
   16.79 -			if (!namedParameters.isEmpty()) {
   16.80 -				e.addProblem(new InvalidOptionsException.OptionProblem("Do not use named parameters if just showing info."));
   16.81 -			}
   16.82 -			if (!numberedParameters.isEmpty()) {
   16.83 -				e.addProblem(new InvalidOptionsException.OptionProblem("Do not use numbered parameters if just showing info."));
   16.84 -			}
   16.85 -			if (isNotEmpty(sql, false)) {
   16.86 -				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify SQL if just showing info."));
   16.87 -			}
   16.88 -			if (isNotEmpty(databaseName, false)) {
   16.89 -				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify database if just showing info."));
   16.90 -			}
   16.91 -			if (batch) {
   16.92 -				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify batch if just showing info."));
   16.93 -			}
   16.94 -			if (!equalz(namePrefix, DEFAULT_NAME_PREFIX)) {
   16.95 -				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify name prefix if just showing info."));
   16.96 -			}
   16.97 -			if (!equalz(nameSuffix, DEFAULT_NAME_SUFFIX)) {
   16.98 -				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify name suffix if just showing info."));
   16.99 -			}
  16.100 -			if (showInfo.contains(InfoType.CONNECTION) && databaseNamesToTest.isEmpty()) {
  16.101 -				e.addProblem(new InvalidOptionsException.OptionProblem("Please specify which database should be tested."));
  16.102 -			}
  16.103 -			if (showInfo.contains(InfoType.JDBC_PROPERTIES) && databaseNamesToListProperties.isEmpty()) {
  16.104 -				e.addProblem(new InvalidOptionsException.OptionProblem("Please specify for which database the properties should be listed."));
  16.105 -			}
  16.106 -		}
  16.107 -
  16.108 -		if (!namedParameters.isEmpty() && !numberedParameters.isEmpty()) {
  16.109 -			e.addProblem(new InvalidOptionsException.OptionProblem("Named and numbered parameters can not be used together in one command."));
  16.110 -		}
  16.111 -
  16.112 -		try {
  16.113 -			Pattern.compile(namePrefix + "test" + nameSuffix);
  16.114 -		} catch (PatternSyntaxException regexException) {
  16.115 -			e.addProblem(new InvalidOptionsException.OptionProblem("Ivalid regular expression in name prefix or suffix", regexException));
  16.116 -		}
  16.117 -
  16.118 -		if (e.hasProblems()) {
  16.119 -			throw e;
  16.120 -		}
  16.121 -	}
  16.122 -
  16.123 -	private boolean hasSql() {
  16.124 -		return isNotEmpty(getSql(), true);
  16.125 -	}
  16.126 -
  16.127 -	private boolean hasDb() {
  16.128 -		return isNotEmpty(getDatabaseName(), true);
  16.129 -	}
  16.130 -
  16.131 -	/**
  16.132 -	 * Depends on options: DB, BATCH, SQL
  16.133 -	 *
  16.134 -	 * @return mode | or null if options are not yet initialized or combination of options is
  16.135 -	 * invalid
  16.136 -	 */
  16.137 -	public MODE getMode() {
  16.138 -		if (hasDb() && !batch && hasSql()) {
  16.139 -			return MODE.QUERY_NOW;
  16.140 -		} else if (!hasDb() && batch && hasSql()) {
  16.141 -			return MODE.PREPARE_BATCH;
  16.142 -		} else if (hasDb() && batch && !hasSql()) {
  16.143 -			return MODE.EXECUTE_BATCH;
  16.144 -		} else {
  16.145 -			return showInfo.isEmpty() ? null : MODE.JUST_SHOW_INFO;
  16.146 -		}
  16.147 -	}
  16.148 -
  16.149 -	public String getSql() {
  16.150 -		return sql;
  16.151 -	}
  16.152 -
  16.153 -	public void setSql(String sql) {
  16.154 -		this.sql = sql;
  16.155 -	}
  16.156 -
  16.157 -	public String getDatabaseName() {
  16.158 -		return databaseName;
  16.159 -	}
  16.160 -
  16.161 -	public void setDatabaseName(String databaseName) {
  16.162 -		this.databaseName = databaseName;
  16.163 -	}
  16.164 -
  16.165 -	public void setBatch(boolean batch) {
  16.166 -		this.batch = batch;
  16.167 -	}
  16.168 -
  16.169 -	public Collection<NamedParameter> getNamedParameters() {
  16.170 -		return namedParameters;
  16.171 -	}
  16.172 -
  16.173 -	public List<Parameter> getNumberedParameters() {
  16.174 -		return numberedParameters;
  16.175 -	}
  16.176 -
  16.177 -	public void addNumberedParameter(Parameter p) {
  16.178 -		numberedParameters.add(p);
  16.179 -	}
  16.180 -
  16.181 -	public void addNamedParameter(NamedParameter p) {
  16.182 -		namedParameters.add(p);
  16.183 -	}
  16.184 -
  16.185 -	public Properties getDatabaseProperties() {
  16.186 -		return databaseProperties;
  16.187 -	}
  16.188 -
  16.189 -	public Properties getFormatterProperties() {
  16.190 -		return formatterProperties;
  16.191 -	}
  16.192 -
  16.193 -	public void addDatabaseProperty(Property p) {
  16.194 -		databaseProperties.add(p);
  16.195 -	}
  16.196 -
  16.197 -	public void addFormatterProperty(Property p) {
  16.198 -		formatterProperties.add(p);
  16.199 -	}
  16.200 -
  16.201 -	/**
  16.202 -	 * @return regular expression describing the name prefix
  16.203 -	 */
  16.204 -	public String getNamePrefix() {
  16.205 -		return namePrefix;
  16.206 -	}
  16.207 -
  16.208 -	/**
  16.209 -	 * @param namePrefix
  16.210 -	 * @see #getNamePrefix()
  16.211 -	 */
  16.212 -	public void setNamePrefix(String namePrefix) {
  16.213 -		this.namePrefix = namePrefix;
  16.214 -	}
  16.215 -
  16.216 -	/**
  16.217 -	 * @return regular expression describing the name prefix
  16.218 -	 */
  16.219 -	public String getNameSuffix() {
  16.220 -		return nameSuffix;
  16.221 -	}
  16.222 -
  16.223 -	/**
  16.224 -	 * @param nameSuffix
  16.225 -	 * @see #getNameSuffix()
  16.226 -	 */
  16.227 -	public void setNameSuffix(String nameSuffix) {
  16.228 -		this.nameSuffix = nameSuffix;
  16.229 -	}
  16.230 -
  16.231 -	public String getFormatterName() {
  16.232 -		return formatterName;
  16.233 -	}
  16.234 -
  16.235 -	public void setFormatterName(String formatterName) {
  16.236 -		this.formatterName = formatterName;
  16.237 -	}
  16.238 -
  16.239 -	public void addShowInfo(InfoType info) {
  16.240 -		showInfo.add(info);
  16.241 -	}
  16.242 -
  16.243 -	public EnumSet<InfoType> getShowInfo() {
  16.244 -		return showInfo;
  16.245 -	}
  16.246 -
  16.247 -	public Set<String> getDatabaseNamesToTest() {
  16.248 -		return databaseNamesToTest;
  16.249 -	}
  16.250 -
  16.251 -	public void addDatabaseNameToTest(String name) {
  16.252 -		databaseNamesToTest.add(name);
  16.253 -	}
  16.254 -
  16.255 -	public Set<String> getDatabaseNamesToListProperties() {
  16.256 -		return databaseNamesToListProperties;
  16.257 -	}
  16.258 -
  16.259 -	public void addDatabaseNameToListProperties(String name) {
  16.260 -		databaseNamesToListProperties.add(name);
  16.261 -	}
  16.262 -
  16.263 -	public Set<String> getFormatterNamesToListProperties() {
  16.264 -		return formatterNamesToListProperties;
  16.265 -	}
  16.266 -
  16.267 -	public void addFormatterNameToListProperties(String name) {
  16.268 -		formatterNamesToListProperties.add(name);
  16.269 -	}
  16.270 -
  16.271 -	public SQLCommand getSQLCommand() {
  16.272 -		if (namedParameters.isEmpty()) {
  16.273 -			return new SQLCommandNumbered(sql, numberedParameters);
  16.274 -		} else {
  16.275 -			return new SQLCommandNamed(sql, namedParameters, namePrefix, nameSuffix);
  16.276 -		}
  16.277 -	}
  16.278 -
  16.279 -	public OutputStream getOutputStream() {
  16.280 -		return System.out;
  16.281 -	}
  16.282 -
  16.283 -	public InputStream getInputStream() {
  16.284 -		return System.in;
  16.285 -	}
  16.286 -}
    17.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/CLIParser.java	Mon Mar 04 17:06:42 2019 +0100
    17.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.3 @@ -1,216 +0,0 @@
    17.4 -/**
    17.5 - * SQL-DK
    17.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    17.7 - *
    17.8 - * This program is free software: you can redistribute it and/or modify
    17.9 - * it under the terms of the GNU General Public License as published by
   17.10 - * the Free Software Foundation, either version 3 of the License, or
   17.11 - * (at your option) any later version.
   17.12 - *
   17.13 - * This program is distributed in the hope that it will be useful,
   17.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   17.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   17.16 - * GNU General Public License for more details.
   17.17 - *
   17.18 - * You should have received a copy of the GNU General Public License
   17.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   17.20 - */
   17.21 -package info.globalcode.sql.dk;
   17.22 -
   17.23 -import static info.globalcode.sql.dk.Functions.readString;
   17.24 -import info.globalcode.sql.dk.InfoLister.InfoType;
   17.25 -import info.globalcode.sql.dk.configuration.Property;
   17.26 -import java.io.IOException;
   17.27 -import java.io.InputStream;
   17.28 -import java.util.ArrayList;
   17.29 -import java.util.HashMap;
   17.30 -import java.util.List;
   17.31 -import java.util.Map;
   17.32 -
   17.33 -/**
   17.34 - * Converts command line arguments from String array to object.
   17.35 - * Checks basic constraints (if only supported options are used and if they have correct number of
   17.36 - * parameters)
   17.37 - *
   17.38 - * @author Ing. František Kučera (frantovo.cz)
   17.39 - */
   17.40 -public class CLIParser {
   17.41 -
   17.42 -	public static final String TYPE_NAME_SEPARATOR = ":";
   17.43 -
   17.44 -	public CLIOptions parseOptions(String[] args, InputStream in) throws CLIParserException {
   17.45 -		CLIOptions options = new CLIOptions();
   17.46 -
   17.47 -		List<SQLType> numberedTypes = new ArrayList<>();
   17.48 -		Map<String, SQLType> namedTypes = new HashMap<>();
   17.49 -
   17.50 -		for (int i = 0; i < args.length; i++) {
   17.51 -			String arg = args[i];
   17.52 -			switch (arg) {
   17.53 -				case Tokens.TYPES:
   17.54 -					String typesString = fetchNext(args, ++i);
   17.55 -
   17.56 -					for (String oneType : typesString.split(",")) {
   17.57 -						int sepatratorIndex = oneType.indexOf(TYPE_NAME_SEPARATOR);
   17.58 -						if (sepatratorIndex == -1) {
   17.59 -							numberedTypes.add(getType(oneType.toUpperCase()));
   17.60 -						} else {
   17.61 -							String namePart = oneType.substring(0, sepatratorIndex).trim();
   17.62 -							String typePart = oneType.substring(sepatratorIndex + TYPE_NAME_SEPARATOR.length(), oneType.length());
   17.63 -							namedTypes.put(namePart, getType(typePart.toUpperCase()));
   17.64 -						}
   17.65 -					}
   17.66 -					break;
   17.67 -				case Tokens.NAME_PREFIX:
   17.68 -					options.setNamePrefix(fetchNext(args, ++i));
   17.69 -					break;
   17.70 -				case Tokens.NAME_SUFFIX:
   17.71 -					options.setNameSuffix(fetchNext(args, ++i));
   17.72 -					break;
   17.73 -				case Tokens.DB:
   17.74 -					options.setDatabaseName(fetchNext(args, ++i));
   17.75 -					break;
   17.76 -				case Tokens.SQL:
   17.77 -					options.setSql(fetchNext(args, ++i));
   17.78 -					break;
   17.79 -				case Tokens.SQL_IN:
   17.80 -					try {
   17.81 -						options.setSql(readString(in));
   17.82 -					} catch (IOException e) {
   17.83 -						throw new CLIParserException("Unable to read SQL from the input stream", e);
   17.84 -					}
   17.85 -					break;
   17.86 -				case Tokens.BATCH:
   17.87 -					options.setBatch(true);
   17.88 -					break;
   17.89 -				case Tokens.DATA: // --data is the last option
   17.90 -					for (i++; i < args.length; i++) {
   17.91 -						arg = args[i];
   17.92 -						Parameter parameter;
   17.93 -						if (numberedTypes.isEmpty()) {
   17.94 -							parameter = new Parameter(arg, null);
   17.95 -						} else {
   17.96 -							int paramIndex = options.getNumberedParameters().size();
   17.97 -							SQLType paramType;
   17.98 -							try {
   17.99 -								paramType = numberedTypes.get(paramIndex);
  17.100 -							} catch (IndexOutOfBoundsException e) {
  17.101 -								throw new CLIParserException("Missing type for parameter #" + paramIndex, e);
  17.102 -							} catch (NullPointerException e) {
  17.103 -								throw new CLIParserException("Invalid type definition for parameter #" + paramIndex, e);
  17.104 -							}
  17.105 -							parameter = new Parameter(arg, paramType);
  17.106 -						}
  17.107 -						options.addNumberedParameter(parameter);
  17.108 -					}
  17.109 -					break;
  17.110 -				case Tokens.DATA_NAMED:
  17.111 -					for (i++; i < args.length; i++) {
  17.112 -						String paramName = args[i];
  17.113 -						String paramValue = fetchNext(args, ++i);
  17.114 -						options.addNamedParameter(new NamedParameter(paramName, paramValue, namedTypes.get(paramName)));
  17.115 -					}
  17.116 -					break;
  17.117 -				case Tokens.FORMATTER:
  17.118 -					options.setFormatterName(fetchNext(args, ++i));
  17.119 -					break;
  17.120 -				case Tokens.DB_PROPERTY:
  17.121 -					options.addDatabaseProperty(new Property(fetchNext(args, ++i), fetchNext(args, ++i)));
  17.122 -					break;
  17.123 -				case Tokens.FORMATTER_PROPERTY:
  17.124 -					options.addFormatterProperty(new Property(fetchNext(args, ++i), fetchNext(args, ++i)));
  17.125 -					break;
  17.126 -				case Tokens.INFO_HELP:
  17.127 -					options.addShowInfo(InfoType.HELP);
  17.128 -					break;
  17.129 -				case Tokens.INFO_FORMATTERS:
  17.130 -					options.addShowInfo(InfoType.FORMATTERS);
  17.131 -					break;
  17.132 -				case Tokens.INFO_FORMATTER_PROPERTIES:
  17.133 -					options.addShowInfo(InfoType.FORMATTER_PROPERTIES);
  17.134 -					options.addFormatterNameToListProperties(fetchNext(args, ++i));
  17.135 -					break;
  17.136 -				case Tokens.INFO_LICENSE:
  17.137 -					options.addShowInfo(InfoType.LICENSE);
  17.138 -					break;
  17.139 -				case Tokens.INFO_JAVA_PROPERTIES:
  17.140 -					options.addShowInfo(InfoType.JAVA_PROPERTIES);
  17.141 -					break;
  17.142 -				case Tokens.INFO_ENVIRONMENT_VARIABLES:
  17.143 -					options.addShowInfo(InfoType.ENVIRONMENT_VARIABLES);
  17.144 -					break;
  17.145 -				case Tokens.INFO_TYPES:
  17.146 -					options.addShowInfo(InfoType.TYPES);
  17.147 -					break;
  17.148 -				case Tokens.INFO_VERSION:
  17.149 -					options.addShowInfo(InfoType.VERSION);
  17.150 -					break;
  17.151 -				case Tokens.INFO_JDBC_DRIVERS:
  17.152 -					options.addShowInfo(InfoType.JDBC_DRIVERS);
  17.153 -					break;
  17.154 -				case Tokens.INFO_JDBC_PROPERTIES:
  17.155 -					options.addShowInfo(InfoType.JDBC_PROPERTIES);
  17.156 -					options.addDatabaseNameToListProperties(fetchNext(args, ++i));
  17.157 -					break;
  17.158 -				case Tokens.INFO_DATABASES:
  17.159 -					options.addShowInfo(InfoType.DATABASES);
  17.160 -					break;
  17.161 -				case Tokens.INFO_CONNECTION:
  17.162 -					options.addShowInfo(InfoType.CONNECTION);
  17.163 -					options.addDatabaseNameToTest(fetchNext(args, ++i));
  17.164 -					break;
  17.165 -				default:
  17.166 -					throw new CLIParserException("Unknown option: " + arg);
  17.167 -			}
  17.168 -		}
  17.169 -		return options;
  17.170 -	}
  17.171 -
  17.172 -	private String fetchNext(String[] args, int index) throws CLIParserException {
  17.173 -		if (index < args.length) {
  17.174 -			return args[index];
  17.175 -		} else {
  17.176 -			throw new CLIParserException("Expecting value for option: " + args[index - 1]);
  17.177 -		}
  17.178 -	}
  17.179 -
  17.180 -	public static class Tokens {
  17.181 -
  17.182 -		// bash-completion:options:
  17.183 -		public static final String DB = "--db"; // bash-completion:option // help: database name
  17.184 -		public static final String DB_PROPERTY = "--db-property"; // bash-completion:option // help: name and value
  17.185 -		public static final String SQL = "--sql"; // bash-completion:option // help: SQL query/command
  17.186 -		public static final String SQL_IN = "--sql-in"; // bash-completion:option // help: SQL query/command
  17.187 -		public static final String BATCH = "--batch"; // bash-completion:option // help: batch mode (no argument)
  17.188 -		public static final String DATA = "--data"; // bash-completion:option // help: list of ordinal parameters
  17.189 -		public static final String DATA_NAMED = "--data-named"; // bash-completion:option // help: list of named parameters
  17.190 -		public static final String NAME_PREFIX = "--name-prefix"; // bash-completion:option // help: parameter name prefix – regular expression
  17.191 -		public static final String NAME_SUFFIX = "--name-suffix"; // bash-completion:option // help: parameter name suffix – regular expression
  17.192 -		public static final String TYPES = "--types"; // bash-completion:option // help: comma separated list of parameter types
  17.193 -		public static final String FORMATTER = "--formatter"; // bash-completion:option // help: name of the output formatter
  17.194 -		public static final String FORMATTER_PROPERTY = "--formatter-property"; // bash-completion:option // help: name and value
  17.195 -		public static final String INFO_HELP = "--help"; // bash-completion:option // help: print this help
  17.196 -		public static final String INFO_VERSION = "--version"; // bash-completion:option // help: print version info
  17.197 -		public static final String INFO_LICENSE = "--license"; // bash-completion:option // help: print license
  17.198 -		public static final String INFO_JAVA_PROPERTIES = "--list-java-properties"; // bash-completion:option // help: list of Java system properties
  17.199 -		public static final String INFO_ENVIRONMENT_VARIABLES = "--list-environment-variables"; // bash-completion:option // help: list of environment variables
  17.200 -		public static final String INFO_FORMATTERS = "--list-formatters"; // bash-completion:option // help: print list of available formatters
  17.201 -		public static final String INFO_FORMATTER_PROPERTIES = "--list-formatter-properties"; // bash-completion:option // help: print list of available properties for given formatter
  17.202 -		public static final String INFO_TYPES = "--list-types"; // bash-completion:option // help: print list of available data types
  17.203 -		public static final String INFO_JDBC_DRIVERS = "--list-jdbc-drivers"; // bash-completion:option // help: list of available JDBC drivers
  17.204 -		public static final String INFO_JDBC_PROPERTIES = "--list-jdbc-properties"; // bash-completion:option // help: list of available JDBC properties for given database
  17.205 -		public static final String INFO_DATABASES = "--list-databases"; // bash-completion:option // help: print list of configured databases
  17.206 -		public static final String INFO_CONNECTION = "--test-connection"; // bash-completion:option // help: test connection to particular database
  17.207 -
  17.208 -		private Tokens() {
  17.209 -		}
  17.210 -	}
  17.211 -
  17.212 -	private SQLType getType(String typeString) throws CLIParserException {
  17.213 -		try {
  17.214 -			return SQLType.valueOf(typeString.trim());
  17.215 -		} catch (IllegalArgumentException e) {
  17.216 -			throw new CLIParserException("Unsupported type: " + typeString, e);
  17.217 -		}
  17.218 -	}
  17.219 -}
    18.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/CLIParserException.java	Mon Mar 04 17:06:42 2019 +0100
    18.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.3 @@ -1,40 +0,0 @@
    18.4 -/**
    18.5 - * SQL-DK
    18.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    18.7 - *
    18.8 - * This program is free software: you can redistribute it and/or modify
    18.9 - * it under the terms of the GNU General Public License as published by
   18.10 - * the Free Software Foundation, either version 3 of the License, or
   18.11 - * (at your option) any later version.
   18.12 - *
   18.13 - * This program is distributed in the hope that it will be useful,
   18.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   18.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   18.16 - * GNU General Public License for more details.
   18.17 - *
   18.18 - * You should have received a copy of the GNU General Public License
   18.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   18.20 - */
   18.21 -package info.globalcode.sql.dk;
   18.22 -
   18.23 -/**
   18.24 - *
   18.25 - * @author Ing. František Kučera (frantovo.cz)
   18.26 - */
   18.27 -public class CLIParserException extends DKException {
   18.28 -
   18.29 -	public CLIParserException() {
   18.30 -	}
   18.31 -
   18.32 -	public CLIParserException(String message) {
   18.33 -		super(message);
   18.34 -	}
   18.35 -
   18.36 -	public CLIParserException(Throwable cause) {
   18.37 -		super(cause);
   18.38 -	}
   18.39 -
   18.40 -	public CLIParserException(String message, Throwable cause) {
   18.41 -		super(message, cause);
   18.42 -	}
   18.43 -}
    19.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/CLIStarter.java	Mon Mar 04 17:06:42 2019 +0100
    19.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.3 @@ -1,274 +0,0 @@
    19.4 -/**
    19.5 - * SQL-DK
    19.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    19.7 - *
    19.8 - * This program is free software: you can redistribute it and/or modify
    19.9 - * it under the terms of the GNU General Public License as published by
   19.10 - * the Free Software Foundation, either version 3 of the License, or
   19.11 - * (at your option) any later version.
   19.12 - *
   19.13 - * This program is distributed in the hope that it will be useful,
   19.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   19.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   19.16 - * GNU General Public License for more details.
   19.17 - *
   19.18 - * You should have received a copy of the GNU General Public License
   19.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   19.20 - */
   19.21 -package info.globalcode.sql.dk;
   19.22 -
   19.23 -import info.globalcode.sql.dk.configuration.ConfigurationProvider;
   19.24 -import info.globalcode.sql.dk.CLIOptions.MODE;
   19.25 -import info.globalcode.sql.dk.batch.Batch;
   19.26 -import info.globalcode.sql.dk.batch.BatchDecoder;
   19.27 -import info.globalcode.sql.dk.batch.BatchException;
   19.28 -import info.globalcode.sql.dk.batch.BatchEncoder;
   19.29 -import info.globalcode.sql.dk.configuration.Configuration;
   19.30 -import info.globalcode.sql.dk.configuration.ConfigurationException;
   19.31 -import info.globalcode.sql.dk.configuration.DatabaseDefinition;
   19.32 -import info.globalcode.sql.dk.configuration.FormatterDefinition;
   19.33 -import info.globalcode.sql.dk.configuration.Loader;
   19.34 -import info.globalcode.sql.dk.configuration.NameIdentified;
   19.35 -import info.globalcode.sql.dk.configuration.PropertyDeclaration;
   19.36 -import info.globalcode.sql.dk.formatting.Formatter;
   19.37 -import info.globalcode.sql.dk.formatting.FormatterContext;
   19.38 -import info.globalcode.sql.dk.formatting.FormatterException;
   19.39 -import info.globalcode.sql.dk.jmx.ConnectionManagement;
   19.40 -import info.globalcode.sql.dk.jmx.ManagementUtils;
   19.41 -import java.io.File;
   19.42 -import java.io.FileNotFoundException;
   19.43 -import java.io.IOException;
   19.44 -import java.io.PrintStream;
   19.45 -import java.io.PrintWriter;
   19.46 -import java.sql.SQLException;
   19.47 -import java.util.Collection;
   19.48 -import java.util.Collections;
   19.49 -import java.util.List;
   19.50 -import java.util.logging.Level;
   19.51 -import java.util.logging.LogRecord;
   19.52 -import java.util.logging.Logger;
   19.53 -
   19.54 -/**
   19.55 - * Entry point of the command line interface of SQL-DK.
   19.56 - *
   19.57 - * @author Ing. František Kučera (frantovo.cz)
   19.58 - */
   19.59 -public class CLIStarter implements ConfigurationProvider {
   19.60 -
   19.61 -	// help:exit-codes
   19.62 -	public static final int EXIT_SUCCESS = 0; // doc:success
   19.63 -	public static final int EXIT_UNEXPECTED_ERROR = 1; // doc:unexpected error (probably bug)
   19.64 -	// 2 is reserved: http://www.tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF
   19.65 -	public static final int EXIT_SQL_ERROR = 3; // doc:SQL error
   19.66 -	public static final int EXIT_CLI_PARSE_ERROR = 4; // doc:CLI options parse error
   19.67 -	public static final int EXIT_CLI_VALIDATE_ERROR = 5; // doc:CLI options validation error
   19.68 -	public static final int EXIT_CONFIGURATION_ERROR = 6; // doc:configuration error
   19.69 -	public static final int EXIT_FORMATTING_ERROR = 7; // doc:formatting error
   19.70 -	public static final int EXIT_BATCH_ERROR = 8; // doc:batch error
   19.71 -	private static final Logger log = Logger.getLogger(CLIStarter.class.getName());
   19.72 -	private final CLIOptions options;
   19.73 -	private final Loader configurationLoader = new Loader();
   19.74 -	private Configuration configuration;
   19.75 -
   19.76 -	public static void main(String[] args) {
   19.77 -		log.log(Level.FINE, "Starting " + Constants.PROGRAM_NAME);
   19.78 -		int exitCode;
   19.79 -
   19.80 -		if (args.length == 0) {
   19.81 -			args = new String[]{CLIParser.Tokens.INFO_HELP};
   19.82 -		}
   19.83 -
   19.84 -		try {
   19.85 -			CLIParser parser = new CLIParser();
   19.86 -			CLIOptions options = parser.parseOptions(args, System.in);
   19.87 -			options.validate();
   19.88 -			CLIStarter starter = new CLIStarter(options);
   19.89 -			starter.installDefaultConfiguration();
   19.90 -			starter.process();
   19.91 -			log.log(Level.FINE, "All done");
   19.92 -			exitCode = EXIT_SUCCESS;
   19.93 -		} catch (CLIParserException e) {
   19.94 -			log.log(Level.SEVERE, "Unable to parse CLI options", e);
   19.95 -			exitCode = EXIT_CLI_PARSE_ERROR;
   19.96 -		} catch (InvalidOptionsException e) {
   19.97 -			log.log(Level.SEVERE, "Invalid CLI options", e);
   19.98 -			for (InvalidOptionsException.OptionProblem p : e.getProblems()) {
   19.99 -				LogRecord r = new LogRecord(Level.SEVERE, "Option problem: {0}");
  19.100 -				r.setThrown(p.getException());
  19.101 -				r.setParameters(new Object[]{p.getDescription()});
  19.102 -				log.log(r);
  19.103 -			}
  19.104 -			exitCode = EXIT_CLI_VALIDATE_ERROR;
  19.105 -		} catch (ConfigurationException e) {
  19.106 -			log.log(Level.SEVERE, "Configuration problem", e);
  19.107 -			exitCode = EXIT_CONFIGURATION_ERROR;
  19.108 -		} catch (SQLException e) {
  19.109 -			log.log(Level.SEVERE, "SQL problem", e);
  19.110 -			exitCode = EXIT_SQL_ERROR;
  19.111 -		} catch (FormatterException e) {
  19.112 -			log.log(Level.SEVERE, "Formatting problem", e);
  19.113 -			exitCode = EXIT_FORMATTING_ERROR;
  19.114 -		} catch (BatchException e) {
  19.115 -			log.log(Level.SEVERE, "Batch problem", e);
  19.116 -			exitCode = EXIT_BATCH_ERROR;
  19.117 -		}
  19.118 -
  19.119 -		System.exit(exitCode);
  19.120 -	}
  19.121 -
  19.122 -	public CLIStarter(CLIOptions options) {
  19.123 -		this.options = options;
  19.124 -	}
  19.125 -
  19.126 -	private void process() throws ConfigurationException, SQLException, FormatterException, BatchException {
  19.127 -		MODE mode = options.getMode();
  19.128 -
  19.129 -		/** Show info */
  19.130 -		if (!options.getShowInfo().isEmpty()) {
  19.131 -			PrintStream infoOut = mode == MODE.JUST_SHOW_INFO ? System.out : System.err;
  19.132 -			InfoLister infoLister = new InfoLister(infoOut, this, options);
  19.133 -			infoLister.showInfo();
  19.134 -		}
  19.135 -
  19.136 -		switch (mode) {
  19.137 -			case QUERY_NOW:
  19.138 -				processQueryNow();
  19.139 -				break;
  19.140 -			case PREPARE_BATCH:
  19.141 -				processPrepareBatch();
  19.142 -				break;
  19.143 -			case EXECUTE_BATCH:
  19.144 -				processExecuteBatch();
  19.145 -				break;
  19.146 -			case JUST_SHOW_INFO:
  19.147 -				// already done above
  19.148 -				break;
  19.149 -			default:
  19.150 -				log.log(Level.SEVERE, "Unsupported mode: {0}", mode);
  19.151 -				break;
  19.152 -		}
  19.153 -
  19.154 -		generateBashCompletion();
  19.155 -	}
  19.156 -
  19.157 -	private void processQueryNow() throws ConfigurationException, SQLException, FormatterException {
  19.158 -		DatabaseDefinition dd = getConfiguration().getDatabase(options.getDatabaseName());
  19.159 -		FormatterDefinition fd = configuration.getFormatter(options.getFormatterName());
  19.160 -		ConnectionManagement jmxBean = ManagementUtils.registerMBean(dd.getName());
  19.161 -
  19.162 -		try (DatabaseConnection c = dd.connect(options.getDatabaseProperties(), jmxBean)) {
  19.163 -			log.log(Level.FINE, "Database connected");
  19.164 -			try (Formatter f = fd.getInstance(new FormatterContext(options.getOutputStream(), options.getFormatterProperties()))) {
  19.165 -				c.executeQuery(options.getSQLCommand(), f);
  19.166 -			}
  19.167 -		}
  19.168 -	}
  19.169 -
  19.170 -	private void processPrepareBatch() throws BatchException {
  19.171 -		BatchEncoder enc = new BatchEncoder();
  19.172 -		int length = enc.encode(options.getSQLCommand(), options.getOutputStream());
  19.173 -		log.log(Level.FINE, "Prepared batch size: {0} bytes", length);
  19.174 -	}
  19.175 -
  19.176 -	private void processExecuteBatch() throws ConfigurationException, SQLException, FormatterException, BatchException {
  19.177 -		BatchDecoder dec = new BatchDecoder();
  19.178 -		Batch b = dec.decode(options.getInputStream());
  19.179 -
  19.180 -		DatabaseDefinition dd = getConfiguration().getDatabase(options.getDatabaseName());
  19.181 -		FormatterDefinition fd = configuration.getFormatter(options.getFormatterName());
  19.182 -		ConnectionManagement jmxBean = ManagementUtils.registerMBean(dd.getName());
  19.183 -
  19.184 -		try (DatabaseConnection c = dd.connect(options.getDatabaseProperties(), jmxBean)) {
  19.185 -			log.log(Level.FINE, "Database connected");
  19.186 -			try (Formatter f = fd.getInstance(new FormatterContext(options.getOutputStream(), options.getFormatterProperties()))) {
  19.187 -				c.executeBatch(b, f);
  19.188 -			}
  19.189 -		}
  19.190 -	}
  19.191 -
  19.192 -	@Override
  19.193 -	public Configuration getConfiguration() throws ConfigurationException {
  19.194 -		if (configuration == null) {
  19.195 -			configuration = configurationLoader.loadConfiguration();
  19.196 -		}
  19.197 -		return configuration;
  19.198 -	}
  19.199 -
  19.200 -	private void installDefaultConfiguration() throws ConfigurationException {
  19.201 -		Constants.DIR.mkdir();
  19.202 -
  19.203 -		if (Constants.CONFIG_FILE.exists()) {
  19.204 -			log.log(Level.FINER, "Config file already exists: {0}", Constants.CONFIG_FILE);
  19.205 -		} else {
  19.206 -			try {
  19.207 -				Functions.installResource(Constants.EXAMPLE_CONFIG_FILE, Constants.CONFIG_FILE);
  19.208 -				log.log(Level.FINE, "Installing default config file: {0}", Constants.CONFIG_FILE);
  19.209 -			} catch (IOException e) {
  19.210 -				throw new ConfigurationException("Unable to write example configuration to " + Constants.CONFIG_FILE, e);
  19.211 -			}
  19.212 -		}
  19.213 -	}
  19.214 -
  19.215 -	private void generateBashCompletion() {
  19.216 -		if (configuration == null) {
  19.217 -			log.log(Level.FINER, "Not writing Bash completion helper files. In order to generate these files please run some command which requires configuration.");
  19.218 -		} else {
  19.219 -			try {
  19.220 -				File dir = new File(Constants.DIR, "bash-completion");
  19.221 -				dir.mkdir();
  19.222 -				writeBashCompletionHelperFile(configuration.getDatabases(), new File(dir, "databases"));
  19.223 -				writeBashCompletionHelperFile(configuration.getAllFormatters(), new File(dir, "formatters"));
  19.224 -				writeBashCompletionHelperFileForFormatterProperties(new File(dir, "formatter-properties"));
  19.225 -			} catch (Exception e) {
  19.226 -				log.log(Level.WARNING, "Unable to generate Bash completion helper files", e);
  19.227 -			}
  19.228 -		}
  19.229 -	}
  19.230 -
  19.231 -	private void writeBashCompletionHelperFile(Collection<? extends NameIdentified> items, File target) throws FileNotFoundException {
  19.232 -		if (Constants.CONFIG_FILE.lastModified() > target.lastModified()) {
  19.233 -			try (PrintWriter fw = new PrintWriter(target)) {
  19.234 -				for (NameIdentified dd : items) {
  19.235 -					fw.println(dd.getName());
  19.236 -				}
  19.237 -				fw.close();
  19.238 -				log.log(Level.FINE, "Bash completion helper file was written: {0}", target);
  19.239 -			}
  19.240 -		} else {
  19.241 -			log.log(Level.FINER, "Not writing Bash completion helper file: {0} because configuration {1} has not been changed", new Object[]{target, Constants.CONFIG_FILE});
  19.242 -		}
  19.243 -	}
  19.244 -
  19.245 -	private void writeBashCompletionHelperFileForFormatterProperties(File formattersDir) throws ClassNotFoundException, FileNotFoundException {
  19.246 -		if (Constants.CONFIG_FILE.lastModified() > formattersDir.lastModified()) {
  19.247 -			// TODO: delete old directory
  19.248 -			formattersDir.mkdir();
  19.249 -			for (FormatterDefinition fd : configuration.getAllFormatters()) {
  19.250 -				File formatterDir = new File(formattersDir, fd.getName());
  19.251 -				formatterDir.mkdir();
  19.252 -
  19.253 -				Class<Formatter> formatterClass = (Class<Formatter>) Class.forName(fd.getClassName());
  19.254 -				List<Class<? extends Formatter>> hierarchy = Functions.getClassHierarchy(formatterClass, Formatter.class);
  19.255 -				Collections.reverse(hierarchy);
  19.256 -				for (Class<? extends Formatter> c : hierarchy) {
  19.257 -					for (PropertyDeclaration p : Functions.getPropertyDeclarations(c)) {
  19.258 -						File propertyDir = new File(formatterDir, p.name());
  19.259 -						propertyDir.mkdir();
  19.260 -						File choicesFile = new File(propertyDir, "choices");
  19.261 -						try (PrintWriter fw = new PrintWriter(choicesFile)) {
  19.262 -							// TODO: refactor, move
  19.263 -							if (p.type() == Boolean.class) {
  19.264 -								fw.println("true");
  19.265 -								fw.println("false");
  19.266 -							}
  19.267 -						}
  19.268 -					}
  19.269 -				}
  19.270 -			}
  19.271 -			log.log(Level.FINE, "Bash completion helper files was written in: {0}", formattersDir);
  19.272 -		} else {
  19.273 -			log.log(Level.FINER, "Not writing Bash completion helper directory: {0} because configuration {1} has not been changed", new Object[]{formattersDir, Constants.CONFIG_FILE});
  19.274 -		}
  19.275 -
  19.276 -	}
  19.277 -}
    20.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/ColorfulPrintWriter.java	Mon Mar 04 17:06:42 2019 +0100
    20.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.3 @@ -1,358 +0,0 @@
    20.4 -/**
    20.5 - * SQL-DK
    20.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    20.7 - *
    20.8 - * This program is free software: you can redistribute it and/or modify
    20.9 - * it under the terms of the GNU General Public License as published by
   20.10 - * the Free Software Foundation, either version 3 of the License, or
   20.11 - * (at your option) any later version.
   20.12 - *
   20.13 - * This program is distributed in the hope that it will be useful,
   20.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   20.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   20.16 - * GNU General Public License for more details.
   20.17 - *
   20.18 - * You should have received a copy of the GNU General Public License
   20.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   20.20 - */
   20.21 -package info.globalcode.sql.dk;
   20.22 -
   20.23 -import java.io.File;
   20.24 -import java.io.FileNotFoundException;
   20.25 -import java.io.OutputStream;
   20.26 -import java.io.PrintWriter;
   20.27 -import java.io.UnsupportedEncodingException;
   20.28 -import java.io.Writer;
   20.29 -import java.util.EnumSet;
   20.30 -
   20.31 -/**
   20.32 - * PrintWriter with convenience methods for printing color and formatted text.
   20.33 - *
   20.34 - * Uses ANSI Escape Sequences.
   20.35 - * See: http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html
   20.36 - *
   20.37 - * @author Ing. František Kučera (frantovo.cz)
   20.38 - */
   20.39 -public class ColorfulPrintWriter extends PrintWriter {
   20.40 -
   20.41 -	public enum TerminalColor {
   20.42 -
   20.43 -		Black(30, 40),
   20.44 -		Red(31, 41),
   20.45 -		Green(32, 42),
   20.46 -		Yellow(33, 43),
   20.47 -		Blue(34, 44),
   20.48 -		Magenta(35, 45),
   20.49 -		Cyan(36, 46),
   20.50 -		White(37, 47);
   20.51 -		private final int foregroundCode;
   20.52 -		private final int backgroundCode;
   20.53 -
   20.54 -		private TerminalColor(int foregroundCode, int backgroundCode) {
   20.55 -			this.foregroundCode = foregroundCode;
   20.56 -			this.backgroundCode = backgroundCode;
   20.57 -		}
   20.58 -
   20.59 -		public int getForegroundCode() {
   20.60 -			return foregroundCode;
   20.61 -		}
   20.62 -
   20.63 -		public int getBackgroundCode() {
   20.64 -			return backgroundCode;
   20.65 -		}
   20.66 -	}
   20.67 -
   20.68 -	public enum TerminalStyle {
   20.69 -
   20.70 -		Reset(0),
   20.71 -		Bright(1),
   20.72 -		Dim(2),
   20.73 -		Underscore(4),
   20.74 -		Blink(5),
   20.75 -		Reverse(7),
   20.76 -		Hidden(8);
   20.77 -		private int code;
   20.78 -
   20.79 -		private TerminalStyle(int code) {
   20.80 -			this.code = code;
   20.81 -		}
   20.82 -
   20.83 -		public int getCode() {
   20.84 -			return code;
   20.85 -		}
   20.86 -	}
   20.87 -	private final boolean COLOR_ENABLED;
   20.88 -	private boolean colorful = true;
   20.89 -
   20.90 -	public void setStyle(TerminalStyle style) {
   20.91 -		setStyle(EnumSet.of(style));
   20.92 -	}
   20.93 -
   20.94 -	public void setStyle(EnumSet<TerminalStyle> styles) {
   20.95 -		printCodes(getStyleCodes(styles));
   20.96 -	}
   20.97 -
   20.98 -	private static int[] getStyleCodes(EnumSet<TerminalStyle> styles) {
   20.99 -		int[] array = new int[styles.size()];
  20.100 -		int i = 0;
  20.101 -		for (TerminalStyle s : styles) {
  20.102 -			array[i++] = s.getCode();
  20.103 -		}
  20.104 -		return array;
  20.105 -	}
  20.106 -
  20.107 -	/**
  20.108 -	 * Print (usually audible) bell code (\007, \a, ^G)
  20.109 -	 */
  20.110 -	public void bell() {
  20.111 -		print("\007");
  20.112 -	}
  20.113 -
  20.114 -	/**
  20.115 -	 * Eat the last character
  20.116 -	 */
  20.117 -	public void backspace() {
  20.118 -		print("\b");
  20.119 -	}
  20.120 -
  20.121 -	/**
  20.122 -	 * Eat n last characters
  20.123 -	 *
  20.124 -	 * @param count n
  20.125 -	 */
  20.126 -	public void backspace(int count) {
  20.127 -		for (int i = 0; i < count; i++) {
  20.128 -			backspace();
  20.129 -		}
  20.130 -	}
  20.131 -
  20.132 -	/**
  20.133 -	 * With 100 ms delay and all colors.
  20.134 -	 *
  20.135 -	 * @see #printRainbow(java.lang.String, int,
  20.136 -	 * info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor[])
  20.137 -	 */
  20.138 -	public void printRainbow(String string) {
  20.139 -		printRainbow(string, 100);
  20.140 -	}
  20.141 -
  20.142 -	/**
  20.143 -	 * With all colors.
  20.144 -	 *
  20.145 -	 * @see #printRainbow(java.lang.String, int,
  20.146 -	 * info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor[])
  20.147 -	 */
  20.148 -	public void printRainbow(String string, int delay) {
  20.149 -		printRainbow(string, delay, TerminalColor.values());
  20.150 -	}
  20.151 -
  20.152 -	/**
  20.153 -	 * Prints rainbow text – (re)writes same text subsequently in given colors and then in default
  20.154 -	 * color.
  20.155 -	 *
  20.156 -	 * @param string text to be printed, should not contain \n new line (then rainbow does not work
  20.157 -	 * – use println() after printRainbow() instead)
  20.158 -	 * @param delay delay between rewrites
  20.159 -	 * @param colors list of colors to be used
  20.160 -	 */
  20.161 -	public void printRainbow(String string, int delay, TerminalColor... colors) {
  20.162 -		for (TerminalColor c : colors) {
  20.163 -			print(c, string);
  20.164 -			try {
  20.165 -				Thread.sleep(delay);
  20.166 -			} catch (InterruptedException e) {
  20.167 -				// no time to sleep
  20.168 -				break;
  20.169 -			}
  20.170 -			backspace(string.length());
  20.171 -			flush();
  20.172 -		}
  20.173 -		print(string);
  20.174 -	}
  20.175 -
  20.176 -	public void setForegroundColor(TerminalColor color) {
  20.177 -		printCodes(color.getForegroundCode());
  20.178 -	}
  20.179 -
  20.180 -	public void setBackgroundColor(TerminalColor color) {
  20.181 -		printCodes(color.getBackgroundCode());
  20.182 -	}
  20.183 -
  20.184 -	public void print(TerminalColor foregroundColor, String string) {
  20.185 -		setForegroundColor(foregroundColor);
  20.186 -		print(string);
  20.187 -		resetAll();
  20.188 -	}
  20.189 -
  20.190 -	public void println(TerminalColor foregroundColor, String string) {
  20.191 -		print(foregroundColor, string);
  20.192 -		println();
  20.193 -	}
  20.194 -
  20.195 -	public void print(TerminalColor foregroundColor, TerminalColor backgroundColor, String string) {
  20.196 -		setForegroundColor(foregroundColor);
  20.197 -		setBackgroundColor(backgroundColor);
  20.198 -		print(string);
  20.199 -		resetAll();
  20.200 -	}
  20.201 -
  20.202 -	public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, String string) {
  20.203 -		print(foregroundColor, backgroundColor, string);
  20.204 -		println();
  20.205 -	}
  20.206 -
  20.207 -	public void print(TerminalColor foregroundColor, TerminalColor backgroundColor, EnumSet<TerminalStyle> styles, String string) {
  20.208 -		setForegroundColor(foregroundColor);
  20.209 -		setBackgroundColor(backgroundColor);
  20.210 -		setStyle(styles);
  20.211 -		print(string);
  20.212 -		resetAll();
  20.213 -	}
  20.214 -
  20.215 -	public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, EnumSet<TerminalStyle> styles, String string) {
  20.216 -		print(foregroundColor, backgroundColor, styles, string);
  20.217 -		println();
  20.218 -	}
  20.219 -
  20.220 -	public void print(TerminalColor foregroundColor, TerminalColor backgroundColor, TerminalStyle style, String string) {
  20.221 -		print(foregroundColor, backgroundColor, EnumSet.of(style), string);
  20.222 -	}
  20.223 -
  20.224 -	public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, TerminalStyle style, String string) {
  20.225 -		print(foregroundColor, backgroundColor, style, string);
  20.226 -		println();
  20.227 -	}
  20.228 -
  20.229 -	public void print(TerminalColor foregroundColor, EnumSet<TerminalStyle> styles, String string) {
  20.230 -		setForegroundColor(foregroundColor);
  20.231 -		setStyle(styles);
  20.232 -		print(string);
  20.233 -		resetAll();
  20.234 -	}
  20.235 -
  20.236 -	public void println(TerminalColor foregroundColor, EnumSet<TerminalStyle> styles, String string) {
  20.237 -		print(foregroundColor, styles, string);
  20.238 -		println();
  20.239 -	}
  20.240 -
  20.241 -	public void print(TerminalColor foregroundColor, TerminalStyle style, String string) {
  20.242 -		print(foregroundColor, EnumSet.of(style), string);
  20.243 -	}
  20.244 -
  20.245 -	public void println(TerminalColor foregroundColor, TerminalStyle style, String string) {
  20.246 -		print(foregroundColor, style, string);
  20.247 -		println();
  20.248 -	}
  20.249 -
  20.250 -	public void print(EnumSet<TerminalStyle> styles, String string) {
  20.251 -		setStyle(styles);
  20.252 -		print(string);
  20.253 -		resetAll();
  20.254 -	}
  20.255 -
  20.256 -	public void println(EnumSet<TerminalStyle> styles, String string) {
  20.257 -		print(styles, string);
  20.258 -		println();
  20.259 -	}
  20.260 -
  20.261 -	public void print(TerminalStyle style, String string) {
  20.262 -		print(EnumSet.of(style), string);
  20.263 -	}
  20.264 -
  20.265 -	public void println(TerminalStyle style, String string) {
  20.266 -		print(style, string);
  20.267 -		println();
  20.268 -	}
  20.269 -
  20.270 -	public void resetAll() {
  20.271 -		printCodes(TerminalStyle.Reset.code);
  20.272 -	}
  20.273 -
  20.274 -	private void printCodes(int... codes) {
  20.275 -		if (COLOR_ENABLED && colorful) {
  20.276 -			print("\033[");
  20.277 -			for (int i = 0; i < codes.length; i++) {
  20.278 -				print(codes[i]);
  20.279 -				if (i < codes.length - 1 && codes.length > 1) {
  20.280 -					print(";");
  20.281 -				}
  20.282 -			}
  20.283 -			print("m");
  20.284 -		}
  20.285 -	}
  20.286 -
  20.287 -	/**
  20.288 -	 * Colors can be switched on/off during usage of this writer.
  20.289 -	 *
  20.290 -	 * @return whether colors are currently turned on
  20.291 -	 * @see #isColorEnabled()
  20.292 -	 */
  20.293 -	public boolean isColorful() {
  20.294 -		return colorful;
  20.295 -	}
  20.296 -
  20.297 -	/**
  20.298 -	 * Collors might be definitively disabled in constructor. If not, they can be turned on/off
  20.299 -	 * during usage of this writer by {@linkplain #setColorful(boolean)}
  20.300 -	 *
  20.301 -	 * @return whether colors are allowed for this instance of this class
  20.302 -	 * @see #isColorful()
  20.303 -	 */
  20.304 -	public boolean isColorEnabled() {
  20.305 -		return COLOR_ENABLED;
  20.306 -	}
  20.307 -
  20.308 -	/**
  20.309 -	 * @see #isColorful()
  20.310 -	 * @see #isColorEnabled()
  20.311 -	 */
  20.312 -	public void setColorful(boolean colorful) {
  20.313 -		this.colorful = colorful;
  20.314 -	}
  20.315 -
  20.316 -	public ColorfulPrintWriter(File file) throws FileNotFoundException {
  20.317 -		super(file);
  20.318 -		COLOR_ENABLED = true;
  20.319 -	}
  20.320 -
  20.321 -	public ColorfulPrintWriter(OutputStream out) {
  20.322 -		super(out);
  20.323 -		COLOR_ENABLED = true;
  20.324 -	}
  20.325 -
  20.326 -	public ColorfulPrintWriter(String fileName) throws FileNotFoundException {
  20.327 -		super(fileName);
  20.328 -		COLOR_ENABLED = true;
  20.329 -	}
  20.330 -
  20.331 -	public ColorfulPrintWriter(Writer out) {
  20.332 -		super(out);
  20.333 -		COLOR_ENABLED = true;
  20.334 -	}
  20.335 -
  20.336 -	public ColorfulPrintWriter(File file, String csn) throws FileNotFoundException, UnsupportedEncodingException {
  20.337 -		super(file, csn);
  20.338 -		COLOR_ENABLED = true;
  20.339 -	}
  20.340 -
  20.341 -	/**
  20.342 -	 * @param colorEnabled colors might be definitively disabled by this option – this might be more
  20.343 -	 * optimalizable than dynamic turning off colors by {@linkplain #setColorful(boolean)} which is
  20.344 -	 * not definitive (colors can be turned on during live of this instance). This might be useful
  20.345 -	 * if you need an instance of this class but don't need colors at all.
  20.346 -	 */
  20.347 -	public ColorfulPrintWriter(OutputStream out, boolean autoFlush, boolean colorEnabled) {
  20.348 -		super(out, autoFlush);
  20.349 -		COLOR_ENABLED = colorEnabled;
  20.350 -	}
  20.351 -
  20.352 -	public ColorfulPrintWriter(String fileName, String csn) throws FileNotFoundException, UnsupportedEncodingException {
  20.353 -		super(fileName, csn);
  20.354 -		COLOR_ENABLED = true;
  20.355 -	}
  20.356 -
  20.357 -	public ColorfulPrintWriter(Writer out, boolean autoFlush) {
  20.358 -		super(out, autoFlush);
  20.359 -		COLOR_ENABLED = true;
  20.360 -	}
  20.361 -}
    21.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/Constants.java	Mon Mar 04 17:06:42 2019 +0100
    21.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.3 @@ -1,44 +0,0 @@
    21.4 -/**
    21.5 - * SQL-DK
    21.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    21.7 - *
    21.8 - * This program is free software: you can redistribute it and/or modify
    21.9 - * it under the terms of the GNU General Public License as published by
   21.10 - * the Free Software Foundation, either version 3 of the License, or
   21.11 - * (at your option) any later version.
   21.12 - *
   21.13 - * This program is distributed in the hope that it will be useful,
   21.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   21.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   21.16 - * GNU General Public License for more details.
   21.17 - *
   21.18 - * You should have received a copy of the GNU General Public License
   21.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   21.20 - */
   21.21 -package info.globalcode.sql.dk;
   21.22 -
   21.23 -import java.io.File;
   21.24 -
   21.25 -/**
   21.26 - *
   21.27 - * @author Ing. František Kučera (frantovo.cz)
   21.28 - */
   21.29 -public class Constants {
   21.30 -
   21.31 -	public static final String PROGRAM_NAME = "SQL-DK";
   21.32 -	public static final String JAVA_PACKAGE = Constants.class.getPackage().getName();
   21.33 -	public static final String WEBSITE = "https://sql-dk.globalcode.info/";
   21.34 -	public static final String LICENSE_FILE = "info/globalcode/sql/dk/license.txt";
   21.35 -	public static final String VERSION_FILE = "info/globalcode/sql/dk/version.txt";
   21.36 -	public static final String HELP_FILE = "info/globalcode/sql/dk/help.txt";
   21.37 -	private static final File HOME_DIR = new File(System.getProperty("user.home"));
   21.38 -	/**
   21.39 -	 * Directory where config and log files are stored.
   21.40 -	 */
   21.41 -	public static final File DIR = new File(HOME_DIR, ".sql-dk"); // bash-completion:dir
   21.42 -	public static final File CONFIG_FILE = new File(DIR, "config.xml");
   21.43 -	public static final String EXAMPLE_CONFIG_FILE = "info/globalcode/sql/dk/example-config.xml";
   21.44 -
   21.45 -	private Constants() {
   21.46 -	}
   21.47 -}
    22.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/DKException.java	Mon Mar 04 17:06:42 2019 +0100
    22.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.3 @@ -1,41 +0,0 @@
    22.4 -/**
    22.5 - * SQL-DK
    22.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    22.7 - *
    22.8 - * This program is free software: you can redistribute it and/or modify
    22.9 - * it under the terms of the GNU General Public License as published by
   22.10 - * the Free Software Foundation, either version 3 of the License, or
   22.11 - * (at your option) any later version.
   22.12 - *
   22.13 - * This program is distributed in the hope that it will be useful,
   22.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   22.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   22.16 - * GNU General Public License for more details.
   22.17 - *
   22.18 - * You should have received a copy of the GNU General Public License
   22.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   22.20 - */
   22.21 -package info.globalcode.sql.dk;
   22.22 -
   22.23 -/**
   22.24 - * TODO: GEC
   22.25 - *
   22.26 - * @author Ing. František Kučera (frantovo.cz)
   22.27 - */
   22.28 -public class DKException extends Exception {
   22.29 -
   22.30 -	public DKException() {
   22.31 -	}
   22.32 -
   22.33 -	public DKException(String message) {
   22.34 -		super(message);
   22.35 -	}
   22.36 -
   22.37 -	public DKException(Throwable cause) {
   22.38 -		super(cause);
   22.39 -	}
   22.40 -
   22.41 -	public DKException(String message, Throwable cause) {
   22.42 -		super(message, cause);
   22.43 -	}
   22.44 -}
    23.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/DatabaseConnection.java	Mon Mar 04 17:06:42 2019 +0100
    23.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    23.3 @@ -1,192 +0,0 @@
    23.4 -/**
    23.5 - * SQL-DK
    23.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    23.7 - *
    23.8 - * This program is free software: you can redistribute it and/or modify
    23.9 - * it under the terms of the GNU General Public License as published by
   23.10 - * the Free Software Foundation, either version 3 of the License, or
   23.11 - * (at your option) any later version.
   23.12 - *
   23.13 - * This program is distributed in the hope that it will be useful,
   23.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   23.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   23.16 - * GNU General Public License for more details.
   23.17 - *
   23.18 - * You should have received a copy of the GNU General Public License
   23.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   23.20 - */
   23.21 -package info.globalcode.sql.dk;
   23.22 -
   23.23 -import static info.globalcode.sql.dk.jmx.ConnectionManagement.incrementCounter;
   23.24 -import static info.globalcode.sql.dk.jmx.ConnectionManagement.resetCounter;
   23.25 -import info.globalcode.sql.dk.batch.Batch;
   23.26 -import info.globalcode.sql.dk.batch.BatchException;
   23.27 -import info.globalcode.sql.dk.configuration.DatabaseDefinition;
   23.28 -import info.globalcode.sql.dk.configuration.Loader;
   23.29 -import info.globalcode.sql.dk.configuration.Properties;
   23.30 -import info.globalcode.sql.dk.formatting.ColumnsHeader;
   23.31 -import info.globalcode.sql.dk.formatting.Formatter;
   23.32 -import info.globalcode.sql.dk.jmx.ConnectionManagement;
   23.33 -import info.globalcode.sql.dk.jmx.ConnectionManagement.COUNTER;
   23.34 -import java.sql.Connection;
   23.35 -import java.sql.PreparedStatement;
   23.36 -import java.sql.ResultSet;
   23.37 -import java.sql.SQLException;
   23.38 -import java.sql.SQLWarning;
   23.39 -import java.util.logging.Level;
   23.40 -import java.util.logging.Logger;
   23.41 -
   23.42 -/**
   23.43 - * Represents connected database. Is derived from {@linkplain DatabaseDefinition}.
   23.44 - * Wraps {@linkplain Connection}.
   23.45 - *
   23.46 - * Is responsible for executing {@linkplain SQLCommand} and passing results to the
   23.47 - * {@linkplain Formatter}.
   23.48 - *
   23.49 - * @author Ing. František Kučera (frantovo.cz)
   23.50 - */
   23.51 -public class DatabaseConnection implements AutoCloseable {
   23.52 -
   23.53 -	private static final Logger log = Logger.getLogger(DatabaseConnection.class.getName());
   23.54 -	public static final String JDBC_PROPERTY_USER = "user";
   23.55 -	public static final String JDBC_PROPERTY_PASSWORD = "password";
   23.56 -	private final DatabaseDefinition databaseDefinition;
   23.57 -	private final Connection connection;
   23.58 -	private final Properties properties;
   23.59 -	/**
   23.60 -	 * Could be null = JMX is disabled → must check, see functions in
   23.61 -	 * {@linkplain ConnectionManagement}
   23.62 -	 */
   23.63 -	private final ConnectionManagement connectionMBean;
   23.64 -
   23.65 -	/**
   23.66 -	 *
   23.67 -	 * @param databaseDefinition DB url, name, password etc.
   23.68 -	 * @param properties additional properties from CLI
   23.69 -	 * @param connectionMBean JMX management bean | null = disabled JMX reporting
   23.70 -	 * @throws SQLException
   23.71 -	 */
   23.72 -	public DatabaseConnection(DatabaseDefinition databaseDefinition, Properties properties, ConnectionManagement connectionMBean) throws SQLException {
   23.73 -		this.databaseDefinition = databaseDefinition;
   23.74 -		this.properties = properties;
   23.75 -		this.connectionMBean = connectionMBean;
   23.76 -		this.connection = Loader.jdbcConnect(databaseDefinition, properties);
   23.77 -	}
   23.78 -
   23.79 -	public void executeQuery(SQLCommand sqlCommand, Formatter formatter) throws SQLException {
   23.80 -		formatter.writeStartBatch();
   23.81 -		formatter.writeStartDatabase(databaseDefinition);
   23.82 -		formatter.writeStartStatement();
   23.83 -		formatter.writeQuery(sqlCommand.getQuery());
   23.84 -		formatter.writeParameters(sqlCommand.getParameters());
   23.85 -		processCommand(sqlCommand, formatter);
   23.86 -		formatter.writeEndStatement();
   23.87 -		formatter.writeEndDatabase();
   23.88 -		formatter.writeEndBatch();
   23.89 -	}
   23.90 -
   23.91 -	public void executeBatch(Batch batch, Formatter formatter) throws SQLException, BatchException {
   23.92 -		formatter.writeStartBatch();
   23.93 -		formatter.writeStartDatabase(databaseDefinition);
   23.94 -		while (batch.hasNext()) {
   23.95 -			SQLCommand sqlCommand = batch.next();
   23.96 -			formatter.writeStartStatement();
   23.97 -			formatter.writeQuery(sqlCommand.getQuery());
   23.98 -			formatter.writeParameters(sqlCommand.getParameters());
   23.99 -			processCommand(sqlCommand, formatter);
  23.100 -			formatter.writeEndStatement();
  23.101 -		}
  23.102 -		formatter.writeEndDatabase();
  23.103 -		formatter.writeEndBatch();
  23.104 -	}
  23.105 -
  23.106 -	private void processCommand(SQLCommand sqlCommand, Formatter formatter) throws SQLException {
  23.107 -		incrementCounter(connectionMBean, COUNTER.COMMAND);
  23.108 -		resetCounter(connectionMBean, COUNTER.RECORD_CURRENT);
  23.109 -
  23.110 -		try (PreparedStatement ps = sqlCommand.prepareStatement(connection)) {
  23.111 -			log.log(Level.FINE, "Statement prepared");
  23.112 -			sqlCommand.parametrize(ps);
  23.113 -
  23.114 -			boolean isRS = ps.execute();
  23.115 -			log.log(Level.FINE, "Statement executed");
  23.116 -			if (isRS) {
  23.117 -				try (ResultSet rs = ps.getResultSet()) {
  23.118 -					processResultSet(rs, formatter);
  23.119 -				}
  23.120 -			} else {
  23.121 -				processUpdateResult(ps, formatter);
  23.122 -			}
  23.123 -			logWarnings(ps);
  23.124 -
  23.125 -			while (ps.getMoreResults() || ps.getUpdateCount() > -1) {
  23.126 -				ResultSet rs = ps.getResultSet();
  23.127 -				if (rs == null) {
  23.128 -					processUpdateResult(ps, formatter);
  23.129 -				} else {
  23.130 -					processResultSet(rs, formatter);
  23.131 -					rs.close();
  23.132 -				}
  23.133 -				logWarnings(ps);
  23.134 -			}
  23.135 -		}
  23.136 -	}
  23.137 -
  23.138 -	private void processUpdateResult(PreparedStatement ps, Formatter formatter) throws SQLException {
  23.139 -		formatter.writeUpdatesResult(ps.getUpdateCount());
  23.140 -	}
  23.141 -
  23.142 -	private void processResultSet(ResultSet rs, Formatter formatter) throws SQLException {
  23.143 -		formatter.writeStartResultSet(new ColumnsHeader(rs.getMetaData()));
  23.144 -
  23.145 -		int columnCount = rs.getMetaData().getColumnCount();
  23.146 -
  23.147 -		while (rs.next()) {
  23.148 -			incrementCounter(connectionMBean, COUNTER.RECORD_CURRENT);
  23.149 -			incrementCounter(connectionMBean, COUNTER.RECORD_TOTAL);
  23.150 -
  23.151 -			formatter.writeStartRow();
  23.152 -
  23.153 -			for (int i = 1; i <= columnCount; i++) {
  23.154 -				formatter.writeColumnValue(rs.getObject(i));
  23.155 -			}
  23.156 -
  23.157 -			formatter.writeEndRow();
  23.158 -		}
  23.159 -
  23.160 -		formatter.writeEndResultSet();
  23.161 -	}
  23.162 -
  23.163 -	private void logWarnings(PreparedStatement ps) throws SQLException {
  23.164 -		SQLWarning w = ps.getWarnings();
  23.165 -		while (w != null) {
  23.166 -			log.log(Level.WARNING, "SQL: {0}", w.getLocalizedMessage());
  23.167 -			w = w.getNextWarning();
  23.168 -		}
  23.169 -		ps.clearWarnings();
  23.170 -	}
  23.171 -
  23.172 -	/**
  23.173 -	 * Tests if this connection is live.
  23.174 -	 *
  23.175 -	 * @return true if test was successful
  23.176 -	 * @throws SQLException if test fails
  23.177 -	 */
  23.178 -	public boolean test() throws SQLException {
  23.179 -		connection.getAutoCommit();
  23.180 -		return true;
  23.181 -	}
  23.182 -
  23.183 -	public String getProductName() throws SQLException {
  23.184 -		return connection.getMetaData().getDatabaseProductName();
  23.185 -	}
  23.186 -
  23.187 -	public String getProductVersion() throws SQLException {
  23.188 -		return connection.getMetaData().getDatabaseProductVersion();
  23.189 -	}
  23.190 -
  23.191 -	@Override
  23.192 -	public void close() throws SQLException {
  23.193 -		connection.close();
  23.194 -	}
  23.195 -}
    24.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/Functions.java	Mon Mar 04 17:06:42 2019 +0100
    24.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    24.3 @@ -1,254 +0,0 @@
    24.4 -/**
    24.5 - * SQL-DK
    24.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    24.7 - *
    24.8 - * This program is free software: you can redistribute it and/or modify
    24.9 - * it under the terms of the GNU General Public License as published by
   24.10 - * the Free Software Foundation, either version 3 of the License, or
   24.11 - * (at your option) any later version.
   24.12 - *
   24.13 - * This program is distributed in the hope that it will be useful,
   24.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   24.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   24.16 - * GNU General Public License for more details.
   24.17 - *
   24.18 - * You should have received a copy of the GNU General Public License
   24.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   24.20 - */
   24.21 -package info.globalcode.sql.dk;
   24.22 -
   24.23 -import info.globalcode.sql.dk.configuration.NameIdentified;
   24.24 -import info.globalcode.sql.dk.configuration.PropertyDeclaration;
   24.25 -import info.globalcode.sql.dk.configuration.PropertyDeclarations;
   24.26 -import info.globalcode.sql.dk.formatting.Formatter;
   24.27 -import java.io.BufferedReader;
   24.28 -import java.io.File;
   24.29 -import java.io.IOException;
   24.30 -import java.io.InputStream;
   24.31 -import java.io.InputStreamReader;
   24.32 -import java.io.PrintWriter;
   24.33 -import java.util.ArrayList;
   24.34 -import java.util.Arrays;
   24.35 -import java.util.Collection;
   24.36 -import java.util.Collections;
   24.37 -import java.util.List;
   24.38 -import java.util.Map;
   24.39 -import java.util.regex.Matcher;
   24.40 -import java.util.regex.Pattern;
   24.41 -
   24.42 -/**
   24.43 - *
   24.44 - * @author Ing. František Kučera (frantovo.cz)
   24.45 - */
   24.46 -public class Functions {
   24.47 -
   24.48 -	private static final String NBSP = " ";
   24.49 -	private static final Pattern WHITESPACE_TO_REPLACE = Pattern.compile("\\n|\\r|\\t|" + NBSP);
   24.50 -
   24.51 -	private Functions() {
   24.52 -	}
   24.53 -
   24.54 -	public static boolean equalz(Object a, Object b) {
   24.55 -		return a == null ? b == null : a.equals(b);
   24.56 -	}
   24.57 -
   24.58 -	/**
   24.59 -	 *
   24.60 -	 * @param text String to be examinated
   24.61 -	 * @param trim whether text should be trimmed before examination
   24.62 -	 * @return whether text is not empty and one or more characters long (after prospective trim)
   24.63 -	 */
   24.64 -	public static boolean isEmpty(String text, boolean trim) {
   24.65 -		if (text == null) {
   24.66 -			return true;
   24.67 -		} else {
   24.68 -			if (trim) {
   24.69 -				text = text.trim();
   24.70 -			}
   24.71 -			return text.isEmpty();
   24.72 -		}
   24.73 -	}
   24.74 -
   24.75 -	/**
   24.76 -	 * @see #isEmpty(java.lang.String, boolean)
   24.77 -	 */
   24.78 -	public static boolean isNotEmpty(String text, boolean trim) {
   24.79 -		return !isEmpty(text, trim);
   24.80 -	}
   24.81 -
   24.82 -	public boolean isEmpty(Collection c) {
   24.83 -		return c == null || c.isEmpty();
   24.84 -	}
   24.85 -
   24.86 -	public boolean isNotEmpty(Collection c) {
   24.87 -		return !isEmpty(c);
   24.88 -	}
   24.89 -
   24.90 -	public boolean isEmpty(Map m) {
   24.91 -		return m == null || m.isEmpty();
   24.92 -	}
   24.93 -
   24.94 -	public boolean isNotEmpty(Map m) {
   24.95 -		return !isEmpty(m);
   24.96 -	}
   24.97 -
   24.98 -	/**
   24.99 -	 * @return empty collection if given one is null | or the original one
  24.100 -	 */
  24.101 -	public static <T> Collection<T> notNull(Collection<T> c) {
  24.102 -		if (c == null) {
  24.103 -			return Collections.emptyList();
  24.104 -		} else {
  24.105 -			return c;
  24.106 -		}
  24.107 -	}
  24.108 -
  24.109 -	public static <T extends NameIdentified> T findByName(Collection<T> collection, String name) {
  24.110 -		for (T element : notNull(collection)) {
  24.111 -			if (element != null && equalz(element.getName(), name)) {
  24.112 -				return element;
  24.113 -			}
  24.114 -		}
  24.115 -
  24.116 -		return null;
  24.117 -	}
  24.118 -
  24.119 -	/**
  24.120 -	 * Copy file from Java resources to file system.
  24.121 -	 */
  24.122 -	public static void installResource(String resourceName, File target) throws IOException {
  24.123 -		try (BufferedReader reader = new BufferedReader(new InputStreamReader(Functions.class.getClassLoader().getResourceAsStream(resourceName)))) {
  24.124 -			try (PrintWriter writer = new PrintWriter(target)) {
  24.125 -				while (true) {
  24.126 -					String line = reader.readLine();
  24.127 -					if (line == null) {
  24.128 -						break;
  24.129 -					} else {
  24.130 -						writer.println(line);
  24.131 -					}
  24.132 -				}
  24.133 -			}
  24.134 -		}
  24.135 -	}
  24.136 -
  24.137 -	public static String rpad(String s, int n) {
  24.138 -		if (n > 0) {
  24.139 -			return String.format("%1$-" + n + "s", s);
  24.140 -		} else {
  24.141 -			return s;
  24.142 -		}
  24.143 -	}
  24.144 -
  24.145 -	public static String lpad(String s, int n) {
  24.146 -		if (n > 0) {
  24.147 -			return String.format("%1$" + n + "s", s);
  24.148 -		} else {
  24.149 -			return s;
  24.150 -		}
  24.151 -	}
  24.152 -
  24.153 -	public static String repeat(char ch, int count) {
  24.154 -		char[] array = new char[count];
  24.155 -		Arrays.fill(array, ch);
  24.156 -		return new String(array);
  24.157 -	}
  24.158 -	private final static char[] HEX_ALPHABET = "0123456789abcdef".toCharArray();
  24.159 -
  24.160 -	public static String toHex(byte[] bytes) {
  24.161 -		char[] hexChars = new char[bytes.length * 2];
  24.162 -		for (int j = 0; j < bytes.length; j++) {
  24.163 -			int v = bytes[j] & 0xFF;
  24.164 -			hexChars[j * 2] = HEX_ALPHABET[v >>> 4];
  24.165 -			hexChars[j * 2 + 1] = HEX_ALPHABET[v & 0x0F];
  24.166 -		}
  24.167 -		return new String(hexChars);
  24.168 -	}
  24.169 -
  24.170 -	public static String readString(InputStream in) throws IOException {
  24.171 -		try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
  24.172 -			StringBuilder result = new StringBuilder();
  24.173 -			for (String line = br.readLine(); line != null; line = br.readLine()) {
  24.174 -				result.append(line);
  24.175 -				result.append('\n');
  24.176 -			}
  24.177 -			return result.toString();
  24.178 -		}
  24.179 -	}
  24.180 -
  24.181 -	/**
  24.182 -	 * @param <P> type of the last parent
  24.183 -	 * @param <T> type of the examined class
  24.184 -	 * @param type examined class
  24.185 -	 * @param lastParent the last parent type to stop at
  24.186 -	 * @return list of types starting with <code>type</code> and ending with <code>lastParent</code>
  24.187 -	 */
  24.188 -	public static <P, T extends P> List<Class<? extends P>> getClassHierarchy(Class<T> type, Class<P> lastParent) {
  24.189 -		List<Class<? extends P>> hierarchy = new ArrayList<>();
  24.190 -
  24.191 -		for (Class current = type; current != null && lastParent.isAssignableFrom(current); current = current.getSuperclass()) {
  24.192 -			hierarchy.add(current);
  24.193 -		}
  24.194 -
  24.195 -		return hierarchy;
  24.196 -	}
  24.197 -
  24.198 -	public static PropertyDeclaration[] getPropertyDeclarations(Class<? extends Formatter> formatterClass) {
  24.199 -		PropertyDeclarations properties = formatterClass.getAnnotation(PropertyDeclarations.class);
  24.200 -
  24.201 -		if (properties == null) {
  24.202 -			PropertyDeclaration p = formatterClass.getAnnotation(PropertyDeclaration.class);
  24.203 -			return p == null ? new PropertyDeclaration[]{} : new PropertyDeclaration[]{p};
  24.204 -		} else {
  24.205 -			return properties.value();
  24.206 -		}
  24.207 -	}
  24.208 -
  24.209 -	/**
  24.210 -	 * TODO: support background or styles and move to ColorfulPrintWriter
  24.211 -	 *
  24.212 -	 * @param out
  24.213 -	 * @param valueString
  24.214 -	 * @param basicColor
  24.215 -	 * @param escapeColor
  24.216 -	 */
  24.217 -	public static void printValueWithWhitespaceReplaced(ColorfulPrintWriter out, String valueString, ColorfulPrintWriter.TerminalColor basicColor, ColorfulPrintWriter.TerminalColor escapeColor) {
  24.218 -
  24.219 -		Matcher m = WHITESPACE_TO_REPLACE.matcher(valueString);
  24.220 -
  24.221 -		int start = 0;
  24.222 -
  24.223 -		while (m.find(start)) {
  24.224 -
  24.225 -			printColorOrNot(out, basicColor, valueString.substring(start, m.start()));
  24.226 -
  24.227 -			switch (m.group()) {
  24.228 -				case "\n":
  24.229 -					out.print(escapeColor, "↲");
  24.230 -					break;
  24.231 -				case "\r":
  24.232 -					out.print(escapeColor, "⏎");
  24.233 -					break;
  24.234 -				case "\t":
  24.235 -					out.print(escapeColor, "↹");
  24.236 -					break;
  24.237 -				case NBSP:
  24.238 -					out.print(escapeColor, "⎵");
  24.239 -					break;
  24.240 -				default:
  24.241 -					throw new IllegalStateException("Unexpected whitespace token: „" + m.group() + "“");
  24.242 -			}
  24.243 -
  24.244 -			start = m.end();
  24.245 -		}
  24.246 -
  24.247 -		printColorOrNot(out, basicColor, valueString.substring(start, valueString.length()));
  24.248 -	}
  24.249 -
  24.250 -	private static void printColorOrNot(ColorfulPrintWriter out, ColorfulPrintWriter.TerminalColor color, String text) {
  24.251 -		if (color == null) {
  24.252 -			out.print(text);
  24.253 -		} else {
  24.254 -			out.print(color, text);
  24.255 -		}
  24.256 -	}
  24.257 -}
    25.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/InfoLister.java	Mon Mar 04 17:06:42 2019 +0100
    25.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    25.3 @@ -1,673 +0,0 @@
    25.4 -/**
    25.5 - * SQL-DK
    25.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    25.7 - *
    25.8 - * This program is free software: you can redistribute it and/or modify
    25.9 - * it under the terms of the GNU General Public License as published by
   25.10 - * the Free Software Foundation, either version 3 of the License, or
   25.11 - * (at your option) any later version.
   25.12 - *
   25.13 - * This program is distributed in the hope that it will be useful,
   25.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   25.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   25.16 - * GNU General Public License for more details.
   25.17 - *
   25.18 - * You should have received a copy of the GNU General Public License
   25.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   25.20 - */
   25.21 -package info.globalcode.sql.dk;
   25.22 -
   25.23 -import info.globalcode.sql.dk.configuration.CommandArgument;
   25.24 -import info.globalcode.sql.dk.configuration.Configuration;
   25.25 -import info.globalcode.sql.dk.configuration.ConfigurationException;
   25.26 -import info.globalcode.sql.dk.configuration.ConfigurationProvider;
   25.27 -import info.globalcode.sql.dk.configuration.DatabaseDefinition;
   25.28 -import info.globalcode.sql.dk.configuration.FormatterDefinition;
   25.29 -import info.globalcode.sql.dk.configuration.Properties;
   25.30 -import info.globalcode.sql.dk.configuration.Property;
   25.31 -import info.globalcode.sql.dk.configuration.PropertyDeclaration;
   25.32 -import info.globalcode.sql.dk.configuration.TunnelDefinition;
   25.33 -import info.globalcode.sql.dk.formatting.ColumnsHeader;
   25.34 -import info.globalcode.sql.dk.formatting.CommonProperties;
   25.35 -import info.globalcode.sql.dk.formatting.FakeSqlArray;
   25.36 -import info.globalcode.sql.dk.formatting.Formatter;
   25.37 -import info.globalcode.sql.dk.formatting.FormatterContext;
   25.38 -import info.globalcode.sql.dk.formatting.FormatterException;
   25.39 -import java.io.BufferedReader;
   25.40 -import java.io.ByteArrayOutputStream;
   25.41 -import java.io.InputStreamReader;
   25.42 -import java.io.PrintStream;
   25.43 -import java.sql.Array;
   25.44 -import java.sql.Driver;
   25.45 -import java.sql.DriverManager;
   25.46 -import java.sql.DriverPropertyInfo;
   25.47 -import java.sql.SQLException;
   25.48 -import java.util.ArrayList;
   25.49 -import java.util.Collections;
   25.50 -import java.util.Comparator;
   25.51 -import java.util.EnumSet;
   25.52 -import java.util.HashMap;
   25.53 -import java.util.HashSet;
   25.54 -import java.util.List;
   25.55 -import java.util.Map;
   25.56 -import java.util.Map.Entry;
   25.57 -import java.util.ServiceLoader;
   25.58 -import java.util.Set;
   25.59 -import java.util.concurrent.ExecutorService;
   25.60 -import java.util.concurrent.Executors;
   25.61 -import java.util.concurrent.TimeUnit;
   25.62 -import java.util.logging.Level;
   25.63 -import java.util.logging.LogRecord;
   25.64 -import java.util.logging.Logger;
   25.65 -import javax.sql.rowset.RowSetMetaDataImpl;
   25.66 -
   25.67 -/**
   25.68 - * Displays info like help, version etc.
   25.69 - *
   25.70 - * @author Ing. František Kučera (frantovo.cz)
   25.71 - */
   25.72 -public class InfoLister {
   25.73 -
   25.74 -	private static final Logger log = Logger.getLogger(InfoLister.class.getName());
   25.75 -	/**
   25.76 -	 * Fake database name for output formatting
   25.77 -	 */
   25.78 -	public static final String CONFIG_DB_NAME = "sqldk_configuration";
   25.79 -	private final PrintStream out;
   25.80 -	private final ConfigurationProvider configurationProvider;
   25.81 -	private final CLIOptions options;
   25.82 -	private Formatter formatter;
   25.83 -
   25.84 -	public InfoLister(PrintStream out, ConfigurationProvider configurationProvider, CLIOptions options) {
   25.85 -		this.out = out;
   25.86 -		this.configurationProvider = configurationProvider;
   25.87 -		this.options = options;
   25.88 -	}
   25.89 -
   25.90 -	public void showInfo() throws ConfigurationException, FormatterException {
   25.91 -		EnumSet<InfoType> commands = options.getShowInfo();
   25.92 -
   25.93 -		boolean formattinNeeded = false;
   25.94 -
   25.95 -		for (InfoType infoType : commands) {
   25.96 -			switch (infoType) {
   25.97 -				case CONNECTION:
   25.98 -				case JDBC_DRIVERS:
   25.99 -				case JDBC_PROPERTIES:
  25.100 -				case DATABASES:
  25.101 -				case FORMATTERS:
  25.102 -				case FORMATTER_PROPERTIES:
  25.103 -				case TYPES:
  25.104 -				case JAVA_PROPERTIES:
  25.105 -				case ENVIRONMENT_VARIABLES:
  25.106 -					formattinNeeded = true;
  25.107 -					break;
  25.108 -			}
  25.109 -		}
  25.110 -
  25.111 -		if (formattinNeeded) {
  25.112 -			try (Formatter f = getFormatter()) {
  25.113 -				formatter = f;
  25.114 -				formatter.writeStartBatch();
  25.115 -				DatabaseDefinition dd = new DatabaseDefinition();
  25.116 -				dd.setName(CONFIG_DB_NAME);
  25.117 -				formatter.writeStartDatabase(dd);
  25.118 -				showInfos(commands);
  25.119 -				formatter.writeEndDatabase();
  25.120 -				formatter.writeEndBatch();
  25.121 -				formatter.close();
  25.122 -			}
  25.123 -		} else {
  25.124 -			showInfos(commands);
  25.125 -		}
  25.126 -	}
  25.127 -
  25.128 -	private void showInfos(EnumSet<InfoType> commands) throws ConfigurationException, FormatterException {
  25.129 -		for (InfoType infoType : commands) {
  25.130 -			infoType.showInfo(this);
  25.131 -		}
  25.132 -	}
  25.133 -
  25.134 -	private void listJavaProperties() throws FormatterException, ConfigurationException {
  25.135 -		ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("value", SQLType.VARCHAR));
  25.136 -		List<Object[]> data = new ArrayList<>();
  25.137 -		for (Entry<Object, Object> e : System.getProperties().entrySet()) {
  25.138 -			data.add(new Object[]{e.getKey(), e.getValue()});
  25.139 -		}
  25.140 -		printTable(formatter, header, "-- Java system properties", null, data, 0);
  25.141 -	}
  25.142 -
  25.143 -	private void listEnvironmentVariables() throws FormatterException, ConfigurationException {
  25.144 -		ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("value", SQLType.VARCHAR));
  25.145 -		List<Object[]> data = new ArrayList<>();
  25.146 -		for (Entry<String, String> e : System.getenv().entrySet()) {
  25.147 -			data.add(new Object[]{e.getKey(), e.getValue()});
  25.148 -		}
  25.149 -		printTable(formatter, header, "-- environment variables", null, data, 0);
  25.150 -	}
  25.151 -
  25.152 -	private void listFormatters() throws ConfigurationException, FormatterException {
  25.153 -		ColumnsHeader header = constructHeader(
  25.154 -				new HeaderField("name", SQLType.VARCHAR),
  25.155 -				new HeaderField("built_in", SQLType.BOOLEAN),
  25.156 -				new HeaderField("default", SQLType.BOOLEAN),
  25.157 -				new HeaderField("class_name", SQLType.VARCHAR),
  25.158 -				new HeaderField("valid", SQLType.BOOLEAN));
  25.159 -		List<Object[]> data = new ArrayList<>();
  25.160 -
  25.161 -		String defaultFormatter = configurationProvider.getConfiguration().getDefaultFormatter();
  25.162 -		defaultFormatter = defaultFormatter == null ? Configuration.DEFAULT_FORMATTER : defaultFormatter;
  25.163 -
  25.164 -		for (FormatterDefinition fd : configurationProvider.getConfiguration().getBuildInFormatters()) {
  25.165 -			data.add(new Object[]{fd.getName(), true, defaultFormatter.equals(fd.getName()), fd.getClassName(), isInstantiable(fd)});
  25.166 -		}
  25.167 -
  25.168 -		for (FormatterDefinition fd : configurationProvider.getConfiguration().getFormatters()) {
  25.169 -			data.add(new Object[]{fd.getName(), false, defaultFormatter.equals(fd.getName()), fd.getClassName(), isInstantiable(fd)});
  25.170 -		}
  25.171 -
  25.172 -		printTable(formatter, header, "-- configured and built-in output formatters", null, data);
  25.173 -	}
  25.174 -
  25.175 -	private boolean isInstantiable(FormatterDefinition fd) {
  25.176 -		try {
  25.177 -			try (ByteArrayOutputStream testStream = new ByteArrayOutputStream()) {
  25.178 -				fd.getInstance(new FormatterContext(testStream, new Properties(0)));
  25.179 -				return true;
  25.180 -			}
  25.181 -		} catch (Exception e) {
  25.182 -			log.log(Level.SEVERE, "Unable to create an instance of formatter: " + fd.getName(), e);
  25.183 -			return false;
  25.184 -		}
  25.185 -	}
  25.186 -
  25.187 -	private void listFormatterProperties() throws FormatterException, ConfigurationException {
  25.188 -		for (String formatterName : options.getFormatterNamesToListProperties()) {
  25.189 -			listFormatterProperties(formatterName);
  25.190 -		}
  25.191 -	}
  25.192 -
  25.193 -	private void listFormatterProperties(String formatterName) throws FormatterException, ConfigurationException {
  25.194 -		FormatterDefinition fd = configurationProvider.getConfiguration().getFormatter(formatterName);
  25.195 -		try {
  25.196 -
  25.197 -			// currently only for debugging purposes
  25.198 -			// TODO: introduce --info-lister-property or generic filtering capability in printTable() ?
  25.199 -			boolean printDeclaredIn = options.getFormatterProperties().getBoolean("InfoLister:print:declared_in", false);
  25.200 -
  25.201 -			List<HeaderField> headerFields = new ArrayList<>();
  25.202 -			headerFields.add(new HeaderField("name", SQLType.VARCHAR));
  25.203 -			headerFields.add(new HeaderField("type", SQLType.VARCHAR));
  25.204 -			headerFields.add(new HeaderField("default", SQLType.VARCHAR));
  25.205 -			headerFields.add(new HeaderField("description", SQLType.VARCHAR));
  25.206 -			if (printDeclaredIn) {
  25.207 -				headerFields.add(new HeaderField("declared_in", SQLType.VARCHAR));
  25.208 -			}
  25.209 -
  25.210 -			ColumnsHeader header = constructHeader(headerFields.toArray(new HeaderField[0]));
  25.211 -
  25.212 -			Map<String, Object[]> data = new HashMap<>();
  25.213 -			Class<Formatter> formatterClass = (Class<Formatter>) Class.forName(fd.getClassName());
  25.214 -			List<Class<? extends Formatter>> hierarchy = Functions.getClassHierarchy(formatterClass, Formatter.class);
  25.215 -			Collections.reverse(hierarchy);
  25.216 -			hierarchy.stream().forEach((c) -> {
  25.217 -				for (PropertyDeclaration p : Functions.getPropertyDeclarations(c)) {
  25.218 -					data.put(p.name(), propertyDeclarationToRow(p, c, printDeclaredIn));
  25.219 -				}
  25.220 -			});
  25.221 -
  25.222 -			List<Parameter> parameters = new ArrayList<>();
  25.223 -			parameters.add(new NamedParameter("formatter", formatterName, SQLType.VARCHAR));
  25.224 -
  25.225 -			printTable(formatter, header, "-- formatter properties", parameters, new ArrayList<>(data.values()));
  25.226 -		} catch (ClassNotFoundException e) {
  25.227 -			throw new ConfigurationException("Unable to find class " + fd.getClassName() + " of formatter" + fd.getName(), e);
  25.228 -		}
  25.229 -	}
  25.230 -
  25.231 -	private static Object[] propertyDeclarationToRow(PropertyDeclaration p, Class formatterClass, boolean printDeclaredIn) {
  25.232 -		List list = new ArrayList();
  25.233 -
  25.234 -		list.add(p.name());
  25.235 -		list.add(CommonProperties.getSimpleTypeName(p.type()));
  25.236 -		list.add(p.defaultValue());
  25.237 -		list.add(p.description());
  25.238 -		if (printDeclaredIn) {
  25.239 -			list.add(formatterClass.getName());
  25.240 -		}
  25.241 -
  25.242 -		return list.toArray();
  25.243 -	}
  25.244 -
  25.245 -	private void listTypes() throws FormatterException, ConfigurationException {
  25.246 -		ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("code", SQLType.INTEGER));
  25.247 -		List<Object[]> data = new ArrayList<>();
  25.248 -		for (SQLType sqlType : SQLType.values()) {
  25.249 -			data.add(new Object[]{sqlType.name(), sqlType.getCode()});
  25.250 -		}
  25.251 -		printTable(formatter, header, "-- data types", null, data);
  25.252 -		log.log(Level.INFO, "Type names in --types option are case insensitive");
  25.253 -	}
  25.254 -
  25.255 -	private void listDatabases() throws ConfigurationException, FormatterException {
  25.256 -		ColumnsHeader header = constructHeader(
  25.257 -				new HeaderField("database_name", SQLType.VARCHAR),
  25.258 -				new HeaderField("user_name", SQLType.VARCHAR),
  25.259 -				new HeaderField("database_url", SQLType.VARCHAR));
  25.260 -		List<Object[]> data = new ArrayList<>();
  25.261 -
  25.262 -		final List<DatabaseDefinition> configuredDatabases = configurationProvider.getConfiguration().getDatabases();
  25.263 -		if (configuredDatabases.isEmpty()) {
  25.264 -			log.log(Level.WARNING, "No databases are configured.");
  25.265 -		} else {
  25.266 -			for (DatabaseDefinition dd : configuredDatabases) {
  25.267 -				data.add(new Object[]{dd.getName(), dd.getUserName(), dd.getUrl()});
  25.268 -
  25.269 -				final TunnelDefinition tunnel = dd.getTunnel();
  25.270 -				if (tunnel != null) {
  25.271 -					log.log(Level.INFO, "Tunnel command: {0}", tunnel.getCommand());
  25.272 -					for (CommandArgument ca : Functions.notNull(tunnel.getArguments())) {
  25.273 -						log.log(Level.INFO, "\targument: {0}/{1}", new Object[]{ca.getType(), ca.getValue()});
  25.274 -					}
  25.275 -				}
  25.276 -
  25.277 -			}
  25.278 -		}
  25.279 -
  25.280 -		printTable(formatter, header, "-- configured databases", null, data);
  25.281 -	}
  25.282 -
  25.283 -	private void listJdbcDrivers() throws FormatterException, ConfigurationException {
  25.284 -		ColumnsHeader header = constructHeader(
  25.285 -				new HeaderField("class", SQLType.VARCHAR),
  25.286 -				new HeaderField("version", SQLType.VARCHAR),
  25.287 -				new HeaderField("major", SQLType.INTEGER),
  25.288 -				new HeaderField("minor", SQLType.INTEGER),
  25.289 -				new HeaderField("jdbc_compliant", SQLType.BOOLEAN));
  25.290 -		List<Object[]> data = new ArrayList<>();
  25.291 -
  25.292 -		final ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class);
  25.293 -		for (Driver d : drivers) {
  25.294 -			data.add(new Object[]{
  25.295 -				d.getClass().getName(),
  25.296 -				d.getMajorVersion() + "." + d.getMinorVersion(),
  25.297 -				d.getMajorVersion(),
  25.298 -				d.getMinorVersion(),
  25.299 -				d.jdbcCompliant()
  25.300 -			});
  25.301 -		}
  25.302 -
  25.303 -		printTable(formatter, header, "-- discovered JDBC drivers (available on the CLASSPATH)", null, data);
  25.304 -	}
  25.305 -
  25.306 -	private void listJdbcProperties() throws FormatterException, ConfigurationException {
  25.307 -		for (String dbName : options.getDatabaseNamesToListProperties()) {
  25.308 -			ColumnsHeader header = constructHeader(
  25.309 -					new HeaderField("property_name", SQLType.VARCHAR),
  25.310 -					new HeaderField("required", SQLType.BOOLEAN),
  25.311 -					new HeaderField("choices", SQLType.ARRAY),
  25.312 -					new HeaderField("configured_value", SQLType.VARCHAR),
  25.313 -					new HeaderField("description", SQLType.VARCHAR));
  25.314 -			List<Object[]> data = new ArrayList<>();
  25.315 -
  25.316 -			DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName);
  25.317 -
  25.318 -			Driver driver = findDriver(dd);
  25.319 -
  25.320 -			if (driver == null) {
  25.321 -				log.log(Level.WARNING, "No JDBC driver was found for DB: {0} with URL: {1}", new Object[]{dd.getName(), dd.getUrl()});
  25.322 -			} else {
  25.323 -				log.log(Level.INFO, "For DB: {0} was found JDBC driver: {1}", new Object[]{dd.getName(), driver.getClass().getName()});
  25.324 -
  25.325 -				try {
  25.326 -					DriverPropertyInfo[] propertyInfos = driver.getPropertyInfo(dd.getUrl(), dd.getProperties().getJavaProperties());
  25.327 -
  25.328 -					Set<String> standardProperties = new HashSet<>();
  25.329 -
  25.330 -					for (DriverPropertyInfo pi : propertyInfos) {
  25.331 -						Array choices = new FakeSqlArray(pi.choices, SQLType.VARCHAR);
  25.332 -						data.add(new Object[]{
  25.333 -							pi.name,
  25.334 -							pi.required,
  25.335 -							choices.getArray() == null ? "" : choices,
  25.336 -							pi.value == null ? "" : pi.value,
  25.337 -							pi.description
  25.338 -						});
  25.339 -						standardProperties.add(pi.name);
  25.340 -					}
  25.341 -
  25.342 -					for (Property p : dd.getProperties()) {
  25.343 -						if (!standardProperties.contains(p.getName())) {
  25.344 -							data.add(new Object[]{
  25.345 -								p.getName(),
  25.346 -								"",
  25.347 -								"",
  25.348 -								p.getValue(),
  25.349 -								""
  25.350 -							});
  25.351 -							log.log(Level.WARNING, "Your configuration contains property „{0}“ not declared by the JDBC driver.", p.getName());
  25.352 -						}
  25.353 -					}
  25.354 -
  25.355 -				} catch (SQLException e) {
  25.356 -					log.log(Level.WARNING, "Error during getting property infos.", e);
  25.357 -				}
  25.358 -
  25.359 -				List<Parameter> parameters = new ArrayList<>();
  25.360 -				parameters.add(new NamedParameter("database", dbName, SQLType.VARCHAR));
  25.361 -				parameters.add(new NamedParameter("driver_class", driver.getClass().getName(), SQLType.VARCHAR));
  25.362 -				parameters.add(new NamedParameter("driver_major_version", driver.getMajorVersion(), SQLType.INTEGER));
  25.363 -				parameters.add(new NamedParameter("driver_minor_version", driver.getMinorVersion(), SQLType.INTEGER));
  25.364 -
  25.365 -				printTable(formatter, header, "-- configured and configurable JDBC driver properties", parameters, data);
  25.366 -			}
  25.367 -		}
  25.368 -
  25.369 -	}
  25.370 -
  25.371 -	private Driver findDriver(DatabaseDefinition dd) {
  25.372 -		final ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class);
  25.373 -		for (Driver d : drivers) {
  25.374 -			try {
  25.375 -				if (d.acceptsURL(dd.getUrl())) {
  25.376 -					return d;
  25.377 -				}
  25.378 -			} catch (SQLException e) {
  25.379 -				log.log(Level.WARNING, "Error during finding JDBC driver for: " + dd.getName(), e);
  25.380 -			}
  25.381 -		}
  25.382 -		return null;
  25.383 -	}
  25.384 -
  25.385 -	/**
  25.386 -	 * Parallelism for connection testing – maximum concurrent database connections.
  25.387 -	 */
  25.388 -	private static final int TESTING_THREAD_COUNT = 64;
  25.389 -	/**
  25.390 -	 * Time limit for all connection testing threads – particular timeouts per connection will be
  25.391 -	 * much smaller.
  25.392 -	 */
  25.393 -	private static final long TESTING_AWAIT_LIMIT = 1;
  25.394 -	private static final TimeUnit TESTING_AWAIT_UNIT = TimeUnit.DAYS;
  25.395 -
  25.396 -	private void testConnections() throws FormatterException, ConfigurationException {
  25.397 -		ColumnsHeader header = constructHeader(
  25.398 -				new HeaderField("database_name", SQLType.VARCHAR),
  25.399 -				new HeaderField("configured", SQLType.BOOLEAN),
  25.400 -				new HeaderField("connected", SQLType.BOOLEAN),
  25.401 -				new HeaderField("product_name", SQLType.VARCHAR),
  25.402 -				new HeaderField("product_version", SQLType.VARCHAR));
  25.403 -
  25.404 -		log.log(Level.FINE, "Testing DB connections in {0} threads", TESTING_THREAD_COUNT);
  25.405 -
  25.406 -		ExecutorService es = Executors.newFixedThreadPool(TESTING_THREAD_COUNT);
  25.407 -
  25.408 -		final Formatter currentFormatter = formatter;
  25.409 -
  25.410 -		printHeader(currentFormatter, header, "-- database configuration and connectivity test", null);
  25.411 -
  25.412 -		for (final String dbName : options.getDatabaseNamesToTest()) {
  25.413 -			preloadDriver(dbName);
  25.414 -		}
  25.415 -
  25.416 -		for (final String dbName : options.getDatabaseNamesToTest()) {
  25.417 -			es.submit(() -> {
  25.418 -				final Object[] row = testConnection(dbName);
  25.419 -				synchronized (currentFormatter) {
  25.420 -					printRow(currentFormatter, row);
  25.421 -				}
  25.422 -			}
  25.423 -			);
  25.424 -		}
  25.425 -
  25.426 -		es.shutdown();
  25.427 -
  25.428 -		try {
  25.429 -			log.log(Level.FINEST, "Waiting for test results: {0} {1}", new Object[]{TESTING_AWAIT_LIMIT, TESTING_AWAIT_UNIT.name()});
  25.430 -			boolean finished = es.awaitTermination(TESTING_AWAIT_LIMIT, TESTING_AWAIT_UNIT);
  25.431 -			if (finished) {
  25.432 -				log.log(Level.FINEST, "All testing threads finished in time limit.");
  25.433 -			} else {
  25.434 -				throw new FormatterException("Exceeded total time limit for test threads – this should never happen");
  25.435 -			}
  25.436 -		} catch (InterruptedException e) {
  25.437 -			throw new FormatterException("Interrupted while waiting for test results", e);
  25.438 -		}
  25.439 -
  25.440 -		printFooter(currentFormatter);
  25.441 -	}
  25.442 -
  25.443 -	/**
  25.444 -	 * JDBC driver classes should be preloaded in single thread to avoid deadlocks while doing
  25.445 -	 * {@linkplain DriverManager#registerDriver(java.sql.Driver)} during parallel connections.
  25.446 -	 *
  25.447 -	 * @param dbName
  25.448 -	 */
  25.449 -	private void preloadDriver(String dbName) {
  25.450 -		try {
  25.451 -			DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName);
  25.452 -			Driver driver = findDriver(dd);
  25.453 -			if (driver == null) {
  25.454 -				log.log(Level.WARNING, "No Driver found for DB: {0}", dbName);
  25.455 -			} else {
  25.456 -				log.log(Level.FINEST, "Driver preloading for DB: {0} was successfull", dbName);
  25.457 -			}
  25.458 -		} catch (Exception e) {
  25.459 -			LogRecord r = new LogRecord(Level.WARNING, "Failed to preload the Driver for DB: {0}");
  25.460 -			r.setParameters(new Object[]{dbName});
  25.461 -			r.setThrown(e);
  25.462 -			log.log(r);
  25.463 -		}
  25.464 -	}
  25.465 -
  25.466 -	private Object[] testConnection(String dbName) {
  25.467 -		log.log(Level.FINE, "Testing connection to database: {0}", dbName);
  25.468 -
  25.469 -		boolean succesfullyConnected = false;
  25.470 -		boolean succesfullyConfigured = false;
  25.471 -		String productName = null;
  25.472 -		String productVersion = null;
  25.473 -
  25.474 -		try {
  25.475 -			DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName);
  25.476 -			log.log(Level.FINE, "Database definition was loaded from configuration");
  25.477 -			succesfullyConfigured = true;
  25.478 -			try (DatabaseConnection dc = dd.connect(options.getDatabaseProperties())) {
  25.479 -				succesfullyConnected = dc.test();
  25.480 -				productName = dc.getProductName();
  25.481 -				productVersion = dc.getProductVersion();
  25.482 -			}
  25.483 -			log.log(Level.FINE, "Database connection test was successful");
  25.484 -		} catch (ConfigurationException | SQLException | RuntimeException e) {
  25.485 -			log.log(Level.SEVERE, "Error during testing connection " + dbName, e);
  25.486 -		}
  25.487 -
  25.488 -		return new Object[]{dbName, succesfullyConfigured, succesfullyConnected, productName, productVersion};
  25.489 -	}
  25.490 -
  25.491 -	private void printResource(String fileName) {
  25.492 -		try (BufferedReader reader = new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(fileName)))) {
  25.493 -			while (true) {
  25.494 -				String line = reader.readLine();
  25.495 -				if (line == null) {
  25.496 -					break;
  25.497 -				} else {
  25.498 -					println(line);
  25.499 -				}
  25.500 -			}
  25.501 -		} catch (Exception e) {
  25.502 -			log.log(Level.SEVERE, "Unable to print this info. Please see our website for it: " + Constants.WEBSITE, e);
  25.503 -		}
  25.504 -	}
  25.505 -
  25.506 -	private void println(String line) {
  25.507 -		out.println(line);
  25.508 -	}
  25.509 -
  25.510 -	private void printTable(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters, List<Object[]> data) throws ConfigurationException, FormatterException {
  25.511 -		printTable(formatter, header, sql, parameters, data, null);
  25.512 -	}
  25.513 -
  25.514 -	private void printTable(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters, List<Object[]> data, final Integer sortByColumn) throws ConfigurationException, FormatterException {
  25.515 -		printHeader(formatter, header, sql, parameters);
  25.516 -
  25.517 -		if (sortByColumn != null) {
  25.518 -			Collections.sort(data, new Comparator<Object[]>() {
  25.519 -
  25.520 -				@Override
  25.521 -				public int compare(Object[] o1, Object[] o2) {
  25.522 -					String s1 = String.valueOf(o1[sortByColumn]);
  25.523 -					String s2 = String.valueOf(o2[sortByColumn]);
  25.524 -					return s1.compareTo(s2);
  25.525 -				}
  25.526 -			});
  25.527 -		}
  25.528 -
  25.529 -		for (Object[] row : data) {
  25.530 -			printRow(formatter, row);
  25.531 -		}
  25.532 -
  25.533 -		printFooter(formatter);
  25.534 -	}
  25.535 -
  25.536 -	private void printHeader(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters) {
  25.537 -		formatter.writeStartStatement();
  25.538 -		if (sql != null) {
  25.539 -			formatter.writeQuery(sql);
  25.540 -			if (parameters != null) {
  25.541 -				formatter.writeParameters(parameters);
  25.542 -			}
  25.543 -		}
  25.544 -		formatter.writeStartResultSet(header);
  25.545 -	}
  25.546 -
  25.547 -	private void printRow(Formatter formatter, Object[] row) {
  25.548 -		formatter.writeStartRow();
  25.549 -		for (Object cell : row) {
  25.550 -			formatter.writeColumnValue(cell);
  25.551 -		}
  25.552 -		formatter.writeEndRow();
  25.553 -	}
  25.554 -
  25.555 -	private void printFooter(Formatter formatter) {
  25.556 -		formatter.writeEndResultSet();
  25.557 -		formatter.writeEndStatement();
  25.558 -	}
  25.559 -
  25.560 -	private Formatter getFormatter() throws ConfigurationException, FormatterException {
  25.561 -		String formatterName = options.getFormatterName();
  25.562 -		formatterName = formatterName == null ? Configuration.DEFAULT_FORMATTER_PREFETCHING : formatterName;
  25.563 -		FormatterDefinition fd = configurationProvider.getConfiguration().getFormatter(formatterName);
  25.564 -		FormatterContext context = new FormatterContext(out, options.getFormatterProperties());
  25.565 -		return fd.getInstance(context);
  25.566 -	}
  25.567 -
  25.568 -	private ColumnsHeader constructHeader(HeaderField... fields) throws FormatterException {
  25.569 -		try {
  25.570 -			RowSetMetaDataImpl metaData = new RowSetMetaDataImpl();
  25.571 -			metaData.setColumnCount(fields.length);
  25.572 -
  25.573 -			for (int i = 0; i < fields.length; i++) {
  25.574 -				HeaderField hf = fields[i];
  25.575 -				int sqlIndex = i + 1;
  25.576 -				metaData.setColumnName(sqlIndex, hf.name);
  25.577 -				metaData.setColumnLabel(sqlIndex, hf.name);
  25.578 -				metaData.setColumnType(sqlIndex, hf.type.getCode());
  25.579 -				metaData.setColumnTypeName(sqlIndex, hf.type.name());
  25.580 -			}
  25.581 -
  25.582 -			return new ColumnsHeader(metaData);
  25.583 -		} catch (SQLException e) {
  25.584 -			throw new FormatterException("Error while constructing table headers", e);
  25.585 -		}
  25.586 -	}
  25.587 -
  25.588 -	private static class HeaderField {
  25.589 -
  25.590 -		String name;
  25.591 -		SQLType type;
  25.592 -
  25.593 -		public HeaderField(String name, SQLType type) {
  25.594 -			this.name = name;
  25.595 -			this.type = type;
  25.596 -		}
  25.597 -	}
  25.598 -
  25.599 -	public enum InfoType {
  25.600 -
  25.601 -		HELP {
  25.602 -			@Override
  25.603 -			public void showInfo(InfoLister infoLister) {
  25.604 -				infoLister.printResource(Constants.HELP_FILE);
  25.605 -			}
  25.606 -		},
  25.607 -		VERSION {
  25.608 -			@Override
  25.609 -			public void showInfo(InfoLister infoLister) {
  25.610 -				infoLister.printResource(Constants.VERSION_FILE);
  25.611 -			}
  25.612 -		},
  25.613 -		LICENSE {
  25.614 -			@Override
  25.615 -			public void showInfo(InfoLister infoLister) {
  25.616 -				infoLister.printResource(Constants.LICENSE_FILE);
  25.617 -			}
  25.618 -		},
  25.619 -		JAVA_PROPERTIES {
  25.620 -			@Override
  25.621 -			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  25.622 -				infoLister.listJavaProperties();
  25.623 -			}
  25.624 -		},
  25.625 -		ENVIRONMENT_VARIABLES {
  25.626 -			@Override
  25.627 -			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  25.628 -				infoLister.listEnvironmentVariables();
  25.629 -			}
  25.630 -		},
  25.631 -		FORMATTERS {
  25.632 -			@Override
  25.633 -			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  25.634 -				infoLister.listFormatters();
  25.635 -			}
  25.636 -		},
  25.637 -		FORMATTER_PROPERTIES {
  25.638 -			@Override
  25.639 -			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  25.640 -				infoLister.listFormatterProperties();
  25.641 -			}
  25.642 -		},
  25.643 -		TYPES {
  25.644 -			@Override
  25.645 -			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  25.646 -				infoLister.listTypes();
  25.647 -			}
  25.648 -		},
  25.649 -		JDBC_DRIVERS {
  25.650 -			@Override
  25.651 -			public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException {
  25.652 -				infoLister.listJdbcDrivers();
  25.653 -			}
  25.654 -		},
  25.655 -		JDBC_PROPERTIES {
  25.656 -			@Override
  25.657 -			public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException {
  25.658 -				infoLister.listJdbcProperties();
  25.659 -			}
  25.660 -		},
  25.661 -		DATABASES {
  25.662 -			@Override
  25.663 -			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  25.664 -				infoLister.listDatabases();
  25.665 -			}
  25.666 -		},
  25.667 -		CONNECTION {
  25.668 -			@Override
  25.669 -			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  25.670 -				infoLister.testConnections();
  25.671 -			}
  25.672 -		};
  25.673 -
  25.674 -		public abstract void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException;
  25.675 -	}
  25.676 -}
    26.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/InvalidOptionsException.java	Mon Mar 04 17:06:42 2019 +0100
    26.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    26.3 @@ -1,66 +0,0 @@
    26.4 -/**
    26.5 - * SQL-DK
    26.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    26.7 - *
    26.8 - * This program is free software: you can redistribute it and/or modify
    26.9 - * it under the terms of the GNU General Public License as published by
   26.10 - * the Free Software Foundation, either version 3 of the License, or
   26.11 - * (at your option) any later version.
   26.12 - *
   26.13 - * This program is distributed in the hope that it will be useful,
   26.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   26.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   26.16 - * GNU General Public License for more details.
   26.17 - *
   26.18 - * You should have received a copy of the GNU General Public License
   26.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   26.20 - */
   26.21 -package info.globalcode.sql.dk;
   26.22 -
   26.23 -import java.util.ArrayList;
   26.24 -import java.util.Collection;
   26.25 -import java.util.Collections;
   26.26 -
   26.27 -/**
   26.28 - *
   26.29 - * @author Ing. František Kučera (frantovo.cz)
   26.30 - */
   26.31 -public class InvalidOptionsException extends Exception {
   26.32 -
   26.33 -	private final Collection<OptionProblem> problems = new ArrayList<>();
   26.34 -
   26.35 -	public Collection<OptionProblem> getProblems() {
   26.36 -		return Collections.unmodifiableCollection(problems);
   26.37 -	}
   26.38 -
   26.39 -	public void addProblem(OptionProblem p) {
   26.40 -		problems.add(p);
   26.41 -	}
   26.42 -
   26.43 -	public boolean hasProblems() {
   26.44 -		return !problems.isEmpty();
   26.45 -	}
   26.46 -
   26.47 -	public static class OptionProblem {
   26.48 -
   26.49 -		private String description;
   26.50 -		private Throwable exception;
   26.51 -
   26.52 -		public OptionProblem(String description) {
   26.53 -			this.description = description;
   26.54 -		}
   26.55 -
   26.56 -		public OptionProblem(String description, Throwable exception) {
   26.57 -			this.description = description;
   26.58 -			this.exception = exception;
   26.59 -		}
   26.60 -
   26.61 -		public String getDescription() {
   26.62 -			return description;
   26.63 -		}
   26.64 -
   26.65 -		public Throwable getException() {
   26.66 -			return exception;
   26.67 -		}
   26.68 -	}
   26.69 -}
    27.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/NamedParameter.java	Mon Mar 04 17:06:42 2019 +0100
    27.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    27.3 @@ -1,48 +0,0 @@
    27.4 -/**
    27.5 - * SQL-DK
    27.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    27.7 - *
    27.8 - * This program is free software: you can redistribute it and/or modify
    27.9 - * it under the terms of the GNU General Public License as published by
   27.10 - * the Free Software Foundation, either version 3 of the License, or
   27.11 - * (at your option) any later version.
   27.12 - *
   27.13 - * This program is distributed in the hope that it will be useful,
   27.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   27.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   27.16 - * GNU General Public License for more details.
   27.17 - *
   27.18 - * You should have received a copy of the GNU General Public License
   27.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   27.20 - */
   27.21 -package info.globalcode.sql.dk;
   27.22 -
   27.23 -import info.globalcode.sql.dk.configuration.NameIdentified;
   27.24 -
   27.25 -/**
   27.26 - *
   27.27 - * @author Ing. František Kučera (frantovo.cz)
   27.28 - */
   27.29 -public class NamedParameter extends Parameter implements NameIdentified {
   27.30 -
   27.31 -	private String name;
   27.32 -
   27.33 -	public NamedParameter(String name, Object value, SQLType type) {
   27.34 -		super(value, type);
   27.35 -		this.name = name;
   27.36 -	}
   27.37 -
   27.38 -	@Override
   27.39 -	public String getName() {
   27.40 -		return name;
   27.41 -	}
   27.42 -
   27.43 -	public void setName(String name) {
   27.44 -		this.name = name;
   27.45 -	}
   27.46 -
   27.47 -	@Override
   27.48 -	public String toString() {
   27.49 -		return "NamedParameter {" + name + " = " + getValue() + "; " + getType() + "}";
   27.50 -	}
   27.51 -}
    28.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/Parameter.java	Mon Mar 04 17:06:42 2019 +0100
    28.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    28.3 @@ -1,69 +0,0 @@
    28.4 -/**
    28.5 - * SQL-DK
    28.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    28.7 - *
    28.8 - * This program is free software: you can redistribute it and/or modify
    28.9 - * it under the terms of the GNU General Public License as published by
   28.10 - * the Free Software Foundation, either version 3 of the License, or
   28.11 - * (at your option) any later version.
   28.12 - *
   28.13 - * This program is distributed in the hope that it will be useful,
   28.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   28.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   28.16 - * GNU General Public License for more details.
   28.17 - *
   28.18 - * You should have received a copy of the GNU General Public License
   28.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   28.20 - */
   28.21 -package info.globalcode.sql.dk;
   28.22 -
   28.23 -import java.sql.Types;
   28.24 -
   28.25 -/**
   28.26 - * Parameter for {@linkplain SQLCommand}
   28.27 - *
   28.28 - * @author Ing. František Kučera (frantovo.cz)
   28.29 - */
   28.30 -public class Parameter {
   28.31 -
   28.32 -	/**
   28.33 -	 * @see Types
   28.34 -	 */
   28.35 -	public static final SQLType DEFAULT_TYPE = SQLType.VARCHAR;
   28.36 -	private Object value;
   28.37 -	private SQLType type;
   28.38 -
   28.39 -	public Parameter() {
   28.40 -	}
   28.41 -
   28.42 -	public Parameter(Object value, SQLType type) {
   28.43 -		this.value = value;
   28.44 -		if (type == null) {
   28.45 -			this.type = DEFAULT_TYPE;
   28.46 -		} else {
   28.47 -			this.type = type;
   28.48 -		}
   28.49 -	}
   28.50 -
   28.51 -	public Object getValue() {
   28.52 -		return value;
   28.53 -	}
   28.54 -
   28.55 -	public void setValue(Object value) {
   28.56 -		this.value = value;
   28.57 -	}
   28.58 -
   28.59 -	/**
   28.60 -	 * @see java.sql.Types
   28.61 -	 */
   28.62 -	public SQLType getType() {
   28.63 -		return type;
   28.64 -	}
   28.65 -
   28.66 -	/**
   28.67 -	 * @see java.sql.Types
   28.68 -	 */
   28.69 -	public void setType(SQLType type) {
   28.70 -		this.type = type;
   28.71 -	}
   28.72 -}
    29.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/SQLCommand.java	Mon Mar 04 17:06:42 2019 +0100
    29.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    29.3 @@ -1,49 +0,0 @@
    29.4 -/**
    29.5 - * SQL-DK
    29.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    29.7 - *
    29.8 - * This program is free software: you can redistribute it and/or modify
    29.9 - * it under the terms of the GNU General Public License as published by
   29.10 - * the Free Software Foundation, either version 3 of the License, or
   29.11 - * (at your option) any later version.
   29.12 - *
   29.13 - * This program is distributed in the hope that it will be useful,
   29.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   29.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   29.16 - * GNU General Public License for more details.
   29.17 - *
   29.18 - * You should have received a copy of the GNU General Public License
   29.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   29.20 - */
   29.21 -package info.globalcode.sql.dk;
   29.22 -
   29.23 -import java.sql.Connection;
   29.24 -import java.sql.PreparedStatement;
   29.25 -import java.sql.SQLException;
   29.26 -import java.util.List;
   29.27 -
   29.28 -/**
   29.29 - * Represents SQL string and its parameters (if there are any).
   29.30 - *
   29.31 - * @author Ing. František Kučera (frantovo.cz)
   29.32 - */
   29.33 -public abstract class SQLCommand {
   29.34 -
   29.35 -	private String query;
   29.36 -
   29.37 -	public SQLCommand(String query) {
   29.38 -		this.query = query;
   29.39 -	}
   29.40 -
   29.41 -	public PreparedStatement prepareStatement(Connection c) throws SQLException {
   29.42 -		return c.prepareStatement(query);
   29.43 -	}
   29.44 -
   29.45 -	public abstract void parametrize(PreparedStatement ps) throws SQLException;
   29.46 -
   29.47 -	public abstract List<? extends Parameter> getParameters();
   29.48 -
   29.49 -	public String getQuery() {
   29.50 -		return query;
   29.51 -	}
   29.52 -}
    30.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/SQLCommandNamed.java	Mon Mar 04 17:06:42 2019 +0100
    30.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    30.3 @@ -1,155 +0,0 @@
    30.4 -/**
    30.5 - * SQL-DK
    30.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    30.7 - *
    30.8 - * This program is free software: you can redistribute it and/or modify
    30.9 - * it under the terms of the GNU General Public License as published by
   30.10 - * the Free Software Foundation, either version 3 of the License, or
   30.11 - * (at your option) any later version.
   30.12 - *
   30.13 - * This program is distributed in the hope that it will be useful,
   30.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   30.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   30.16 - * GNU General Public License for more details.
   30.17 - *
   30.18 - * You should have received a copy of the GNU General Public License
   30.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   30.20 - */
   30.21 -package info.globalcode.sql.dk;
   30.22 -
   30.23 -import static info.globalcode.sql.dk.Functions.findByName;
   30.24 -import java.sql.Connection;
   30.25 -import java.sql.PreparedStatement;
   30.26 -import java.sql.SQLException;
   30.27 -import java.util.ArrayList;
   30.28 -import java.util.List;
   30.29 -import java.util.logging.Level;
   30.30 -import java.util.logging.Logger;
   30.31 -import java.util.regex.Matcher;
   30.32 -import java.util.regex.Pattern;
   30.33 -import java.util.regex.PatternSyntaxException;
   30.34 -
   30.35 -/**
   30.36 - * Has named parameters.
   30.37 - *
   30.38 - * @author Ing. František Kučera (frantovo.cz)
   30.39 - */
   30.40 -public class SQLCommandNamed extends SQLCommand {
   30.41 -
   30.42 -	private static final Logger log = Logger.getLogger(SQLCommandNamed.class.getName());
   30.43 -	private String namePrefix;
   30.44 -	private String nameSuffix;
   30.45 -	private List<NamedParameter> parameters;
   30.46 -	private List<NamedParameter> parametersUsed = new ArrayList<>();
   30.47 -	private StringBuilder updatedQuery;
   30.48 -	private Pattern pattern;
   30.49 -	private SQLCommandNumbered numbered;
   30.50 -
   30.51 -	public SQLCommandNamed(String query, List<NamedParameter> parameters, String namePrefix, String nameSuffix) {
   30.52 -		super(query);
   30.53 -		this.updatedQuery = new StringBuilder(query.length());
   30.54 -		this.parameters = parameters;
   30.55 -		this.namePrefix = namePrefix;
   30.56 -		this.nameSuffix = nameSuffix;
   30.57 -	}
   30.58 -
   30.59 -	@Override
   30.60 -	public PreparedStatement prepareStatement(Connection c) throws SQLException {
   30.61 -		return getSQLCommandNumbered().prepareStatement(c);
   30.62 -	}
   30.63 -
   30.64 -	@Override
   30.65 -	public void parametrize(PreparedStatement ps) throws SQLException {
   30.66 -		getSQLCommandNumbered().parametrize(ps);
   30.67 -	}
   30.68 -
   30.69 -	private void prepare() throws SQLException {
   30.70 -		try {
   30.71 -			buildPattern();
   30.72 -			placeParametersAndUpdateQuery();
   30.73 -			logPossiblyMissingParameters();
   30.74 -		} catch (PatternSyntaxException e) {
   30.75 -			throw new SQLException("Name prefix „" + namePrefix + "“ or suffix „" + nameSuffix + "“ contain a wrong regular expression. " + e.getLocalizedMessage(), e);
   30.76 -		}
   30.77 -	}
   30.78 -
   30.79 -	/**
   30.80 -	 * @return SQL command with named parameters converted to SQL command with numbered parameters
   30.81 -	 */
   30.82 -	public SQLCommandNumbered getSQLCommandNumbered() throws SQLException {
   30.83 -		if (numbered == null) {
   30.84 -			prepare();
   30.85 -			numbered = new SQLCommandNumbered(updatedQuery.toString(), parametersUsed);
   30.86 -		}
   30.87 -
   30.88 -		return numbered;
   30.89 -	}
   30.90 -
   30.91 -	/**
   30.92 -	 * Builds a regexp pattern that matches all parameter names (with prefix/suffix) and which has
   30.93 -	 * one group: parameter name (without prefix/suffix)
   30.94 -	 */
   30.95 -	private void buildPattern() throws PatternSyntaxException {
   30.96 -		StringBuilder patternString = new StringBuilder();
   30.97 -
   30.98 -		patternString.append(namePrefix);
   30.99 -		patternString.append("(?<paramName>");
  30.100 -		for (int i = 0; i < parameters.size(); i++) {
  30.101 -			patternString.append(Pattern.quote(parameters.get(i).getName()));
  30.102 -			if (i < parameters.size() - 1) {
  30.103 -				patternString.append("|");
  30.104 -			}
  30.105 -		}
  30.106 -		patternString.append(")");
  30.107 -		patternString.append(nameSuffix);
  30.108 -
  30.109 -		pattern = Pattern.compile(patternString.toString());
  30.110 -	}
  30.111 -
  30.112 -	private void placeParametersAndUpdateQuery() {
  30.113 -		final String originalQuery = getQuery();
  30.114 -		Matcher m = pattern.matcher(originalQuery);
  30.115 -
  30.116 -		int lastPosition = 0;
  30.117 -		while (m.find(lastPosition)) {
  30.118 -			String name = m.group("paramName");
  30.119 -
  30.120 -			updatedQuery.append(originalQuery.substring(lastPosition, m.start()));
  30.121 -			updatedQuery.append("?");
  30.122 -
  30.123 -			parametersUsed.add(findByName(parameters, name));
  30.124 -
  30.125 -			lastPosition = m.end();
  30.126 -		}
  30.127 -		updatedQuery.append(originalQuery.substring(lastPosition, originalQuery.length()));
  30.128 -
  30.129 -		for (NamedParameter definedParameter : parameters) {
  30.130 -			if (findByName(parametersUsed, definedParameter.getName()) == null) {
  30.131 -				/**
  30.132 -				 * User can have predefined set of parameters and use them with different SQL
  30.133 -				 * queries that use only subset of these parameters → just warning, not exception.
  30.134 -				 */
  30.135 -				log.log(Level.WARNING, "Parameter „{0}“ is defined but not used in the query: „{1}“", new Object[]{definedParameter.getName(), originalQuery});
  30.136 -			}
  30.137 -		}
  30.138 -	}
  30.139 -
  30.140 -	private void logPossiblyMissingParameters() {
  30.141 -		Pattern p = Pattern.compile(namePrefix + "(?<paramName>.+?)" + nameSuffix);
  30.142 -		Matcher m = p.matcher(updatedQuery);
  30.143 -		int lastPosition = 0;
  30.144 -		while (m.find(lastPosition)) {
  30.145 -			/**
  30.146 -			 * We have not parsed and understood the SQL query; the parameter-like looking string
  30.147 -			 * could be inside a literal part of the query → just warning, not exception.
  30.148 -			 */
  30.149 -			log.log(Level.WARNING, "Possibly missing parameter „{0}“ in the query: „{1}“", new Object[]{m.group("paramName"), getQuery()});
  30.150 -			lastPosition = m.end();
  30.151 -		}
  30.152 -	}
  30.153 -
  30.154 -	@Override
  30.155 -	public List<NamedParameter> getParameters() {
  30.156 -		return parameters;
  30.157 -	}
  30.158 -}
    31.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/SQLCommandNumbered.java	Mon Mar 04 17:06:42 2019 +0100
    31.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    31.3 @@ -1,51 +0,0 @@
    31.4 -/**
    31.5 - * SQL-DK
    31.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    31.7 - *
    31.8 - * This program is free software: you can redistribute it and/or modify
    31.9 - * it under the terms of the GNU General Public License as published by
   31.10 - * the Free Software Foundation, either version 3 of the License, or
   31.11 - * (at your option) any later version.
   31.12 - *
   31.13 - * This program is distributed in the hope that it will be useful,
   31.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   31.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   31.16 - * GNU General Public License for more details.
   31.17 - *
   31.18 - * You should have received a copy of the GNU General Public License
   31.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   31.20 - */
   31.21 -package info.globalcode.sql.dk;
   31.22 -
   31.23 -import static info.globalcode.sql.dk.Functions.notNull;
   31.24 -import java.sql.PreparedStatement;
   31.25 -import java.sql.SQLException;
   31.26 -import java.util.List;
   31.27 -
   31.28 -/**
   31.29 - * Has ordinal/numbered parameters.
   31.30 - *
   31.31 - * @author Ing. František Kučera (frantovo.cz)
   31.32 - */
   31.33 -public class SQLCommandNumbered extends SQLCommand {
   31.34 -
   31.35 -	private List<? extends Parameter> parameters;
   31.36 -
   31.37 -	public SQLCommandNumbered(String query, List<? extends Parameter> parameters) {
   31.38 -		super(query);
   31.39 -		this.parameters = parameters;
   31.40 -	}
   31.41 -
   31.42 -	@Override
   31.43 -	public void parametrize(PreparedStatement ps) throws SQLException {
   31.44 -		int i = 1;
   31.45 -		for (Parameter p : notNull(parameters)) {
   31.46 -			ps.setObject(i++, p.getValue(), p.getType().getCode());
   31.47 -		}
   31.48 -	}
   31.49 -
   31.50 -	@Override
   31.51 -	public List<? extends Parameter> getParameters() {
   31.52 -		return parameters;
   31.53 -	}
   31.54 -}
    32.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/SQLType.java	Mon Mar 04 17:06:42 2019 +0100
    32.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    32.3 @@ -1,95 +0,0 @@
    32.4 -/**
    32.5 - * SQL-DK
    32.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    32.7 - *
    32.8 - * This program is free software: you can redistribute it and/or modify
    32.9 - * it under the terms of the GNU General Public License as published by
   32.10 - * the Free Software Foundation, either version 3 of the License, or
   32.11 - * (at your option) any later version.
   32.12 - *
   32.13 - * This program is distributed in the hope that it will be useful,
   32.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   32.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   32.16 - * GNU General Public License for more details.
   32.17 - *
   32.18 - * You should have received a copy of the GNU General Public License
   32.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   32.20 - */
   32.21 -package info.globalcode.sql.dk;
   32.22 -
   32.23 -import java.sql.Types;
   32.24 -
   32.25 -/**
   32.26 - * Data types of SQL parameters.
   32.27 - *
   32.28 - * @author Ing. František Kučera (frantovo.cz)
   32.29 - */
   32.30 -public enum SQLType {
   32.31 -
   32.32 -	/**
   32.33 -	 * Names must be upper case – user input is also converted to upper case → case insensitive
   32.34 -	 */
   32.35 -	BIT(Types.BIT),
   32.36 -	TINYINT(Types.TINYINT),
   32.37 -	SMALLINT(Types.SMALLINT),
   32.38 -	INTEGER(Types.INTEGER),
   32.39 -	BIGINT(Types.BIGINT),
   32.40 -	FLOAT(Types.FLOAT),
   32.41 -	REAL(Types.REAL),
   32.42 -	DOUBLE(Types.DOUBLE),
   32.43 -	NUMERIC(Types.NUMERIC),
   32.44 -	DECIMAL(Types.DECIMAL),
   32.45 -	CHAR(Types.CHAR),
   32.46 -	VARCHAR(Types.VARCHAR),
   32.47 -	LONGVARCHAR(Types.LONGVARCHAR),
   32.48 -	DATE(Types.DATE),
   32.49 -	TIME(Types.TIME),
   32.50 -	TIMESTAMP(Types.TIMESTAMP),
   32.51 -	BINARY(Types.BINARY),
   32.52 -	VARBINARY(Types.VARBINARY),
   32.53 -	LONGVARBINARY(Types.LONGVARBINARY),
   32.54 -	NULL(Types.NULL),
   32.55 -	OTHER(Types.OTHER),
   32.56 -	JAVA_OBJECT(Types.JAVA_OBJECT),
   32.57 -	DISTINCT(Types.DISTINCT),
   32.58 -	STRUCT(Types.STRUCT),
   32.59 -	ARRAY(Types.ARRAY),
   32.60 -	BLOB(Types.BLOB),
   32.61 -	CLOB(Types.CLOB),
   32.62 -	REF(Types.REF),
   32.63 -	DATALINK(Types.DATALINK),
   32.64 -	BOOLEAN(Types.BOOLEAN),
   32.65 -	ROWID(Types.ROWID),
   32.66 -	NCHAR(Types.NCHAR),
   32.67 -	NVARCHAR(Types.NVARCHAR),
   32.68 -	LONGNVARCHAR(Types.LONGNVARCHAR),
   32.69 -	NCLOB(Types.NCLOB),
   32.70 -	SQLXML(Types.SQLXML);
   32.71 -	/** value from java.sql.Types */
   32.72 -	private int code;
   32.73 -
   32.74 -	private SQLType(int code) {
   32.75 -		this.code = code;
   32.76 -	}
   32.77 -
   32.78 -	/**
   32.79 -	 * @see java.sql.Types.Types
   32.80 -	 */
   32.81 -	public int getCode() {
   32.82 -		return code;
   32.83 -	}
   32.84 -
   32.85 -	/**
   32.86 -	 * @param code see {@linkplain java.sql.Types.Types}
   32.87 -	 * @return found SQLType
   32.88 -	 * @throws IllegalArgumentException if no data type has given code
   32.89 -	 */
   32.90 -	public static SQLType valueOf(int code) {
   32.91 -		for (SQLType t : values()) {
   32.92 -			if (t.code == code) {
   32.93 -				return t;
   32.94 -			}
   32.95 -		}
   32.96 -		throw new IllegalArgumentException("No data type has code: " + code);
   32.97 -	}
   32.98 -}
    33.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/Xmlns.java	Mon Mar 04 17:06:42 2019 +0100
    33.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    33.3 @@ -1,33 +0,0 @@
    33.4 -/**
    33.5 - * SQL-DK
    33.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    33.7 - *
    33.8 - * This program is free software: you can redistribute it and/or modify
    33.9 - * it under the terms of the GNU General Public License as published by
   33.10 - * the Free Software Foundation, either version 3 of the License, or
   33.11 - * (at your option) any later version.
   33.12 - *
   33.13 - * This program is distributed in the hope that it will be useful,
   33.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   33.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   33.16 - * GNU General Public License for more details.
   33.17 - *
   33.18 - * You should have received a copy of the GNU General Public License
   33.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   33.20 - */
   33.21 -package info.globalcode.sql.dk;
   33.22 -
   33.23 -/**
   33.24 - * XML namespaces
   33.25 - *
   33.26 - * @author Ing. František Kučera (frantovo.cz)
   33.27 - */
   33.28 -public class Xmlns {
   33.29 -
   33.30 -	public static final String CONFIGURATION = "https://sql-dk.globalcode.info/xmlns/configuration";
   33.31 -	public static final String BATCH_RESULT = "https://sql-dk.globalcode.info/xmlns/batchResult";
   33.32 -	public static final String XHTML = "http://www.w3.org/1999/xhtml";
   33.33 -
   33.34 -	private Xmlns() {
   33.35 -	}
   33.36 -}
    34.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/batch/Batch.java	Mon Mar 04 17:06:42 2019 +0100
    34.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    34.3 @@ -1,32 +0,0 @@
    34.4 -/**
    34.5 - * SQL-DK
    34.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    34.7 - *
    34.8 - * This program is free software: you can redistribute it and/or modify
    34.9 - * it under the terms of the GNU General Public License as published by
   34.10 - * the Free Software Foundation, either version 3 of the License, or
   34.11 - * (at your option) any later version.
   34.12 - *
   34.13 - * This program is distributed in the hope that it will be useful,
   34.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   34.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   34.16 - * GNU General Public License for more details.
   34.17 - *
   34.18 - * You should have received a copy of the GNU General Public License
   34.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   34.20 - */
   34.21 -package info.globalcode.sql.dk.batch;
   34.22 -
   34.23 -import info.globalcode.sql.dk.SQLCommand;
   34.24 -
   34.25 -/**
   34.26 - * Iterator which reads SQL commands from encoded (serialized) batch.
   34.27 - *
   34.28 - * @author Ing. František Kučera (frantovo.cz)
   34.29 - */
   34.30 -public interface Batch {
   34.31 -
   34.32 -	public boolean hasNext() throws BatchException;
   34.33 -
   34.34 -	public SQLCommand next() throws BatchException;
   34.35 -}
    35.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/batch/BatchConstants.java	Mon Mar 04 17:06:42 2019 +0100
    35.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    35.3 @@ -1,35 +0,0 @@
    35.4 -/**
    35.5 - * SQL-DK
    35.6 - * Copyright © 2014 František Kučera (frantovo.cz)
    35.7 - *
    35.8 - * This program is free software: you can redistribute it and/or modify
    35.9 - * it under the terms of the GNU General Public License as published by
   35.10 - * the Free Software Foundation, either version 3 of the License, or
   35.11 - * (at your option) any later version.
   35.12 - *
   35.13 - * This program is distributed in the hope that it will be useful,
   35.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   35.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   35.16 - * GNU General Public License for more details.
   35.17 - *
   35.18 - * You should have received a copy of the GNU General Public License
   35.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   35.20 - */
   35.21 -package info.globalcode.sql.dk.batch;
   35.22 -
   35.23 -import java.nio.charset.Charset;
   35.24 -import java.nio.charset.StandardCharsets;
   35.25 -
   35.26 -/**
   35.27 - *
   35.28 - * @author Ing. František Kučera (frantovo.cz)
   35.29 - */
   35.30 -public class BatchConstants {
   35.31 -
   35.32 -	public static final Charset CHARSET = StandardCharsets.UTF_8;
   35.33 -	public static final byte VERSION = 0x01;
   35.34 -	public static final byte[] BATCH_HEADER = {0x00, 0x53, 0x51, 0x4C, VERSION};
   35.35 -
   35.36 -	private BatchConstants() {
   35.37 -	}
   35.38 -}
    36.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/batch/BatchDecoder.java	Mon Mar 04 17:06:42 2019 +0100
    36.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    36.3 @@ -1,108 +0,0 @@
    36.4 -/**
    36.5 - * SQL-DK
    36.6 - * Copyright © 2014 František Kučera (frantovo.cz)
    36.7 - *
    36.8 - * This program is free software: you can redistribute it and/or modify
    36.9 - * it under the terms of the GNU General Public License as published by
   36.10 - * the Free Software Foundation, either version 3 of the License, or
   36.11 - * (at your option) any later version.
   36.12 - *
   36.13 - * This program is distributed in the hope that it will be useful,
   36.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   36.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   36.16 - * GNU General Public License for more details.
   36.17 - *
   36.18 - * You should have received a copy of the GNU General Public License
   36.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   36.20 - */
   36.21 -package info.globalcode.sql.dk.batch;
   36.22 -
   36.23 -import info.globalcode.sql.dk.Parameter;
   36.24 -import info.globalcode.sql.dk.SQLCommand;
   36.25 -import info.globalcode.sql.dk.SQLCommandNumbered;
   36.26 -import java.io.DataInputStream;
   36.27 -import java.io.InputStream;
   36.28 -import static info.globalcode.sql.dk.batch.BatchConstants.*;
   36.29 -import static info.globalcode.sql.dk.Functions.toHex;
   36.30 -import info.globalcode.sql.dk.SQLType;
   36.31 -import java.io.IOException;
   36.32 -import java.util.ArrayList;
   36.33 -import java.util.Arrays;
   36.34 -import java.util.List;
   36.35 -
   36.36 -/**
   36.37 - *
   36.38 - * @author Ing. František Kučera (frantovo.cz)
   36.39 - */
   36.40 -public class BatchDecoder {
   36.41 -
   36.42 -	public Batch decode(InputStream in) throws BatchException {
   36.43 -		return new BatchFromStream(new DataInputStream(in));
   36.44 -	}
   36.45 -
   36.46 -	private class BatchFromStream implements Batch {
   36.47 -
   36.48 -		private DataInputStream in;
   36.49 -		private boolean hasNext;
   36.50 -
   36.51 -		public BatchFromStream(DataInputStream in) throws BatchException {
   36.52 -			this.in = in;
   36.53 -			hasNext = verifyHeader();
   36.54 -		}
   36.55 -
   36.56 -		@Override
   36.57 -		public boolean hasNext() throws BatchException {
   36.58 -			return hasNext;
   36.59 -		}
   36.60 -
   36.61 -		@Override
   36.62 -		public SQLCommand next() throws BatchException {
   36.63 -			try {
   36.64 -				String sql = readNextString();
   36.65 -
   36.66 -				int paramCount = in.readInt();
   36.67 -				List<Parameter> parameters = new ArrayList<>(paramCount);
   36.68 -
   36.69 -				for (int i = 0; i < paramCount; i++) {
   36.70 -					SQLType type = SQLType.valueOf(in.readInt());
   36.71 -					String value = readNextString();
   36.72 -					parameters.add(new Parameter(value, type));
   36.73 -				}
   36.74 -
   36.75 -				hasNext = verifyHeader();
   36.76 -
   36.77 -				SQLCommand sqlCommand = new SQLCommandNumbered(sql, parameters);
   36.78 -				return sqlCommand;
   36.79 -			} catch (IOException e) {
   36.80 -				throw new BatchException("Unable to read batch", e);
   36.81 -			}
   36.82 -		}
   36.83 -
   36.84 -		private String readNextString() throws IOException {
   36.85 -			byte[] buffer = new byte[in.readInt()];
   36.86 -			in.read(buffer);
   36.87 -			return new String(buffer, CHARSET);
   36.88 -		}
   36.89 -
   36.90 -		/**
   36.91 -		 * @return true if correct batch header was found | false if EOF was found
   36.92 -		 * @throws BatchException if unexpected data was found (not batch header nor EOF)
   36.93 -		 */
   36.94 -		private boolean verifyHeader() throws BatchException {
   36.95 -			try {
   36.96 -				byte[] buffer = new byte[BATCH_HEADER.length];
   36.97 -				int bytesRead = in.read(buffer);
   36.98 -
   36.99 -				if (bytesRead == BATCH_HEADER.length && Arrays.equals(buffer, BATCH_HEADER)) {
  36.100 -					return true;
  36.101 -				} else if (bytesRead == -1) {
  36.102 -					return false;
  36.103 -				} else {
  36.104 -					throw new BatchException("This is not SQL-DK batch: " + toHex(buffer));
  36.105 -				}
  36.106 -			} catch (IOException e) {
  36.107 -				throw new BatchException("Unable to read batch header", e);
  36.108 -			}
  36.109 -		}
  36.110 -	}
  36.111 -}
    37.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/batch/BatchEncoder.java	Mon Mar 04 17:06:42 2019 +0100
    37.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    37.3 @@ -1,83 +0,0 @@
    37.4 -/**
    37.5 - * SQL-DK
    37.6 - * Copyright © 2014 František Kučera (frantovo.cz)
    37.7 - *
    37.8 - * This program is free software: you can redistribute it and/or modify
    37.9 - * it under the terms of the GNU General Public License as published by
   37.10 - * the Free Software Foundation, either version 3 of the License, or
   37.11 - * (at your option) any later version.
   37.12 - *
   37.13 - * This program is distributed in the hope that it will be useful,
   37.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   37.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   37.16 - * GNU General Public License for more details.
   37.17 - *
   37.18 - * You should have received a copy of the GNU General Public License
   37.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   37.20 - */
   37.21 -package info.globalcode.sql.dk.batch;
   37.22 -
   37.23 -import info.globalcode.sql.dk.Parameter;
   37.24 -import info.globalcode.sql.dk.SQLCommand;
   37.25 -import info.globalcode.sql.dk.SQLCommandNamed;
   37.26 -import java.io.DataOutputStream;
   37.27 -import java.io.IOException;
   37.28 -import java.io.OutputStream;
   37.29 -import static info.globalcode.sql.dk.batch.BatchConstants.*;
   37.30 -import java.io.ByteArrayOutputStream;
   37.31 -import java.sql.SQLException;
   37.32 -import java.util.List;
   37.33 -
   37.34 -/**
   37.35 - *
   37.36 - * @author Ing. František Kučera (frantovo.cz)
   37.37 - */
   37.38 -public class BatchEncoder {
   37.39 -
   37.40 -	public int encode(SQLCommand sqlCommand, OutputStream out) throws BatchException {
   37.41 -		try {
   37.42 -			ByteArrayOutputStream bufferAOS = new ByteArrayOutputStream();
   37.43 -			DataOutputStream buffer = new DataOutputStream(bufferAOS);
   37.44 -
   37.45 -			buffer.write(BATCH_HEADER);
   37.46 -
   37.47 -			if (sqlCommand instanceof SQLCommandNamed) {
   37.48 -				sqlCommand = ((SQLCommandNamed) sqlCommand).getSQLCommandNumbered();
   37.49 -			}
   37.50 -
   37.51 -			writeNextString(sqlCommand.getQuery(), buffer);
   37.52 -
   37.53 -			List<? extends Parameter> parameters = sqlCommand.getParameters();
   37.54 -
   37.55 -			buffer.writeInt(parameters.size());
   37.56 -
   37.57 -			for (Parameter p : parameters) {
   37.58 -				buffer.writeInt(p.getType().getCode());
   37.59 -				writeNextString((String) p.getValue(), buffer); // parameters are encoded before any preprocessing
   37.60 -			}
   37.61 -
   37.62 -			buffer.flush();
   37.63 -			bufferAOS.writeTo(out);
   37.64 -			out.flush();
   37.65 -			return bufferAOS.size();
   37.66 -		} catch (IOException e) {
   37.67 -			throw new BatchException("Unable to write SQL command: " + sqlCommand, e);
   37.68 -		} catch (SQLException e) {
   37.69 -			throw new BatchException("Unable to converd named SQL command to numbered: " + sqlCommand, e);
   37.70 -		}
   37.71 -	}
   37.72 -
   37.73 -	private void writeNextString(String s, DataOutputStream out) throws IOException {
   37.74 -		byte[] bytes = toBytes(s);
   37.75 -		out.writeInt(bytes.length);
   37.76 -		out.write(bytes);
   37.77 -	}
   37.78 -
   37.79 -	private static byte[] toBytes(String s) {
   37.80 -		if (s == null) {
   37.81 -			return new byte[]{};
   37.82 -		} else {
   37.83 -			return s.getBytes(CHARSET);
   37.84 -		}
   37.85 -	}
   37.86 -}
    38.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/batch/BatchException.java	Mon Mar 04 17:06:42 2019 +0100
    38.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    38.3 @@ -1,42 +0,0 @@
    38.4 -/**
    38.5 - * SQL-DK
    38.6 - * Copyright © 2014 František Kučera (frantovo.cz)
    38.7 - *
    38.8 - * This program is free software: you can redistribute it and/or modify
    38.9 - * it under the terms of the GNU General Public License as published by
   38.10 - * the Free Software Foundation, either version 3 of the License, or
   38.11 - * (at your option) any later version.
   38.12 - *
   38.13 - * This program is distributed in the hope that it will be useful,
   38.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   38.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   38.16 - * GNU General Public License for more details.
   38.17 - *
   38.18 - * You should have received a copy of the GNU General Public License
   38.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   38.20 - */
   38.21 -package info.globalcode.sql.dk.batch;
   38.22 -
   38.23 -import info.globalcode.sql.dk.DKException;
   38.24 -
   38.25 -/**
   38.26 - *
   38.27 - * @author Ing. František Kučera (frantovo.cz)
   38.28 - */
   38.29 -public class BatchException extends DKException {
   38.30 -
   38.31 -	public BatchException() {
   38.32 -	}
   38.33 -
   38.34 -	public BatchException(String message) {
   38.35 -		super(message);
   38.36 -	}
   38.37 -
   38.38 -	public BatchException(Throwable cause) {
   38.39 -		super(cause);
   38.40 -	}
   38.41 -
   38.42 -	public BatchException(String message, Throwable cause) {
   38.43 -		super(message, cause);
   38.44 -	}
   38.45 -}
    39.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/CommandArgument.java	Mon Mar 04 17:06:42 2019 +0100
    39.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    39.3 @@ -1,82 +0,0 @@
    39.4 -/**
    39.5 - * SQL-DK
    39.6 - * Copyright © 2015 František Kučera (frantovo.cz)
    39.7 - *
    39.8 - * This program is free software: you can redistribute it and/or modify
    39.9 - * it under the terms of the GNU General Public License as published by
   39.10 - * the Free Software Foundation, either version 3 of the License, or
   39.11 - * (at your option) any later version.
   39.12 - *
   39.13 - * This program is distributed in the hope that it will be useful,
   39.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   39.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   39.16 - * GNU General Public License for more details.
   39.17 - *
   39.18 - * You should have received a copy of the GNU General Public License
   39.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   39.20 - */
   39.21 -package info.globalcode.sql.dk.configuration;
   39.22 -
   39.23 -import javax.xml.bind.annotation.XmlAttribute;
   39.24 -import javax.xml.bind.annotation.XmlEnum;
   39.25 -import javax.xml.bind.annotation.XmlEnumValue;
   39.26 -import javax.xml.bind.annotation.XmlValue;
   39.27 -
   39.28 -/**
   39.29 - *
   39.30 - * @author Ing. František Kučera (frantovo.cz)
   39.31 - */
   39.32 -public class CommandArgument {
   39.33 -
   39.34 -	private String value;
   39.35 -	private TYPE type;
   39.36 -
   39.37 -	@XmlEnum
   39.38 -	public static enum TYPE {
   39.39 -
   39.40 -		/**
   39.41 -		 * value = literal (text) argument
   39.42 -		 */
   39.43 -		@XmlEnumValue("literal")
   39.44 -		LITERAL,
   39.45 -		/**
   39.46 -		 * value will be substituted by hostname or IP address of the DB server
   39.47 -		 */
   39.48 -		@XmlEnumValue("host")
   39.49 -		HOST,
   39.50 -		/**
   39.51 -		 * value will be substituted by the port of the DB server
   39.52 -		 */
   39.53 -		@XmlEnumValue("port")
   39.54 -		PORT,
   39.55 -		/**
   39.56 -		 * value will be substituted by environmental variable of given name
   39.57 -		 */
   39.58 -		@XmlEnumValue("env")
   39.59 -		ENVIRONMENT_VARIABLE,
   39.60 -		/**
   39.61 -		 * value will be substituted by database property of given name
   39.62 -		 */
   39.63 -		@XmlEnumValue("dbProperty")
   39.64 -		DB_PROPERTY;
   39.65 -	}
   39.66 -
   39.67 -	@XmlValue
   39.68 -	public String getValue() {
   39.69 -		return value;
   39.70 -	}
   39.71 -
   39.72 -	public void setValue(String value) {
   39.73 -		this.value = value;
   39.74 -	}
   39.75 -
   39.76 -	@XmlAttribute(name = "type")
   39.77 -	public TYPE getType() {
   39.78 -		return type;
   39.79 -	}
   39.80 -
   39.81 -	public void setType(TYPE type) {
   39.82 -		this.type = type;
   39.83 -	}
   39.84 -
   39.85 -}
    40.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java	Mon Mar 04 17:06:42 2019 +0100
    40.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    40.3 @@ -1,173 +0,0 @@
    40.4 -/**
    40.5 - * SQL-DK
    40.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    40.7 - *
    40.8 - * This program is free software: you can redistribute it and/or modify
    40.9 - * it under the terms of the GNU General Public License as published by
   40.10 - * the Free Software Foundation, either version 3 of the License, or
   40.11 - * (at your option) any later version.
   40.12 - *
   40.13 - * This program is distributed in the hope that it will be useful,
   40.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   40.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   40.16 - * GNU General Public License for more details.
   40.17 - *
   40.18 - * You should have received a copy of the GNU General Public License
   40.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   40.20 - */
   40.21 -package info.globalcode.sql.dk.configuration;
   40.22 -
   40.23 -import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
   40.24 -import static info.globalcode.sql.dk.Functions.findByName;
   40.25 -import info.globalcode.sql.dk.formatting.BarChartFormatter;
   40.26 -import info.globalcode.sql.dk.formatting.SilentFormatter;
   40.27 -import info.globalcode.sql.dk.formatting.SingleRecordFormatter;
   40.28 -import info.globalcode.sql.dk.formatting.SingleValueFormatter;
   40.29 -import info.globalcode.sql.dk.formatting.TabularFormatter;
   40.30 -import info.globalcode.sql.dk.formatting.TabularPrefetchingFormatter;
   40.31 -import info.globalcode.sql.dk.formatting.TabularWrappingFormatter;
   40.32 -import info.globalcode.sql.dk.formatting.TeXFormatter;
   40.33 -import info.globalcode.sql.dk.formatting.XhtmlFormatter;
   40.34 -import info.globalcode.sql.dk.formatting.XmlFormatter;
   40.35 -import java.util.ArrayList;
   40.36 -import java.util.Collection;
   40.37 -import java.util.Collections;
   40.38 -import java.util.List;
   40.39 -import javax.xml.bind.annotation.XmlElement;
   40.40 -import javax.xml.bind.annotation.XmlRootElement;
   40.41 -import javax.xml.bind.annotation.XmlTransient;
   40.42 -
   40.43 -/**
   40.44 - * Object representation of user configuration loaded from XML.
   40.45 - *
   40.46 - * @author Ing. František Kučera (frantovo.cz)
   40.47 - */
   40.48 -@XmlRootElement(name = "configuration", namespace = CONFIGURATION)
   40.49 -public class Configuration {
   40.50 -
   40.51 -	private List<DatabaseDefinition> databases = new ArrayList<>();
   40.52 -	private List<FormatterDefinition> formatters = new ArrayList<>();
   40.53 -	/**
   40.54 -	 * is used if no formatter is specified on CLI nor in user configuration
   40.55 -	 */
   40.56 -	public static final String DEFAULT_FORMATTER = TabularFormatter.NAME;
   40.57 -	/**
   40.58 -	 * Can be used as default if prefetching is ok – for configuration listings (config is alread in
   40.59 -	 * memory, so this does not matter)
   40.60 -	 */
   40.61 -	public static final String DEFAULT_FORMATTER_PREFETCHING = TabularPrefetchingFormatter.NAME;
   40.62 -	private String defaultFormatter;
   40.63 -	/**
   40.64 -	 * Default list of formatters. Is used if particular name is not found in user configuration.
   40.65 -	 */
   40.66 -	private static final Collection<FormatterDefinition> buildInFormatters;
   40.67 -
   40.68 -	static {
   40.69 -		Collection<FormatterDefinition> l = new ArrayList<>();
   40.70 -		l.add(new FormatterDefinition(SilentFormatter.NAME, SilentFormatter.class.getName()));
   40.71 -		l.add(new FormatterDefinition(SingleValueFormatter.NAME, SingleValueFormatter.class.getName()));
   40.72 -		l.add(new FormatterDefinition(SingleRecordFormatter.NAME, SingleRecordFormatter.class.getName()));
   40.73 -		l.add(new FormatterDefinition(XmlFormatter.NAME, XmlFormatter.class.getName()));
   40.74 -		l.add(new FormatterDefinition(XhtmlFormatter.NAME, XhtmlFormatter.class.getName()));
   40.75 -		l.add(new FormatterDefinition(TabularFormatter.NAME, TabularFormatter.class.getName()));
   40.76 -		l.add(new FormatterDefinition(TabularPrefetchingFormatter.NAME, TabularPrefetchingFormatter.class.getName()));
   40.77 -		l.add(new FormatterDefinition(TabularWrappingFormatter.NAME, TabularWrappingFormatter.class.getName()));
   40.78 -		l.add(new FormatterDefinition(TeXFormatter.NAME, TeXFormatter.class.getName()));
   40.79 -		//l.add(new FormatterDefinition(DsvFormatter.NAME, DsvFormatter.class.getName()));
   40.80 -		//l.add(new FormatterDefinition(SystemCommandExecutor.NAME, SystemCommandExecutor.class.getName()));
   40.81 -		l.add(new FormatterDefinition(BarChartFormatter.NAME, BarChartFormatter.class.getName()));
   40.82 -		buildInFormatters = Collections.unmodifiableCollection(l);
   40.83 -	}
   40.84 -
   40.85 -	@XmlElement(name = "database", namespace = CONFIGURATION)
   40.86 -	public List<DatabaseDefinition> getDatabases() {
   40.87 -		return databases;
   40.88 -	}
   40.89 -
   40.90 -	public void setDatabases(List<DatabaseDefinition> databases) {
   40.91 -		this.databases = databases;
   40.92 -	}
   40.93 -
   40.94 -	/**
   40.95 -	 * @param name
   40.96 -	 * @return
   40.97 -	 * @throws ConfigurationException if no database with this name is configured
   40.98 -	 */
   40.99 -	public DatabaseDefinition getDatabase(String name) throws ConfigurationException {
  40.100 -		DatabaseDefinition dd = findByName(databases, name);
  40.101 -		if (dd == null) {
  40.102 -			throw new ConfigurationException("Database is not configured: " + name);
  40.103 -		} else {
  40.104 -			return dd;
  40.105 -		}
  40.106 -	}
  40.107 -
  40.108 -	/**
  40.109 -	 * @return only configured formatters
  40.110 -	 * @see #getBuildInFormatters()
  40.111 -	 * @see #getAllFormatters()
  40.112 -	 */
  40.113 -	@XmlElement(name = "formatter", namespace = CONFIGURATION)
  40.114 -	public List<FormatterDefinition> getFormatters() {
  40.115 -		return formatters;
  40.116 -	}
  40.117 -
  40.118 -	public void setFormatters(List<FormatterDefinition> formatters) {
  40.119 -		this.formatters = formatters;
  40.120 -	}
  40.121 -
  40.122 -	/**
  40.123 -	 * @param name name of desired formatter. Looking for this name in user configuration, then in
  40.124 -	 * buil-in formatters. If null, default from configuration or (if not configured) built-in
  40.125 -	 * default is used.
  40.126 -	 * @return formatter definition
  40.127 -	 * @throws ConfigurationException if no formatter with this name was found
  40.128 -	 */
  40.129 -	public FormatterDefinition getFormatter(String name) throws ConfigurationException {
  40.130 -		if (name == null) {
  40.131 -			return defaultFormatter == null ? getFormatter(DEFAULT_FORMATTER) : getFormatter(defaultFormatter);
  40.132 -		} else {
  40.133 -			FormatterDefinition fd = findByName(formatters, name);
  40.134 -			fd = fd == null ? findByName(buildInFormatters, name) : fd;
  40.135 -			if (fd == null) {
  40.136 -				throw new ConfigurationException("Formatter is not configured: " + name);
  40.137 -			} else {
  40.138 -				return fd;
  40.139 -			}
  40.140 -		}
  40.141 -	}
  40.142 -
  40.143 -	/**
  40.144 -	 * @return only built-in formatters
  40.145 -	 * @see #getAllFormatters()
  40.146 -	 * @see #getFormatters()
  40.147 -	 */
  40.148 -	@XmlTransient
  40.149 -	public Collection<FormatterDefinition> getBuildInFormatters() {
  40.150 -		return buildInFormatters;
  40.151 -	}
  40.152 -
  40.153 -	/**
  40.154 -	 * @return built-in + configured formatters
  40.155 -	 * @see #getFormatters()
  40.156 -	 */
  40.157 -	@XmlTransient
  40.158 -	public Collection<FormatterDefinition> getAllFormatters() {
  40.159 -		Collection<FormatterDefinition> allFormatters = new ArrayList<>();
  40.160 -		allFormatters.addAll(buildInFormatters);
  40.161 -		allFormatters.addAll(formatters);
  40.162 -		return allFormatters;
  40.163 -	}
  40.164 -
  40.165 -	/**
  40.166 -	 * @return name of default formatter, is used if name is not specified on CLI
  40.167 -	 */
  40.168 -	@XmlElement(name = "defaultFormatter", namespace = CONFIGURATION)
  40.169 -	public String getDefaultFormatter() {
  40.170 -		return defaultFormatter;
  40.171 -	}
  40.172 -
  40.173 -	public void setDefaultFormatter(String defaultFormatter) {
  40.174 -		this.defaultFormatter = defaultFormatter;
  40.175 -	}
  40.176 -}
    41.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/ConfigurationException.java	Mon Mar 04 17:06:42 2019 +0100
    41.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    41.3 @@ -1,42 +0,0 @@
    41.4 -/**
    41.5 - * SQL-DK
    41.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    41.7 - *
    41.8 - * This program is free software: you can redistribute it and/or modify
    41.9 - * it under the terms of the GNU General Public License as published by
   41.10 - * the Free Software Foundation, either version 3 of the License, or
   41.11 - * (at your option) any later version.
   41.12 - *
   41.13 - * This program is distributed in the hope that it will be useful,
   41.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   41.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   41.16 - * GNU General Public License for more details.
   41.17 - *
   41.18 - * You should have received a copy of the GNU General Public License
   41.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   41.20 - */
   41.21 -package info.globalcode.sql.dk.configuration;
   41.22 -
   41.23 -import info.globalcode.sql.dk.DKException;
   41.24 -
   41.25 -/**
   41.26 - *
   41.27 - * @author Ing. František Kučera (frantovo.cz)
   41.28 - */
   41.29 -public class ConfigurationException extends DKException {
   41.30 -
   41.31 -	public ConfigurationException() {
   41.32 -	}
   41.33 -
   41.34 -	public ConfigurationException(String message) {
   41.35 -		super(message);
   41.36 -	}
   41.37 -
   41.38 -	public ConfigurationException(Throwable cause) {
   41.39 -		super(cause);
   41.40 -	}
   41.41 -
   41.42 -	public ConfigurationException(String message, Throwable cause) {
   41.43 -		super(message, cause);
   41.44 -	}
   41.45 -}
    42.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/ConfigurationProvider.java	Mon Mar 04 17:06:42 2019 +0100
    42.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    42.3 @@ -1,28 +0,0 @@
    42.4 -/**
    42.5 - * SQL-DK
    42.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    42.7 - *
    42.8 - * This program is free software: you can redistribute it and/or modify
    42.9 - * it under the terms of the GNU General Public License as published by
   42.10 - * the Free Software Foundation, either version 3 of the License, or
   42.11 - * (at your option) any later version.
   42.12 - *
   42.13 - * This program is distributed in the hope that it will be useful,
   42.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   42.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   42.16 - * GNU General Public License for more details.
   42.17 - *
   42.18 - * You should have received a copy of the GNU General Public License
   42.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   42.20 - */
   42.21 -package info.globalcode.sql.dk.configuration;
   42.22 -
   42.23 -/**
   42.24 - * Use for lazy-loading of the configuration.
   42.25 - *
   42.26 - * @author Ing. František Kučera (frantovo.cz)
   42.27 - */
   42.28 -public interface ConfigurationProvider {
   42.29 -
   42.30 -	public Configuration getConfiguration() throws ConfigurationException;
   42.31 -}
    43.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/DatabaseDefinition.java	Mon Mar 04 17:06:42 2019 +0100
    43.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    43.3 @@ -1,147 +0,0 @@
    43.4 -/**
    43.5 - * SQL-DK
    43.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    43.7 - *
    43.8 - * This program is free software: you can redistribute it and/or modify
    43.9 - * it under the terms of the GNU General Public License as published by
   43.10 - * the Free Software Foundation, either version 3 of the License, or
   43.11 - * (at your option) any later version.
   43.12 - *
   43.13 - * This program is distributed in the hope that it will be useful,
   43.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   43.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   43.16 - * GNU General Public License for more details.
   43.17 - *
   43.18 - * You should have received a copy of the GNU General Public License
   43.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   43.20 - */
   43.21 -package info.globalcode.sql.dk.configuration;
   43.22 -
   43.23 -import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
   43.24 -import info.globalcode.sql.dk.DatabaseConnection;
   43.25 -import info.globalcode.sql.dk.jmx.ConnectionManagement;
   43.26 -import java.sql.SQLException;
   43.27 -import java.util.logging.Logger;
   43.28 -import javax.xml.bind.annotation.XmlElement;
   43.29 -
   43.30 -/**
   43.31 - * Configured (but not yet connected) database connection.
   43.32 - *
   43.33 - * @author Ing. František Kučera (frantovo.cz)
   43.34 - */
   43.35 -public class DatabaseDefinition implements NameIdentified {
   43.36 -
   43.37 -	private static final Logger log = Logger.getLogger(DatabaseDefinition.class.getName());
   43.38 -	/**
   43.39 -	 * database name in SQL-DK configuration
   43.40 -	 */
   43.41 -	private String name;
   43.42 -	/**
   43.43 -	 * JDBC URL
   43.44 -	 */
   43.45 -	private String url;
   43.46 -	/**
   43.47 -	 * JDBC user name
   43.48 -	 */
   43.49 -	private String userName;
   43.50 -	/**
   43.51 -	 * JDBC password
   43.52 -	 */
   43.53 -	private String password;
   43.54 -	/**
   43.55 -	 * optional JDBC driver – if empty, the DriverManager is used to lookup specific Driver for
   43.56 -	 * given URL
   43.57 -	 */
   43.58 -	private String driver;
   43.59 -	/**
   43.60 -	 * JDBC properties
   43.61 -	 */
   43.62 -	private Properties properties = new Properties();
   43.63 -	/**
   43.64 -	 * optional definition of tunnel to the remote database
   43.65 -	 */
   43.66 -	private TunnelDefinition tunnel;
   43.67 -
   43.68 -	@XmlElement(name = "name", namespace = CONFIGURATION)
   43.69 -	@Override
   43.70 -	public String getName() {
   43.71 -		return name;
   43.72 -	}
   43.73 -
   43.74 -	public void setName(String name) {
   43.75 -		this.name = name;
   43.76 -	}
   43.77 -
   43.78 -	@XmlElement(name = "url", namespace = CONFIGURATION)
   43.79 -	public String getUrl() {
   43.80 -		return url;
   43.81 -	}
   43.82 -
   43.83 -	public void setUrl(String url) {
   43.84 -		this.url = url;
   43.85 -	}
   43.86 -
   43.87 -	@XmlElement(name = "userName", namespace = CONFIGURATION)
   43.88 -	public String getUserName() {
   43.89 -		return userName;
   43.90 -	}
   43.91 -
   43.92 -	public void setUserName(String userName) {
   43.93 -		this.userName = userName;
   43.94 -	}
   43.95 -
   43.96 -	@XmlElement(name = "password", namespace = CONFIGURATION)
   43.97 -	public String getPassword() {
   43.98 -		return password;
   43.99 -	}
  43.100 -
  43.101 -	public void setPassword(String password) {
  43.102 -		this.password = password;
  43.103 -	}
  43.104 -
  43.105 -	public String getDriver() {
  43.106 -		return driver;
  43.107 -	}
  43.108 -
  43.109 -	public void setDriver(String driver) {
  43.110 -		this.driver = driver;
  43.111 -	}
  43.112 -
  43.113 -	@XmlElement(name = "property", namespace = CONFIGURATION)
  43.114 -	public Properties getProperties() {
  43.115 -		return properties;
  43.116 -	}
  43.117 -
  43.118 -	public void setProperties(Properties properties) {
  43.119 -		this.properties = properties;
  43.120 -	}
  43.121 -
  43.122 -	public TunnelDefinition getTunnel() {
  43.123 -		return tunnel;
  43.124 -	}
  43.125 -
  43.126 -	public void setTunnel(TunnelDefinition tunnel) {
  43.127 -		this.tunnel = tunnel;
  43.128 -	}
  43.129 -
  43.130 -	/**
  43.131 -	 * @param properties ad-hoc properties from CLI options (for the JDBC driver)
  43.132 -	 * @param jmxBean JMX management bean for progress reporting | null = disable JMX
  43.133 -	 * @return
  43.134 -	 * @throws java.sql.SQLException
  43.135 -	 */
  43.136 -	public DatabaseConnection connect(Properties properties, ConnectionManagement jmxBean) throws SQLException {
  43.137 -		return new DatabaseConnection(this, properties, jmxBean);
  43.138 -	}
  43.139 -
  43.140 -	/**
  43.141 -	 * @param properties
  43.142 -	 * @return
  43.143 -	 * @throws java.sql.SQLException
  43.144 -	 * @see #connect(info.globalcode.sql.dk.configuration.Properties, java.lang.String)
  43.145 -	 * With disabled JMX reporting.
  43.146 -	 */
  43.147 -	public DatabaseConnection connect(Properties properties) throws SQLException {
  43.148 -		return new DatabaseConnection(this, properties, null);
  43.149 -	}
  43.150 -}
    44.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/FormatterDefinition.java	Mon Mar 04 17:06:42 2019 +0100
    44.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    44.3 @@ -1,114 +0,0 @@
    44.4 -/**
    44.5 - * SQL-DK
    44.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    44.7 - *
    44.8 - * This program is free software: you can redistribute it and/or modify
    44.9 - * it under the terms of the GNU General Public License as published by
   44.10 - * the Free Software Foundation, either version 3 of the License, or
   44.11 - * (at your option) any later version.
   44.12 - *
   44.13 - * This program is distributed in the hope that it will be useful,
   44.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   44.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   44.16 - * GNU General Public License for more details.
   44.17 - *
   44.18 - * You should have received a copy of the GNU General Public License
   44.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   44.20 - */
   44.21 -package info.globalcode.sql.dk.configuration;
   44.22 -
   44.23 -import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
   44.24 -import info.globalcode.sql.dk.formatting.Formatter;
   44.25 -import info.globalcode.sql.dk.formatting.FormatterContext;
   44.26 -import info.globalcode.sql.dk.formatting.FormatterException;
   44.27 -import java.lang.reflect.Constructor;
   44.28 -import java.lang.reflect.InvocationTargetException;
   44.29 -import javax.xml.bind.annotation.XmlElement;
   44.30 -
   44.31 -/**
   44.32 - * Configured (but not yet instantiated) formatter.
   44.33 - *
   44.34 - * @author Ing. František Kučera (frantovo.cz)
   44.35 - */
   44.36 -public class FormatterDefinition implements NameIdentified {
   44.37 -
   44.38 -	private String name;
   44.39 -	private String className;
   44.40 -	private Properties properties = new Properties();
   44.41 -
   44.42 -	public FormatterDefinition() {
   44.43 -	}
   44.44 -
   44.45 -	public FormatterDefinition(String name, String className) {
   44.46 -		this.name = name;
   44.47 -		this.className = className;
   44.48 -	}
   44.49 -
   44.50 -	public FormatterDefinition(String name, String className, Properties properties) {
   44.51 -		this(name, className);
   44.52 -		this.properties = properties;
   44.53 -	}
   44.54 -
   44.55 -	@XmlElement(name = "name", namespace = CONFIGURATION)
   44.56 -	@Override
   44.57 -	public String getName() {
   44.58 -		return name;
   44.59 -	}
   44.60 -
   44.61 -	public void setName(String name) {
   44.62 -		this.name = name;
   44.63 -	}
   44.64 -
   44.65 -	/**
   44.66 -	 * Filter's class. Must implement the
   44.67 -	 * <code>info.globalcode.sql.dk.formatting.Formatter</code> interface.
   44.68 -	 * Subclassing the
   44.69 -	 * <code>info.globalcode.sql.dk.formatting.AbstractFormatter</code> is strongly recommended.
   44.70 -	 * The constructor must accept one parameter:
   44.71 -	 * <code>info.globalcode.sql.dk.formatting.FormatterContext</code>
   44.72 -	 *
   44.73 -	 * @return fully qualified class name
   44.74 -	 */
   44.75 -	@XmlElement(name = "class", namespace = CONFIGURATION)
   44.76 -	public String getClassName() {
   44.77 -		return className;
   44.78 -	}
   44.79 -
   44.80 -	public void setClassName(String className) {
   44.81 -		this.className = className;
   44.82 -	}
   44.83 -
   44.84 -	@XmlElement(name = "property", namespace = CONFIGURATION)
   44.85 -	public Properties getProperties() {
   44.86 -		return properties;
   44.87 -	}
   44.88 -
   44.89 -	public void setProperties(Properties properties) {
   44.90 -		this.properties = properties;
   44.91 -	}
   44.92 -
   44.93 -	/**
   44.94 -	 * @param context
   44.95 -	 * @return
   44.96 -	 * @throws FormatterException
   44.97 -	 */
   44.98 -	public Formatter getInstance(FormatterContext context) throws FormatterException {
   44.99 -		context.getProperties().setDefaults(properties);
  44.100 -		try {
  44.101 -			Constructor constructor = Class.forName(className).getConstructor(context.getClass());
  44.102 -
  44.103 -			Object instance = constructor.newInstance(context);
  44.104 -			if (instance instanceof Formatter) {
  44.105 -				return (Formatter) instance;
  44.106 -			} else {
  44.107 -				throw new FormatterException("Formatter " + instance + " does not implement the " + Formatter.class.getName() + " interface");
  44.108 -			}
  44.109 -		} catch (ClassNotFoundException e) {
  44.110 -			throw new FormatterException("Formatter class does not exist: " + className, e);
  44.111 -		} catch (NoSuchMethodException e) {
  44.112 -			throw new FormatterException("Formatter class with no valid constructor: " + className, e);
  44.113 -		} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
  44.114 -			throw new FormatterException("Formatter's constructor caused an error: " + className, e);
  44.115 -		}
  44.116 -	}
  44.117 -}
    45.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/Loader.java	Mon Mar 04 17:06:42 2019 +0100
    45.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    45.3 @@ -1,101 +0,0 @@
    45.4 -/**
    45.5 - * SQL-DK
    45.6 - * Copyright © 2015 František Kučera (frantovo.cz)
    45.7 - *
    45.8 - * This program is free software: you can redistribute it and/or modify
    45.9 - * it under the terms of the GNU General Public License as published by
   45.10 - * the Free Software Foundation, either version 3 of the License, or
   45.11 - * (at your option) any later version.
   45.12 - *
   45.13 - * This program is distributed in the hope that it will be useful,
   45.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   45.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   45.16 - * GNU General Public License for more details.
   45.17 - *
   45.18 - * You should have received a copy of the GNU General Public License
   45.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   45.20 - */
   45.21 -package info.globalcode.sql.dk.configuration;
   45.22 -
   45.23 -import info.globalcode.sql.dk.*;
   45.24 -import static info.globalcode.sql.dk.DatabaseConnection.JDBC_PROPERTY_USER;
   45.25 -import static info.globalcode.sql.dk.DatabaseConnection.JDBC_PROPERTY_PASSWORD;
   45.26 -import java.sql.Connection;
   45.27 -import java.sql.Driver;
   45.28 -import java.sql.DriverManager;
   45.29 -import java.sql.SQLException;
   45.30 -import java.util.logging.Level;
   45.31 -import java.util.logging.Logger;
   45.32 -import javax.xml.bind.JAXBContext;
   45.33 -import javax.xml.bind.Unmarshaller;
   45.34 -
   45.35 -/**
   45.36 - * Configuration loader – deserializes Configuration from the XML file.
   45.37 - *
   45.38 - * @author Ing. František Kučera (frantovo.cz)
   45.39 - */
   45.40 -public class Loader {
   45.41 -
   45.42 -	private static final Logger log = Logger.getLogger(Loader.class.getName());
   45.43 -
   45.44 -	public Configuration loadConfiguration() throws ConfigurationException {
   45.45 -		try {
   45.46 -			JAXBContext jaxb = JAXBContext.newInstance(Configuration.class.getPackage().getName(), Configuration.class.getClassLoader());
   45.47 -			Unmarshaller u = jaxb.createUnmarshaller();
   45.48 -			return (Configuration) u.unmarshal(Constants.CONFIG_FILE);
   45.49 -		} catch (Exception e) {
   45.50 -			throw new ConfigurationException("Unable to load configuration from " + Constants.CONFIG_FILE, e);
   45.51 -		}
   45.52 -	}
   45.53 -
   45.54 -	/**
   45.55 -	 * JDBC connection should not be used directly in SQL-DK.
   45.56 -	 *
   45.57 -	 * @see DatabaseDefinition#connect(info.globalcode.sql.dk.configuration.Properties)
   45.58 -	 * @param properties
   45.59 -	 * @param databaseDefinition
   45.60 -	 * @return
   45.61 -	 * @throws java.sql.SQLException
   45.62 -	 */
   45.63 -	public static Connection jdbcConnect(DatabaseDefinition databaseDefinition, Properties properties) throws SQLException {
   45.64 -		synchronized (properties) {
   45.65 -			/**
   45.66 -			 * Avoid rewriting the properties. Usually, the connection is created only once, but
   45.67 -			 * with --test-connection and with SQL-DK JDBC driver, it might be reused.
   45.68 -			 */
   45.69 -			properties = properties.clone();
   45.70 -		}
   45.71 -		if (properties.hasProperty(JDBC_PROPERTY_PASSWORD)) {
   45.72 -			log.log(Level.WARNING, "Passing DB password as CLI parameter is insecure!");
   45.73 -		}
   45.74 -		Properties credentials = new Properties();
   45.75 -		credentials.add(new Property(JDBC_PROPERTY_USER, databaseDefinition.getUserName()));
   45.76 -		credentials.add(new Property(JDBC_PROPERTY_PASSWORD, databaseDefinition.getPassword()));
   45.77 -		credentials.setDefaults(databaseDefinition.getProperties());
   45.78 -		properties.setDefaults(credentials);
   45.79 -		java.util.Properties javaProperties = properties.getJavaProperties();
   45.80 -
   45.81 -		String driverClassName = databaseDefinition.getDriver();
   45.82 -		final String url = databaseDefinition.getUrl();
   45.83 -		if (driverClassName == null) {
   45.84 -			log.log(Level.FINE, "Using DriverManager to create connection for „{0}“", url);
   45.85 -			return DriverManager.getConnection(url, javaProperties);
   45.86 -		} else {
   45.87 -			log.log(Level.FINE, "Using custom Driver „{0}“ to create connection for „{1}“", new Object[]{driverClassName, url});
   45.88 -			try {
   45.89 -				Class<Driver> driverClass = (Class<Driver>) Class.forName(driverClassName);
   45.90 -				Driver driver = driverClass.newInstance();
   45.91 -				Connection connection = driver.connect(url, javaProperties);
   45.92 -				if (connection == null) {
   45.93 -					log.log(Level.SEVERE, "Driver „{0}“ returend null → it does not accept the URL: „{1}“", new Object[]{driverClassName, url});
   45.94 -					throw new SQLException("Unable to connect: driver returned null.");
   45.95 -				} else {
   45.96 -					return connection;
   45.97 -				}
   45.98 -			} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | SQLException e) {
   45.99 -				throw new SQLException("Unable to connect usig specific driver: " + driverClassName, e);
  45.100 -			}
  45.101 -		}
  45.102 -	}
  45.103 -
  45.104 -}
    46.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/NameIdentified.java	Mon Mar 04 17:06:42 2019 +0100
    46.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    46.3 @@ -1,27 +0,0 @@
    46.4 -/**
    46.5 - * SQL-DK
    46.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    46.7 - *
    46.8 - * This program is free software: you can redistribute it and/or modify
    46.9 - * it under the terms of the GNU General Public License as published by
   46.10 - * the Free Software Foundation, either version 3 of the License, or
   46.11 - * (at your option) any later version.
   46.12 - *
   46.13 - * This program is distributed in the hope that it will be useful,
   46.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   46.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   46.16 - * GNU General Public License for more details.
   46.17 - *
   46.18 - * You should have received a copy of the GNU General Public License
   46.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   46.20 - */
   46.21 -package info.globalcode.sql.dk.configuration;
   46.22 -
   46.23 -/**
   46.24 - *
   46.25 - * @author Ing. František Kučera (frantovo.cz)
   46.26 - */
   46.27 -public interface NameIdentified {
   46.28 -
   46.29 -	public String getName();
   46.30 -}
    47.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/Properties.java	Mon Mar 04 17:06:42 2019 +0100
    47.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    47.3 @@ -1,129 +0,0 @@
    47.4 -/**
    47.5 - * SQL-DK
    47.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    47.7 - *
    47.8 - * This program is free software: you can redistribute it and/or modify
    47.9 - * it under the terms of the GNU General Public License as published by
   47.10 - * the Free Software Foundation, either version 3 of the License, or
   47.11 - * (at your option) any later version.
   47.12 - *
   47.13 - * This program is distributed in the hope that it will be useful,
   47.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   47.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   47.16 - * GNU General Public License for more details.
   47.17 - *
   47.18 - * You should have received a copy of the GNU General Public License
   47.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   47.20 - */
   47.21 -package info.globalcode.sql.dk.configuration;
   47.22 -
   47.23 -import java.util.ArrayList;
   47.24 -import javax.xml.bind.annotation.XmlTransient;
   47.25 -import static info.globalcode.sql.dk.Functions.findByName;
   47.26 -import java.util.Collections;
   47.27 -
   47.28 -/**
   47.29 - * <p>
   47.30 - * List of configurables.</p>
   47.31 - *
   47.32 - * <p>
   47.33 - * Can be backed by defaults – if value for given name is nof found in this instance, we will
   47.34 - * look into defaults. Methods also accept defaultValue parameter – is used if property is nof found
   47.35 - * even in default properties.</p>
   47.36 - *
   47.37 - * <p>
   47.38 - * Typical use: </p>
   47.39 - * <ul>
   47.40 - * <li>this instance – ad-hoc properties from CLI options</li>
   47.41 - * <li>default properties – from config file</li>
   47.42 - * <li>defaultValue – hardcoded default</li>
   47.43 - * </ul>
   47.44 - *
   47.45 - * @author Ing. František Kučera (frantovo.cz)
   47.46 - */
   47.47 -public class Properties extends ArrayList<Property> implements Cloneable {
   47.48 -
   47.49 -	private Properties defaults;
   47.50 -
   47.51 -	public Properties() {
   47.52 -	}
   47.53 -
   47.54 -	public Properties(int initialCapacity) {
   47.55 -		super(initialCapacity);
   47.56 -	}
   47.57 -
   47.58 -	@XmlTransient
   47.59 -	public Properties getDefaults() {
   47.60 -		return defaults;
   47.61 -	}
   47.62 -
   47.63 -	public void setDefaults(Properties defaults) {
   47.64 -		this.defaults = defaults;
   47.65 -	}
   47.66 -
   47.67 -	/**
   47.68 -	 * @param defaults the last/deepest defaults
   47.69 -	 */
   47.70 -	public void setLastDefaults(Properties defaults) {
   47.71 -		if (this.defaults == null) {
   47.72 -			this.defaults = defaults;
   47.73 -		} else {
   47.74 -			this.defaults.setLastDefaults(defaults);
   47.75 -		}
   47.76 -	}
   47.77 -
   47.78 -	private Property findProperty(String name) {
   47.79 -		Property p = findByName(this, name);
   47.80 -		if (p == null && defaults != null) {
   47.81 -			p = defaults.findProperty(name);
   47.82 -		}
   47.83 -		return p;
   47.84 -	}
   47.85 -
   47.86 -	public String getString(String name, String defaultValue) {
   47.87 -		Property p = findProperty(name);
   47.88 -		return p == null ? defaultValue : p.getValue();
   47.89 -	}
   47.90 -
   47.91 -	public boolean getBoolean(String name, boolean defaultValue) {
   47.92 -		Property p = findProperty(name);
   47.93 -		return p == null ? defaultValue : Boolean.valueOf(p.getValue());
   47.94 -	}
   47.95 -
   47.96 -	public int getInteger(String name, int defaultValue) {
   47.97 -		Property p = findProperty(name);
   47.98 -		return p == null ? defaultValue : Integer.valueOf(p.getValue());
   47.99 -	}
  47.100 -
  47.101 -	public boolean hasProperty(String name) {
  47.102 -		return findByName(this, name) != null;
  47.103 -	}
  47.104 -
  47.105 -	@Override
  47.106 -	public Properties clone() {
  47.107 -		Properties clone = new Properties(size());
  47.108 -		Collections.copy(clone, this);
  47.109 -		return clone;
  47.110 -	}
  47.111 -
  47.112 -	/**
  47.113 -	 * @return merged this and backing defaults as Java Properties
  47.114 -	 */
  47.115 -	public java.util.Properties getJavaProperties() {
  47.116 -		java.util.Properties javaProperties = new java.util.Properties();
  47.117 -		duplicateTo(javaProperties);
  47.118 -		return javaProperties;
  47.119 -	}
  47.120 -
  47.121 -	private void duplicateTo(java.util.Properties javaProperties) {
  47.122 -		if (defaults != null) {
  47.123 -			defaults.duplicateTo(javaProperties);
  47.124 -		}
  47.125 -		for (Property p : this) {
  47.126 -			String value = p.getValue();
  47.127 -			if (value != null) {
  47.128 -				javaProperties.setProperty(p.getName(), value);
  47.129 -			}
  47.130 -		}
  47.131 -	}
  47.132 -}
    48.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/Property.java	Mon Mar 04 17:06:42 2019 +0100
    48.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    48.3 @@ -1,69 +0,0 @@
    48.4 -/**
    48.5 - * SQL-DK
    48.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    48.7 - *
    48.8 - * This program is free software: you can redistribute it and/or modify
    48.9 - * it under the terms of the GNU General Public License as published by
   48.10 - * the Free Software Foundation, either version 3 of the License, or
   48.11 - * (at your option) any later version.
   48.12 - *
   48.13 - * This program is distributed in the hope that it will be useful,
   48.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   48.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   48.16 - * GNU General Public License for more details.
   48.17 - *
   48.18 - * You should have received a copy of the GNU General Public License
   48.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   48.20 - */
   48.21 -package info.globalcode.sql.dk.configuration;
   48.22 -
   48.23 -import javax.xml.bind.annotation.XmlAttribute;
   48.24 -import javax.xml.bind.annotation.XmlValue;
   48.25 -
   48.26 -/**
   48.27 - * One configurable
   48.28 - *
   48.29 - * @author Ing. František Kučera (frantovo.cz)
   48.30 - */
   48.31 -public class Property implements NameIdentified, Cloneable {
   48.32 -
   48.33 -	private String name;
   48.34 -	private String value;
   48.35 -
   48.36 -	public Property() {
   48.37 -	}
   48.38 -
   48.39 -	public Property(String name, String value) {
   48.40 -		this.name = name;
   48.41 -		this.value = value;
   48.42 -	}
   48.43 -
   48.44 -	@XmlAttribute(name = "name")
   48.45 -	@Override
   48.46 -	public String getName() {
   48.47 -		return name;
   48.48 -	}
   48.49 -
   48.50 -	public void setName(String name) {
   48.51 -		this.name = name;
   48.52 -	}
   48.53 -
   48.54 -	@XmlValue
   48.55 -	public String getValue() {
   48.56 -		return value;
   48.57 -	}
   48.58 -
   48.59 -	public void setValue(String value) {
   48.60 -		this.value = value;
   48.61 -	}
   48.62 -
   48.63 -	@Override
   48.64 -	public String toString() {
   48.65 -		return name + "='" + value + "'";
   48.66 -	}
   48.67 -
   48.68 -	@Override
   48.69 -	protected Property clone() {
   48.70 -		return new Property(name, value);
   48.71 -	}
   48.72 -}
    49.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/PropertyDeclaration.java	Mon Mar 04 17:06:42 2019 +0100
    49.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    49.3 @@ -1,57 +0,0 @@
    49.4 -/**
    49.5 - * SQL-DK
    49.6 - * Copyright © 2015 František Kučera (frantovo.cz)
    49.7 - *
    49.8 - * This program is free software: you can redistribute it and/or modify
    49.9 - * it under the terms of the GNU General Public License as published by
   49.10 - * the Free Software Foundation, either version 3 of the License, or
   49.11 - * (at your option) any later version.
   49.12 - *
   49.13 - * This program is distributed in the hope that it will be useful,
   49.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   49.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   49.16 - * GNU General Public License for more details.
   49.17 - *
   49.18 - * You should have received a copy of the GNU General Public License
   49.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   49.20 - */
   49.21 -package info.globalcode.sql.dk.configuration;
   49.22 -
   49.23 -import java.lang.annotation.ElementType;
   49.24 -import java.lang.annotation.Repeatable;
   49.25 -import java.lang.annotation.Retention;
   49.26 -import static java.lang.annotation.RetentionPolicy.RUNTIME;
   49.27 -import java.lang.annotation.Target;
   49.28 -
   49.29 -/**
   49.30 - * Declaration of the (formatter) properties – for documentation purposes.
   49.31 - *
   49.32 - * TODO: automatically inject properties (configured, ad-hoc, default ones) to the formatters
   49.33 - *
   49.34 - * @author Ing. František Kučera (frantovo.cz)
   49.35 - */
   49.36 -@Retention(RUNTIME)
   49.37 -@Target({ElementType.TYPE})
   49.38 -@Repeatable(PropertyDeclarations.class)
   49.39 -public @interface PropertyDeclaration {
   49.40 -
   49.41 -	/**
   49.42 -	 * @return name of the property
   49.43 -	 */
   49.44 -	String name();
   49.45 -
   49.46 -	/**
   49.47 -	 * @return data type of the value: String, numbers, Boolean or Enum
   49.48 -	 */
   49.49 -	Class type();
   49.50 -	
   49.51 -	/**
   49.52 -	 * @return documentation for the users
   49.53 -	 */
   49.54 -	String description();
   49.55 -
   49.56 -	/**
   49.57 -	 * @return default value of this property
   49.58 -	 */
   49.59 -	String defaultValue();
   49.60 -}
    50.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/PropertyDeclarations.java	Mon Mar 04 17:06:42 2019 +0100
    50.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    50.3 @@ -1,35 +0,0 @@
    50.4 -/**
    50.5 - * SQL-DK
    50.6 - * Copyright © 2015 František Kučera (frantovo.cz)
    50.7 - *
    50.8 - * This program is free software: you can redistribute it and/or modify
    50.9 - * it under the terms of the GNU General Public License as published by
   50.10 - * the Free Software Foundation, either version 3 of the License, or
   50.11 - * (at your option) any later version.
   50.12 - *
   50.13 - * This program is distributed in the hope that it will be useful,
   50.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   50.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   50.16 - * GNU General Public License for more details.
   50.17 - *
   50.18 - * You should have received a copy of the GNU General Public License
   50.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   50.20 - */
   50.21 -package info.globalcode.sql.dk.configuration;
   50.22 -
   50.23 -import java.lang.annotation.ElementType;
   50.24 -import java.lang.annotation.Retention;
   50.25 -import static java.lang.annotation.RetentionPolicy.RUNTIME;
   50.26 -import java.lang.annotation.Target;
   50.27 -
   50.28 -/**
   50.29 - *
   50.30 - * @author Ing. František Kučera (frantovo.cz)
   50.31 - */
   50.32 -@Retention(RUNTIME)
   50.33 -@Target({ElementType.TYPE})
   50.34 -public @interface PropertyDeclarations {
   50.35 -
   50.36 -	PropertyDeclaration[] value();
   50.37 -
   50.38 -}
    51.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/TunnelDefinition.java	Mon Mar 04 17:06:42 2019 +0100
    51.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    51.3 @@ -1,51 +0,0 @@
    51.4 -/**
    51.5 - * SQL-DK
    51.6 - * Copyright © 2015 František Kučera (frantovo.cz)
    51.7 - *
    51.8 - * This program is free software: you can redistribute it and/or modify
    51.9 - * it under the terms of the GNU General Public License as published by
   51.10 - * the Free Software Foundation, either version 3 of the License, or
   51.11 - * (at your option) any later version.
   51.12 - *
   51.13 - * This program is distributed in the hope that it will be useful,
   51.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   51.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   51.16 - * GNU General Public License for more details.
   51.17 - *
   51.18 - * You should have received a copy of the GNU General Public License
   51.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   51.20 - */
   51.21 -package info.globalcode.sql.dk.configuration;
   51.22 -
   51.23 -import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
   51.24 -import java.util.List;
   51.25 -import javax.xml.bind.annotation.XmlElement;
   51.26 -
   51.27 -/**
   51.28 - *
   51.29 - * @author Ing. František Kučera (frantovo.cz)
   51.30 - */
   51.31 -public class TunnelDefinition {
   51.32 -
   51.33 -	private String command;
   51.34 -	private List<CommandArgument> arguments;
   51.35 -
   51.36 -	@XmlElement(name = "command", namespace = CONFIGURATION)
   51.37 -	public String getCommand() {
   51.38 -		return command;
   51.39 -	}
   51.40 -
   51.41 -	public void setCommand(String command) {
   51.42 -		this.command = command;
   51.43 -	}
   51.44 -
   51.45 -	@XmlElement(name = "argument", namespace = CONFIGURATION)
   51.46 -	public List<CommandArgument> getArguments() {
   51.47 -		return arguments;
   51.48 -	}
   51.49 -
   51.50 -	public void setArguments(List<CommandArgument> arguments) {
   51.51 -		this.arguments = arguments;
   51.52 -	}
   51.53 -
   51.54 -}
    52.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/jaxb.index	Mon Mar 04 17:06:42 2019 +0100
    52.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    52.3 @@ -1,1 +0,0 @@
    52.4 -Configuration
    52.5 \ No newline at end of file
    53.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractFormatter.java	Mon Mar 04 17:06:42 2019 +0100
    53.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    53.3 @@ -1,254 +0,0 @@
    53.4 -/**
    53.5 - * SQL-DK
    53.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    53.7 - *
    53.8 - * This program is free software: you can redistribute it and/or modify
    53.9 - * it under the terms of the GNU General Public License as published by
   53.10 - * the Free Software Foundation, either version 3 of the License, or
   53.11 - * (at your option) any later version.
   53.12 - *
   53.13 - * This program is distributed in the hope that it will be useful,
   53.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   53.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   53.16 - * GNU General Public License for more details.
   53.17 - *
   53.18 - * You should have received a copy of the GNU General Public License
   53.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   53.20 - */
   53.21 -package info.globalcode.sql.dk.formatting;
   53.22 -
   53.23 -import info.globalcode.sql.dk.Parameter;
   53.24 -import info.globalcode.sql.dk.configuration.DatabaseDefinition;
   53.25 -import java.util.EmptyStackException;
   53.26 -import java.util.EnumSet;
   53.27 -import java.util.List;
   53.28 -import java.util.Stack;
   53.29 -
   53.30 -/**
   53.31 - * <ol>
   53.32 - * <li>ensures integrity – if methods are called in correct order and context</li>
   53.33 - * <li>provides default implmentations of methods that does not produce any output for given
   53.34 - * events</li>
   53.35 - * </ol>
   53.36 - *
   53.37 - * @author Ing. František Kučera (frantovo.cz)
   53.38 - */
   53.39 -public abstract class AbstractFormatter implements Formatter {
   53.40 -
   53.41 -	private Stack<State> state = new Stack<>();
   53.42 -	private FormatterContext formatterContext;
   53.43 -	private ColumnsHeader currentColumnsHeader;
   53.44 -	private String currentQuery;
   53.45 -	private int currentColumnsCount;
   53.46 -	private int currentRowCount;
   53.47 -
   53.48 -	public AbstractFormatter(FormatterContext formatterContext) {
   53.49 -		this.formatterContext = formatterContext;
   53.50 -		state.push(State.ROOT);
   53.51 -	}
   53.52 -
   53.53 -	/*
   53.54 -	 * root
   53.55 -	 * .batch
   53.56 -	 * ..database
   53.57 -	 * ...statement
   53.58 -	 * ....@query
   53.59 -	 * ....@parameters
   53.60 -	 * ....resultSet
   53.61 -	 * .....row
   53.62 -	 * ......@columnValue
   53.63 -	 * ....@updatesResult
   53.64 -	 */
   53.65 -	protected enum State {
   53.66 -
   53.67 -		ROOT,
   53.68 -		BATCH,
   53.69 -		DATABASE,
   53.70 -		STATEMENT,
   53.71 -		RESULT_SET,
   53.72 -		ROW
   53.73 -	}
   53.74 -
   53.75 -	/**
   53.76 -	 * Go down in hierarchy.
   53.77 -	 * Pushes new state and verifies the old one.
   53.78 -	 *
   53.79 -	 * @param current the new state – currently entering
   53.80 -	 * @param expected expected previous states (any of them is valid)
   53.81 -	 * @return previous state
   53.82 -	 * @throws IllegalStateException if previous state was not one from expected
   53.83 -	 */
   53.84 -	private State pushState(State current, EnumSet expected) {
   53.85 -		State previous = state.peek();
   53.86 -
   53.87 -		if (expected.contains(previous)) {
   53.88 -			state.push(current);
   53.89 -			return previous;
   53.90 -		} else {
   53.91 -			throw new IllegalStateException("Formatter was in wrong state: " + previous + " when it should be in one of: " + expected);
   53.92 -		}
   53.93 -	}
   53.94 -
   53.95 -	protected State peekState(EnumSet expected) {
   53.96 -		State current = state.peek();
   53.97 -
   53.98 -		if (expected.contains(current)) {
   53.99 -			return current;
  53.100 -		} else {
  53.101 -			throw new IllegalStateException("Formatter is in wrong state: " + current + " when it should be in one of: " + expected);
  53.102 -		}
  53.103 -
  53.104 -	}
  53.105 -
  53.106 -	/**
  53.107 -	 * Go up in hierarchy.
  53.108 -	 * Pops the superior state/branch.
  53.109 -	 *
  53.110 -	 * @param expected expected superior state
  53.111 -	 * @return the superior state
  53.112 -	 * @throws IllegalStateException if superior state was not one from expected or if there is no
  53.113 -	 * more superior state (we are at root level)
  53.114 -	 */
  53.115 -	private State popState(EnumSet expected) {
  53.116 -		try {
  53.117 -			state.pop();
  53.118 -			State superior = state.peek();
  53.119 -			if (expected.contains(superior)) {
  53.120 -				return superior;
  53.121 -			} else {
  53.122 -				throw new IllegalStateException("Formatter had wrong superior state: " + superior + " when it should be in one of: " + expected);
  53.123 -			}
  53.124 -		} catch (EmptyStackException e) {
  53.125 -			throw new IllegalStateException("Formatter was already at root level – there is nothing above that.", e);
  53.126 -		}
  53.127 -	}
  53.128 -
  53.129 -	@Override
  53.130 -	public void writeStartBatch() {
  53.131 -		pushState(State.BATCH, EnumSet.of(State.ROOT));
  53.132 -	}
  53.133 -
  53.134 -	@Override
  53.135 -	public void writeEndBatch() {
  53.136 -		popState(EnumSet.of(State.ROOT));
  53.137 -	}
  53.138 -
  53.139 -	@Override
  53.140 -	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
  53.141 -		pushState(State.DATABASE, EnumSet.of(State.BATCH));
  53.142 -	}
  53.143 -
  53.144 -	@Override
  53.145 -	public void writeEndDatabase() {
  53.146 -		popState(EnumSet.of(State.BATCH));
  53.147 -	}
  53.148 -
  53.149 -	@Override
  53.150 -	public void writeStartStatement() {
  53.151 -		pushState(State.STATEMENT, EnumSet.of(State.DATABASE));
  53.152 -	}
  53.153 -
  53.154 -	@Override
  53.155 -	public void writeEndStatement() {
  53.156 -		popState(EnumSet.of(State.DATABASE));
  53.157 -	}
  53.158 -
  53.159 -	@Override
  53.160 -	public void writeStartResultSet(ColumnsHeader header) {
  53.161 -		pushState(State.RESULT_SET, EnumSet.of(State.STATEMENT));
  53.162 -		currentRowCount = 0;
  53.163 -		currentColumnsHeader = header;
  53.164 -	}
  53.165 -
  53.166 -	@Override
  53.167 -	public void writeEndResultSet() {
  53.168 -		popState(EnumSet.of(State.STATEMENT));
  53.169 -		currentColumnsHeader = null;
  53.170 -	}
  53.171 -
  53.172 -	@Override
  53.173 -	public void writeQuery(String sql) {
  53.174 -		peekState(EnumSet.of(State.STATEMENT));
  53.175 -
  53.176 -		if (currentColumnsHeader == null) {
  53.177 -			currentQuery = sql;
  53.178 -		} else {
  53.179 -			throw new IllegalStateException("Query string '" + sql + "' must be set before columns header – was already set: " + currentColumnsHeader);
  53.180 -		}
  53.181 -	}
  53.182 -
  53.183 -	@Override
  53.184 -	public void writeParameters(List<? extends Parameter> parameters) {
  53.185 -		peekState(EnumSet.of(State.STATEMENT));
  53.186 -
  53.187 -		if (currentColumnsHeader != null) {
  53.188 -			throw new IllegalStateException("Parameters '" + parameters + "' must be set before columns header – was already set: " + currentColumnsHeader);
  53.189 -		}
  53.190 -
  53.191 -		if (currentQuery == null && parameters != null) {
  53.192 -			throw new IllegalStateException("Parameters '" + parameters + "' must be set after query – was not yet set.");
  53.193 -		}
  53.194 -	}
  53.195 -
  53.196 -	@Override
  53.197 -	public void writeStartRow() {
  53.198 -		pushState(State.ROW, EnumSet.of(State.RESULT_SET));
  53.199 -		currentColumnsCount = 0;
  53.200 -		currentRowCount++;
  53.201 -	}
  53.202 -
  53.203 -	@Override
  53.204 -	public void writeEndRow() {
  53.205 -		popState(EnumSet.of(State.RESULT_SET));
  53.206 -	}
  53.207 -
  53.208 -	@Override
  53.209 -	public void writeColumnValue(Object value) {
  53.210 -		peekState(EnumSet.of(State.ROW));
  53.211 -		currentColumnsCount++;
  53.212 -
  53.213 -		int declaredCount = currentColumnsHeader.getColumnCount();
  53.214 -		if (currentColumnsCount > declaredCount) {
  53.215 -			throw new IllegalStateException("Current columns count is " + currentColumnsCount + " which is more than declared " + declaredCount + " in header.");
  53.216 -		}
  53.217 -	}
  53.218 -
  53.219 -	@Override
  53.220 -	public void writeUpdatesResult(int updatedRowsCount) {
  53.221 -		peekState(EnumSet.of(State.STATEMENT));
  53.222 -	}
  53.223 -
  53.224 -	@Override
  53.225 -	public void close() throws FormatterException {
  53.226 -	}
  53.227 -
  53.228 -	public FormatterContext getFormatterContext() {
  53.229 -		return formatterContext;
  53.230 -	}
  53.231 -
  53.232 -	protected ColumnsHeader getCurrentColumnsHeader() {
  53.233 -		return currentColumnsHeader;
  53.234 -	}
  53.235 -
  53.236 -	/**
  53.237 -	 * @return column number, 1 = first
  53.238 -	 */
  53.239 -	protected int getCurrentColumnsCount() {
  53.240 -		return currentColumnsCount;
  53.241 -	}
  53.242 -
  53.243 -	protected boolean isCurrentColumnFirst() {
  53.244 -		return currentColumnsCount == 1;
  53.245 -	}
  53.246 -
  53.247 -	protected boolean isCurrentColumnLast() {
  53.248 -		return currentColumnsCount == currentColumnsHeader.getColumnCount();
  53.249 -	}
  53.250 -
  53.251 -	/**
  53.252 -	 * @return row number, 1 = first
  53.253 -	 */
  53.254 -	protected int getCurrentRowCount() {
  53.255 -		return currentRowCount;
  53.256 -	}
  53.257 -}
    54.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java	Mon Mar 04 17:06:42 2019 +0100
    54.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    54.3 @@ -1,241 +0,0 @@
    54.4 -/**
    54.5 - * SQL-DK
    54.6 - * Copyright © 2014 František Kučera (frantovo.cz)
    54.7 - *
    54.8 - * This program is free software: you can redistribute it and/or modify
    54.9 - * it under the terms of the GNU General Public License as published by
   54.10 - * the Free Software Foundation, either version 3 of the License, or
   54.11 - * (at your option) any later version.
   54.12 - *
   54.13 - * This program is distributed in the hope that it will be useful,
   54.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   54.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   54.16 - * GNU General Public License for more details.
   54.17 - *
   54.18 - * You should have received a copy of the GNU General Public License
   54.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   54.20 - */
   54.21 -package info.globalcode.sql.dk.formatting;
   54.22 -
   54.23 -import info.globalcode.sql.dk.ColorfulPrintWriter;
   54.24 -import info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
   54.25 -import java.util.Stack;
   54.26 -import javax.xml.namespace.QName;
   54.27 -import static info.globalcode.sql.dk.Functions.isEmpty;
   54.28 -import static info.globalcode.sql.dk.Functions.toHex;
   54.29 -import info.globalcode.sql.dk.configuration.PropertyDeclaration;
   54.30 -import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
   54.31 -import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
   54.32 -import java.nio.charset.Charset;
   54.33 -import java.util.EmptyStackException;
   54.34 -import java.util.HashMap;
   54.35 -import java.util.LinkedHashMap;
   54.36 -import java.util.Map;
   54.37 -import java.util.Map.Entry;
   54.38 -import java.util.logging.Level;
   54.39 -import java.util.logging.Logger;
   54.40 -
   54.41 -/**
   54.42 - * <p>
   54.43 - * Provides helper methods for printing pretty intended and optionally colorful (syntax highlighted)
   54.44 - * XML output.
   54.45 - * </p>
   54.46 - *
   54.47 - * <p>
   54.48 - * Must be used with care – bad usage can lead to invalid XML (e.g. using undeclared namespaces).
   54.49 - * </p>
   54.50 - *
   54.51 - * @author Ing. František Kučera (frantovo.cz)
   54.52 - */
   54.53 -@PropertyDeclaration(name = COLORFUL, defaultValue = "false", type = Boolean.class, description = COLORFUL_DESCRIPTION)
   54.54 -@PropertyDeclaration(name = AbstractXmlFormatter.PROPERTY_INDENT, defaultValue = AbstractXmlFormatter.PROPERTY_INDENT_DEFAULT, type = String.class, description = "tab or sequence of spaces used for indentation of nested elements")
   54.55 -@PropertyDeclaration(name = AbstractXmlFormatter.PROPERTY_INDENT_TEXT, defaultValue = "true", type = Boolean.class, description = "whether text with line breaks should be indented; if not original whitespace will be preserved.")
   54.56 -public abstract class AbstractXmlFormatter extends AbstractFormatter {
   54.57 -
   54.58 -	private static final Logger log = Logger.getLogger(AbstractXmlFormatter.class.getName());
   54.59 -	public static final String PROPERTY_INDENT = "indent";
   54.60 -	protected static final String PROPERTY_INDENT_DEFAULT = "\t";
   54.61 -	public static final String PROPERTY_INDENT_TEXT = "indentText";
   54.62 -	private static final TerminalColor ELEMENT_COLOR = TerminalColor.Magenta;
   54.63 -	private static final TerminalColor ATTRIBUTE_NAME_COLOR = TerminalColor.Green;
   54.64 -	private static final TerminalColor ATTRIBUTE_VALUE_COLOR = TerminalColor.Yellow;
   54.65 -	private static final TerminalColor XML_DECLARATION_COLOR = TerminalColor.Red;
   54.66 -	private static final TerminalColor XML_DOCTYPE_COLOR = TerminalColor.Cyan;
   54.67 -	private Stack<QName> treePosition = new Stack<>();
   54.68 -	private final ColorfulPrintWriter out;
   54.69 -	private final String indent;
   54.70 -	private final boolean indentText;
   54.71 -
   54.72 -	public AbstractXmlFormatter(FormatterContext formatterContext) {
   54.73 -		super(formatterContext);
   54.74 -		boolean colorful = formatterContext.getProperties().getBoolean(COLORFUL, false);
   54.75 -		out = new ColorfulPrintWriter(formatterContext.getOutputStream(), false, colorful);
   54.76 -		indent = formatterContext.getProperties().getString(PROPERTY_INDENT, PROPERTY_INDENT_DEFAULT);
   54.77 -		indentText = formatterContext.getProperties().getBoolean(PROPERTY_INDENT_TEXT, true);
   54.78 -
   54.79 -		if (!indent.matches("\\s*")) {
   54.80 -			log.log(Level.WARNING, "Setting indent to „{0}“ is weird & freaky; in hex: {1}", new Object[]{indent, toHex(indent.getBytes())});
   54.81 -		}
   54.82 -
   54.83 -	}
   54.84 -
   54.85 -	protected void printStartDocument() {
   54.86 -		out.print(XML_DECLARATION_COLOR, "<?xml version=\"1.0\" encoding=\"" + Charset.defaultCharset().name() + "\"?>");
   54.87 -	}
   54.88 -
   54.89 -	protected void printDoctype(String doctype) {
   54.90 -		out.print(XML_DOCTYPE_COLOR, "\n<!DOCTYPE " + doctype + ">");
   54.91 -	}
   54.92 -
   54.93 -	protected void printEndDocument() {
   54.94 -		out.println();
   54.95 -		out.flush();
   54.96 -		if (!treePosition.empty()) {
   54.97 -			throw new IllegalStateException("Some elements are not closed: " + treePosition);
   54.98 -		}
   54.99 -	}
  54.100 -
  54.101 -	protected void printStartElement(QName element) {
  54.102 -		printStartElement(element, null);
  54.103 -	}
  54.104 -
  54.105 -	protected Map<QName, String> singleAttribute(QName name, String value) {
  54.106 -		Map<QName, String> attributes = new HashMap<>(2);
  54.107 -		attributes.put(name, value);
  54.108 -		return attributes;
  54.109 -	}
  54.110 -
  54.111 -	protected void printStartElement(QName element, Map<QName, String> attributes) {
  54.112 -		printStartElement(element, attributes, false);
  54.113 -	}
  54.114 -
  54.115 -	/**
  54.116 -	 * @param empty whether element should be closed <codfe>… /&gt;</code> (has no content, do not
  54.117 -	 * call {@linkplain #printEndElement()})
  54.118 -	 */
  54.119 -	private void printStartElement(QName element, Map<QName, String> attributes, boolean empty) {
  54.120 -		printIndent();
  54.121 -
  54.122 -		out.print(ELEMENT_COLOR, "<" + toString(element));
  54.123 -
  54.124 -		if (attributes != null) {
  54.125 -			for (Entry<QName, String> attribute : attributes.entrySet()) {
  54.126 -				out.print(" ");
  54.127 -				out.print(ATTRIBUTE_NAME_COLOR, toString(attribute.getKey()));
  54.128 -				out.print("=");
  54.129 -				out.print(ATTRIBUTE_VALUE_COLOR, '"' + escapeXmlAttribute(attribute.getValue()) + '"');
  54.130 -			}
  54.131 -		}
  54.132 -
  54.133 -		if (empty) {
  54.134 -			out.print(ELEMENT_COLOR, "/>");
  54.135 -		} else {
  54.136 -			out.print(ELEMENT_COLOR, ">");
  54.137 -			treePosition.add(element);
  54.138 -		}
  54.139 -
  54.140 -		out.flush();
  54.141 -	}
  54.142 -
  54.143 -	/**
  54.144 -	 * Prints text node wrapped in given element without indenting the text and adding line breaks
  54.145 -	 * (useful for short texts).
  54.146 -	 *
  54.147 -	 * @param attributes use {@linkplain  LinkedHashMap} to preserve attributes order
  54.148 -	 */
  54.149 -	protected void printTextElement(QName element, Map<QName, String> attributes, String text) {
  54.150 -		printStartElement(element, attributes);
  54.151 -
  54.152 -		String[] lines = text.split("\\n");
  54.153 -
  54.154 -		if (indentText && lines.length > 1) {
  54.155 -			for (String line : lines) {
  54.156 -				printText(line, true);
  54.157 -			}
  54.158 -			printEndElement(true);
  54.159 -		} else {
  54.160 -			/*
  54.161 -			 * line breaks at the end of the text will be eaten – if you need them, use indentText = false
  54.162 -			 */
  54.163 -			if (lines.length == 1 && text.endsWith("\n")) {
  54.164 -				text = text.substring(0, text.length() - 1);
  54.165 -			}
  54.166 -
  54.167 -			printText(text, false);
  54.168 -			printEndElement(false);
  54.169 -		}
  54.170 -	}
  54.171 -
  54.172 -	protected void printEmptyElement(QName element, Map<QName, String> attributes) {
  54.173 -		printStartElement(element, attributes, true);
  54.174 -	}
  54.175 -
  54.176 -	protected void printEndElement() {
  54.177 -		printEndElement(true);
  54.178 -	}
  54.179 -
  54.180 -	private void printEndElement(boolean indent) {
  54.181 -		try {
  54.182 -			QName name = treePosition.pop();
  54.183 -
  54.184 -			if (indent) {
  54.185 -				printIndent();
  54.186 -			}
  54.187 -
  54.188 -			out.print(ELEMENT_COLOR, "</" + toString(name) + ">");
  54.189 -			out.flush();
  54.190 -
  54.191 -		} catch (EmptyStackException e) {
  54.192 -			throw new IllegalStateException("No more elements to end.", e);
  54.193 -		}
  54.194 -	}
  54.195 -
  54.196 -	protected void printText(String s, boolean indent) {
  54.197 -		if (indent) {
  54.198 -			printIndent();
  54.199 -		}
  54.200 -		out.print(escapeXmlText(s));
  54.201 -		out.flush();
  54.202 -	}
  54.203 -
  54.204 -	protected void printIndent() {
  54.205 -		out.println();
  54.206 -		for (int i = 0; i < treePosition.size(); i++) {
  54.207 -			out.print(indent);
  54.208 -		}
  54.209 -	}
  54.210 -
  54.211 -	protected static QName qname(String name) {
  54.212 -		return new QName(name);
  54.213 -	}
  54.214 -
  54.215 -	protected static QName qname(String prefix, String name) {
  54.216 -		return new QName(null, name, prefix);
  54.217 -	}
  54.218 -
  54.219 -	private String toString(QName name) {
  54.220 -		if (isEmpty(name.getPrefix(), true)) {
  54.221 -			return escapeName(name.getLocalPart());
  54.222 -		} else {
  54.223 -			return escapeName(name.getPrefix()) + ":" + escapeName(name.getLocalPart());
  54.224 -		}
  54.225 -	}
  54.226 -
  54.227 -	private String escapeName(String s) {
  54.228 -		// TODO: avoid ugly values in <name name="…"/>		
  54.229 -		return s;
  54.230 -	}
  54.231 -
  54.232 -	private static String escapeXmlText(String s) {
  54.233 -		return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
  54.234 -		// Not needed:
  54.235 -		// return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
  54.236 -	}
  54.237 -
  54.238 -	/**
  54.239 -	 * Expects attribute values enclosed in "quotes" not 'apostrophes'.
  54.240 -	 */
  54.241 -	private static String escapeXmlAttribute(String s) {
  54.242 -		return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
  54.243 -	}
  54.244 -}
    55.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/BarChartFormatter.java	Mon Mar 04 17:06:42 2019 +0100
    55.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    55.3 @@ -1,104 +0,0 @@
    55.4 -/**
    55.5 - * SQL-DK
    55.6 - * Copyright © 2015 František Kučera (frantovo.cz)
    55.7 - *
    55.8 - * This program is free software: you can redistribute it and/or modify
    55.9 - * it under the terms of the GNU General Public License as published by
   55.10 - * the Free Software Foundation, either version 3 of the License, or
   55.11 - * (at your option) any later version.
   55.12 - *
   55.13 - * This program is distributed in the hope that it will be useful,
   55.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   55.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   55.16 - * GNU General Public License for more details.
   55.17 - *
   55.18 - * You should have received a copy of the GNU General Public License
   55.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   55.20 - */
   55.21 -package info.globalcode.sql.dk.formatting;
   55.22 -
   55.23 -import info.globalcode.sql.dk.Functions;
   55.24 -import info.globalcode.sql.dk.configuration.PropertyDeclaration;
   55.25 -import info.globalcode.sql.dk.logging.LoggerProducer;
   55.26 -import java.math.BigDecimal;
   55.27 -import java.math.MathContext;
   55.28 -import java.math.RoundingMode;
   55.29 -import java.util.List;
   55.30 -import java.util.logging.Level;
   55.31 -import java.util.logging.Logger;
   55.32 -
   55.33 -/**
   55.34 - * TODO: min/max values – range for case that no value is 100 %
   55.35 - *
   55.36 - * TODO: multiple barcharts in same table (last column is still default) + multiple resultsets
   55.37 - *
   55.38 - * TODO: negative values - bar starting from the middle, not always from the left
   55.39 - *
   55.40 - * @author Ing. František Kučera (frantovo.cz)
   55.41 - */
   55.42 -@PropertyDeclaration(name = BarChartFormatter.PROPERTY_PRECISION, type = Integer.class, defaultValue = BarChartFormatter.PROPERTY_PRECISION_DEFAULT, description = "number of characters representing 100 % in the bar chart")
   55.43 -public class BarChartFormatter extends TabularPrefetchingFormatter {
   55.44 -
   55.45 -	public static final String NAME = "barchart"; // bash-completion:formatter
   55.46 -	public static final String PROPERTY_PRECISION = "precision";
   55.47 -	protected static final String PROPERTY_PRECISION_DEFAULT = "100";
   55.48 -	private static final MathContext mathContext = MathContext.DECIMAL128;
   55.49 -	public static final Logger log = LoggerProducer.getLogger();
   55.50 -	private final BigDecimal chartPrecision;
   55.51 -	private final char chartFull;
   55.52 -	private final char chartEmpty;
   55.53 -
   55.54 -	public BarChartFormatter(FormatterContext formatterContext) {
   55.55 -		super(formatterContext);
   55.56 -		chartPrecision = BigDecimal.valueOf(formatterContext.getProperties().getInteger(PROPERTY_PRECISION, Integer.parseInt(PROPERTY_PRECISION_DEFAULT)));
   55.57 -		chartFull = isAsciiNostalgia() ? '#' : '█';
   55.58 -		chartEmpty = isAsciiNostalgia() ? '~' : '░';
   55.59 -		// TODO: consider using partial blocks for more precision: https://en.wikipedia.org/wiki/Block_Elements
   55.60 -	}
   55.61 -
   55.62 -	@Override
   55.63 -	protected void postprocessPrefetchedResultSet(ColumnsHeader currentHeader, List<Object[]> currentResultSet) {
   55.64 -		super.postprocessPrefetchedResultSet(currentHeader, currentResultSet);
   55.65 -
   55.66 -		updateColumnWidth(currentHeader.getColumnCount(), chartPrecision.intValue());
   55.67 -
   55.68 -		BigDecimal maximum = BigDecimal.ZERO;
   55.69 -		BigDecimal minimum = BigDecimal.ZERO;
   55.70 -		int lastIndex = currentHeader.getColumnCount() - 1;
   55.71 -
   55.72 -		Object valueObject = null;
   55.73 -		try {
   55.74 -			for (Object[] row : currentResultSet) {
   55.75 -				valueObject = row[lastIndex];
   55.76 -				if (valueObject != null) {
   55.77 -					BigDecimal value = new BigDecimal(valueObject.toString());
   55.78 -					maximum = maximum.max(value);
   55.79 -					minimum = minimum.min(value);
   55.80 -				}
   55.81 -			}
   55.82 -
   55.83 -			BigDecimal range = maximum.subtract(minimum);
   55.84 -
   55.85 -			for (Object[] row : currentResultSet) {
   55.86 -				valueObject = row[lastIndex];
   55.87 -				if (valueObject == null) {
   55.88 -					row[lastIndex] = "";
   55.89 -				} else {
   55.90 -					BigDecimal value = new BigDecimal(valueObject.toString());
   55.91 -					BigDecimal valueFromMinimum = value.subtract(minimum);
   55.92 -
   55.93 -					BigDecimal points = chartPrecision.divide(range, mathContext).multiply(valueFromMinimum, mathContext);
   55.94 -					int pointsRounded = points.setScale(0, RoundingMode.HALF_UP).intValue();
   55.95 -					row[lastIndex] = Functions.repeat(chartFull, pointsRounded) + Functions.repeat(chartEmpty, chartPrecision.intValue() - pointsRounded);
   55.96 -				}
   55.97 -			}
   55.98 -
   55.99 -		} catch (NumberFormatException e) {
  55.100 -			// https://en.wiktionary.org/wiki/parsable
  55.101 -			log.log(Level.SEVERE, "Last column must be number or an object with toString() value parsable to a number. But was „{0}“", valueObject);
  55.102 -			// FIXME: throw FormatterException
  55.103 -			throw e;
  55.104 -		}
  55.105 -	}
  55.106 -
  55.107 -}
    56.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/ColumnDescriptor.java	Mon Mar 04 17:06:42 2019 +0100
    56.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    56.3 @@ -1,122 +0,0 @@
    56.4 -/**
    56.5 - * SQL-DK
    56.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    56.7 - *
    56.8 - * This program is free software: you can redistribute it and/or modify
    56.9 - * it under the terms of the GNU General Public License as published by
   56.10 - * the Free Software Foundation, either version 3 of the License, or
   56.11 - * (at your option) any later version.
   56.12 - *
   56.13 - * This program is distributed in the hope that it will be useful,
   56.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   56.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   56.16 - * GNU General Public License for more details.
   56.17 - *
   56.18 - * You should have received a copy of the GNU General Public License
   56.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   56.20 - */
   56.21 -package info.globalcode.sql.dk.formatting;
   56.22 -
   56.23 -import java.sql.Types;
   56.24 -
   56.25 -/**
   56.26 - *
   56.27 - * @author Ing. František Kučera (frantovo.cz)
   56.28 - */
   56.29 -public class ColumnDescriptor {
   56.30 -
   56.31 -	private String name;
   56.32 -	private String label;
   56.33 -	private int type;
   56.34 -	private String typeName;
   56.35 -	private boolean firstColumn;
   56.36 -	private boolean lastColumn;
   56.37 -	private int columnNumber;
   56.38 -
   56.39 -	/**
   56.40 -	 * @return column name
   56.41 -	 * @see #getLabel()
   56.42 -	 */
   56.43 -	public String getName() {
   56.44 -		return name;
   56.45 -	}
   56.46 -
   56.47 -	public void setName(String name) {
   56.48 -		this.name = name;
   56.49 -	}
   56.50 -
   56.51 -	/**
   56.52 -	 * @return label specified by the SQL AS clause
   56.53 -	 */
   56.54 -	public String getLabel() {
   56.55 -		return label;
   56.56 -	}
   56.57 -
   56.58 -	public void setLabel(String label) {
   56.59 -		this.label = label;
   56.60 -	}
   56.61 -
   56.62 -	public int getType() {
   56.63 -		return type;
   56.64 -	}
   56.65 -
   56.66 -	public void setType(int type) {
   56.67 -		this.type = type;
   56.68 -	}
   56.69 -
   56.70 -	public String getTypeName() {
   56.71 -		return typeName;
   56.72 -	}
   56.73 -
   56.74 -	public void setTypeName(String typeName) {
   56.75 -		this.typeName = typeName;
   56.76 -	}
   56.77 -
   56.78 -	public boolean isFirstColumn() {
   56.79 -		return firstColumn;
   56.80 -	}
   56.81 -
   56.82 -	public void setFirstColumn(boolean firstColumn) {
   56.83 -		this.firstColumn = firstColumn;
   56.84 -	}
   56.85 -
   56.86 -	public boolean isLastColumn() {
   56.87 -		return lastColumn;
   56.88 -	}
   56.89 -
   56.90 -	public void setLastColumn(boolean lastColumn) {
   56.91 -		this.lastColumn = lastColumn;
   56.92 -	}
   56.93 -
   56.94 -	/**
   56.95 -	 * @return number of this column, 1 = first
   56.96 -	 */
   56.97 -	public int getColumnNumber() {
   56.98 -		return columnNumber;
   56.99 -	}
  56.100 -
  56.101 -	public void setColumnNumber(int columnNumber) {
  56.102 -		this.columnNumber = columnNumber;
  56.103 -	}
  56.104 -
  56.105 -	public boolean isBoolean() {
  56.106 -		return type == Types.BOOLEAN;
  56.107 -	}
  56.108 -
  56.109 -	public boolean isNumeric() {
  56.110 -		switch (type) {
  56.111 -			case Types.BIGINT:
  56.112 -			case Types.DECIMAL:
  56.113 -			case Types.DOUBLE:
  56.114 -			case Types.FLOAT:
  56.115 -			case Types.INTEGER:
  56.116 -			case Types.NUMERIC:
  56.117 -			case Types.REAL:
  56.118 -			case Types.SMALLINT:
  56.119 -			case Types.TINYINT:
  56.120 -				return true;
  56.121 -			default:
  56.122 -				return false;
  56.123 -		}
  56.124 -	}
  56.125 -}
    57.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/ColumnsHeader.java	Mon Mar 04 17:06:42 2019 +0100
    57.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    57.3 @@ -1,70 +0,0 @@
    57.4 -/**
    57.5 - * SQL-DK
    57.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    57.7 - *
    57.8 - * This program is free software: you can redistribute it and/or modify
    57.9 - * it under the terms of the GNU General Public License as published by
   57.10 - * the Free Software Foundation, either version 3 of the License, or
   57.11 - * (at your option) any later version.
   57.12 - *
   57.13 - * This program is distributed in the hope that it will be useful,
   57.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   57.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   57.16 - * GNU General Public License for more details.
   57.17 - *
   57.18 - * You should have received a copy of the GNU General Public License
   57.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   57.20 - */
   57.21 -package info.globalcode.sql.dk.formatting;
   57.22 -
   57.23 -import java.sql.ResultSetMetaData;
   57.24 -import java.sql.SQLException;
   57.25 -import java.util.ArrayList;
   57.26 -import java.util.List;
   57.27 -
   57.28 -/**
   57.29 - *
   57.30 - * @author Ing. František Kučera (frantovo.cz)
   57.31 - */
   57.32 -public class ColumnsHeader {
   57.33 -	
   57.34 -	private ResultSetMetaData metaData;
   57.35 -	
   57.36 -	public ColumnsHeader(ResultSetMetaData metaData) {
   57.37 -		this.metaData = metaData;
   57.38 -	}
   57.39 -	
   57.40 -	public int getColumnCount() {
   57.41 -		try {
   57.42 -			return metaData.getColumnCount();
   57.43 -		} catch (SQLException e) {
   57.44 -			throw new IllegalStateException("Error during getting column count.", e);
   57.45 -		}
   57.46 -	}
   57.47 -	
   57.48 -	public List<ColumnDescriptor> getColumnDescriptors() {
   57.49 -		try {
   57.50 -			int count = metaData.getColumnCount();
   57.51 -			List<ColumnDescriptor> list = new ArrayList<>(count);
   57.52 -			
   57.53 -			for (int i = 1; i <= count; i++) {
   57.54 -				ColumnDescriptor cd = new ColumnDescriptor();
   57.55 -				
   57.56 -				cd.setFirstColumn(i == 1);
   57.57 -				cd.setLastColumn(i == count);
   57.58 -				cd.setColumnNumber(i);
   57.59 -				
   57.60 -				cd.setLabel(metaData.getColumnLabel(i));
   57.61 -				cd.setName(metaData.getColumnName(i));
   57.62 -				cd.setType(metaData.getColumnType(i));
   57.63 -				cd.setTypeName(metaData.getColumnTypeName(i));
   57.64 -				/** TODO: more properties */
   57.65 -				list.add(cd);
   57.66 -			}
   57.67 -			
   57.68 -			return list;
   57.69 -		} catch (SQLException e) {
   57.70 -			throw new IllegalStateException("Error during building column descriptors.", e);
   57.71 -		}
   57.72 -	}
   57.73 -}
    58.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/CommonProperties.java	Mon Mar 04 17:06:42 2019 +0100
    58.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    58.3 @@ -1,50 +0,0 @@
    58.4 -/**
    58.5 - * SQL-DK
    58.6 - * Copyright © 2015 František Kučera (frantovo.cz)
    58.7 - *
    58.8 - * This program is free software: you can redistribute it and/or modify
    58.9 - * it under the terms of the GNU General Public License as published by
   58.10 - * the Free Software Foundation, either version 3 of the License, or
   58.11 - * (at your option) any later version.
   58.12 - *
   58.13 - * This program is distributed in the hope that it will be useful,
   58.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   58.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   58.16 - * GNU General Public License for more details.
   58.17 - *
   58.18 - * You should have received a copy of the GNU General Public License
   58.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   58.20 - */
   58.21 -package info.globalcode.sql.dk.formatting;
   58.22 -
   58.23 -import java.util.Collections;
   58.24 -import java.util.HashMap;
   58.25 -import java.util.Map;
   58.26 -
   58.27 -/**
   58.28 - *
   58.29 - * @author Ing. František Kučera (frantovo.cz)
   58.30 - */
   58.31 -public class CommonProperties {
   58.32 -
   58.33 -	private static final Map<Class, String> TYPE_SIMPLE_NAMES;
   58.34 -
   58.35 -	static {
   58.36 -		Map<Class, String> m = new HashMap<>();
   58.37 -		m.put(Boolean.class, "boolean");
   58.38 -		m.put(String.class, "String");
   58.39 -		m.put(Character.class, "char");
   58.40 -		m.put(Integer.class, "int");
   58.41 -		m.put(Long.class, "long");
   58.42 -		m.put(Double.class, "double");
   58.43 -		TYPE_SIMPLE_NAMES = Collections.unmodifiableMap(m);
   58.44 -	}
   58.45 -
   58.46 -	public static String getSimpleTypeName(Class type) {
   58.47 -		String name = TYPE_SIMPLE_NAMES.get(type);
   58.48 -		return name == null ? type.getName() : name;
   58.49 -	}
   58.50 -
   58.51 -	public static final String COLORFUL = "color";
   58.52 -	public static final String COLORFUL_DESCRIPTION = "whether the output should be printed in color (ANSI Escape Sequences)";
   58.53 -}
    59.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/FakeSqlArray.java	Mon Mar 04 17:06:42 2019 +0100
    59.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    59.3 @@ -1,106 +0,0 @@
    59.4 -/**
    59.5 - * SQL-DK
    59.6 - * Copyright © 2014 František Kučera (frantovo.cz)
    59.7 - *
    59.8 - * This program is free software: you can redistribute it and/or modify
    59.9 - * it under the terms of the GNU General Public License as published by
   59.10 - * the Free Software Foundation, either version 3 of the License, or
   59.11 - * (at your option) any later version.
   59.12 - *
   59.13 - * This program is distributed in the hope that it will be useful,
   59.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   59.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   59.16 - * GNU General Public License for more details.
   59.17 - *
   59.18 - * You should have received a copy of the GNU General Public License
   59.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   59.20 - */
   59.21 -package info.globalcode.sql.dk.formatting;
   59.22 -
   59.23 -import info.globalcode.sql.dk.SQLType;
   59.24 -import java.sql.Array;
   59.25 -import java.sql.ResultSet;
   59.26 -import java.sql.SQLException;
   59.27 -import java.util.Map;
   59.28 -
   59.29 -/**
   59.30 - * Fake SQL array, for formatting purposes only
   59.31 - *
   59.32 - * @author Ing. František Kučera (frantovo.cz)
   59.33 - */
   59.34 -public class FakeSqlArray implements Array {
   59.35 -
   59.36 -	private static final UnsupportedOperationException exception = new UnsupportedOperationException("This is just a fake SQL array.");
   59.37 -	private final Object[] data;
   59.38 -	private final SQLType baseType;
   59.39 -
   59.40 -	public FakeSqlArray(Object[] data, SQLType baseType) {
   59.41 -		this.data = data;
   59.42 -		this.baseType = baseType;
   59.43 -	}
   59.44 -
   59.45 -	@Override
   59.46 -	public String toString() {
   59.47 -		StringBuilder string = new StringBuilder();
   59.48 -		for (Object o : data) {
   59.49 -			string.append(o);
   59.50 -			string.append("\n");
   59.51 -		}
   59.52 -		return string.toString();
   59.53 -	}
   59.54 -
   59.55 -	@Override
   59.56 -	public String getBaseTypeName() throws SQLException {
   59.57 -		return baseType.name();
   59.58 -	}
   59.59 -
   59.60 -	@Override
   59.61 -	public int getBaseType() throws SQLException {
   59.62 -		return baseType.getCode();
   59.63 -	}
   59.64 -
   59.65 -	@Override
   59.66 -	public Object getArray() throws SQLException {
   59.67 -		return data;
   59.68 -	}
   59.69 -
   59.70 -	@Override
   59.71 -	public Object getArray(Map<String, Class<?>> map) throws SQLException {
   59.72 -		throw exception;
   59.73 -	}
   59.74 -
   59.75 -	@Override
   59.76 -	public Object getArray(long index, int count) throws SQLException {
   59.77 -		throw exception;
   59.78 -	}
   59.79 -
   59.80 -	@Override
   59.81 -	public Object getArray(long index, int count, Map<String, Class<?>> map) throws SQLException {
   59.82 -		throw exception;
   59.83 -	}
   59.84 -
   59.85 -	@Override
   59.86 -	public ResultSet getResultSet() throws SQLException {
   59.87 -		throw exception;
   59.88 -	}
   59.89 -
   59.90 -	@Override
   59.91 -	public ResultSet getResultSet(Map<String, Class<?>> map) throws SQLException {
   59.92 -		throw exception;
   59.93 -	}
   59.94 -
   59.95 -	@Override
   59.96 -	public ResultSet getResultSet(long index, int count) throws SQLException {
   59.97 -		throw exception;
   59.98 -	}
   59.99 -
  59.100 -	@Override
  59.101 -	public ResultSet getResultSet(long index, int count, Map<String, Class<?>> map) throws SQLException {
  59.102 -		throw exception;
  59.103 -	}
  59.104 -
  59.105 -	@Override
  59.106 -	public void free() throws SQLException {
  59.107 -		throw exception;
  59.108 -	}
  59.109 -}
    60.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/Formatter.java	Mon Mar 04 17:06:42 2019 +0100
    60.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    60.3 @@ -1,67 +0,0 @@
    60.4 -/**
    60.5 - * SQL-DK
    60.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    60.7 - *
    60.8 - * This program is free software: you can redistribute it and/or modify
    60.9 - * it under the terms of the GNU General Public License as published by
   60.10 - * the Free Software Foundation, either version 3 of the License, or
   60.11 - * (at your option) any later version.
   60.12 - *
   60.13 - * This program is distributed in the hope that it will be useful,
   60.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   60.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   60.16 - * GNU General Public License for more details.
   60.17 - *
   60.18 - * You should have received a copy of the GNU General Public License
   60.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   60.20 - */
   60.21 -package info.globalcode.sql.dk.formatting;
   60.22 -
   60.23 -import info.globalcode.sql.dk.Parameter;
   60.24 -import info.globalcode.sql.dk.configuration.DatabaseDefinition;
   60.25 -import java.util.List;
   60.26 -
   60.27 -/**
   60.28 - * The formatter is responsible for printing the result sets and/or updates result (count of
   60.29 - * inserted/updated rows). The formatter can produce output in arbitrary format – text, some markup
   60.30 - * or even binary data.
   60.31 - *
   60.32 - * @author Ing. František Kučera (frantovo.cz)
   60.33 - */
   60.34 -public interface Formatter extends AutoCloseable {
   60.35 -
   60.36 -	void writeStartBatch();
   60.37 -
   60.38 -	void writeStartDatabase(DatabaseDefinition databaseDefinition);
   60.39 -
   60.40 -	void writeEndDatabase();
   60.41 -
   60.42 -	void writeStartStatement();
   60.43 -
   60.44 -	void writeEndStatement();
   60.45 -
   60.46 -	void writeQuery(String sql);
   60.47 -
   60.48 -	void writeParameters(List<? extends Parameter> parameters);
   60.49 -
   60.50 -	void writeStartResultSet(ColumnsHeader header);
   60.51 -
   60.52 -	void writeEndResultSet();
   60.53 -
   60.54 -	void writeStartRow();
   60.55 -
   60.56 -	void writeColumnValue(Object value);
   60.57 -
   60.58 -	void writeEndRow();
   60.59 -
   60.60 -	void writeUpdatesResult(int updatedRowsCount);
   60.61 -
   60.62 -	void writeEndBatch();
   60.63 -
   60.64 -	/**
   60.65 -	 * If an error occurs (e.g. lost connection during result set reading) this method will be
   60.66 -	 * called even if there was no {@linkplain #writeEndBach()}.
   60.67 -	 */
   60.68 -	@Override
   60.69 -	void close() throws FormatterException;
   60.70 -}
    61.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/FormatterContext.java	Mon Mar 04 17:06:42 2019 +0100
    61.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    61.3 @@ -1,49 +0,0 @@
    61.4 -/**
    61.5 - * SQL-DK
    61.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    61.7 - *
    61.8 - * This program is free software: you can redistribute it and/or modify
    61.9 - * it under the terms of the GNU General Public License as published by
   61.10 - * the Free Software Foundation, either version 3 of the License, or
   61.11 - * (at your option) any later version.
   61.12 - *
   61.13 - * This program is distributed in the hope that it will be useful,
   61.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   61.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   61.16 - * GNU General Public License for more details.
   61.17 - *
   61.18 - * You should have received a copy of the GNU General Public License
   61.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   61.20 - */
   61.21 -package info.globalcode.sql.dk.formatting;
   61.22 -
   61.23 -import info.globalcode.sql.dk.configuration.Properties;
   61.24 -import java.io.OutputStream;
   61.25 -
   61.26 -/**
   61.27 - * To be passed from the SQL-DK core to the formatter.
   61.28 - *
   61.29 - * @author Ing. František Kučera (frantovo.cz)
   61.30 - */
   61.31 -public class FormatterContext {
   61.32 -
   61.33 -	private OutputStream outputStream;
   61.34 -	private Properties properties;
   61.35 -
   61.36 -	public FormatterContext(OutputStream outputStream, Properties properties) {
   61.37 -		this.outputStream = outputStream;
   61.38 -		this.properties = properties;
   61.39 -	}
   61.40 -
   61.41 -	public OutputStream getOutputStream() {
   61.42 -		return outputStream;
   61.43 -	}
   61.44 -
   61.45 -	public Properties getProperties() {
   61.46 -		return properties;
   61.47 -	}
   61.48 -
   61.49 -	public void setProperties(Properties properties) {
   61.50 -		this.properties = properties;
   61.51 -	}
   61.52 -}
    62.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/FormatterException.java	Mon Mar 04 17:06:42 2019 +0100
    62.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    62.3 @@ -1,42 +0,0 @@
    62.4 -/**
    62.5 - * SQL-DK
    62.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    62.7 - *
    62.8 - * This program is free software: you can redistribute it and/or modify
    62.9 - * it under the terms of the GNU General Public License as published by
   62.10 - * the Free Software Foundation, either version 3 of the License, or
   62.11 - * (at your option) any later version.
   62.12 - *
   62.13 - * This program is distributed in the hope that it will be useful,
   62.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   62.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   62.16 - * GNU General Public License for more details.
   62.17 - *
   62.18 - * You should have received a copy of the GNU General Public License
   62.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   62.20 - */
   62.21 -package info.globalcode.sql.dk.formatting;
   62.22 -
   62.23 -import info.globalcode.sql.dk.DKException;
   62.24 -
   62.25 -/**
   62.26 - *
   62.27 - * @author Ing. František Kučera (frantovo.cz)
   62.28 - */
   62.29 -public class FormatterException extends DKException {
   62.30 -
   62.31 -	public FormatterException() {
   62.32 -	}
   62.33 -
   62.34 -	public FormatterException(String message) {
   62.35 -		super(message);
   62.36 -	}
   62.37 -
   62.38 -	public FormatterException(Throwable cause) {
   62.39 -		super(cause);
   62.40 -	}
   62.41 -
   62.42 -	public FormatterException(String message, Throwable cause) {
   62.43 -		super(message, cause);
   62.44 -	}
   62.45 -}
    63.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/SilentFormatter.java	Mon Mar 04 17:06:42 2019 +0100
    63.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    63.3 @@ -1,33 +0,0 @@
    63.4 -/**
    63.5 - * SQL-DK
    63.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    63.7 - *
    63.8 - * This program is free software: you can redistribute it and/or modify
    63.9 - * it under the terms of the GNU General Public License as published by
   63.10 - * the Free Software Foundation, either version 3 of the License, or
   63.11 - * (at your option) any later version.
   63.12 - *
   63.13 - * This program is distributed in the hope that it will be useful,
   63.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   63.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   63.16 - * GNU General Public License for more details.
   63.17 - *
   63.18 - * You should have received a copy of the GNU General Public License
   63.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   63.20 - */
   63.21 -package info.globalcode.sql.dk.formatting;
   63.22 -
   63.23 -/**
   63.24 - * Does not output anything, can be used instead of
   63.25 - * <code>/dev/null</code>.
   63.26 - *
   63.27 - * @author Ing. František Kučera (frantovo.cz)
   63.28 - */
   63.29 -public class SilentFormatter extends AbstractFormatter {
   63.30 -	
   63.31 -	public static final String NAME = "silent"; // bash-completion:formatter
   63.32 -
   63.33 -	public SilentFormatter(FormatterContext formatterContext) {
   63.34 -		super(formatterContext);
   63.35 -	}
   63.36 -}
    64.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/SingleRecordFormatter.java	Mon Mar 04 17:06:42 2019 +0100
    64.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    64.3 @@ -1,105 +0,0 @@
    64.4 -/**
    64.5 - * SQL-DK
    64.6 - * Copyright © 2015 František Kučera (frantovo.cz)
    64.7 - *
    64.8 - * This program is free software: you can redistribute it and/or modify
    64.9 - * it under the terms of the GNU General Public License as published by
   64.10 - * the Free Software Foundation, either version 3 of the License, or
   64.11 - * (at your option) any later version.
   64.12 - *
   64.13 - * This program is distributed in the hope that it will be useful,
   64.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   64.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   64.16 - * GNU General Public License for more details.
   64.17 - *
   64.18 - * You should have received a copy of the GNU General Public License
   64.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   64.20 - */
   64.21 -package info.globalcode.sql.dk.formatting;
   64.22 -
   64.23 -import info.globalcode.sql.dk.ColorfulPrintWriter;
   64.24 -import info.globalcode.sql.dk.Functions;
   64.25 -import info.globalcode.sql.dk.configuration.PropertyDeclaration;
   64.26 -import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
   64.27 -import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
   64.28 -
   64.29 -/**
   64.30 - * Formatter intended for printing one record (or few records) with many columns.
   64.31 - * Prints each colum name and its value on separate line.
   64.32 - *
   64.33 - * @author Ing. František Kučera (frantovo.cz)
   64.34 - */
   64.35 -@PropertyDeclaration(name = COLORFUL, defaultValue = "true", type = Boolean.class, description = COLORFUL_DESCRIPTION)
   64.36 -public class SingleRecordFormatter extends AbstractFormatter {
   64.37 -
   64.38 -	public static final String NAME = "record"; // bash-completion:formatter
   64.39 -	private final ColorfulPrintWriter out;
   64.40 -	private boolean firstResult = true;
   64.41 -
   64.42 -	public SingleRecordFormatter(FormatterContext formatterContext) {
   64.43 -		super(formatterContext);
   64.44 -		out = new ColorfulPrintWriter(formatterContext.getOutputStream());
   64.45 -		out.setColorful(formatterContext.getProperties().getBoolean(COLORFUL, true));
   64.46 -	}
   64.47 -
   64.48 -	@Override
   64.49 -	public void writeStartResultSet(ColumnsHeader header) {
   64.50 -		super.writeStartResultSet(header);
   64.51 -		printResultSeparator();
   64.52 -	}
   64.53 -
   64.54 -	@Override
   64.55 -	public void writeStartRow() {
   64.56 -		super.writeStartRow();
   64.57 -		printRecordSeparator();
   64.58 -		out.print(ColorfulPrintWriter.TerminalColor.Red, "Record: ");
   64.59 -		out.print(getCurrentRowCount());
   64.60 -		println();
   64.61 -	}
   64.62 -
   64.63 -	@Override
   64.64 -	public void writeColumnValue(Object value) {
   64.65 -		super.writeColumnValue(value);
   64.66 -		String columnName = getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getLabel();
   64.67 -		out.print(ColorfulPrintWriter.TerminalColor.Green, columnName + ": ");
   64.68 -		Functions.printValueWithWhitespaceReplaced(out, toString(value), null, ColorfulPrintWriter.TerminalColor.Red);
   64.69 -		println();
   64.70 -	}
   64.71 -
   64.72 -	private static String toString(Object value) {
   64.73 -		return String.valueOf(value);
   64.74 -	}
   64.75 -
   64.76 -	@Override
   64.77 -	public void writeUpdatesResult(int updatedRowsCount) {
   64.78 -		super.writeUpdatesResult(updatedRowsCount);
   64.79 -		printResultSeparator();
   64.80 -		out.print(ColorfulPrintWriter.TerminalColor.Red, "Updated records: ");
   64.81 -		out.println(updatedRowsCount);
   64.82 -		printBellAndFlush();
   64.83 -	}
   64.84 -
   64.85 -	private void printBellAndFlush() {
   64.86 -		out.bell();
   64.87 -		out.flush();
   64.88 -	}
   64.89 -
   64.90 -	private void println() {
   64.91 -		out.println();
   64.92 -		printBellAndFlush();
   64.93 -	}
   64.94 -
   64.95 -	private void printRecordSeparator() {
   64.96 -		if (getCurrentRowCount() > 1) {
   64.97 -			println();
   64.98 -		}
   64.99 -	}
  64.100 -
  64.101 -	private void printResultSeparator() {
  64.102 -		if (firstResult) {
  64.103 -			firstResult = false;
  64.104 -		} else {
  64.105 -			println();
  64.106 -		}
  64.107 -	}
  64.108 -}
    65.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/SingleValueFormatter.java	Mon Mar 04 17:06:42 2019 +0100
    65.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    65.3 @@ -1,52 +0,0 @@
    65.4 -/**
    65.5 - * SQL-DK
    65.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    65.7 - *
    65.8 - * This program is free software: you can redistribute it and/or modify
    65.9 - * it under the terms of the GNU General Public License as published by
   65.10 - * the Free Software Foundation, either version 3 of the License, or
   65.11 - * (at your option) any later version.
   65.12 - *
   65.13 - * This program is distributed in the hope that it will be useful,
   65.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   65.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   65.16 - * GNU General Public License for more details.
   65.17 - *
   65.18 - * You should have received a copy of the GNU General Public License
   65.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   65.20 - */
   65.21 -package info.globalcode.sql.dk.formatting;
   65.22 -
   65.23 -import java.io.PrintWriter;
   65.24 -
   65.25 -/**
   65.26 - * Prints just the value without any formatting. If the result set contains multiple records or
   65.27 - * columns, the values are simply concatenate without any separators. If updates result is returned,
   65.28 - * the updated records count is printed.
   65.29 - *
   65.30 - * @author Ing. František Kučera (frantovo.cz)
   65.31 - */
   65.32 -public class SingleValueFormatter extends AbstractFormatter {
   65.33 -
   65.34 -	public static final String NAME = "single"; // bash-completion:formatter
   65.35 -	private PrintWriter out;
   65.36 -
   65.37 -	public SingleValueFormatter(FormatterContext formatterContext) {
   65.38 -		super(formatterContext);
   65.39 -		this.out = new PrintWriter(formatterContext.getOutputStream());
   65.40 -	}
   65.41 -
   65.42 -	@Override
   65.43 -	public void writeColumnValue(Object value) {
   65.44 -		super.writeColumnValue(value);
   65.45 -		out.print(String.valueOf(value));
   65.46 -		out.flush();
   65.47 -	}
   65.48 -
   65.49 -	@Override
   65.50 -	public void writeUpdatesResult(int updatedRowsCount) {
   65.51 -		super.writeUpdatesResult(updatedRowsCount);
   65.52 -		out.print(updatedRowsCount);
   65.53 -		out.flush();
   65.54 -	}
   65.55 -}
    66.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularFormatter.java	Mon Mar 04 17:06:42 2019 +0100
    66.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    66.3 @@ -1,308 +0,0 @@
    66.4 -/**
    66.5 - * SQL-DK
    66.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    66.7 - *
    66.8 - * This program is free software: you can redistribute it and/or modify
    66.9 - * it under the terms of the GNU General Public License as published by
   66.10 - * the Free Software Foundation, either version 3 of the License, or
   66.11 - * (at your option) any later version.
   66.12 - *
   66.13 - * This program is distributed in the hope that it will be useful,
   66.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   66.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   66.16 - * GNU General Public License for more details.
   66.17 - *
   66.18 - * You should have received a copy of the GNU General Public License
   66.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   66.20 - */
   66.21 -package info.globalcode.sql.dk.formatting;
   66.22 -
   66.23 -import info.globalcode.sql.dk.ColorfulPrintWriter;
   66.24 -import static info.globalcode.sql.dk.ColorfulPrintWriter.*;
   66.25 -import info.globalcode.sql.dk.Functions;
   66.26 -import static info.globalcode.sql.dk.Functions.lpad;
   66.27 -import static info.globalcode.sql.dk.Functions.rpad;
   66.28 -import static info.globalcode.sql.dk.Functions.repeat;
   66.29 -import info.globalcode.sql.dk.configuration.PropertyDeclaration;
   66.30 -import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
   66.31 -import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
   66.32 -import java.sql.SQLException;
   66.33 -import java.sql.SQLXML;
   66.34 -import java.util.List;
   66.35 -import java.util.logging.Level;
   66.36 -import java.util.logging.Logger;
   66.37 -
   66.38 -/**
   66.39 - * <p>
   66.40 - * Prints human-readable output – tables of result sets and text messages with update counts.
   66.41 - * </p>
   66.42 - *
   66.43 - * <p>
   66.44 - * Longer values might break the table – overflow the cells – see alternative tabular formatters and
   66.45 - * the {@linkplain #PROPERTY_TRIM} property.
   66.46 - * </p>
   66.47 - *
   66.48 - * @author Ing. František Kučera (frantovo.cz)
   66.49 - * @see TabularPrefetchingFormatter
   66.50 - * @see TabularWrappingFormatter
   66.51 - */
   66.52 -@PropertyDeclaration(name = COLORFUL, defaultValue = "true", type = Boolean.class, description = COLORFUL_DESCRIPTION)
   66.53 -@PropertyDeclaration(name = TabularFormatter.PROPERTY_ASCII, defaultValue = "false", type = Boolean.class, description = "whether to use ASCII table borders instead of unicode ones")
   66.54 -@PropertyDeclaration(name = TabularFormatter.PROPERTY_TRIM, defaultValue = "false", type = Boolean.class, description = "whether to trim the values to fit the column width")
   66.55 -@PropertyDeclaration(name = TabularFormatter.PROPERTY_HEADER_TYPE, defaultValue = "true", type = Boolean.class, description = "whether to print data types in column headers")
   66.56 -public class TabularFormatter extends AbstractFormatter {
   66.57 -
   66.58 -	private static final Logger log = Logger.getLogger(TabularFormatter.class.getName());
   66.59 -	public static final String NAME = "tabular"; // bash-completion:formatter
   66.60 -	private static final String HEADER_TYPE_PREFIX = " (";
   66.61 -	private static final String HEADER_TYPE_SUFFIX = ")";
   66.62 -	public static final String PROPERTY_ASCII = "ascii";
   66.63 -	public static final String PROPERTY_TRIM = "trim";
   66.64 -	public static final String PROPERTY_HEADER_TYPE = "headerTypes";
   66.65 -	protected ColorfulPrintWriter out;
   66.66 -	private boolean firstResult = true;
   66.67 -	private int[] columnWidth;
   66.68 -	/**
   66.69 -	 * use ASCII borders instead of unicode ones
   66.70 -	 */
   66.71 -	private final boolean asciiNostalgia;
   66.72 -	/**
   66.73 -	 * Trim values if they are longer than cell size
   66.74 -	 */
   66.75 -	private final boolean trimValues;
   66.76 -	/**
   66.77 -	 * Print data type of each column in the header
   66.78 -	 */
   66.79 -	private final boolean printHeaderTypes;
   66.80 -
   66.81 -	public TabularFormatter(FormatterContext formatterContext) {
   66.82 -		super(formatterContext);
   66.83 -		out = new ColorfulPrintWriter(formatterContext.getOutputStream());
   66.84 -		asciiNostalgia = formatterContext.getProperties().getBoolean(PROPERTY_ASCII, false);
   66.85 -		trimValues = formatterContext.getProperties().getBoolean(PROPERTY_TRIM, false);
   66.86 -		printHeaderTypes = formatterContext.getProperties().getBoolean(PROPERTY_HEADER_TYPE, true);
   66.87 -		out.setColorful(formatterContext.getProperties().getBoolean(COLORFUL, true));
   66.88 -	}
   66.89 -
   66.90 -	@Override
   66.91 -	public void writeStartResultSet(ColumnsHeader header) {
   66.92 -		super.writeStartResultSet(header);
   66.93 -		printResultSeparator();
   66.94 -
   66.95 -		initColumnWidths(header.getColumnCount());
   66.96 -
   66.97 -		printTableIndent();
   66.98 -		printTableBorder("╭");
   66.99 -
  66.100 -		List<ColumnDescriptor> columnDescriptors = header.getColumnDescriptors();
  66.101 -
  66.102 -		for (ColumnDescriptor cd : columnDescriptors) {
  66.103 -			// padding: make header cell at least same width as data cells in this column
  66.104 -			int typeWidth = printHeaderTypes ? cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length() : 0;
  66.105 -			cd.setLabel(rpad(cd.getLabel(), getColumnWidth(cd.getColumnNumber()) - typeWidth));
  66.106 -			updateColumnWidth(cd.getColumnNumber(), cd.getLabel().length() + typeWidth);
  66.107 -
  66.108 -			if (!cd.isFirstColumn()) {
  66.109 -				printTableBorder("┬");
  66.110 -			}
  66.111 -			printTableBorder(repeat('─', getColumnWidth(cd.getColumnNumber()) + 2));
  66.112 -		}
  66.113 -		printTableBorder("╮");
  66.114 -		out.println();
  66.115 -
  66.116 -		for (ColumnDescriptor cd : columnDescriptors) {
  66.117 -			if (cd.isFirstColumn()) {
  66.118 -				printTableIndent();
  66.119 -				printTableBorder("│ ");
  66.120 -			} else {
  66.121 -				printTableBorder(" │ ");
  66.122 -			}
  66.123 -			out.print(TerminalStyle.Bright, cd.getLabel());
  66.124 -			if (printHeaderTypes) {
  66.125 -				out.print(HEADER_TYPE_PREFIX);
  66.126 -				out.print(cd.getTypeName());
  66.127 -				out.print(HEADER_TYPE_SUFFIX);
  66.128 -			}
  66.129 -			if (cd.isLastColumn()) {
  66.130 -				printTableBorder(" │");
  66.131 -			}
  66.132 -		}
  66.133 -		out.println();
  66.134 -
  66.135 -		printTableIndent();
  66.136 -		printTableBorder("├");
  66.137 -		for (int i = 1; i <= header.getColumnCount(); i++) {
  66.138 -			if (i > 1) {
  66.139 -				printTableBorder("┼");
  66.140 -			}
  66.141 -			printTableBorder(repeat('─', getColumnWidth(i) + 2));
  66.142 -		}
  66.143 -		printTableBorder("┤");
  66.144 -		out.println();
  66.145 -
  66.146 -		out.flush();
  66.147 -	}
  66.148 -
  66.149 -	/**
  66.150 -	 * Must be called before {@linkplain #updateColumnWidth(int, int)} and
  66.151 -	 * {@linkplain #getColumnWidth(int)} for each result set.
  66.152 -	 *
  66.153 -	 * @param columnCount number of columns in current result set
  66.154 -	 */
  66.155 -	protected void initColumnWidths(int columnCount) {
  66.156 -		if (columnWidth == null) {
  66.157 -			columnWidth = new int[columnCount];
  66.158 -		}
  66.159 -	}
  66.160 -
  66.161 -	protected void cleanColumnWidths() {
  66.162 -		columnWidth = null;
  66.163 -	}
  66.164 -
  66.165 -	@Override
  66.166 -	public void writeColumnValue(Object value) {
  66.167 -		super.writeColumnValue(value);
  66.168 -		writeColumnValueInternal(value);
  66.169 -	}
  66.170 -
  66.171 -	protected void writeColumnValueInternal(Object value) {
  66.172 -
  66.173 -		if (isCurrentColumnFirst()) {
  66.174 -			printTableIndent();
  66.175 -			printTableBorder("│ ");
  66.176 -		} else {
  66.177 -			printTableBorder(" │ ");
  66.178 -		}
  66.179 -
  66.180 -		printValueWithWhitespaceReplaced(toString(value));
  66.181 -
  66.182 -		if (isCurrentColumnLast()) {
  66.183 -			printTableBorder(" │");
  66.184 -		}
  66.185 -
  66.186 -	}
  66.187 -
  66.188 -	protected void printValueWithWhitespaceReplaced(String text) {
  66.189 -		Functions.printValueWithWhitespaceReplaced(out, text, TerminalColor.Cyan, TerminalColor.Red);
  66.190 -	}
  66.191 -
  66.192 -	protected int getColumnWidth(int columnNumber) {
  66.193 -		return columnWidth[columnNumber - 1];
  66.194 -	}
  66.195 -
  66.196 -	private void setColumnWidth(int columnNumber, int width) {
  66.197 -		columnWidth[columnNumber - 1] = width;
  66.198 -	}
  66.199 -
  66.200 -	protected void updateColumnWidth(int columnNumber, int width) {
  66.201 -		int oldWidth = getColumnWidth(columnNumber);
  66.202 -		setColumnWidth(columnNumber, Math.max(width, oldWidth));
  66.203 -
  66.204 -	}
  66.205 -
  66.206 -	protected String toString(Object value) {
  66.207 -		final int width = getColumnWidth(getCurrentColumnsCount());
  66.208 -		String result;
  66.209 -		if (value instanceof Number || value instanceof Boolean) {
  66.210 -			result = lpad(String.valueOf(value), width);
  66.211 -		} else {
  66.212 -			if (value instanceof SQLXML) {
  66.213 -				// TODO: move to a common method, share with other formatters
  66.214 -				try {
  66.215 -					value = ((SQLXML) value).getString();
  66.216 -				} catch (SQLException e) {
  66.217 -					log.log(Level.SEVERE, "Unable to format XML", e);
  66.218 -				}
  66.219 -			}
  66.220 -
  66.221 -			result = rpad(String.valueOf(value), width);
  66.222 -		}
  66.223 -		// ?	value = (boolean) value ? "✔" : "✗";
  66.224 -
  66.225 -		if (trimValues && result.length() > width) {
  66.226 -			result = result.substring(0, width - 1) + "…";
  66.227 -		}
  66.228 -
  66.229 -		return result;
  66.230 -	}
  66.231 -
  66.232 -	@Override
  66.233 -	public void writeEndRow() {
  66.234 -		super.writeEndRow();
  66.235 -		writeEndRowInternal();
  66.236 -	}
  66.237 -
  66.238 -	public void writeEndRowInternal() {
  66.239 -		out.println();
  66.240 -		out.flush();
  66.241 -	}
  66.242 -
  66.243 -	@Override
  66.244 -	public void writeEndResultSet() {
  66.245 -		int columnCount = getCurrentColumnsHeader().getColumnCount();
  66.246 -		super.writeEndResultSet();
  66.247 -
  66.248 -		printTableIndent();
  66.249 -		printTableBorder("╰");
  66.250 -		for (int i = 1; i <= columnCount; i++) {
  66.251 -			if (i > 1) {
  66.252 -				printTableBorder("┴");
  66.253 -			}
  66.254 -			printTableBorder(repeat('─', getColumnWidth(i) + 2));
  66.255 -		}
  66.256 -		printTableBorder("╯");
  66.257 -		out.println();
  66.258 -
  66.259 -		cleanColumnWidths();
  66.260 -
  66.261 -		out.print(TerminalColor.Yellow, "Record count: ");
  66.262 -		out.println(getCurrentRowCount());
  66.263 -		out.bell();
  66.264 -		out.flush();
  66.265 -	}
  66.266 -
  66.267 -	@Override
  66.268 -	public void writeUpdatesResult(int updatedRowsCount) {
  66.269 -		super.writeUpdatesResult(updatedRowsCount);
  66.270 -		printResultSeparator();
  66.271 -		out.print(TerminalColor.Red, "Updated records: ");
  66.272 -		out.println(updatedRowsCount);
  66.273 -		out.bell();
  66.274 -		out.flush();
  66.275 -	}
  66.276 -
  66.277 -	@Override
  66.278 -	public void writeEndDatabase() {
  66.279 -		super.writeEndDatabase();
  66.280 -		out.flush();
  66.281 -	}
  66.282 -
  66.283 -	private void printResultSeparator() {
  66.284 -		if (firstResult) {
  66.285 -			firstResult = false;
  66.286 -		} else {
  66.287 -			out.println();
  66.288 -		}
  66.289 -	}
  66.290 -
  66.291 -	protected void printTableBorder(String border) {
  66.292 -		if (asciiNostalgia) {
  66.293 -			border = border.replaceAll("─", "-");
  66.294 -			border = border.replaceAll("│", "|");
  66.295 -			border = border.replaceAll("[╭┬╮├┼┤╰┴╯]", "+");
  66.296 -		}
  66.297 -
  66.298 -		out.print(TerminalColor.Green, border);
  66.299 -	}
  66.300 -
  66.301 -	protected void printTableIndent() {
  66.302 -		out.print(" ");
  66.303 -	}
  66.304 -
  66.305 -	/**
  66.306 -	 * @return whether should print only ASCII characters instead of unlimited Unicode.
  66.307 -	 */
  66.308 -	protected boolean isAsciiNostalgia() {
  66.309 -		return asciiNostalgia;
  66.310 -	}
  66.311 -}
    67.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java	Mon Mar 04 17:06:42 2019 +0100
    67.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    67.3 @@ -1,119 +0,0 @@
    67.4 -/**
    67.5 - * SQL-DK
    67.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    67.7 - *
    67.8 - * This program is free software: you can redistribute it and/or modify
    67.9 - * it under the terms of the GNU General Public License as published by
   67.10 - * the Free Software Foundation, either version 3 of the License, or
   67.11 - * (at your option) any later version.
   67.12 - *
   67.13 - * This program is distributed in the hope that it will be useful,
   67.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   67.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   67.16 - * GNU General Public License for more details.
   67.17 - *
   67.18 - * You should have received a copy of the GNU General Public License
   67.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   67.20 - */
   67.21 -package info.globalcode.sql.dk.formatting;
   67.22 -
   67.23 -import java.util.ArrayList;
   67.24 -import java.util.List;
   67.25 -
   67.26 -/**
   67.27 - * <p>
   67.28 - * Prefetches whole result set and computes column widths. Whole table is flushed at once in
   67.29 - * {@linkplain #writeEndResultSet()}.
   67.30 - * </p>
   67.31 - *
   67.32 - * <p>
   67.33 - * Long values will not overflow the cells, but whole result set must be loaded into the memory.
   67.34 - * </p>
   67.35 - *
   67.36 - * @author Ing. František Kučera (frantovo.cz)
   67.37 - */
   67.38 -public class TabularPrefetchingFormatter extends TabularFormatter {
   67.39 -
   67.40 -	public static final String NAME = "tabular-prefetching"; // bash-completion:formatter
   67.41 -	private ColumnsHeader currentHeader;
   67.42 -	private List<Object[]> currentResultSet;
   67.43 -	private Object[] currentRow;
   67.44 -	private int currentColumnsCount;
   67.45 -	private boolean prefetchDone = false;
   67.46 -
   67.47 -	public TabularPrefetchingFormatter(FormatterContext formatterContext) {
   67.48 -		super(formatterContext);
   67.49 -	}
   67.50 -
   67.51 -	@Override
   67.52 -	protected int getCurrentColumnsCount() {
   67.53 -		if (prefetchDone) {
   67.54 -			return super.getCurrentColumnsCount();
   67.55 -		} else {
   67.56 -			return currentColumnsCount;
   67.57 -		}
   67.58 -	}
   67.59 -
   67.60 -	@Override
   67.61 -	public void writeStartResultSet(ColumnsHeader header) {
   67.62 -		currentResultSet = new ArrayList<>();
   67.63 -		currentHeader = header;
   67.64 -		initColumnWidths(header.getColumnCount());
   67.65 -	}
   67.66 -
   67.67 -	@Override
   67.68 -	public void writeStartRow() {
   67.69 -		currentRow = new Object[currentHeader.getColumnCount()];
   67.70 -		currentResultSet.add(currentRow);
   67.71 -		currentColumnsCount = 0;
   67.72 -	}
   67.73 -
   67.74 -	@Override
   67.75 -	public void writeColumnValue(Object value) {
   67.76 -		currentRow[currentColumnsCount] = value;
   67.77 -		currentColumnsCount++;
   67.78 -		String textRepresentation = toString(value);
   67.79 -		/** TODO: count only printable characters (currently not an issue) */
   67.80 -		updateColumnWidth(currentColumnsCount, textRepresentation.length());
   67.81 -	}
   67.82 -
   67.83 -	@Override
   67.84 -	public void writeEndRow() {
   67.85 -		// do nothing
   67.86 -	}
   67.87 -
   67.88 -	@Override
   67.89 -	public void writeEndResultSet() {
   67.90 -		prefetchDone = true;
   67.91 -
   67.92 -		postprocessPrefetchedResultSet(currentHeader, currentResultSet);
   67.93 -
   67.94 -		super.writeStartResultSet(currentHeader);
   67.95 -
   67.96 -		for (Object[] row : currentResultSet) {
   67.97 -			super.writeStartRow();
   67.98 -			for (Object cell : row) {
   67.99 -				super.writeColumnValue(cell);
  67.100 -			}
  67.101 -			super.writeEndRow();
  67.102 -		}
  67.103 -
  67.104 -		currentColumnsCount = 0;
  67.105 -		currentHeader = null;
  67.106 -		currentRow = null;
  67.107 -		currentResultSet = null;
  67.108 -		super.writeEndResultSet();
  67.109 -		prefetchDone = false;
  67.110 -	}
  67.111 -
  67.112 -	/**
  67.113 -	 * Optional post-processing – override in sub-classes if needed.
  67.114 -	 * Don't forget to {@linkplain #updateColumnWidth(int, int)}
  67.115 -	 *
  67.116 -	 * @param currentHeader
  67.117 -	 * @param currentResultSet
  67.118 -	 */
  67.119 -	protected void postprocessPrefetchedResultSet(ColumnsHeader currentHeader, List<Object[]> currentResultSet) {
  67.120 -	}
  67.121 -
  67.122 -}
    68.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularWrappingFormatter.java	Mon Mar 04 17:06:42 2019 +0100
    68.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    68.3 @@ -1,116 +0,0 @@
    68.4 -/**
    68.5 - * SQL-DK
    68.6 - * Copyright © 2014 František Kučera (frantovo.cz)
    68.7 - *
    68.8 - * This program is free software: you can redistribute it and/or modify
    68.9 - * it under the terms of the GNU General Public License as published by
   68.10 - * the Free Software Foundation, either version 3 of the License, or
   68.11 - * (at your option) any later version.
   68.12 - *
   68.13 - * This program is distributed in the hope that it will be useful,
   68.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   68.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   68.16 - * GNU General Public License for more details.
   68.17 - *
   68.18 - * You should have received a copy of the GNU General Public License
   68.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   68.20 - */
   68.21 -package info.globalcode.sql.dk.formatting;
   68.22 -
   68.23 -import info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
   68.24 -import java.util.ArrayList;
   68.25 -import java.util.List;
   68.26 -import static info.globalcode.sql.dk.Functions.lpad;
   68.27 -import static info.globalcode.sql.dk.Functions.rpad;
   68.28 -import static info.globalcode.sql.dk.Functions.repeat;
   68.29 -
   68.30 -/**
   68.31 - * Longer values are line-wrapped – the cell then contains multiple lines. Marks are added to
   68.32 - * signalize forced line ends (not present in original data).
   68.33 - *
   68.34 - * @author Ing. František Kučera (frantovo.cz)
   68.35 - */
   68.36 -public class TabularWrappingFormatter extends TabularFormatter {
   68.37 -
   68.38 -	public static final String NAME = "tabular-wrapping"; // bash-completion:formatter
   68.39 -	private List<String[]> currentRow;
   68.40 -
   68.41 -	public TabularWrappingFormatter(FormatterContext formatterContext) {
   68.42 -		super(formatterContext);
   68.43 -	}
   68.44 -
   68.45 -	@Override
   68.46 -	public void writeStartResultSet(ColumnsHeader header) {
   68.47 -		super.writeStartResultSet(header);
   68.48 -		currentRow = new ArrayList<>(header.getColumnCount());
   68.49 -	}
   68.50 -
   68.51 -	@Override
   68.52 -	protected void writeColumnValueInternal(Object value) {
   68.53 -		boolean rightAlign = value instanceof Number || value instanceof Boolean;
   68.54 -		String valueString = String.valueOf(value);
   68.55 -		int columnWidth = getColumnWidth(getCurrentColumnsCount()) - 1;  // -1 = space for new line symbol
   68.56 -		currentRow.add(wrapLines(valueString, columnWidth, rightAlign));
   68.57 -	}
   68.58 -
   68.59 -	@Override
   68.60 -	public void writeEndRow() {
   68.61 -		super.writeEndRow();
   68.62 -
   68.63 -		int wrappedLine = 0;
   68.64 -		boolean hasMoreWrappedLines;
   68.65 -
   68.66 -		do {
   68.67 -			hasMoreWrappedLines = false;
   68.68 -			for (int i = 0; i < currentRow.size(); i++) {
   68.69 -				if (i == 0) {
   68.70 -					printTableIndent();
   68.71 -					printTableBorder("│ ");
   68.72 -				} else {
   68.73 -					printTableBorder(" │ ");
   68.74 -				}
   68.75 -				String[] columnArray = currentRow.get(i);
   68.76 -				if (wrappedLine < columnArray.length) {
   68.77 -					printValueWithWhitespaceReplaced(columnArray[wrappedLine]);
   68.78 -
   68.79 -					if (wrappedLine < columnArray.length - 1) {
   68.80 -						out.print(TerminalColor.Red, "↩");
   68.81 -						hasMoreWrappedLines = true;
   68.82 -					} else {
   68.83 -						out.print(" ");
   68.84 -					}
   68.85 -
   68.86 -				} else {
   68.87 -					out.print(repeat(' ', getColumnWidth(i + 1)));
   68.88 -				}
   68.89 -
   68.90 -				if (i == (currentRow.size() - 1)) {
   68.91 -					printTableBorder(" │");
   68.92 -				}
   68.93 -			}
   68.94 -			out.println();
   68.95 -			out.flush();
   68.96 -			wrappedLine++;
   68.97 -		} while (hasMoreWrappedLines);
   68.98 -
   68.99 -		currentRow.clear();
  68.100 -	}
  68.101 -
  68.102 -	@Override
  68.103 -	public void writeEndRowInternal() {
  68.104 -		// already done – wrapped row ends
  68.105 -	}
  68.106 -
  68.107 -	private static String[] wrapLines(String s, int width, boolean rightAlign) {
  68.108 -		String[] array = new String[(s.length() - 1) / width + 1];
  68.109 -		for (int i = 0; i < array.length; i++) {
  68.110 -			if (i == array.length - 1) {
  68.111 -				String part = s.substring(i * width, s.length());
  68.112 -				array[i] = rightAlign ? lpad(part, width) : rpad(part, width);
  68.113 -			} else {
  68.114 -				array[i] = s.substring(i * width, (i + 1) * width);
  68.115 -			}
  68.116 -		}
  68.117 -		return array;
  68.118 -	}
  68.119 -}
    69.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/TeXFormatter.java	Mon Mar 04 17:06:42 2019 +0100
    69.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    69.3 @@ -1,208 +0,0 @@
    69.4 -/**
    69.5 - * SQL-DK
    69.6 - * Copyright © 2014 František Kučera (frantovo.cz)
    69.7 - *
    69.8 - * This program is free software: you can redistribute it and/or modify
    69.9 - * it under the terms of the GNU General Public License as published by
   69.10 - * the Free Software Foundation, either version 3 of the License, or
   69.11 - * (at your option) any later version.
   69.12 - *
   69.13 - * This program is distributed in the hope that it will be useful,
   69.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   69.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   69.16 - * GNU General Public License for more details.
   69.17 - *
   69.18 - * You should have received a copy of the GNU General Public License
   69.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   69.20 - */
   69.21 -package info.globalcode.sql.dk.formatting;
   69.22 -
   69.23 -import info.globalcode.sql.dk.ColorfulPrintWriter;
   69.24 -import info.globalcode.sql.dk.Constants;
   69.25 -import info.globalcode.sql.dk.configuration.PropertyDeclaration;
   69.26 -import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
   69.27 -import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
   69.28 -import java.util.Collections;
   69.29 -import java.util.HashMap;
   69.30 -import java.util.List;
   69.31 -import java.util.Map;
   69.32 -
   69.33 -/**
   69.34 - * Outputs result sets in (La)TeX format.
   69.35 - *
   69.36 - * @author Ing. František Kučera (frantovo.cz)
   69.37 - */
   69.38 -@PropertyDeclaration(name = COLORFUL, defaultValue = "false", type = Boolean.class, description = COLORFUL_DESCRIPTION)
   69.39 -public class TeXFormatter extends AbstractFormatter {
   69.40 -
   69.41 -	public static final String NAME = "tex"; // bash-completion:formatter
   69.42 -	private static final ColorfulPrintWriter.TerminalColor COMMAND_COLOR = ColorfulPrintWriter.TerminalColor.Magenta;
   69.43 -	private static final ColorfulPrintWriter.TerminalColor OPTIONS_COLOR = ColorfulPrintWriter.TerminalColor.Yellow;
   69.44 -	private static final Map<Character, String> TEX_ESCAPE_MAP;
   69.45 -	private final ColorfulPrintWriter out;
   69.46 -
   69.47 -	static {
   69.48 -		Map<Character, String> replacements = new HashMap<>();
   69.49 -
   69.50 -		replacements.put('\\', "\\textbackslash{}");
   69.51 -		replacements.put('{', "\\{{}");
   69.52 -		replacements.put('}', "\\}{}");
   69.53 -		replacements.put('_', "\\_{}");
   69.54 -		replacements.put('^', "\\textasciicircum{}");
   69.55 -		replacements.put('#', "\\#{}");
   69.56 -		replacements.put('&', "\\&{}");
   69.57 -		replacements.put('$', "\\${}");
   69.58 -		replacements.put('%', "\\%{}");
   69.59 -		replacements.put('~', "\\textasciitilde{}");
   69.60 -		replacements.put('-', "{-}");
   69.61 -
   69.62 -		TEX_ESCAPE_MAP = Collections.unmodifiableMap(replacements);
   69.63 -	}
   69.64 -
   69.65 -	public TeXFormatter(FormatterContext formatterContext) {
   69.66 -		super(formatterContext);
   69.67 -		boolean colorful = formatterContext.getProperties().getBoolean(COLORFUL, false);
   69.68 -		out = new ColorfulPrintWriter(formatterContext.getOutputStream(), false, colorful);
   69.69 -	}
   69.70 -
   69.71 -	@Override
   69.72 -	public void writeStartBatch() {
   69.73 -		super.writeStartBatch();
   69.74 -
   69.75 -		printCommand("documentclass", "a4paper,twoside", "article", true);
   69.76 -		printCommand("usepackage", "T1", "fontenc", true);
   69.77 -		printCommand("usepackage", "utf8x", "inputenc", true);
   69.78 -		printCommand("usepackage", "pdfauthor={" + Constants.WEBSITE + "}, bookmarks=true,unicode,colorlinks=true,linkcolor=black,urlcolor=blue,citecolor=blue", "hyperref", true);
   69.79 -		printBegin("document");
   69.80 -	}
   69.81 -
   69.82 -	@Override
   69.83 -	public void writeEndBatch() {
   69.84 -		super.writeEndBatch();
   69.85 -		printEnd("document");
   69.86 -	}
   69.87 -
   69.88 -	@Override
   69.89 -	public void writeColumnValue(Object value) {
   69.90 -		super.writeColumnValue(value);
   69.91 -		// TODO: arrays, numbers, booleans, nulls etc.:
   69.92 -		out.print(escapeTex(toString(value)));
   69.93 -
   69.94 -		if (!isCurrentColumnLast()) {
   69.95 -			printColumnSeparator();
   69.96 -		}
   69.97 -	}
   69.98 -
   69.99 -	@Override
  69.100 -	public void writeEndRow() {
  69.101 -		super.writeEndRow();
  69.102 -		printEndRow();
  69.103 -	}
  69.104 -
  69.105 -	@Override
  69.106 -	public void writeStartResultSet(ColumnsHeader header) {
  69.107 -		super.writeStartResultSet(header);
  69.108 -		printCommand("begin", null, "tabular", false);
  69.109 -
  69.110 -		List<ColumnDescriptor> columnDescriptors = header.getColumnDescriptors();
  69.111 -
  69.112 -		StringBuilder columnAlignments = new StringBuilder();
  69.113 -		for (ColumnDescriptor cd : columnDescriptors) {
  69.114 -			if (cd.isNumeric() || cd.isBoolean()) {
  69.115 -				columnAlignments.append('r');
  69.116 -			} else {
  69.117 -				columnAlignments.append('l');
  69.118 -			}
  69.119 -		}
  69.120 -
  69.121 -		printCommand(null, null, columnAlignments.toString(), true);
  69.122 -		printCommand("hline", null, null, true);
  69.123 -
  69.124 -		for (ColumnDescriptor cd : columnDescriptors) {
  69.125 -			printCommand("textbf", null, cd.getLabel(), false);
  69.126 -			if (cd.isLastColumn()) {
  69.127 -				printEndRow();
  69.128 -			} else {
  69.129 -				printColumnSeparator();
  69.130 -			}
  69.131 -		}
  69.132 -
  69.133 -		printCommand("hline", null, null, true);
  69.134 -	}
  69.135 -
  69.136 -	@Override
  69.137 -	public void writeEndResultSet() {
  69.138 -		super.writeEndResultSet();
  69.139 -		printCommand("hline", null, null, true);
  69.140 -		printEnd("tabular");
  69.141 -	}
  69.142 -
  69.143 -	private String escapeTex(String text) {
  69.144 -		if (text == null) {
  69.145 -			return null;
  69.146 -		} else {
  69.147 -			StringBuilder result = new StringBuilder(text.length() * 2);
  69.148 -
  69.149 -			for (char ch : text.toCharArray()) {
  69.150 -				String replacement = TEX_ESCAPE_MAP.get(ch);
  69.151 -				result.append(replacement == null ? ch : replacement);
  69.152 -			}
  69.153 -
  69.154 -			return result.toString();
  69.155 -		}
  69.156 -	}
  69.157 -
  69.158 -	protected String toString(Object value) {
  69.159 -		return String.valueOf(value);
  69.160 -	}
  69.161 -
  69.162 -	private void printColumnSeparator() {
  69.163 -		out.print(COMMAND_COLOR, " & ");
  69.164 -	}
  69.165 -
  69.166 -	private void printEndRow() {
  69.167 -		out.println(COMMAND_COLOR, " \\\\");
  69.168 -		out.flush();
  69.169 -	}
  69.170 -
  69.171 -	/**
  69.172 -	 *
  69.173 -	 * @param command will not be escaped – should contain just a valid TeX command name
  69.174 -	 * @param options will not be escaped – should be properly formatted to be printed inside [
  69.175 -	 * and ]
  69.176 -	 * @param value will be escaped
  69.177 -	 * @param println whether to print line end and flush
  69.178 -	 */
  69.179 -	private void printCommand(String command, String options, String value, boolean println) {
  69.180 -
  69.181 -		if (command != null) {
  69.182 -			out.print(COMMAND_COLOR, "\\" + command);
  69.183 -		}
  69.184 -
  69.185 -		if (options != null) {
  69.186 -			out.print(COMMAND_COLOR, "[");
  69.187 -			out.print(OPTIONS_COLOR, options);
  69.188 -			out.print(COMMAND_COLOR, "]");
  69.189 -		}
  69.190 -
  69.191 -		if (value != null) {
  69.192 -			out.print(COMMAND_COLOR, "{");
  69.193 -			out.print(escapeTex(value));
  69.194 -			out.print(COMMAND_COLOR, "}");
  69.195 -		}
  69.196 -
  69.197 -		if (println) {
  69.198 -			out.println();
  69.199 -			out.flush();
  69.200 -		}
  69.201 -	}
  69.202 -
  69.203 -	private void printBegin(String environment) {
  69.204 -		printCommand("begin", null, environment, true);
  69.205 -	}
  69.206 -
  69.207 -	private void printEnd(String environment) {
  69.208 -		printCommand("end", null, environment, true);
  69.209 -	}
  69.210 -
  69.211 -}
    70.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/XhtmlFormatter.java	Mon Mar 04 17:06:42 2019 +0100
    70.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    70.3 @@ -1,262 +0,0 @@
    70.4 -/**
    70.5 - * SQL-DK
    70.6 - * Copyright © 2014 František Kučera (frantovo.cz)
    70.7 - *
    70.8 - * This program is free software: you can redistribute it and/or modify
    70.9 - * it under the terms of the GNU General Public License as published by
   70.10 - * the Free Software Foundation, either version 3 of the License, or
   70.11 - * (at your option) any later version.
   70.12 - *
   70.13 - * This program is distributed in the hope that it will be useful,
   70.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   70.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   70.16 - * GNU General Public License for more details.
   70.17 - *
   70.18 - * You should have received a copy of the GNU General Public License
   70.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   70.20 - */
   70.21 -package info.globalcode.sql.dk.formatting;
   70.22 -
   70.23 -import info.globalcode.sql.dk.Constants;
   70.24 -import info.globalcode.sql.dk.NamedParameter;
   70.25 -import info.globalcode.sql.dk.Parameter;
   70.26 -import info.globalcode.sql.dk.Xmlns;
   70.27 -import info.globalcode.sql.dk.configuration.DatabaseDefinition;
   70.28 -import info.globalcode.sql.dk.configuration.Properties;
   70.29 -import info.globalcode.sql.dk.configuration.Property;
   70.30 -import static info.globalcode.sql.dk.formatting.AbstractXmlFormatter.qname;
   70.31 -import java.sql.Array;
   70.32 -import java.sql.SQLException;
   70.33 -import java.util.Date;
   70.34 -import java.util.List;
   70.35 -import java.util.Map;
   70.36 -import java.util.Scanner;
   70.37 -import java.util.logging.Level;
   70.38 -import java.util.logging.Logger;
   70.39 -import javax.xml.namespace.QName;
   70.40 -
   70.41 -/**
   70.42 - * Prints result sets and parameters as tables, SQL as preformatted and updates counts as
   70.43 - * paragraphs. You can pick XHTML fragments (usually tabular data) and use it on your website or use
   70.44 - * whole output as preview or report.
   70.45 - *
   70.46 - * @author Ing. František Kučera (frantovo.cz)
   70.47 - */
   70.48 -public class XhtmlFormatter extends AbstractXmlFormatter {
   70.49 -
   70.50 -	private static final Logger log = Logger.getLogger(XhtmlFormatter.class.getName());
   70.51 -	public static final String NAME = "xhtml"; // bash-completion:formatter
   70.52 -	private static final String DOCTYPE = "html PUBLIC \"-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN\" \"http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd\"";
   70.53 -	private static final String CSS_FILE = "info/globalcode/sql/dk/formatter/XhtmlFormatter.css";
   70.54 -	private int statementCounter = 0;
   70.55 -	private int resultSetCounter = 0;
   70.56 -	private int updatesResultCounter = 0;
   70.57 -
   70.58 -	public XhtmlFormatter(FormatterContext formatterContext) {
   70.59 -		super(addDefaults(formatterContext));
   70.60 -	}
   70.61 -
   70.62 -	/**
   70.63 -	 * Do not indent text – preserve whitespace for pre elements
   70.64 -	 */
   70.65 -	private static FormatterContext addDefaults(FormatterContext formatterContext) {
   70.66 -		Properties defaults = new Properties(1);
   70.67 -		defaults.add(new Property(PROPERTY_INDENT_TEXT, "false"));
   70.68 -		formatterContext.getProperties().setLastDefaults(defaults);
   70.69 -		return formatterContext;
   70.70 -	}
   70.71 -
   70.72 -	@Override
   70.73 -	public void writeStartBatch() {
   70.74 -		super.writeStartBatch();
   70.75 -		printStartDocument();
   70.76 -		printDoctype(DOCTYPE);
   70.77 -		printStartElement(qname("html"), singleAttribute(qname("xmlns"), Xmlns.XHTML));
   70.78 -
   70.79 -		printStartElement(qname("head"));
   70.80 -		printTextElement(qname("title"), null, Constants.PROGRAM_NAME + ": batch results");
   70.81 -		printCss();
   70.82 -		printEndElement();
   70.83 -
   70.84 -		printStartElement(qname("body"));
   70.85 -	}
   70.86 -
   70.87 -	private void printCss() {
   70.88 -
   70.89 -		try (Scanner css = new Scanner(getClass().getClassLoader().getResourceAsStream(CSS_FILE))) {
   70.90 -			printStartElement(qname("style"), singleAttribute(qname("type"), "text/css"));
   70.91 -			while (css.hasNext()) {
   70.92 -				printText(css.nextLine(), true);
   70.93 -			}
   70.94 -			printEndElement();
   70.95 -		}
   70.96 -	}
   70.97 -
   70.98 -	@Override
   70.99 -	public void writeEndBatch() {
  70.100 -		super.writeEndBatch();
  70.101 -		printEndElement();
  70.102 -		printEndElement();
  70.103 -		printEndDocument();
  70.104 -	}
  70.105 -
  70.106 -	@Override
  70.107 -	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
  70.108 -		super.writeStartDatabase(databaseDefinition);
  70.109 -		printTextElement(qname("h1"), null, "Database: " + databaseDefinition.getName());
  70.110 -
  70.111 -		printStartElement(qname("p"));
  70.112 -		printText("This is XHTML output of batch executed at: ", true);
  70.113 -		printText(new Date().toString(), true);
  70.114 -		printEndElement();
  70.115 -	}
  70.116 -
  70.117 -	@Override
  70.118 -	public void writeQuery(String sql) {
  70.119 -		super.writeQuery(sql);
  70.120 -		printTextElement(qname("pre"), null, sql);
  70.121 -	}
  70.122 -
  70.123 -	@Override
  70.124 -	public void writeParameters(List<? extends Parameter> parameters) {
  70.125 -		super.writeParameters(parameters);
  70.126 -
  70.127 -		if (parameters == null || parameters.isEmpty()) {
  70.128 -			printTextElement(qname("p"), null, "(this query has no parameters)");
  70.129 -		} else {
  70.130 -			printTextElement(qname("h3"), null, "Parameters:");
  70.131 -
  70.132 -			printStartElement(qname("table"));
  70.133 -
  70.134 -			printStartElement(qname("thead"));
  70.135 -			printStartElement(qname("tr"));
  70.136 -			printTextElement(qname("td"), null, "id");
  70.137 -			printTextElement(qname("td"), null, "type");
  70.138 -			printTextElement(qname("td"), null, "value");
  70.139 -			printEndElement();
  70.140 -			printEndElement();
  70.141 -
  70.142 -			printStartElement(qname("tbody"));
  70.143 -			for (int i = 0; i < parameters.size(); i++) {
  70.144 -				Parameter p = parameters.get(i);
  70.145 -				printStartElement(qname("tr"));
  70.146 -				String numberOrName;
  70.147 -				if (p instanceof NamedParameter) {
  70.148 -					numberOrName = ((NamedParameter) p).getName();
  70.149 -				} else {
  70.150 -					numberOrName = String.valueOf(i + 1);
  70.151 -				}
  70.152 -				printTextElement(qname("td"), null, numberOrName);
  70.153 -				printTextElement(qname("td"), null, p.getType().name());
  70.154 -				printTableData(p.getValue());
  70.155 -				printEndElement();
  70.156 -			}
  70.157 -			printEndElement();
  70.158 -
  70.159 -			printEndElement();
  70.160 -		}
  70.161 -	}
  70.162 -
  70.163 -	private void printTableData(Object value) {
  70.164 -
  70.165 -		if (value instanceof Array) {
  70.166 -			Array sqlArray = (Array) value;
  70.167 -			try {
  70.168 -				Object[] array = (Object[]) sqlArray.getArray();
  70.169 -				printStartElement(qname("td"));
  70.170 -				printArray(array);
  70.171 -				printEndElement();
  70.172 -			} catch (SQLException e) {
  70.173 -				log.log(Level.SEVERE, "Unable to format array", e);
  70.174 -				printTableData(String.valueOf(value));
  70.175 -			}
  70.176 -		} else {
  70.177 -			Map<QName, String> attributes = null;
  70.178 -			if (value instanceof Number) {
  70.179 -				attributes = singleAttribute(qname("class"), "number");
  70.180 -			} else if (value instanceof Boolean) {
  70.181 -				attributes = singleAttribute(qname("class"), "boolean");
  70.182 -			}
  70.183 -			printTextElement(qname("td"), attributes, String.valueOf(value));
  70.184 -		}
  70.185 -	}
  70.186 -
  70.187 -	private void printArray(Object[] array) {
  70.188 -		printStartElement(qname("ul"));
  70.189 -		for (Object o : array) {
  70.190 -			if (o instanceof Object[]) {
  70.191 -				printStartElement(qname("li"));
  70.192 -				printTextElement(qname("p"), null, "nested array:");
  70.193 -				printArray((Object[]) o);
  70.194 -				printEndElement();
  70.195 -			} else {
  70.196 -				printTextElement(qname("li"), null, String.valueOf(o));
  70.197 -			}
  70.198 -		}
  70.199 -		printEndElement();
  70.200 -	}
  70.201 -
  70.202 -	@Override
  70.203 -	public void writeStartResultSet(ColumnsHeader header) {
  70.204 -		super.writeStartResultSet(header);
  70.205 -		resultSetCounter++;
  70.206 -		printEmptyElement(qname("hr"), null);
  70.207 -		printTextElement(qname("h3"), null, "Result set #" + resultSetCounter);
  70.208 -		printStartElement(qname("table"));
  70.209 -		printStartElement(qname("thead"));
  70.210 -		printStartElement(qname("tr"));
  70.211 -		for (ColumnDescriptor cd : header.getColumnDescriptors()) {
  70.212 -			// TODO: type
  70.213 -			printTextElement(qname("td"), null, cd.getLabel());
  70.214 -		}
  70.215 -		printEndElement();
  70.216 -		printEndElement();
  70.217 -
  70.218 -		printStartElement(qname("tbody"));
  70.219 -	}
  70.220 -
  70.221 -	@Override
  70.222 -	public void writeEndResultSet() {
  70.223 -		super.writeEndResultSet();
  70.224 -		printEndElement();
  70.225 -		printEndElement();
  70.226 -		printTextElement(qname("p"), null, "Record count: " + getCurrentRowCount());
  70.227 -	}
  70.228 -
  70.229 -	@Override
  70.230 -	public void writeStartRow() {
  70.231 -		super.writeStartRow();
  70.232 -		printStartElement(qname("tr"));
  70.233 -	}
  70.234 -
  70.235 -	@Override
  70.236 -	public void writeColumnValue(Object value) {
  70.237 -		super.writeColumnValue(value);
  70.238 -		printTableData(value);
  70.239 -	}
  70.240 -
  70.241 -	@Override
  70.242 -	public void writeEndRow() {
  70.243 -		super.writeEndRow();
  70.244 -		printEndElement();
  70.245 -	}
  70.246 -
  70.247 -	@Override
  70.248 -	public void writeStartStatement() {
  70.249 -		super.writeStartStatement();
  70.250 -		statementCounter++;
  70.251 -		printEmptyElement(qname("hr"), null);
  70.252 -		printTextElement(qname("h2"), null, "SQL statement #" + statementCounter);
  70.253 -		resultSetCounter = 0;
  70.254 -		updatesResultCounter = 0;
  70.255 -	}
  70.256 -
  70.257 -	@Override
  70.258 -	public void writeUpdatesResult(int updatedRowsCount) {
  70.259 -		super.writeUpdatesResult(updatedRowsCount);
  70.260 -		updatesResultCounter++;
  70.261 -		printEmptyElement(qname("hr"), null);
  70.262 -		printTextElement(qname("h3"), null, "Updates result #" + updatesResultCounter);
  70.263 -		printTextElement(qname("p"), null, "Updated rows: " + updatedRowsCount);
  70.264 -	}
  70.265 -}
    71.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/XmlFormatter.java	Mon Mar 04 17:06:42 2019 +0100
    71.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    71.3 @@ -1,245 +0,0 @@
    71.4 -/**
    71.5 - * SQL-DK
    71.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    71.7 - *
    71.8 - * This program is free software: you can redistribute it and/or modify
    71.9 - * it under the terms of the GNU General Public License as published by
   71.10 - * the Free Software Foundation, either version 3 of the License, or
   71.11 - * (at your option) any later version.
   71.12 - *
   71.13 - * This program is distributed in the hope that it will be useful,
   71.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   71.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   71.16 - * GNU General Public License for more details.
   71.17 - *
   71.18 - * You should have received a copy of the GNU General Public License
   71.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   71.20 - */
   71.21 -package info.globalcode.sql.dk.formatting;
   71.22 -
   71.23 -import info.globalcode.sql.dk.Parameter;
   71.24 -import info.globalcode.sql.dk.Xmlns;
   71.25 -import info.globalcode.sql.dk.configuration.DatabaseDefinition;
   71.26 -import static info.globalcode.sql.dk.Functions.notNull;
   71.27 -import info.globalcode.sql.dk.NamedParameter;
   71.28 -import info.globalcode.sql.dk.configuration.PropertyDeclaration;
   71.29 -import static info.globalcode.sql.dk.formatting.AbstractXmlFormatter.qname;
   71.30 -import java.sql.Array;
   71.31 -import java.sql.ResultSet;
   71.32 -import java.sql.SQLException;
   71.33 -import java.sql.SQLXML;
   71.34 -import java.util.ArrayList;
   71.35 -import java.util.LinkedHashMap;
   71.36 -import java.util.List;
   71.37 -import java.util.Map;
   71.38 -import java.util.logging.Level;
   71.39 -import java.util.logging.Logger;
   71.40 -import javax.xml.namespace.QName;
   71.41 -
   71.42 -/**
   71.43 - * <p>
   71.44 - * Prints machine-readable output – XML document containing resultsets and updates count. Good
   71.45 - * choice for further processing – e.g. XSL transformation.</p>
   71.46 - *
   71.47 - * <p>
   71.48 - * TODO: XSD</p>
   71.49 - *
   71.50 - * @author Ing. František Kučera (frantovo.cz)
   71.51 - */
   71.52 -@PropertyDeclaration(name = XmlFormatter.PROPERTY_LABELED_COLUMNS, defaultValue = "false", type = Boolean.class, description = "whether to add 'label' attribute to each 'column' element")
   71.53 -public class XmlFormatter extends AbstractXmlFormatter {
   71.54 -
   71.55 -	public static final String NAME = "xml"; // bash-completion:formatter
   71.56 -	public static final String PROPERTY_LABELED_COLUMNS = "labeledColumns";
   71.57 -	private static final Logger log = Logger.getLogger(XmlFormatter.class.getName());
   71.58 -	private final boolean labeledColumns;
   71.59 -
   71.60 -	public XmlFormatter(FormatterContext formatterContext) {
   71.61 -		super(formatterContext);
   71.62 -		labeledColumns = formatterContext.getProperties().getBoolean(PROPERTY_LABELED_COLUMNS, false);
   71.63 -	}
   71.64 -
   71.65 -	@Override
   71.66 -	public void writeStartBatch() {
   71.67 -		super.writeStartBatch();
   71.68 -		printStartDocument();
   71.69 -		printStartElement(qname("batchResult"), singleAttribute(qname("xmlns"), Xmlns.BATCH_RESULT));
   71.70 -	}
   71.71 -
   71.72 -	@Override
   71.73 -	public void writeEndBatch() {
   71.74 -		super.writeEndBatch();
   71.75 -		printEndElement();
   71.76 -		printEndDocument();
   71.77 -	}
   71.78 -
   71.79 -	@Override
   71.80 -	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
   71.81 -		super.writeStartDatabase(databaseDefinition);
   71.82 -		Map<QName, String> attributes = databaseDefinition.getName() == null ? null : singleAttribute(qname("name"), databaseDefinition.getName());
   71.83 -		printStartElement(qname("database"), attributes);
   71.84 -	}
   71.85 -
   71.86 -	@Override
   71.87 -	public void writeEndDatabase() {
   71.88 -		super.writeEndDatabase();
   71.89 -		printEndElement();
   71.90 -	}
   71.91 -
   71.92 -	@Override
   71.93 -	public void writeStartStatement() {
   71.94 -		super.writeStartStatement();
   71.95 -		printStartElement(qname("statement"));
   71.96 -	}
   71.97 -
   71.98 -	@Override
   71.99 -	public void writeEndStatement() {
  71.100 -		super.writeEndStatement();
  71.101 -		printEndElement();
  71.102 -	}
  71.103 -
  71.104 -	@Override
  71.105 -	public void writeQuery(String sql) {
  71.106 -		super.writeQuery(sql);
  71.107 -		printTextElement(qname("sql"), null, sql);
  71.108 -	}
  71.109 -
  71.110 -	@Override
  71.111 -	public void writeParameters(List<? extends Parameter> parameters) {
  71.112 -		super.writeParameters(parameters);
  71.113 -
  71.114 -		for (Parameter p : notNull(parameters)) {
  71.115 -
  71.116 -			Map<QName, String> attributes = new LinkedHashMap<>(2);
  71.117 -			if (p instanceof NamedParameter) {
  71.118 -				attributes.put(qname("name"), ((NamedParameter) p).getName());
  71.119 -			}
  71.120 -			attributes.put(qname("type"), p.getType().name());
  71.121 -
  71.122 -			printTextElement(qname("parameter"), attributes, String.valueOf(p.getValue()));
  71.123 -		}
  71.124 -
  71.125 -	}
  71.126 -
  71.127 -	@Override
  71.128 -	public void writeStartResultSet(ColumnsHeader header) {
  71.129 -		super.writeStartResultSet(header);
  71.130 -		printStartElement(qname("resultSet"));
  71.131 -
  71.132 -		for (ColumnDescriptor cd : header.getColumnDescriptors()) {
  71.133 -			Map<QName, String> attributes = new LinkedHashMap<>(4);
  71.134 -			attributes.put(qname("label"), cd.getLabel());
  71.135 -			attributes.put(qname("name"), cd.getName());
  71.136 -			attributes.put(qname("typeName"), cd.getTypeName());
  71.137 -			attributes.put(qname("type"), String.valueOf(cd.getType()));
  71.138 -			printEmptyElement(qname("columnHeader"), attributes);
  71.139 -		}
  71.140 -	}
  71.141 -
  71.142 -	@Override
  71.143 -	public void writeEndResultSet() {
  71.144 -		super.writeEndResultSet();
  71.145 -		printEndElement();
  71.146 -	}
  71.147 -
  71.148 -	@Override
  71.149 -	public void writeStartRow() {
  71.150 -		super.writeStartRow();
  71.151 -		printStartElement(qname("row"));
  71.152 -	}
  71.153 -
  71.154 -	@Override
  71.155 -	public void writeColumnValue(Object value) {
  71.156 -		super.writeColumnValue(value);
  71.157 -
  71.158 -		Map<QName, String> attributes = null;
  71.159 -		if (labeledColumns) {
  71.160 -			attributes = new LinkedHashMap<>(2);
  71.161 -			attributes.put(qname("label"), getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getLabel());
  71.162 -		}
  71.163 -
  71.164 -		if (value == null) {
  71.165 -			if (attributes == null) {
  71.166 -				attributes = new LinkedHashMap<>(2);
  71.167 -			}
  71.168 -			attributes.put(qname("null"), "true");
  71.169 -			printEmptyElement(qname("column"), attributes);
  71.170 -		} else if (value instanceof Array) {
  71.171 -
  71.172 -			Array sqlArray = (Array) value;
  71.173 -			try {
  71.174 -				Object[] array = (Object[]) sqlArray.getArray();
  71.175 -				printStartElement(qname("column"), attributes);
  71.176 -				printArray(array);
  71.177 -				printEndElement();
  71.178 -			} catch (SQLException e) {
  71.179 -				// FIXME: rewrite array formatting, remember array mode, don't try sqlArray.getArray() again and again if it has failed
  71.180 -				log.log(Level.SEVERE, "Unable to format array", e);
  71.181 -				try {
  71.182 -					ResultSet arrayResultSet = sqlArray.getResultSet();
  71.183 -					//int columnCount = arrayResultSet.getMetaData().getColumnCount();
  71.184 -					ArrayList<Object> arrayList = new ArrayList<>();
  71.185 -					while (arrayResultSet.next()) {
  71.186 -						arrayList.add(arrayResultSet.getObject(2));
  71.187 -						// for (int i = 1; i <= columnCount; i++) {
  71.188 -						// 	log.log(Level.INFO, "Array column {0} = {1}", new Object[]{i, arrayResultSet.getObject(i)});
  71.189 -						// }
  71.190 -					}
  71.191 -
  71.192 -					printStartElement(qname("column"), attributes);
  71.193 -					// FIXME: instanceof SQLXML, see below
  71.194 -					printArray(arrayList.toArray());
  71.195 -					printEndElement();
  71.196 -
  71.197 -				} catch (SQLException e2) {
  71.198 -					// FIXME: fix logging, error recovery
  71.199 -					log.log(Level.SEVERE, "Second level fuck up !!!", e2);
  71.200 -				}
  71.201 -
  71.202 -				writeColumnValue(String.valueOf(value));
  71.203 -			}
  71.204 -
  71.205 -		} else if (value instanceof SQLXML) { // FIXME: move to separate method, to AbstractFormatter?
  71.206 -			SQLXML xml = (SQLXML) value;
  71.207 -			// TODO: parse DOM/SAX and transplant XML, don't escape (optional)
  71.208 -			try {
  71.209 -				printTextElement(qname("column"), attributes, xml.getString());
  71.210 -			} catch (SQLException e) {
  71.211 -				log.log(Level.SEVERE, "Unable to format XML", e);
  71.212 -				writeColumnValue(String.valueOf(value));
  71.213 -			}
  71.214 -		} else {
  71.215 -			printTextElement(qname("column"), attributes, toString(value));
  71.216 -		}
  71.217 -	}
  71.218 -
  71.219 -	private void printArray(Object[] array) {
  71.220 -		printStartElement(qname("array"));
  71.221 -		for (Object o : array) {
  71.222 -			if (o instanceof Object[]) {
  71.223 -				printStartElement(qname("item"));
  71.224 -				printArray((Object[]) o);
  71.225 -				printEndElement();
  71.226 -			} else {
  71.227 -				printTextElement(qname("item"), null, String.valueOf(o));
  71.228 -			}
  71.229 -		}
  71.230 -		printEndElement();
  71.231 -	}
  71.232 -
  71.233 -	@Override
  71.234 -	public void writeEndRow() {
  71.235 -		super.writeEndRow();
  71.236 -		printEndElement();
  71.237 -	}
  71.238 -
  71.239 -	@Override
  71.240 -	public void writeUpdatesResult(int updatedRowsCount) {
  71.241 -		super.writeUpdatesResult(updatedRowsCount);
  71.242 -		printTextElement(qname("updatedRows"), null, String.valueOf(updatedRowsCount));
  71.243 -	}
  71.244 -
  71.245 -	protected String toString(Object value) {
  71.246 -		return String.valueOf(value);
  71.247 -	}
  71.248 -}
    72.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagement.java	Mon Mar 04 17:06:42 2019 +0100
    72.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    72.3 @@ -1,97 +0,0 @@
    72.4 -/**
    72.5 - * SQL-DK
    72.6 - * Copyright © 2014 František Kučera (frantovo.cz)
    72.7 - *
    72.8 - * This program is free software: you can redistribute it and/or modify
    72.9 - * it under the terms of the GNU General Public License as published by
   72.10 - * the Free Software Foundation, either version 3 of the License, or
   72.11 - * (at your option) any later version.
   72.12 - *
   72.13 - * This program is distributed in the hope that it will be useful,
   72.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   72.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   72.16 - * GNU General Public License for more details.
   72.17 - *
   72.18 - * You should have received a copy of the GNU General Public License
   72.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   72.20 - */
   72.21 -package info.globalcode.sql.dk.jmx;
   72.22 -
   72.23 -import java.util.EnumMap;
   72.24 -import java.util.Map;
   72.25 -
   72.26 -/**
   72.27 - * JMX management bean for progress reporting.
   72.28 - *
   72.29 - * @author Ing. František Kučera (frantovo.cz)
   72.30 - */
   72.31 -public class ConnectionManagement implements ConnectionManagementMBean {
   72.32 -
   72.33 -	private final String databaseName;
   72.34 -	private final Map<COUNTER, Integer> counters = new EnumMap(COUNTER.class);
   72.35 -
   72.36 -	public ConnectionManagement(String databaseName) {
   72.37 -		this.databaseName = databaseName;
   72.38 -		for (COUNTER c : COUNTER.values()) {
   72.39 -			counters.put(c, 0);
   72.40 -		}
   72.41 -	}
   72.42 -
   72.43 -	public enum COUNTER {
   72.44 -
   72.45 -		COMMAND,
   72.46 -		RECORD_CURRENT,
   72.47 -		RECORD_TOTAL
   72.48 -	};
   72.49 -
   72.50 -	public void incrementCounter(COUNTER counter) {
   72.51 -		synchronized (counters) {
   72.52 -			int old = counters.get(counter);
   72.53 -			counters.put(counter, old + 1);
   72.54 -		}
   72.55 -	}
   72.56 -
   72.57 -	public void resetCounter(COUNTER counter) {
   72.58 -		synchronized (counters) {
   72.59 -			counters.put(counter, 0);
   72.60 -		}
   72.61 -	}
   72.62 -
   72.63 -	public static void incrementCounter(ConnectionManagement mbean, COUNTER counter) {
   72.64 -		if (mbean != null) {
   72.65 -			mbean.incrementCounter(counter);
   72.66 -		}
   72.67 -	}
   72.68 -
   72.69 -	public static void resetCounter(ConnectionManagement mbean, COUNTER counter) {
   72.70 -		if (mbean != null) {
   72.71 -			mbean.resetCounter(counter);
   72.72 -		}
   72.73 -	}
   72.74 -
   72.75 -	@Override
   72.76 -	public String getDatabaseName() {
   72.77 -		return databaseName;
   72.78 -	}
   72.79 -
   72.80 -	@Override
   72.81 -	public int getCommandCount() {
   72.82 -		synchronized (counters) {
   72.83 -			return counters.get(COUNTER.COMMAND);
   72.84 -		}
   72.85 -	}
   72.86 -
   72.87 -	@Override
   72.88 -	public int getCurrentRecordCount() {
   72.89 -		synchronized (counters) {
   72.90 -			return counters.get(COUNTER.RECORD_CURRENT);
   72.91 -		}
   72.92 -	}
   72.93 -	
   72.94 -	@Override
   72.95 -	public int getTotalRecordCount() {
   72.96 -		synchronized (counters) {
   72.97 -			return counters.get(COUNTER.RECORD_TOTAL);
   72.98 -		}
   72.99 -	}
  72.100 -}
    73.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagementMBean.java	Mon Mar 04 17:06:42 2019 +0100
    73.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    73.3 @@ -1,34 +0,0 @@
    73.4 -/**
    73.5 - * SQL-DK
    73.6 - * Copyright © 2014 František Kučera (frantovo.cz)
    73.7 - *
    73.8 - * This program is free software: you can redistribute it and/or modify
    73.9 - * it under the terms of the GNU General Public License as published by
   73.10 - * the Free Software Foundation, either version 3 of the License, or
   73.11 - * (at your option) any later version.
   73.12 - *
   73.13 - * This program is distributed in the hope that it will be useful,
   73.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   73.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   73.16 - * GNU General Public License for more details.
   73.17 - *
   73.18 - * You should have received a copy of the GNU General Public License
   73.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   73.20 - */
   73.21 -package info.globalcode.sql.dk.jmx;
   73.22 -
   73.23 -/**
   73.24 - *
   73.25 - * @author Ing. František Kučera (frantovo.cz)
   73.26 - */
   73.27 -public interface ConnectionManagementMBean {
   73.28 -
   73.29 -	public String getDatabaseName();
   73.30 -
   73.31 -	public int getCommandCount();
   73.32 -
   73.33 -	public int getCurrentRecordCount();
   73.34 -
   73.35 -	public int getTotalRecordCount();
   73.36 -
   73.37 -}
    74.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/jmx/ManagementUtils.java	Mon Mar 04 17:06:42 2019 +0100
    74.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    74.3 @@ -1,68 +0,0 @@
    74.4 -/**
    74.5 - * SQL-DK
    74.6 - * Copyright © 2014 František Kučera (frantovo.cz)
    74.7 - *
    74.8 - * This program is free software: you can redistribute it and/or modify
    74.9 - * it under the terms of the GNU General Public License as published by
   74.10 - * the Free Software Foundation, either version 3 of the License, or
   74.11 - * (at your option) any later version.
   74.12 - *
   74.13 - * This program is distributed in the hope that it will be useful,
   74.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   74.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   74.16 - * GNU General Public License for more details.
   74.17 - *
   74.18 - * You should have received a copy of the GNU General Public License
   74.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   74.20 - */
   74.21 -package info.globalcode.sql.dk.jmx;
   74.22 -
   74.23 -import java.lang.management.ManagementFactory;
   74.24 -import java.util.Hashtable;
   74.25 -import java.util.logging.Level;
   74.26 -import java.util.logging.Logger;
   74.27 -import javax.management.MBeanServer;
   74.28 -import javax.management.ObjectName;
   74.29 -
   74.30 -/**
   74.31 - *
   74.32 - * @author Ing. František Kučera (frantovo.cz)
   74.33 - */
   74.34 -public class ManagementUtils {
   74.35 -
   74.36 -	private static final Logger log = Logger.getLogger(ManagementUtils.class.getName());
   74.37 -	public static final String DEFAULT_CONNECTION_JMX_NAME = "main";
   74.38 -
   74.39 -	/**
   74.40 -	 * @see #registerMBean(java.lang.String, java.lang.String) with default JMX name
   74.41 -	 */
   74.42 -	public static ConnectionManagement registerMBean(String dbName) {
   74.43 -		return registerMBean(dbName, DEFAULT_CONNECTION_JMX_NAME);
   74.44 -	}
   74.45 -
   74.46 -	/**
   74.47 -	 *
   74.48 -	 * @param dbName database name
   74.49 -	 * @param jmxName name of JMX bean
   74.50 -	 * @return registered JMX bean | or null if registration fails (should not)
   74.51 -	 */
   74.52 -	public static ConnectionManagement registerMBean(String dbName, String jmxName) {
   74.53 -		try {
   74.54 -			ConnectionManagement mbean = new ConnectionManagement(dbName);
   74.55 -			MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
   74.56 -			Hashtable<String, String> objectProperties = new Hashtable<>();
   74.57 -			objectProperties.put("type", "Connection");
   74.58 -			objectProperties.put("name", jmxName);
   74.59 -			ObjectName objectName = new ObjectName("info.globalcode.sql.dk", objectProperties);
   74.60 -			mbs.registerMBean(mbean, objectName);
   74.61 -			log.log(Level.FINE, "JMX MBean was registered as: {0}", objectName);
   74.62 -			return mbean;
   74.63 -		} catch (Exception e) {
   74.64 -			log.log(Level.WARNING, "Unable to register JMX MBean", e);
   74.65 -			return null;
   74.66 -		}
   74.67 -	}
   74.68 -
   74.69 -	private ManagementUtils() {
   74.70 -	}
   74.71 -}
    75.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/logging/ColorfulConsoleFormatter.java	Mon Mar 04 17:06:42 2019 +0100
    75.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    75.3 @@ -1,97 +0,0 @@
    75.4 -/**
    75.5 - * SQL-DK
    75.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    75.7 - *
    75.8 - * This program is free software: you can redistribute it and/or modify
    75.9 - * it under the terms of the GNU General Public License as published by
   75.10 - * the Free Software Foundation, either version 3 of the License, or
   75.11 - * (at your option) any later version.
   75.12 - *
   75.13 - * This program is distributed in the hope that it will be useful,
   75.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   75.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   75.16 - * GNU General Public License for more details.
   75.17 - *
   75.18 - * You should have received a copy of the GNU General Public License
   75.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   75.20 - */
   75.21 -package info.globalcode.sql.dk.logging;
   75.22 -
   75.23 -import info.globalcode.sql.dk.ColorfulPrintWriter;
   75.24 -import static info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
   75.25 -import static info.globalcode.sql.dk.ColorfulPrintWriter.TerminalStyle;
   75.26 -import static info.globalcode.sql.dk.Functions.rpad;
   75.27 -import java.io.StringWriter;
   75.28 -import java.util.logging.Formatter;
   75.29 -import java.util.logging.Level;
   75.30 -import java.util.logging.LogRecord;
   75.31 -
   75.32 -/**
   75.33 - * For console/terminal log output. Log messages are printed in brief and colorful form.
   75.34 - *
   75.35 - * @author Ing. František Kučera (frantovo.cz)
   75.36 - */
   75.37 -public class ColorfulConsoleFormatter extends Formatter {
   75.38 -
   75.39 -	private boolean printStacktrace = false;
   75.40 -
   75.41 -	@Override
   75.42 -	public String format(LogRecord r) {
   75.43 -		StringWriter sw = new StringWriter();
   75.44 -		try (ColorfulPrintWriter out = new ColorfulPrintWriter(sw)) {
   75.45 -			printLevel(out, r.getLevel());
   75.46 -			printMessage(out, r);
   75.47 -			printThrowable(out, r);
   75.48 -			out.println();
   75.49 -		}
   75.50 -		return sw.toString();
   75.51 -	}
   75.52 -
   75.53 -	private void printLevel(ColorfulPrintWriter out, Level l) {
   75.54 -		TerminalColor color = TerminalColor.Magenta;
   75.55 -
   75.56 -		if (l == Level.SEVERE) {
   75.57 -			color = TerminalColor.Red;
   75.58 -		} else if (l == Level.WARNING) {
   75.59 -			color = TerminalColor.Yellow;
   75.60 -		}
   75.61 -
   75.62 -		out.print(color, rpad(l.getLocalizedName() + ": ", 10));
   75.63 -	}
   75.64 -
   75.65 -	private void printMessage(ColorfulPrintWriter out, LogRecord r) {
   75.66 -		out.print(formatMessage(r));
   75.67 -	}
   75.68 -
   75.69 -	private void printThrowable(ColorfulPrintWriter out, LogRecord r) {
   75.70 -		Throwable t = r.getThrown();
   75.71 -		if (t != null) {
   75.72 -			out.print(": ");
   75.73 -			out.print(TerminalColor.Red, t.getClass().getSimpleName());
   75.74 -			String message = t.getLocalizedMessage();
   75.75 -			if (message != null) {
   75.76 -				out.print(": ");
   75.77 -				if (printStacktrace) {
   75.78 -					out.print(message);
   75.79 -				} else {
   75.80 -					out.print(message.replaceAll("\\n", " "));
   75.81 -				}
   75.82 -			}
   75.83 -			if (printStacktrace) {
   75.84 -				out.println();
   75.85 -				out.setForegroundColor(TerminalColor.Yellow);
   75.86 -				out.setStyle(TerminalStyle.Dim);
   75.87 -				t.printStackTrace(out);
   75.88 -				out.resetAll();
   75.89 -			}
   75.90 -		}
   75.91 -	}
   75.92 -
   75.93 -	public boolean isPrintStacktrace() {
   75.94 -		return printStacktrace;
   75.95 -	}
   75.96 -
   75.97 -	public void setPrintStacktrace(boolean printStacktrace) {
   75.98 -		this.printStacktrace = printStacktrace;
   75.99 -	}
  75.100 -}
    76.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/logging/LoggerInitializer.java	Mon Mar 04 17:06:42 2019 +0100
    76.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    76.3 @@ -1,78 +0,0 @@
    76.4 -/**
    76.5 - * SQL-DK
    76.6 - * Copyright © 2013 František Kučera (frantovo.cz)
    76.7 - *
    76.8 - * This program is free software: you can redistribute it and/or modify
    76.9 - * it under the terms of the GNU General Public License as published by
   76.10 - * the Free Software Foundation, either version 3 of the License, or
   76.11 - * (at your option) any later version.
   76.12 - *
   76.13 - * This program is distributed in the hope that it will be useful,
   76.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   76.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   76.16 - * GNU General Public License for more details.
   76.17 - *
   76.18 - * You should have received a copy of the GNU General Public License
   76.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   76.20 - */
   76.21 -package info.globalcode.sql.dk.logging;
   76.22 -
   76.23 -import info.globalcode.sql.dk.Constants;
   76.24 -import java.util.logging.ConsoleHandler;
   76.25 -import java.util.logging.Handler;
   76.26 -import java.util.logging.Level;
   76.27 -import java.util.logging.Logger;
   76.28 -
   76.29 -/**
   76.30 - * Configures logging subsystem.
   76.31 - * Usage: java -Djava.util.logging.config.class=info.globalcode.sql.dk.logging.LoggerInitializer …
   76.32 - *
   76.33 - * @author Ing. František Kučera (frantovo.cz)
   76.34 - */
   76.35 -public class LoggerInitializer {
   76.36 -
   76.37 -	private static final Logger log = Logger.getLogger(LoggerInitializer.class.getName());
   76.38 -	public static final String LEVEL_PROPERTY = LoggerInitializer.class.getName() + ".level";
   76.39 -	private static final Level DEFAULT_LEVEL = Level.INFO;
   76.40 -
   76.41 -	public LoggerInitializer() {
   76.42 -		Logger logger = Logger.getLogger(Constants.JAVA_PACKAGE);
   76.43 -		ConsoleHandler handler = new ConsoleHandler();
   76.44 -		ColorfulConsoleFormatter formatter = new ColorfulConsoleFormatter();
   76.45 -
   76.46 -		logger.addHandler(handler);
   76.47 -		handler.setFormatter(formatter);
   76.48 -
   76.49 -		setLevel(logger, handler, formatter);
   76.50 -
   76.51 -
   76.52 -		/**
   76.53 -		 * TODO: optional FileHandler – detailed logs in file in ~/sql-dk/log/…
   76.54 -		 */
   76.55 -	}
   76.56 -
   76.57 -	private void setLevel(Logger logger, Handler handler, ColorfulConsoleFormatter formatter) {
   76.58 -		boolean levelParseError = false;
   76.59 -		Level level;
   76.60 -		String cliLevel = System.getProperty(LEVEL_PROPERTY);
   76.61 -		if (cliLevel == null) {
   76.62 -			level = DEFAULT_LEVEL;
   76.63 -		} else {
   76.64 -			try {
   76.65 -				level = Level.parse(cliLevel);
   76.66 -			} catch (IllegalArgumentException e) {
   76.67 -				level = DEFAULT_LEVEL;
   76.68 -				levelParseError = true;
   76.69 -			}
   76.70 -		}
   76.71 -
   76.72 -		handler.setLevel(level);
   76.73 -		logger.setLevel(level);
   76.74 -
   76.75 -		if (levelParseError) {
   76.76 -			log.log(Level.WARNING, "Invalid logging level „{0}“ specified in „{1}“ → using default level „{2}“", new Object[]{cliLevel, LEVEL_PROPERTY, DEFAULT_LEVEL});
   76.77 -		}
   76.78 -
   76.79 -		formatter.setPrintStacktrace(level.intValue() < Level.INFO.intValue());
   76.80 -	}
   76.81 -}
    77.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/logging/LoggerProducer.java	Mon Mar 04 17:06:42 2019 +0100
    77.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    77.3 @@ -1,36 +0,0 @@
    77.4 -/**
    77.5 - * SQL-DK
    77.6 - * Copyright © 2015 František Kučera (frantovo.cz)
    77.7 - *
    77.8 - * This program is free software: you can redistribute it and/or modify
    77.9 - * it under the terms of the GNU General Public License as published by
   77.10 - * the Free Software Foundation, either version 3 of the License, or
   77.11 - * (at your option) any later version.
   77.12 - *
   77.13 - * This program is distributed in the hope that it will be useful,
   77.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
   77.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   77.16 - * GNU General Public License for more details.
   77.17 - *
   77.18 - * You should have received a copy of the GNU General Public License
   77.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
   77.20 - */
   77.21 -package info.globalcode.sql.dk.logging;
   77.22 -
   77.23 -import java.util.logging.Logger;
   77.24 -
   77.25 -/**
   77.26 - *
   77.27 - * @author Ing. František Kučera (frantovo.cz)
   77.28 - */
   77.29 -public class LoggerProducer {
   77.30 -
   77.31 -	/**
   77.32 -	 * @return created logger for the caller class
   77.33 -	 */
   77.34 -	public static Logger getLogger() {
   77.35 -		String className = Thread.currentThread().getStackTrace()[2].getClassName();
   77.36 -		return Logger.getLogger(className);
   77.37 -	}
   77.38 -
   77.39 -}
    78.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    78.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIOptions.java	Mon Mar 04 20:15:24 2019 +0100
    78.3 @@ -0,0 +1,283 @@
    78.4 +/**
    78.5 + * SQL-DK
    78.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    78.7 + *
    78.8 + * This program is free software: you can redistribute it and/or modify
    78.9 + * it under the terms of the GNU General Public License as published by
   78.10 + * the Free Software Foundation, either version 3 of the License, or
   78.11 + * (at your option) any later version.
   78.12 + *
   78.13 + * This program is distributed in the hope that it will be useful,
   78.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   78.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   78.16 + * GNU General Public License for more details.
   78.17 + *
   78.18 + * You should have received a copy of the GNU General Public License
   78.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   78.20 + */
   78.21 +package info.globalcode.sql.dk;
   78.22 +
   78.23 +import static info.globalcode.sql.dk.Functions.isNotEmpty;
   78.24 +import static info.globalcode.sql.dk.Functions.equalz;
   78.25 +import info.globalcode.sql.dk.InfoLister.InfoType;
   78.26 +import info.globalcode.sql.dk.configuration.Properties;
   78.27 +import info.globalcode.sql.dk.configuration.Property;
   78.28 +import java.io.InputStream;
   78.29 +import java.io.OutputStream;
   78.30 +import java.util.ArrayList;
   78.31 +import java.util.Collection;
   78.32 +import java.util.EnumSet;
   78.33 +import java.util.LinkedHashSet;
   78.34 +import java.util.List;
   78.35 +import java.util.Set;
   78.36 +import java.util.regex.Pattern;
   78.37 +import java.util.regex.PatternSyntaxException;
   78.38 +
   78.39 +/**
   78.40 + * Holds options from command line, validates them, combines with configuration and provides derived
   78.41 + * objects.
   78.42 + *
   78.43 + * @author Ing. František Kučera (frantovo.cz)
   78.44 + */
   78.45 +public class CLIOptions {
   78.46 +
   78.47 +	public static final String DEFAULT_NAME_PREFIX = ":";
   78.48 +	public static final String DEFAULT_NAME_SUFFIX = "(?=([^\\w]|$))";
   78.49 +	private String sql;
   78.50 +	private String databaseName;
   78.51 +	private final Set<String> databaseNamesToTest = new LinkedHashSet<>();
   78.52 +	private final Set<String> databaseNamesToListProperties = new LinkedHashSet<>();
   78.53 +	private final Set<String> formatterNamesToListProperties = new LinkedHashSet<>();
   78.54 +	private String namePrefix = DEFAULT_NAME_PREFIX;
   78.55 +	private String nameSuffix = DEFAULT_NAME_SUFFIX;
   78.56 +	private String formatterName;
   78.57 +	private boolean batch;
   78.58 +	private final Properties formatterProperties = new Properties();
   78.59 +	private final Properties databaseProperties = new Properties();
   78.60 +
   78.61 +	public enum MODE {
   78.62 +
   78.63 +		QUERY_NOW,
   78.64 +		PREPARE_BATCH,
   78.65 +		EXECUTE_BATCH,
   78.66 +		JUST_SHOW_INFO
   78.67 +	}
   78.68 +	private final List<NamedParameter> namedParameters = new ArrayList<>();
   78.69 +	private final List<Parameter> numberedParameters = new ArrayList<>();
   78.70 +	private final EnumSet<InfoType> showInfo = EnumSet.noneOf(InfoType.class);
   78.71 +
   78.72 +	public void validate() throws InvalidOptionsException {
   78.73 +		InvalidOptionsException e = new InvalidOptionsException();
   78.74 +
   78.75 +		MODE mode = getMode();
   78.76 +		if (mode == null) {
   78.77 +			e.addProblem(new InvalidOptionsException.OptionProblem("Invalid combination of DB, SQL and BATCH – please specify just 2 of this 3 options"));
   78.78 +		} else if (mode == MODE.JUST_SHOW_INFO) {
   78.79 +			if (!namedParameters.isEmpty()) {
   78.80 +				e.addProblem(new InvalidOptionsException.OptionProblem("Do not use named parameters if just showing info."));
   78.81 +			}
   78.82 +			if (!numberedParameters.isEmpty()) {
   78.83 +				e.addProblem(new InvalidOptionsException.OptionProblem("Do not use numbered parameters if just showing info."));
   78.84 +			}
   78.85 +			if (isNotEmpty(sql, false)) {
   78.86 +				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify SQL if just showing info."));
   78.87 +			}
   78.88 +			if (isNotEmpty(databaseName, false)) {
   78.89 +				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify database if just showing info."));
   78.90 +			}
   78.91 +			if (batch) {
   78.92 +				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify batch if just showing info."));
   78.93 +			}
   78.94 +			if (!equalz(namePrefix, DEFAULT_NAME_PREFIX)) {
   78.95 +				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify name prefix if just showing info."));
   78.96 +			}
   78.97 +			if (!equalz(nameSuffix, DEFAULT_NAME_SUFFIX)) {
   78.98 +				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify name suffix if just showing info."));
   78.99 +			}
  78.100 +			if (showInfo.contains(InfoType.CONNECTION) && databaseNamesToTest.isEmpty()) {
  78.101 +				e.addProblem(new InvalidOptionsException.OptionProblem("Please specify which database should be tested."));
  78.102 +			}
  78.103 +			if (showInfo.contains(InfoType.JDBC_PROPERTIES) && databaseNamesToListProperties.isEmpty()) {
  78.104 +				e.addProblem(new InvalidOptionsException.OptionProblem("Please specify for which database the properties should be listed."));
  78.105 +			}
  78.106 +		}
  78.107 +
  78.108 +		if (!namedParameters.isEmpty() && !numberedParameters.isEmpty()) {
  78.109 +			e.addProblem(new InvalidOptionsException.OptionProblem("Named and numbered parameters can not be used together in one command."));
  78.110 +		}
  78.111 +
  78.112 +		try {
  78.113 +			Pattern.compile(namePrefix + "test" + nameSuffix);
  78.114 +		} catch (PatternSyntaxException regexException) {
  78.115 +			e.addProblem(new InvalidOptionsException.OptionProblem("Ivalid regular expression in name prefix or suffix", regexException));
  78.116 +		}
  78.117 +
  78.118 +		if (e.hasProblems()) {
  78.119 +			throw e;
  78.120 +		}
  78.121 +	}
  78.122 +
  78.123 +	private boolean hasSql() {
  78.124 +		return isNotEmpty(getSql(), true);
  78.125 +	}
  78.126 +
  78.127 +	private boolean hasDb() {
  78.128 +		return isNotEmpty(getDatabaseName(), true);
  78.129 +	}
  78.130 +
  78.131 +	/**
  78.132 +	 * Depends on options: DB, BATCH, SQL
  78.133 +	 *
  78.134 +	 * @return mode | or null if options are not yet initialized or combination of options is
  78.135 +	 * invalid
  78.136 +	 */
  78.137 +	public MODE getMode() {
  78.138 +		if (hasDb() && !batch && hasSql()) {
  78.139 +			return MODE.QUERY_NOW;
  78.140 +		} else if (!hasDb() && batch && hasSql()) {
  78.141 +			return MODE.PREPARE_BATCH;
  78.142 +		} else if (hasDb() && batch && !hasSql()) {
  78.143 +			return MODE.EXECUTE_BATCH;
  78.144 +		} else {
  78.145 +			return showInfo.isEmpty() ? null : MODE.JUST_SHOW_INFO;
  78.146 +		}
  78.147 +	}
  78.148 +
  78.149 +	public String getSql() {
  78.150 +		return sql;
  78.151 +	}
  78.152 +
  78.153 +	public void setSql(String sql) {
  78.154 +		this.sql = sql;
  78.155 +	}
  78.156 +
  78.157 +	public String getDatabaseName() {
  78.158 +		return databaseName;
  78.159 +	}
  78.160 +
  78.161 +	public void setDatabaseName(String databaseName) {
  78.162 +		this.databaseName = databaseName;
  78.163 +	}
  78.164 +
  78.165 +	public void setBatch(boolean batch) {
  78.166 +		this.batch = batch;
  78.167 +	}
  78.168 +
  78.169 +	public Collection<NamedParameter> getNamedParameters() {
  78.170 +		return namedParameters;
  78.171 +	}
  78.172 +
  78.173 +	public List<Parameter> getNumberedParameters() {
  78.174 +		return numberedParameters;
  78.175 +	}
  78.176 +
  78.177 +	public void addNumberedParameter(Parameter p) {
  78.178 +		numberedParameters.add(p);
  78.179 +	}
  78.180 +
  78.181 +	public void addNamedParameter(NamedParameter p) {
  78.182 +		namedParameters.add(p);
  78.183 +	}
  78.184 +
  78.185 +	public Properties getDatabaseProperties() {
  78.186 +		return databaseProperties;
  78.187 +	}
  78.188 +
  78.189 +	public Properties getFormatterProperties() {
  78.190 +		return formatterProperties;
  78.191 +	}
  78.192 +
  78.193 +	public void addDatabaseProperty(Property p) {
  78.194 +		databaseProperties.add(p);
  78.195 +	}
  78.196 +
  78.197 +	public void addFormatterProperty(Property p) {
  78.198 +		formatterProperties.add(p);
  78.199 +	}
  78.200 +
  78.201 +	/**
  78.202 +	 * @return regular expression describing the name prefix
  78.203 +	 */
  78.204 +	public String getNamePrefix() {
  78.205 +		return namePrefix;
  78.206 +	}
  78.207 +
  78.208 +	/**
  78.209 +	 * @param namePrefix
  78.210 +	 * @see #getNamePrefix()
  78.211 +	 */
  78.212 +	public void setNamePrefix(String namePrefix) {
  78.213 +		this.namePrefix = namePrefix;
  78.214 +	}
  78.215 +
  78.216 +	/**
  78.217 +	 * @return regular expression describing the name prefix
  78.218 +	 */
  78.219 +	public String getNameSuffix() {
  78.220 +		return nameSuffix;
  78.221 +	}
  78.222 +
  78.223 +	/**
  78.224 +	 * @param nameSuffix
  78.225 +	 * @see #getNameSuffix()
  78.226 +	 */
  78.227 +	public void setNameSuffix(String nameSuffix) {
  78.228 +		this.nameSuffix = nameSuffix;
  78.229 +	}
  78.230 +
  78.231 +	public String getFormatterName() {
  78.232 +		return formatterName;
  78.233 +	}
  78.234 +
  78.235 +	public void setFormatterName(String formatterName) {
  78.236 +		this.formatterName = formatterName;
  78.237 +	}
  78.238 +
  78.239 +	public void addShowInfo(InfoType info) {
  78.240 +		showInfo.add(info);
  78.241 +	}
  78.242 +
  78.243 +	public EnumSet<InfoType> getShowInfo() {
  78.244 +		return showInfo;
  78.245 +	}
  78.246 +
  78.247 +	public Set<String> getDatabaseNamesToTest() {
  78.248 +		return databaseNamesToTest;
  78.249 +	}
  78.250 +
  78.251 +	public void addDatabaseNameToTest(String name) {
  78.252 +		databaseNamesToTest.add(name);
  78.253 +	}
  78.254 +
  78.255 +	public Set<String> getDatabaseNamesToListProperties() {
  78.256 +		return databaseNamesToListProperties;
  78.257 +	}
  78.258 +
  78.259 +	public void addDatabaseNameToListProperties(String name) {
  78.260 +		databaseNamesToListProperties.add(name);
  78.261 +	}
  78.262 +
  78.263 +	public Set<String> getFormatterNamesToListProperties() {
  78.264 +		return formatterNamesToListProperties;
  78.265 +	}
  78.266 +
  78.267 +	public void addFormatterNameToListProperties(String name) {
  78.268 +		formatterNamesToListProperties.add(name);
  78.269 +	}
  78.270 +
  78.271 +	public SQLCommand getSQLCommand() {
  78.272 +		if (namedParameters.isEmpty()) {
  78.273 +			return new SQLCommandNumbered(sql, numberedParameters);
  78.274 +		} else {
  78.275 +			return new SQLCommandNamed(sql, namedParameters, namePrefix, nameSuffix);
  78.276 +		}
  78.277 +	}
  78.278 +
  78.279 +	public OutputStream getOutputStream() {
  78.280 +		return System.out;
  78.281 +	}
  78.282 +
  78.283 +	public InputStream getInputStream() {
  78.284 +		return System.in;
  78.285 +	}
  78.286 +}
    79.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    79.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIParser.java	Mon Mar 04 20:15:24 2019 +0100
    79.3 @@ -0,0 +1,216 @@
    79.4 +/**
    79.5 + * SQL-DK
    79.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    79.7 + *
    79.8 + * This program is free software: you can redistribute it and/or modify
    79.9 + * it under the terms of the GNU General Public License as published by
   79.10 + * the Free Software Foundation, either version 3 of the License, or
   79.11 + * (at your option) any later version.
   79.12 + *
   79.13 + * This program is distributed in the hope that it will be useful,
   79.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   79.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   79.16 + * GNU General Public License for more details.
   79.17 + *
   79.18 + * You should have received a copy of the GNU General Public License
   79.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   79.20 + */
   79.21 +package info.globalcode.sql.dk;
   79.22 +
   79.23 +import static info.globalcode.sql.dk.Functions.readString;
   79.24 +import info.globalcode.sql.dk.InfoLister.InfoType;
   79.25 +import info.globalcode.sql.dk.configuration.Property;
   79.26 +import java.io.IOException;
   79.27 +import java.io.InputStream;
   79.28 +import java.util.ArrayList;
   79.29 +import java.util.HashMap;
   79.30 +import java.util.List;
   79.31 +import java.util.Map;
   79.32 +
   79.33 +/**
   79.34 + * Converts command line arguments from String array to object.
   79.35 + * Checks basic constraints (if only supported options are used and if they have correct number of
   79.36 + * parameters)
   79.37 + *
   79.38 + * @author Ing. František Kučera (frantovo.cz)
   79.39 + */
   79.40 +public class CLIParser {
   79.41 +
   79.42 +	public static final String TYPE_NAME_SEPARATOR = ":";
   79.43 +
   79.44 +	public CLIOptions parseOptions(String[] args, InputStream in) throws CLIParserException {
   79.45 +		CLIOptions options = new CLIOptions();
   79.46 +
   79.47 +		List<SQLType> numberedTypes = new ArrayList<>();
   79.48 +		Map<String, SQLType> namedTypes = new HashMap<>();
   79.49 +
   79.50 +		for (int i = 0; i < args.length; i++) {
   79.51 +			String arg = args[i];
   79.52 +			switch (arg) {
   79.53 +				case Tokens.TYPES:
   79.54 +					String typesString = fetchNext(args, ++i);
   79.55 +
   79.56 +					for (String oneType : typesString.split(",")) {
   79.57 +						int sepatratorIndex = oneType.indexOf(TYPE_NAME_SEPARATOR);
   79.58 +						if (sepatratorIndex == -1) {
   79.59 +							numberedTypes.add(getType(oneType.toUpperCase()));
   79.60 +						} else {
   79.61 +							String namePart = oneType.substring(0, sepatratorIndex).trim();
   79.62 +							String typePart = oneType.substring(sepatratorIndex + TYPE_NAME_SEPARATOR.length(), oneType.length());
   79.63 +							namedTypes.put(namePart, getType(typePart.toUpperCase()));
   79.64 +						}
   79.65 +					}
   79.66 +					break;
   79.67 +				case Tokens.NAME_PREFIX:
   79.68 +					options.setNamePrefix(fetchNext(args, ++i));
   79.69 +					break;
   79.70 +				case Tokens.NAME_SUFFIX:
   79.71 +					options.setNameSuffix(fetchNext(args, ++i));
   79.72 +					break;
   79.73 +				case Tokens.DB:
   79.74 +					options.setDatabaseName(fetchNext(args, ++i));
   79.75 +					break;
   79.76 +				case Tokens.SQL:
   79.77 +					options.setSql(fetchNext(args, ++i));
   79.78 +					break;
   79.79 +				case Tokens.SQL_IN:
   79.80 +					try {
   79.81 +						options.setSql(readString(in));
   79.82 +					} catch (IOException e) {
   79.83 +						throw new CLIParserException("Unable to read SQL from the input stream", e);
   79.84 +					}
   79.85 +					break;
   79.86 +				case Tokens.BATCH:
   79.87 +					options.setBatch(true);
   79.88 +					break;
   79.89 +				case Tokens.DATA: // --data is the last option
   79.90 +					for (i++; i < args.length; i++) {
   79.91 +						arg = args[i];
   79.92 +						Parameter parameter;
   79.93 +						if (numberedTypes.isEmpty()) {
   79.94 +							parameter = new Parameter(arg, null);
   79.95 +						} else {
   79.96 +							int paramIndex = options.getNumberedParameters().size();
   79.97 +							SQLType paramType;
   79.98 +							try {
   79.99 +								paramType = numberedTypes.get(paramIndex);
  79.100 +							} catch (IndexOutOfBoundsException e) {
  79.101 +								throw new CLIParserException("Missing type for parameter #" + paramIndex, e);
  79.102 +							} catch (NullPointerException e) {
  79.103 +								throw new CLIParserException("Invalid type definition for parameter #" + paramIndex, e);
  79.104 +							}
  79.105 +							parameter = new Parameter(arg, paramType);
  79.106 +						}
  79.107 +						options.addNumberedParameter(parameter);
  79.108 +					}
  79.109 +					break;
  79.110 +				case Tokens.DATA_NAMED:
  79.111 +					for (i++; i < args.length; i++) {
  79.112 +						String paramName = args[i];
  79.113 +						String paramValue = fetchNext(args, ++i);
  79.114 +						options.addNamedParameter(new NamedParameter(paramName, paramValue, namedTypes.get(paramName)));
  79.115 +					}
  79.116 +					break;
  79.117 +				case Tokens.FORMATTER:
  79.118 +					options.setFormatterName(fetchNext(args, ++i));
  79.119 +					break;
  79.120 +				case Tokens.DB_PROPERTY:
  79.121 +					options.addDatabaseProperty(new Property(fetchNext(args, ++i), fetchNext(args, ++i)));
  79.122 +					break;
  79.123 +				case Tokens.FORMATTER_PROPERTY:
  79.124 +					options.addFormatterProperty(new Property(fetchNext(args, ++i), fetchNext(args, ++i)));
  79.125 +					break;
  79.126 +				case Tokens.INFO_HELP:
  79.127 +					options.addShowInfo(InfoType.HELP);
  79.128 +					break;
  79.129 +				case Tokens.INFO_FORMATTERS:
  79.130 +					options.addShowInfo(InfoType.FORMATTERS);
  79.131 +					break;
  79.132 +				case Tokens.INFO_FORMATTER_PROPERTIES:
  79.133 +					options.addShowInfo(InfoType.FORMATTER_PROPERTIES);
  79.134 +					options.addFormatterNameToListProperties(fetchNext(args, ++i));
  79.135 +					break;
  79.136 +				case Tokens.INFO_LICENSE:
  79.137 +					options.addShowInfo(InfoType.LICENSE);
  79.138 +					break;
  79.139 +				case Tokens.INFO_JAVA_PROPERTIES:
  79.140 +					options.addShowInfo(InfoType.JAVA_PROPERTIES);
  79.141 +					break;
  79.142 +				case Tokens.INFO_ENVIRONMENT_VARIABLES:
  79.143 +					options.addShowInfo(InfoType.ENVIRONMENT_VARIABLES);
  79.144 +					break;
  79.145 +				case Tokens.INFO_TYPES:
  79.146 +					options.addShowInfo(InfoType.TYPES);
  79.147 +					break;
  79.148 +				case Tokens.INFO_VERSION:
  79.149 +					options.addShowInfo(InfoType.VERSION);
  79.150 +					break;
  79.151 +				case Tokens.INFO_JDBC_DRIVERS:
  79.152 +					options.addShowInfo(InfoType.JDBC_DRIVERS);
  79.153 +					break;
  79.154 +				case Tokens.INFO_JDBC_PROPERTIES:
  79.155 +					options.addShowInfo(InfoType.JDBC_PROPERTIES);
  79.156 +					options.addDatabaseNameToListProperties(fetchNext(args, ++i));
  79.157 +					break;
  79.158 +				case Tokens.INFO_DATABASES:
  79.159 +					options.addShowInfo(InfoType.DATABASES);
  79.160 +					break;
  79.161 +				case Tokens.INFO_CONNECTION:
  79.162 +					options.addShowInfo(InfoType.CONNECTION);
  79.163 +					options.addDatabaseNameToTest(fetchNext(args, ++i));
  79.164 +					break;
  79.165 +				default:
  79.166 +					throw new CLIParserException("Unknown option: " + arg);
  79.167 +			}
  79.168 +		}
  79.169 +		return options;
  79.170 +	}
  79.171 +
  79.172 +	private String fetchNext(String[] args, int index) throws CLIParserException {
  79.173 +		if (index < args.length) {
  79.174 +			return args[index];
  79.175 +		} else {
  79.176 +			throw new CLIParserException("Expecting value for option: " + args[index - 1]);
  79.177 +		}
  79.178 +	}
  79.179 +
  79.180 +	public static class Tokens {
  79.181 +
  79.182 +		// bash-completion:options:
  79.183 +		public static final String DB = "--db"; // bash-completion:option // help: database name
  79.184 +		public static final String DB_PROPERTY = "--db-property"; // bash-completion:option // help: name and value
  79.185 +		public static final String SQL = "--sql"; // bash-completion:option // help: SQL query/command
  79.186 +		public static final String SQL_IN = "--sql-in"; // bash-completion:option // help: SQL query/command
  79.187 +		public static final String BATCH = "--batch"; // bash-completion:option // help: batch mode (no argument)
  79.188 +		public static final String DATA = "--data"; // bash-completion:option // help: list of ordinal parameters
  79.189 +		public static final String DATA_NAMED = "--data-named"; // bash-completion:option // help: list of named parameters
  79.190 +		public static final String NAME_PREFIX = "--name-prefix"; // bash-completion:option // help: parameter name prefix – regular expression
  79.191 +		public static final String NAME_SUFFIX = "--name-suffix"; // bash-completion:option // help: parameter name suffix – regular expression
  79.192 +		public static final String TYPES = "--types"; // bash-completion:option // help: comma separated list of parameter types
  79.193 +		public static final String FORMATTER = "--formatter"; // bash-completion:option // help: name of the output formatter
  79.194 +		public static final String FORMATTER_PROPERTY = "--formatter-property"; // bash-completion:option // help: name and value
  79.195 +		public static final String INFO_HELP = "--help"; // bash-completion:option // help: print this help
  79.196 +		public static final String INFO_VERSION = "--version"; // bash-completion:option // help: print version info
  79.197 +		public static final String INFO_LICENSE = "--license"; // bash-completion:option // help: print license
  79.198 +		public static final String INFO_JAVA_PROPERTIES = "--list-java-properties"; // bash-completion:option // help: list of Java system properties
  79.199 +		public static final String INFO_ENVIRONMENT_VARIABLES = "--list-environment-variables"; // bash-completion:option // help: list of environment variables
  79.200 +		public static final String INFO_FORMATTERS = "--list-formatters"; // bash-completion:option // help: print list of available formatters
  79.201 +		public static final String INFO_FORMATTER_PROPERTIES = "--list-formatter-properties"; // bash-completion:option // help: print list of available properties for given formatter
  79.202 +		public static final String INFO_TYPES = "--list-types"; // bash-completion:option // help: print list of available data types
  79.203 +		public static final String INFO_JDBC_DRIVERS = "--list-jdbc-drivers"; // bash-completion:option // help: list of available JDBC drivers
  79.204 +		public static final String INFO_JDBC_PROPERTIES = "--list-jdbc-properties"; // bash-completion:option // help: list of available JDBC properties for given database
  79.205 +		public static final String INFO_DATABASES = "--list-databases"; // bash-completion:option // help: print list of configured databases
  79.206 +		public static final String INFO_CONNECTION = "--test-connection"; // bash-completion:option // help: test connection to particular database
  79.207 +
  79.208 +		private Tokens() {
  79.209 +		}
  79.210 +	}
  79.211 +
  79.212 +	private SQLType getType(String typeString) throws CLIParserException {
  79.213 +		try {
  79.214 +			return SQLType.valueOf(typeString.trim());
  79.215 +		} catch (IllegalArgumentException e) {
  79.216 +			throw new CLIParserException("Unsupported type: " + typeString, e);
  79.217 +		}
  79.218 +	}
  79.219 +}
    80.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    80.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIParserException.java	Mon Mar 04 20:15:24 2019 +0100
    80.3 @@ -0,0 +1,40 @@
    80.4 +/**
    80.5 + * SQL-DK
    80.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    80.7 + *
    80.8 + * This program is free software: you can redistribute it and/or modify
    80.9 + * it under the terms of the GNU General Public License as published by
   80.10 + * the Free Software Foundation, either version 3 of the License, or
   80.11 + * (at your option) any later version.
   80.12 + *
   80.13 + * This program is distributed in the hope that it will be useful,
   80.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   80.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   80.16 + * GNU General Public License for more details.
   80.17 + *
   80.18 + * You should have received a copy of the GNU General Public License
   80.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   80.20 + */
   80.21 +package info.globalcode.sql.dk;
   80.22 +
   80.23 +/**
   80.24 + *
   80.25 + * @author Ing. František Kučera (frantovo.cz)
   80.26 + */
   80.27 +public class CLIParserException extends DKException {
   80.28 +
   80.29 +	public CLIParserException() {
   80.30 +	}
   80.31 +
   80.32 +	public CLIParserException(String message) {
   80.33 +		super(message);
   80.34 +	}
   80.35 +
   80.36 +	public CLIParserException(Throwable cause) {
   80.37 +		super(cause);
   80.38 +	}
   80.39 +
   80.40 +	public CLIParserException(String message, Throwable cause) {
   80.41 +		super(message, cause);
   80.42 +	}
   80.43 +}
    81.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    81.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIStarter.java	Mon Mar 04 20:15:24 2019 +0100
    81.3 @@ -0,0 +1,274 @@
    81.4 +/**
    81.5 + * SQL-DK
    81.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    81.7 + *
    81.8 + * This program is free software: you can redistribute it and/or modify
    81.9 + * it under the terms of the GNU General Public License as published by
   81.10 + * the Free Software Foundation, either version 3 of the License, or
   81.11 + * (at your option) any later version.
   81.12 + *
   81.13 + * This program is distributed in the hope that it will be useful,
   81.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   81.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   81.16 + * GNU General Public License for more details.
   81.17 + *
   81.18 + * You should have received a copy of the GNU General Public License
   81.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   81.20 + */
   81.21 +package info.globalcode.sql.dk;
   81.22 +
   81.23 +import info.globalcode.sql.dk.configuration.ConfigurationProvider;
   81.24 +import info.globalcode.sql.dk.CLIOptions.MODE;
   81.25 +import info.globalcode.sql.dk.batch.Batch;
   81.26 +import info.globalcode.sql.dk.batch.BatchDecoder;
   81.27 +import info.globalcode.sql.dk.batch.BatchException;
   81.28 +import info.globalcode.sql.dk.batch.BatchEncoder;
   81.29 +import info.globalcode.sql.dk.configuration.Configuration;
   81.30 +import info.globalcode.sql.dk.configuration.ConfigurationException;
   81.31 +import info.globalcode.sql.dk.configuration.DatabaseDefinition;
   81.32 +import info.globalcode.sql.dk.configuration.FormatterDefinition;
   81.33 +import info.globalcode.sql.dk.configuration.Loader;
   81.34 +import info.globalcode.sql.dk.configuration.NameIdentified;
   81.35 +import info.globalcode.sql.dk.configuration.PropertyDeclaration;
   81.36 +import info.globalcode.sql.dk.formatting.Formatter;
   81.37 +import info.globalcode.sql.dk.formatting.FormatterContext;
   81.38 +import info.globalcode.sql.dk.formatting.FormatterException;
   81.39 +import info.globalcode.sql.dk.jmx.ConnectionManagement;
   81.40 +import info.globalcode.sql.dk.jmx.ManagementUtils;
   81.41 +import java.io.File;
   81.42 +import java.io.FileNotFoundException;
   81.43 +import java.io.IOException;
   81.44 +import java.io.PrintStream;
   81.45 +import java.io.PrintWriter;
   81.46 +import java.sql.SQLException;
   81.47 +import java.util.Collection;
   81.48 +import java.util.Collections;
   81.49 +import java.util.List;
   81.50 +import java.util.logging.Level;
   81.51 +import java.util.logging.LogRecord;
   81.52 +import java.util.logging.Logger;
   81.53 +
   81.54 +/**
   81.55 + * Entry point of the command line interface of SQL-DK.
   81.56 + *
   81.57 + * @author Ing. František Kučera (frantovo.cz)
   81.58 + */
   81.59 +public class CLIStarter implements ConfigurationProvider {
   81.60 +
   81.61 +	// help:exit-codes
   81.62 +	public static final int EXIT_SUCCESS = 0; // doc:success
   81.63 +	public static final int EXIT_UNEXPECTED_ERROR = 1; // doc:unexpected error (probably bug)
   81.64 +	// 2 is reserved: http://www.tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF
   81.65 +	public static final int EXIT_SQL_ERROR = 3; // doc:SQL error
   81.66 +	public static final int EXIT_CLI_PARSE_ERROR = 4; // doc:CLI options parse error
   81.67 +	public static final int EXIT_CLI_VALIDATE_ERROR = 5; // doc:CLI options validation error
   81.68 +	public static final int EXIT_CONFIGURATION_ERROR = 6; // doc:configuration error
   81.69 +	public static final int EXIT_FORMATTING_ERROR = 7; // doc:formatting error
   81.70 +	public static final int EXIT_BATCH_ERROR = 8; // doc:batch error
   81.71 +	private static final Logger log = Logger.getLogger(CLIStarter.class.getName());
   81.72 +	private final CLIOptions options;
   81.73 +	private final Loader configurationLoader = new Loader();
   81.74 +	private Configuration configuration;
   81.75 +
   81.76 +	public static void main(String[] args) {
   81.77 +		log.log(Level.FINE, "Starting " + Constants.PROGRAM_NAME);
   81.78 +		int exitCode;
   81.79 +
   81.80 +		if (args.length == 0) {
   81.81 +			args = new String[]{CLIParser.Tokens.INFO_HELP};
   81.82 +		}
   81.83 +
   81.84 +		try {
   81.85 +			CLIParser parser = new CLIParser();
   81.86 +			CLIOptions options = parser.parseOptions(args, System.in);
   81.87 +			options.validate();
   81.88 +			CLIStarter starter = new CLIStarter(options);
   81.89 +			starter.installDefaultConfiguration();
   81.90 +			starter.process();
   81.91 +			log.log(Level.FINE, "All done");
   81.92 +			exitCode = EXIT_SUCCESS;
   81.93 +		} catch (CLIParserException e) {
   81.94 +			log.log(Level.SEVERE, "Unable to parse CLI options", e);
   81.95 +			exitCode = EXIT_CLI_PARSE_ERROR;
   81.96 +		} catch (InvalidOptionsException e) {
   81.97 +			log.log(Level.SEVERE, "Invalid CLI options", e);
   81.98 +			for (InvalidOptionsException.OptionProblem p : e.getProblems()) {
   81.99 +				LogRecord r = new LogRecord(Level.SEVERE, "Option problem: {0}");
  81.100 +				r.setThrown(p.getException());
  81.101 +				r.setParameters(new Object[]{p.getDescription()});
  81.102 +				log.log(r);
  81.103 +			}
  81.104 +			exitCode = EXIT_CLI_VALIDATE_ERROR;
  81.105 +		} catch (ConfigurationException e) {
  81.106 +			log.log(Level.SEVERE, "Configuration problem", e);
  81.107 +			exitCode = EXIT_CONFIGURATION_ERROR;
  81.108 +		} catch (SQLException e) {
  81.109 +			log.log(Level.SEVERE, "SQL problem", e);
  81.110 +			exitCode = EXIT_SQL_ERROR;
  81.111 +		} catch (FormatterException e) {
  81.112 +			log.log(Level.SEVERE, "Formatting problem", e);
  81.113 +			exitCode = EXIT_FORMATTING_ERROR;
  81.114 +		} catch (BatchException e) {
  81.115 +			log.log(Level.SEVERE, "Batch problem", e);
  81.116 +			exitCode = EXIT_BATCH_ERROR;
  81.117 +		}
  81.118 +
  81.119 +		System.exit(exitCode);
  81.120 +	}
  81.121 +
  81.122 +	public CLIStarter(CLIOptions options) {
  81.123 +		this.options = options;
  81.124 +	}
  81.125 +
  81.126 +	private void process() throws ConfigurationException, SQLException, FormatterException, BatchException {
  81.127 +		MODE mode = options.getMode();
  81.128 +
  81.129 +		/** Show info */
  81.130 +		if (!options.getShowInfo().isEmpty()) {
  81.131 +			PrintStream infoOut = mode == MODE.JUST_SHOW_INFO ? System.out : System.err;
  81.132 +			InfoLister infoLister = new InfoLister(infoOut, this, options);
  81.133 +			infoLister.showInfo();
  81.134 +		}
  81.135 +
  81.136 +		switch (mode) {
  81.137 +			case QUERY_NOW:
  81.138 +				processQueryNow();
  81.139 +				break;
  81.140 +			case PREPARE_BATCH:
  81.141 +				processPrepareBatch();
  81.142 +				break;
  81.143 +			case EXECUTE_BATCH:
  81.144 +				processExecuteBatch();
  81.145 +				break;
  81.146 +			case JUST_SHOW_INFO:
  81.147 +				// already done above
  81.148 +				break;
  81.149 +			default:
  81.150 +				log.log(Level.SEVERE, "Unsupported mode: {0}", mode);
  81.151 +				break;
  81.152 +		}
  81.153 +
  81.154 +		generateBashCompletion();
  81.155 +	}
  81.156 +
  81.157 +	private void processQueryNow() throws ConfigurationException, SQLException, FormatterException {
  81.158 +		DatabaseDefinition dd = getConfiguration().getDatabase(options.getDatabaseName());
  81.159 +		FormatterDefinition fd = configuration.getFormatter(options.getFormatterName());
  81.160 +		ConnectionManagement jmxBean = ManagementUtils.registerMBean(dd.getName());
  81.161 +
  81.162 +		try (DatabaseConnection c = dd.connect(options.getDatabaseProperties(), jmxBean)) {
  81.163 +			log.log(Level.FINE, "Database connected");
  81.164 +			try (Formatter f = fd.getInstance(new FormatterContext(options.getOutputStream(), options.getFormatterProperties()))) {
  81.165 +				c.executeQuery(options.getSQLCommand(), f);
  81.166 +			}
  81.167 +		}
  81.168 +	}
  81.169 +
  81.170 +	private void processPrepareBatch() throws BatchException {
  81.171 +		BatchEncoder enc = new BatchEncoder();
  81.172 +		int length = enc.encode(options.getSQLCommand(), options.getOutputStream());
  81.173 +		log.log(Level.FINE, "Prepared batch size: {0} bytes", length);
  81.174 +	}
  81.175 +
  81.176 +	private void processExecuteBatch() throws ConfigurationException, SQLException, FormatterException, BatchException {
  81.177 +		BatchDecoder dec = new BatchDecoder();
  81.178 +		Batch b = dec.decode(options.getInputStream());
  81.179 +
  81.180 +		DatabaseDefinition dd = getConfiguration().getDatabase(options.getDatabaseName());
  81.181 +		FormatterDefinition fd = configuration.getFormatter(options.getFormatterName());
  81.182 +		ConnectionManagement jmxBean = ManagementUtils.registerMBean(dd.getName());
  81.183 +
  81.184 +		try (DatabaseConnection c = dd.connect(options.getDatabaseProperties(), jmxBean)) {
  81.185 +			log.log(Level.FINE, "Database connected");
  81.186 +			try (Formatter f = fd.getInstance(new FormatterContext(options.getOutputStream(), options.getFormatterProperties()))) {
  81.187 +				c.executeBatch(b, f);
  81.188 +			}
  81.189 +		}
  81.190 +	}
  81.191 +
  81.192 +	@Override
  81.193 +	public Configuration getConfiguration() throws ConfigurationException {
  81.194 +		if (configuration == null) {
  81.195 +			configuration = configurationLoader.loadConfiguration();
  81.196 +		}
  81.197 +		return configuration;
  81.198 +	}
  81.199 +
  81.200 +	private void installDefaultConfiguration() throws ConfigurationException {
  81.201 +		Constants.DIR.mkdir();
  81.202 +
  81.203 +		if (Constants.CONFIG_FILE.exists()) {
  81.204 +			log.log(Level.FINER, "Config file already exists: {0}", Constants.CONFIG_FILE);
  81.205 +		} else {
  81.206 +			try {
  81.207 +				Functions.installResource(Constants.EXAMPLE_CONFIG_FILE, Constants.CONFIG_FILE);
  81.208 +				log.log(Level.FINE, "Installing default config file: {0}", Constants.CONFIG_FILE);
  81.209 +			} catch (IOException e) {
  81.210 +				throw new ConfigurationException("Unable to write example configuration to " + Constants.CONFIG_FILE, e);
  81.211 +			}
  81.212 +		}
  81.213 +	}
  81.214 +
  81.215 +	private void generateBashCompletion() {
  81.216 +		if (configuration == null) {
  81.217 +			log.log(Level.FINER, "Not writing Bash completion helper files. In order to generate these files please run some command which requires configuration.");
  81.218 +		} else {
  81.219 +			try {
  81.220 +				File dir = new File(Constants.DIR, "bash-completion");
  81.221 +				dir.mkdir();
  81.222 +				writeBashCompletionHelperFile(configuration.getDatabases(), new File(dir, "databases"));
  81.223 +				writeBashCompletionHelperFile(configuration.getAllFormatters(), new File(dir, "formatters"));
  81.224 +				writeBashCompletionHelperFileForFormatterProperties(new File(dir, "formatter-properties"));
  81.225 +			} catch (Exception e) {
  81.226 +				log.log(Level.WARNING, "Unable to generate Bash completion helper files", e);
  81.227 +			}
  81.228 +		}
  81.229 +	}
  81.230 +
  81.231 +	private void writeBashCompletionHelperFile(Collection<? extends NameIdentified> items, File target) throws FileNotFoundException {
  81.232 +		if (Constants.CONFIG_FILE.lastModified() > target.lastModified()) {
  81.233 +			try (PrintWriter fw = new PrintWriter(target)) {
  81.234 +				for (NameIdentified dd : items) {
  81.235 +					fw.println(dd.getName());
  81.236 +				}
  81.237 +				fw.close();
  81.238 +				log.log(Level.FINE, "Bash completion helper file was written: {0}", target);
  81.239 +			}
  81.240 +		} else {
  81.241 +			log.log(Level.FINER, "Not writing Bash completion helper file: {0} because configuration {1} has not been changed", new Object[]{target, Constants.CONFIG_FILE});
  81.242 +		}
  81.243 +	}
  81.244 +
  81.245 +	private void writeBashCompletionHelperFileForFormatterProperties(File formattersDir) throws ClassNotFoundException, FileNotFoundException {
  81.246 +		if (Constants.CONFIG_FILE.lastModified() > formattersDir.lastModified()) {
  81.247 +			// TODO: delete old directory
  81.248 +			formattersDir.mkdir();
  81.249 +			for (FormatterDefinition fd : configuration.getAllFormatters()) {
  81.250 +				File formatterDir = new File(formattersDir, fd.getName());
  81.251 +				formatterDir.mkdir();
  81.252 +
  81.253 +				Class<Formatter> formatterClass = (Class<Formatter>) Class.forName(fd.getClassName());
  81.254 +				List<Class<? extends Formatter>> hierarchy = Functions.getClassHierarchy(formatterClass, Formatter.class);
  81.255 +				Collections.reverse(hierarchy);
  81.256 +				for (Class<? extends Formatter> c : hierarchy) {
  81.257 +					for (PropertyDeclaration p : Functions.getPropertyDeclarations(c)) {
  81.258 +						File propertyDir = new File(formatterDir, p.name());
  81.259 +						propertyDir.mkdir();
  81.260 +						File choicesFile = new File(propertyDir, "choices");
  81.261 +						try (PrintWriter fw = new PrintWriter(choicesFile)) {
  81.262 +							// TODO: refactor, move
  81.263 +							if (p.type() == Boolean.class) {
  81.264 +								fw.println("true");
  81.265 +								fw.println("false");
  81.266 +							}
  81.267 +						}
  81.268 +					}
  81.269 +				}
  81.270 +			}
  81.271 +			log.log(Level.FINE, "Bash completion helper files was written in: {0}", formattersDir);
  81.272 +		} else {
  81.273 +			log.log(Level.FINER, "Not writing Bash completion helper directory: {0} because configuration {1} has not been changed", new Object[]{formattersDir, Constants.CONFIG_FILE});
  81.274 +		}
  81.275 +
  81.276 +	}
  81.277 +}
    82.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    82.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/ColorfulPrintWriter.java	Mon Mar 04 20:15:24 2019 +0100
    82.3 @@ -0,0 +1,358 @@
    82.4 +/**
    82.5 + * SQL-DK
    82.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    82.7 + *
    82.8 + * This program is free software: you can redistribute it and/or modify
    82.9 + * it under the terms of the GNU General Public License as published by
   82.10 + * the Free Software Foundation, either version 3 of the License, or
   82.11 + * (at your option) any later version.
   82.12 + *
   82.13 + * This program is distributed in the hope that it will be useful,
   82.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   82.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   82.16 + * GNU General Public License for more details.
   82.17 + *
   82.18 + * You should have received a copy of the GNU General Public License
   82.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   82.20 + */
   82.21 +package info.globalcode.sql.dk;
   82.22 +
   82.23 +import java.io.File;
   82.24 +import java.io.FileNotFoundException;
   82.25 +import java.io.OutputStream;
   82.26 +import java.io.PrintWriter;
   82.27 +import java.io.UnsupportedEncodingException;
   82.28 +import java.io.Writer;
   82.29 +import java.util.EnumSet;
   82.30 +
   82.31 +/**
   82.32 + * PrintWriter with convenience methods for printing color and formatted text.
   82.33 + *
   82.34 + * Uses ANSI Escape Sequences.
   82.35 + * See: http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html
   82.36 + *
   82.37 + * @author Ing. František Kučera (frantovo.cz)
   82.38 + */
   82.39 +public class ColorfulPrintWriter extends PrintWriter {
   82.40 +
   82.41 +	public enum TerminalColor {
   82.42 +
   82.43 +		Black(30, 40),
   82.44 +		Red(31, 41),
   82.45 +		Green(32, 42),
   82.46 +		Yellow(33, 43),
   82.47 +		Blue(34, 44),
   82.48 +		Magenta(35, 45),
   82.49 +		Cyan(36, 46),
   82.50 +		White(37, 47);
   82.51 +		private final int foregroundCode;
   82.52 +		private final int backgroundCode;
   82.53 +
   82.54 +		private TerminalColor(int foregroundCode, int backgroundCode) {
   82.55 +			this.foregroundCode = foregroundCode;
   82.56 +			this.backgroundCode = backgroundCode;
   82.57 +		}
   82.58 +
   82.59 +		public int getForegroundCode() {
   82.60 +			return foregroundCode;
   82.61 +		}
   82.62 +
   82.63 +		public int getBackgroundCode() {
   82.64 +			return backgroundCode;
   82.65 +		}
   82.66 +	}
   82.67 +
   82.68 +	public enum TerminalStyle {
   82.69 +
   82.70 +		Reset(0),
   82.71 +		Bright(1),
   82.72 +		Dim(2),
   82.73 +		Underscore(4),
   82.74 +		Blink(5),
   82.75 +		Reverse(7),
   82.76 +		Hidden(8);
   82.77 +		private int code;
   82.78 +
   82.79 +		private TerminalStyle(int code) {
   82.80 +			this.code = code;
   82.81 +		}
   82.82 +
   82.83 +		public int getCode() {
   82.84 +			return code;
   82.85 +		}
   82.86 +	}
   82.87 +	private final boolean COLOR_ENABLED;
   82.88 +	private boolean colorful = true;
   82.89 +
   82.90 +	public void setStyle(TerminalStyle style) {
   82.91 +		setStyle(EnumSet.of(style));
   82.92 +	}
   82.93 +
   82.94 +	public void setStyle(EnumSet<TerminalStyle> styles) {
   82.95 +		printCodes(getStyleCodes(styles));
   82.96 +	}
   82.97 +
   82.98 +	private static int[] getStyleCodes(EnumSet<TerminalStyle> styles) {
   82.99 +		int[] array = new int[styles.size()];
  82.100 +		int i = 0;
  82.101 +		for (TerminalStyle s : styles) {
  82.102 +			array[i++] = s.getCode();
  82.103 +		}
  82.104 +		return array;
  82.105 +	}
  82.106 +
  82.107 +	/**
  82.108 +	 * Print (usually audible) bell code (\007, \a, ^G)
  82.109 +	 */
  82.110 +	public void bell() {
  82.111 +		print("\007");
  82.112 +	}
  82.113 +
  82.114 +	/**
  82.115 +	 * Eat the last character
  82.116 +	 */
  82.117 +	public void backspace() {
  82.118 +		print("\b");
  82.119 +	}
  82.120 +
  82.121 +	/**
  82.122 +	 * Eat n last characters
  82.123 +	 *
  82.124 +	 * @param count n
  82.125 +	 */
  82.126 +	public void backspace(int count) {
  82.127 +		for (int i = 0; i < count; i++) {
  82.128 +			backspace();
  82.129 +		}
  82.130 +	}
  82.131 +
  82.132 +	/**
  82.133 +	 * With 100 ms delay and all colors.
  82.134 +	 *
  82.135 +	 * @see #printRainbow(java.lang.String, int,
  82.136 +	 * info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor[])
  82.137 +	 */
  82.138 +	public void printRainbow(String string) {
  82.139 +		printRainbow(string, 100);
  82.140 +	}
  82.141 +
  82.142 +	/**
  82.143 +	 * With all colors.
  82.144 +	 *
  82.145 +	 * @see #printRainbow(java.lang.String, int,
  82.146 +	 * info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor[])
  82.147 +	 */
  82.148 +	public void printRainbow(String string, int delay) {
  82.149 +		printRainbow(string, delay, TerminalColor.values());
  82.150 +	}
  82.151 +
  82.152 +	/**
  82.153 +	 * Prints rainbow text – (re)writes same text subsequently in given colors and then in default
  82.154 +	 * color.
  82.155 +	 *
  82.156 +	 * @param string text to be printed, should not contain \n new line (then rainbow does not work
  82.157 +	 * – use println() after printRainbow() instead)
  82.158 +	 * @param delay delay between rewrites
  82.159 +	 * @param colors list of colors to be used
  82.160 +	 */
  82.161 +	public void printRainbow(String string, int delay, TerminalColor... colors) {
  82.162 +		for (TerminalColor c : colors) {
  82.163 +			print(c, string);
  82.164 +			try {
  82.165 +				Thread.sleep(delay);
  82.166 +			} catch (InterruptedException e) {
  82.167 +				// no time to sleep
  82.168 +				break;
  82.169 +			}
  82.170 +			backspace(string.length());
  82.171 +			flush();
  82.172 +		}
  82.173 +		print(string);
  82.174 +	}
  82.175 +
  82.176 +	public void setForegroundColor(TerminalColor color) {
  82.177 +		printCodes(color.getForegroundCode());
  82.178 +	}
  82.179 +
  82.180 +	public void setBackgroundColor(TerminalColor color) {
  82.181 +		printCodes(color.getBackgroundCode());
  82.182 +	}
  82.183 +
  82.184 +	public void print(TerminalColor foregroundColor, String string) {
  82.185 +		setForegroundColor(foregroundColor);
  82.186 +		print(string);
  82.187 +		resetAll();
  82.188 +	}
  82.189 +
  82.190 +	public void println(TerminalColor foregroundColor, String string) {
  82.191 +		print(foregroundColor, string);
  82.192 +		println();
  82.193 +	}
  82.194 +
  82.195 +	public void print(TerminalColor foregroundColor, TerminalColor backgroundColor, String string) {
  82.196 +		setForegroundColor(foregroundColor);
  82.197 +		setBackgroundColor(backgroundColor);
  82.198 +		print(string);
  82.199 +		resetAll();
  82.200 +	}
  82.201 +
  82.202 +	public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, String string) {
  82.203 +		print(foregroundColor, backgroundColor, string);
  82.204 +		println();
  82.205 +	}
  82.206 +
  82.207 +	public void print(TerminalColor foregroundColor, TerminalColor backgroundColor, EnumSet<TerminalStyle> styles, String string) {
  82.208 +		setForegroundColor(foregroundColor);
  82.209 +		setBackgroundColor(backgroundColor);
  82.210 +		setStyle(styles);
  82.211 +		print(string);
  82.212 +		resetAll();
  82.213 +	}
  82.214 +
  82.215 +	public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, EnumSet<TerminalStyle> styles, String string) {
  82.216 +		print(foregroundColor, backgroundColor, styles, string);
  82.217 +		println();
  82.218 +	}
  82.219 +
  82.220 +	public void print(TerminalColor foregroundColor, TerminalColor backgroundColor, TerminalStyle style, String string) {
  82.221 +		print(foregroundColor, backgroundColor, EnumSet.of(style), string);
  82.222 +	}
  82.223 +
  82.224 +	public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, TerminalStyle style, String string) {
  82.225 +		print(foregroundColor, backgroundColor, style, string);
  82.226 +		println();
  82.227 +	}
  82.228 +
  82.229 +	public void print(TerminalColor foregroundColor, EnumSet<TerminalStyle> styles, String string) {
  82.230 +		setForegroundColor(foregroundColor);
  82.231 +		setStyle(styles);
  82.232 +		print(string);
  82.233 +		resetAll();
  82.234 +	}
  82.235 +
  82.236 +	public void println(TerminalColor foregroundColor, EnumSet<TerminalStyle> styles, String string) {
  82.237 +		print(foregroundColor, styles, string);
  82.238 +		println();
  82.239 +	}
  82.240 +
  82.241 +	public void print(TerminalColor foregroundColor, TerminalStyle style, String string) {
  82.242 +		print(foregroundColor, EnumSet.of(style), string);
  82.243 +	}
  82.244 +
  82.245 +	public void println(TerminalColor foregroundColor, TerminalStyle style, String string) {
  82.246 +		print(foregroundColor, style, string);
  82.247 +		println();
  82.248 +	}
  82.249 +
  82.250 +	public void print(EnumSet<TerminalStyle> styles, String string) {
  82.251 +		setStyle(styles);
  82.252 +		print(string);
  82.253 +		resetAll();
  82.254 +	}
  82.255 +
  82.256 +	public void println(EnumSet<TerminalStyle> styles, String string) {
  82.257 +		print(styles, string);
  82.258 +		println();
  82.259 +	}
  82.260 +
  82.261 +	public void print(TerminalStyle style, String string) {
  82.262 +		print(EnumSet.of(style), string);
  82.263 +	}
  82.264 +
  82.265 +	public void println(TerminalStyle style, String string) {
  82.266 +		print(style, string);
  82.267 +		println();
  82.268 +	}
  82.269 +
  82.270 +	public void resetAll() {
  82.271 +		printCodes(TerminalStyle.Reset.code);
  82.272 +	}
  82.273 +
  82.274 +	private void printCodes(int... codes) {
  82.275 +		if (COLOR_ENABLED && colorful) {
  82.276 +			print("\033[");
  82.277 +			for (int i = 0; i < codes.length; i++) {
  82.278 +				print(codes[i]);
  82.279 +				if (i < codes.length - 1 && codes.length > 1) {
  82.280 +					print(";");
  82.281 +				}
  82.282 +			}
  82.283 +			print("m");
  82.284 +		}
  82.285 +	}
  82.286 +
  82.287 +	/**
  82.288 +	 * Colors can be switched on/off during usage of this writer.
  82.289 +	 *
  82.290 +	 * @return whether colors are currently turned on
  82.291 +	 * @see #isColorEnabled()
  82.292 +	 */
  82.293 +	public boolean isColorful() {
  82.294 +		return colorful;
  82.295 +	}
  82.296 +
  82.297 +	/**
  82.298 +	 * Collors might be definitively disabled in constructor. If not, they can be turned on/off
  82.299 +	 * during usage of this writer by {@linkplain #setColorful(boolean)}
  82.300 +	 *
  82.301 +	 * @return whether colors are allowed for this instance of this class
  82.302 +	 * @see #isColorful()
  82.303 +	 */
  82.304 +	public boolean isColorEnabled() {
  82.305 +		return COLOR_ENABLED;
  82.306 +	}
  82.307 +
  82.308 +	/**
  82.309 +	 * @see #isColorful()
  82.310 +	 * @see #isColorEnabled()
  82.311 +	 */
  82.312 +	public void setColorful(boolean colorful) {
  82.313 +		this.colorful = colorful;
  82.314 +	}
  82.315 +
  82.316 +	public ColorfulPrintWriter(File file) throws FileNotFoundException {
  82.317 +		super(file);
  82.318 +		COLOR_ENABLED = true;
  82.319 +	}
  82.320 +
  82.321 +	public ColorfulPrintWriter(OutputStream out) {
  82.322 +		super(out);
  82.323 +		COLOR_ENABLED = true;
  82.324 +	}
  82.325 +
  82.326 +	public ColorfulPrintWriter(String fileName) throws FileNotFoundException {
  82.327 +		super(fileName);
  82.328 +		COLOR_ENABLED = true;
  82.329 +	}
  82.330 +
  82.331 +	public ColorfulPrintWriter(Writer out) {
  82.332 +		super(out);
  82.333 +		COLOR_ENABLED = true;
  82.334 +	}
  82.335 +
  82.336 +	public ColorfulPrintWriter(File file, String csn) throws FileNotFoundException, UnsupportedEncodingException {
  82.337 +		super(file, csn);
  82.338 +		COLOR_ENABLED = true;
  82.339 +	}
  82.340 +
  82.341 +	/**
  82.342 +	 * @param colorEnabled colors might be definitively disabled by this option – this might be more
  82.343 +	 * optimalizable than dynamic turning off colors by {@linkplain #setColorful(boolean)} which is
  82.344 +	 * not definitive (colors can be turned on during live of this instance). This might be useful
  82.345 +	 * if you need an instance of this class but don't need colors at all.
  82.346 +	 */
  82.347 +	public ColorfulPrintWriter(OutputStream out, boolean autoFlush, boolean colorEnabled) {
  82.348 +		super(out, autoFlush);
  82.349 +		COLOR_ENABLED = colorEnabled;
  82.350 +	}
  82.351 +
  82.352 +	public ColorfulPrintWriter(String fileName, String csn) throws FileNotFoundException, UnsupportedEncodingException {
  82.353 +		super(fileName, csn);
  82.354 +		COLOR_ENABLED = true;
  82.355 +	}
  82.356 +
  82.357 +	public ColorfulPrintWriter(Writer out, boolean autoFlush) {
  82.358 +		super(out, autoFlush);
  82.359 +		COLOR_ENABLED = true;
  82.360 +	}
  82.361 +}
    83.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    83.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/Constants.java	Mon Mar 04 20:15:24 2019 +0100
    83.3 @@ -0,0 +1,44 @@
    83.4 +/**
    83.5 + * SQL-DK
    83.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    83.7 + *
    83.8 + * This program is free software: you can redistribute it and/or modify
    83.9 + * it under the terms of the GNU General Public License as published by
   83.10 + * the Free Software Foundation, either version 3 of the License, or
   83.11 + * (at your option) any later version.
   83.12 + *
   83.13 + * This program is distributed in the hope that it will be useful,
   83.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   83.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   83.16 + * GNU General Public License for more details.
   83.17 + *
   83.18 + * You should have received a copy of the GNU General Public License
   83.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   83.20 + */
   83.21 +package info.globalcode.sql.dk;
   83.22 +
   83.23 +import java.io.File;
   83.24 +
   83.25 +/**
   83.26 + *
   83.27 + * @author Ing. František Kučera (frantovo.cz)
   83.28 + */
   83.29 +public class Constants {
   83.30 +
   83.31 +	public static final String PROGRAM_NAME = "SQL-DK";
   83.32 +	public static final String JAVA_PACKAGE = Constants.class.getPackage().getName();
   83.33 +	public static final String WEBSITE = "https://sql-dk.globalcode.info/";
   83.34 +	public static final String LICENSE_FILE = "info/globalcode/sql/dk/license.txt";
   83.35 +	public static final String VERSION_FILE = "info/globalcode/sql/dk/version.txt";
   83.36 +	public static final String HELP_FILE = "info/globalcode/sql/dk/help.txt";
   83.37 +	private static final File HOME_DIR = new File(System.getProperty("user.home"));
   83.38 +	/**
   83.39 +	 * Directory where config and log files are stored.
   83.40 +	 */
   83.41 +	public static final File DIR = new File(HOME_DIR, ".sql-dk"); // bash-completion:dir
   83.42 +	public static final File CONFIG_FILE = new File(DIR, "config.xml");
   83.43 +	public static final String EXAMPLE_CONFIG_FILE = "info/globalcode/sql/dk/example-config.xml";
   83.44 +
   83.45 +	private Constants() {
   83.46 +	}
   83.47 +}
    84.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    84.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/DKException.java	Mon Mar 04 20:15:24 2019 +0100
    84.3 @@ -0,0 +1,41 @@
    84.4 +/**
    84.5 + * SQL-DK
    84.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    84.7 + *
    84.8 + * This program is free software: you can redistribute it and/or modify
    84.9 + * it under the terms of the GNU General Public License as published by
   84.10 + * the Free Software Foundation, either version 3 of the License, or
   84.11 + * (at your option) any later version.
   84.12 + *
   84.13 + * This program is distributed in the hope that it will be useful,
   84.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   84.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   84.16 + * GNU General Public License for more details.
   84.17 + *
   84.18 + * You should have received a copy of the GNU General Public License
   84.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   84.20 + */
   84.21 +package info.globalcode.sql.dk;
   84.22 +
   84.23 +/**
   84.24 + * TODO: GEC
   84.25 + *
   84.26 + * @author Ing. František Kučera (frantovo.cz)
   84.27 + */
   84.28 +public class DKException extends Exception {
   84.29 +
   84.30 +	public DKException() {
   84.31 +	}
   84.32 +
   84.33 +	public DKException(String message) {
   84.34 +		super(message);
   84.35 +	}
   84.36 +
   84.37 +	public DKException(Throwable cause) {
   84.38 +		super(cause);
   84.39 +	}
   84.40 +
   84.41 +	public DKException(String message, Throwable cause) {
   84.42 +		super(message, cause);
   84.43 +	}
   84.44 +}
    85.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    85.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/DatabaseConnection.java	Mon Mar 04 20:15:24 2019 +0100
    85.3 @@ -0,0 +1,192 @@
    85.4 +/**
    85.5 + * SQL-DK
    85.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    85.7 + *
    85.8 + * This program is free software: you can redistribute it and/or modify
    85.9 + * it under the terms of the GNU General Public License as published by
   85.10 + * the Free Software Foundation, either version 3 of the License, or
   85.11 + * (at your option) any later version.
   85.12 + *
   85.13 + * This program is distributed in the hope that it will be useful,
   85.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   85.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   85.16 + * GNU General Public License for more details.
   85.17 + *
   85.18 + * You should have received a copy of the GNU General Public License
   85.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   85.20 + */
   85.21 +package info.globalcode.sql.dk;
   85.22 +
   85.23 +import static info.globalcode.sql.dk.jmx.ConnectionManagement.incrementCounter;
   85.24 +import static info.globalcode.sql.dk.jmx.ConnectionManagement.resetCounter;
   85.25 +import info.globalcode.sql.dk.batch.Batch;
   85.26 +import info.globalcode.sql.dk.batch.BatchException;
   85.27 +import info.globalcode.sql.dk.configuration.DatabaseDefinition;
   85.28 +import info.globalcode.sql.dk.configuration.Loader;
   85.29 +import info.globalcode.sql.dk.configuration.Properties;
   85.30 +import info.globalcode.sql.dk.formatting.ColumnsHeader;
   85.31 +import info.globalcode.sql.dk.formatting.Formatter;
   85.32 +import info.globalcode.sql.dk.jmx.ConnectionManagement;
   85.33 +import info.globalcode.sql.dk.jmx.ConnectionManagement.COUNTER;
   85.34 +import java.sql.Connection;
   85.35 +import java.sql.PreparedStatement;
   85.36 +import java.sql.ResultSet;
   85.37 +import java.sql.SQLException;
   85.38 +import java.sql.SQLWarning;
   85.39 +import java.util.logging.Level;
   85.40 +import java.util.logging.Logger;
   85.41 +
   85.42 +/**
   85.43 + * Represents connected database. Is derived from {@linkplain DatabaseDefinition}.
   85.44 + * Wraps {@linkplain Connection}.
   85.45 + *
   85.46 + * Is responsible for executing {@linkplain SQLCommand} and passing results to the
   85.47 + * {@linkplain Formatter}.
   85.48 + *
   85.49 + * @author Ing. František Kučera (frantovo.cz)
   85.50 + */
   85.51 +public class DatabaseConnection implements AutoCloseable {
   85.52 +
   85.53 +	private static final Logger log = Logger.getLogger(DatabaseConnection.class.getName());
   85.54 +	public static final String JDBC_PROPERTY_USER = "user";
   85.55 +	public static final String JDBC_PROPERTY_PASSWORD = "password";
   85.56 +	private final DatabaseDefinition databaseDefinition;
   85.57 +	private final Connection connection;
   85.58 +	private final Properties properties;
   85.59 +	/**
   85.60 +	 * Could be null = JMX is disabled → must check, see functions in
   85.61 +	 * {@linkplain ConnectionManagement}
   85.62 +	 */
   85.63 +	private final ConnectionManagement connectionMBean;
   85.64 +
   85.65 +	/**
   85.66 +	 *
   85.67 +	 * @param databaseDefinition DB url, name, password etc.
   85.68 +	 * @param properties additional properties from CLI
   85.69 +	 * @param connectionMBean JMX management bean | null = disabled JMX reporting
   85.70 +	 * @throws SQLException
   85.71 +	 */
   85.72 +	public DatabaseConnection(DatabaseDefinition databaseDefinition, Properties properties, ConnectionManagement connectionMBean) throws SQLException {
   85.73 +		this.databaseDefinition = databaseDefinition;
   85.74 +		this.properties = properties;
   85.75 +		this.connectionMBean = connectionMBean;
   85.76 +		this.connection = Loader.jdbcConnect(databaseDefinition, properties);
   85.77 +	}
   85.78 +
   85.79 +	public void executeQuery(SQLCommand sqlCommand, Formatter formatter) throws SQLException {
   85.80 +		formatter.writeStartBatch();
   85.81 +		formatter.writeStartDatabase(databaseDefinition);
   85.82 +		formatter.writeStartStatement();
   85.83 +		formatter.writeQuery(sqlCommand.getQuery());
   85.84 +		formatter.writeParameters(sqlCommand.getParameters());
   85.85 +		processCommand(sqlCommand, formatter);
   85.86 +		formatter.writeEndStatement();
   85.87 +		formatter.writeEndDatabase();
   85.88 +		formatter.writeEndBatch();
   85.89 +	}
   85.90 +
   85.91 +	public void executeBatch(Batch batch, Formatter formatter) throws SQLException, BatchException {
   85.92 +		formatter.writeStartBatch();
   85.93 +		formatter.writeStartDatabase(databaseDefinition);
   85.94 +		while (batch.hasNext()) {
   85.95 +			SQLCommand sqlCommand = batch.next();
   85.96 +			formatter.writeStartStatement();
   85.97 +			formatter.writeQuery(sqlCommand.getQuery());
   85.98 +			formatter.writeParameters(sqlCommand.getParameters());
   85.99 +			processCommand(sqlCommand, formatter);
  85.100 +			formatter.writeEndStatement();
  85.101 +		}
  85.102 +		formatter.writeEndDatabase();
  85.103 +		formatter.writeEndBatch();
  85.104 +	}
  85.105 +
  85.106 +	private void processCommand(SQLCommand sqlCommand, Formatter formatter) throws SQLException {
  85.107 +		incrementCounter(connectionMBean, COUNTER.COMMAND);
  85.108 +		resetCounter(connectionMBean, COUNTER.RECORD_CURRENT);
  85.109 +
  85.110 +		try (PreparedStatement ps = sqlCommand.prepareStatement(connection)) {
  85.111 +			log.log(Level.FINE, "Statement prepared");
  85.112 +			sqlCommand.parametrize(ps);
  85.113 +
  85.114 +			boolean isRS = ps.execute();
  85.115 +			log.log(Level.FINE, "Statement executed");
  85.116 +			if (isRS) {
  85.117 +				try (ResultSet rs = ps.getResultSet()) {
  85.118 +					processResultSet(rs, formatter);
  85.119 +				}
  85.120 +			} else {
  85.121 +				processUpdateResult(ps, formatter);
  85.122 +			}
  85.123 +			logWarnings(ps);
  85.124 +
  85.125 +			while (ps.getMoreResults() || ps.getUpdateCount() > -1) {
  85.126 +				ResultSet rs = ps.getResultSet();
  85.127 +				if (rs == null) {
  85.128 +					processUpdateResult(ps, formatter);
  85.129 +				} else {
  85.130 +					processResultSet(rs, formatter);
  85.131 +					rs.close();
  85.132 +				}
  85.133 +				logWarnings(ps);
  85.134 +			}
  85.135 +		}
  85.136 +	}
  85.137 +
  85.138 +	private void processUpdateResult(PreparedStatement ps, Formatter formatter) throws SQLException {
  85.139 +		formatter.writeUpdatesResult(ps.getUpdateCount());
  85.140 +	}
  85.141 +
  85.142 +	private void processResultSet(ResultSet rs, Formatter formatter) throws SQLException {
  85.143 +		formatter.writeStartResultSet(new ColumnsHeader(rs.getMetaData()));
  85.144 +
  85.145 +		int columnCount = rs.getMetaData().getColumnCount();
  85.146 +
  85.147 +		while (rs.next()) {
  85.148 +			incrementCounter(connectionMBean, COUNTER.RECORD_CURRENT);
  85.149 +			incrementCounter(connectionMBean, COUNTER.RECORD_TOTAL);
  85.150 +
  85.151 +			formatter.writeStartRow();
  85.152 +
  85.153 +			for (int i = 1; i <= columnCount; i++) {
  85.154 +				formatter.writeColumnValue(rs.getObject(i));
  85.155 +			}
  85.156 +
  85.157 +			formatter.writeEndRow();
  85.158 +		}
  85.159 +
  85.160 +		formatter.writeEndResultSet();
  85.161 +	}
  85.162 +
  85.163 +	private void logWarnings(PreparedStatement ps) throws SQLException {
  85.164 +		SQLWarning w = ps.getWarnings();
  85.165 +		while (w != null) {
  85.166 +			log.log(Level.WARNING, "SQL: {0}", w.getLocalizedMessage());
  85.167 +			w = w.getNextWarning();
  85.168 +		}
  85.169 +		ps.clearWarnings();
  85.170 +	}
  85.171 +
  85.172 +	/**
  85.173 +	 * Tests if this connection is live.
  85.174 +	 *
  85.175 +	 * @return true if test was successful
  85.176 +	 * @throws SQLException if test fails
  85.177 +	 */
  85.178 +	public boolean test() throws SQLException {
  85.179 +		connection.getAutoCommit();
  85.180 +		return true;
  85.181 +	}
  85.182 +
  85.183 +	public String getProductName() throws SQLException {
  85.184 +		return connection.getMetaData().getDatabaseProductName();
  85.185 +	}
  85.186 +
  85.187 +	public String getProductVersion() throws SQLException {
  85.188 +		return connection.getMetaData().getDatabaseProductVersion();
  85.189 +	}
  85.190 +
  85.191 +	@Override
  85.192 +	public void close() throws SQLException {
  85.193 +		connection.close();
  85.194 +	}
  85.195 +}
    86.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    86.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/Functions.java	Mon Mar 04 20:15:24 2019 +0100
    86.3 @@ -0,0 +1,254 @@
    86.4 +/**
    86.5 + * SQL-DK
    86.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    86.7 + *
    86.8 + * This program is free software: you can redistribute it and/or modify
    86.9 + * it under the terms of the GNU General Public License as published by
   86.10 + * the Free Software Foundation, either version 3 of the License, or
   86.11 + * (at your option) any later version.
   86.12 + *
   86.13 + * This program is distributed in the hope that it will be useful,
   86.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   86.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   86.16 + * GNU General Public License for more details.
   86.17 + *
   86.18 + * You should have received a copy of the GNU General Public License
   86.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   86.20 + */
   86.21 +package info.globalcode.sql.dk;
   86.22 +
   86.23 +import info.globalcode.sql.dk.configuration.NameIdentified;
   86.24 +import info.globalcode.sql.dk.configuration.PropertyDeclaration;
   86.25 +import info.globalcode.sql.dk.configuration.PropertyDeclarations;
   86.26 +import info.globalcode.sql.dk.formatting.Formatter;
   86.27 +import java.io.BufferedReader;
   86.28 +import java.io.File;
   86.29 +import java.io.IOException;
   86.30 +import java.io.InputStream;
   86.31 +import java.io.InputStreamReader;
   86.32 +import java.io.PrintWriter;
   86.33 +import java.util.ArrayList;
   86.34 +import java.util.Arrays;
   86.35 +import java.util.Collection;
   86.36 +import java.util.Collections;
   86.37 +import java.util.List;
   86.38 +import java.util.Map;
   86.39 +import java.util.regex.Matcher;
   86.40 +import java.util.regex.Pattern;
   86.41 +
   86.42 +/**
   86.43 + *
   86.44 + * @author Ing. František Kučera (frantovo.cz)
   86.45 + */
   86.46 +public class Functions {
   86.47 +
   86.48 +	private static final String NBSP = " ";
   86.49 +	private static final Pattern WHITESPACE_TO_REPLACE = Pattern.compile("\\n|\\r|\\t|" + NBSP);
   86.50 +
   86.51 +	private Functions() {
   86.52 +	}
   86.53 +
   86.54 +	public static boolean equalz(Object a, Object b) {
   86.55 +		return a == null ? b == null : a.equals(b);
   86.56 +	}
   86.57 +
   86.58 +	/**
   86.59 +	 *
   86.60 +	 * @param text String to be examinated
   86.61 +	 * @param trim whether text should be trimmed before examination
   86.62 +	 * @return whether text is not empty and one or more characters long (after prospective trim)
   86.63 +	 */
   86.64 +	public static boolean isEmpty(String text, boolean trim) {
   86.65 +		if (text == null) {
   86.66 +			return true;
   86.67 +		} else {
   86.68 +			if (trim) {
   86.69 +				text = text.trim();
   86.70 +			}
   86.71 +			return text.isEmpty();
   86.72 +		}
   86.73 +	}
   86.74 +
   86.75 +	/**
   86.76 +	 * @see #isEmpty(java.lang.String, boolean)
   86.77 +	 */
   86.78 +	public static boolean isNotEmpty(String text, boolean trim) {
   86.79 +		return !isEmpty(text, trim);
   86.80 +	}
   86.81 +
   86.82 +	public boolean isEmpty(Collection c) {
   86.83 +		return c == null || c.isEmpty();
   86.84 +	}
   86.85 +
   86.86 +	public boolean isNotEmpty(Collection c) {
   86.87 +		return !isEmpty(c);
   86.88 +	}
   86.89 +
   86.90 +	public boolean isEmpty(Map m) {
   86.91 +		return m == null || m.isEmpty();
   86.92 +	}
   86.93 +
   86.94 +	public boolean isNotEmpty(Map m) {
   86.95 +		return !isEmpty(m);
   86.96 +	}
   86.97 +
   86.98 +	/**
   86.99 +	 * @return empty collection if given one is null | or the original one
  86.100 +	 */
  86.101 +	public static <T> Collection<T> notNull(Collection<T> c) {
  86.102 +		if (c == null) {
  86.103 +			return Collections.emptyList();
  86.104 +		} else {
  86.105 +			return c;
  86.106 +		}
  86.107 +	}
  86.108 +
  86.109 +	public static <T extends NameIdentified> T findByName(Collection<T> collection, String name) {
  86.110 +		for (T element : notNull(collection)) {
  86.111 +			if (element != null && equalz(element.getName(), name)) {
  86.112 +				return element;
  86.113 +			}
  86.114 +		}
  86.115 +
  86.116 +		return null;
  86.117 +	}
  86.118 +
  86.119 +	/**
  86.120 +	 * Copy file from Java resources to file system.
  86.121 +	 */
  86.122 +	public static void installResource(String resourceName, File target) throws IOException {
  86.123 +		try (BufferedReader reader = new BufferedReader(new InputStreamReader(Functions.class.getClassLoader().getResourceAsStream(resourceName)))) {
  86.124 +			try (PrintWriter writer = new PrintWriter(target)) {
  86.125 +				while (true) {
  86.126 +					String line = reader.readLine();
  86.127 +					if (line == null) {
  86.128 +						break;
  86.129 +					} else {
  86.130 +						writer.println(line);
  86.131 +					}
  86.132 +				}
  86.133 +			}
  86.134 +		}
  86.135 +	}
  86.136 +
  86.137 +	public static String rpad(String s, int n) {
  86.138 +		if (n > 0) {
  86.139 +			return String.format("%1$-" + n + "s", s);
  86.140 +		} else {
  86.141 +			return s;
  86.142 +		}
  86.143 +	}
  86.144 +
  86.145 +	public static String lpad(String s, int n) {
  86.146 +		if (n > 0) {
  86.147 +			return String.format("%1$" + n + "s", s);
  86.148 +		} else {
  86.149 +			return s;
  86.150 +		}
  86.151 +	}
  86.152 +
  86.153 +	public static String repeat(char ch, int count) {
  86.154 +		char[] array = new char[count];
  86.155 +		Arrays.fill(array, ch);
  86.156 +		return new String(array);
  86.157 +	}
  86.158 +	private final static char[] HEX_ALPHABET = "0123456789abcdef".toCharArray();
  86.159 +
  86.160 +	public static String toHex(byte[] bytes) {
  86.161 +		char[] hexChars = new char[bytes.length * 2];
  86.162 +		for (int j = 0; j < bytes.length; j++) {
  86.163 +			int v = bytes[j] & 0xFF;
  86.164 +			hexChars[j * 2] = HEX_ALPHABET[v >>> 4];
  86.165 +			hexChars[j * 2 + 1] = HEX_ALPHABET[v & 0x0F];
  86.166 +		}
  86.167 +		return new String(hexChars);
  86.168 +	}
  86.169 +
  86.170 +	public static String readString(InputStream in) throws IOException {
  86.171 +		try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
  86.172 +			StringBuilder result = new StringBuilder();
  86.173 +			for (String line = br.readLine(); line != null; line = br.readLine()) {
  86.174 +				result.append(line);
  86.175 +				result.append('\n');
  86.176 +			}
  86.177 +			return result.toString();
  86.178 +		}
  86.179 +	}
  86.180 +
  86.181 +	/**
  86.182 +	 * @param <P> type of the last parent
  86.183 +	 * @param <T> type of the examined class
  86.184 +	 * @param type examined class
  86.185 +	 * @param lastParent the last parent type to stop at
  86.186 +	 * @return list of types starting with <code>type</code> and ending with <code>lastParent</code>
  86.187 +	 */
  86.188 +	public static <P, T extends P> List<Class<? extends P>> getClassHierarchy(Class<T> type, Class<P> lastParent) {
  86.189 +		List<Class<? extends P>> hierarchy = new ArrayList<>();
  86.190 +
  86.191 +		for (Class current = type; current != null && lastParent.isAssignableFrom(current); current = current.getSuperclass()) {
  86.192 +			hierarchy.add(current);
  86.193 +		}
  86.194 +
  86.195 +		return hierarchy;
  86.196 +	}
  86.197 +
  86.198 +	public static PropertyDeclaration[] getPropertyDeclarations(Class<? extends Formatter> formatterClass) {
  86.199 +		PropertyDeclarations properties = formatterClass.getAnnotation(PropertyDeclarations.class);
  86.200 +
  86.201 +		if (properties == null) {
  86.202 +			PropertyDeclaration p = formatterClass.getAnnotation(PropertyDeclaration.class);
  86.203 +			return p == null ? new PropertyDeclaration[]{} : new PropertyDeclaration[]{p};
  86.204 +		} else {
  86.205 +			return properties.value();
  86.206 +		}
  86.207 +	}
  86.208 +
  86.209 +	/**
  86.210 +	 * TODO: support background or styles and move to ColorfulPrintWriter
  86.211 +	 *
  86.212 +	 * @param out
  86.213 +	 * @param valueString
  86.214 +	 * @param basicColor
  86.215 +	 * @param escapeColor
  86.216 +	 */
  86.217 +	public static void printValueWithWhitespaceReplaced(ColorfulPrintWriter out, String valueString, ColorfulPrintWriter.TerminalColor basicColor, ColorfulPrintWriter.TerminalColor escapeColor) {
  86.218 +
  86.219 +		Matcher m = WHITESPACE_TO_REPLACE.matcher(valueString);
  86.220 +
  86.221 +		int start = 0;
  86.222 +
  86.223 +		while (m.find(start)) {
  86.224 +
  86.225 +			printColorOrNot(out, basicColor, valueString.substring(start, m.start()));
  86.226 +
  86.227 +			switch (m.group()) {
  86.228 +				case "\n":
  86.229 +					out.print(escapeColor, "↲");
  86.230 +					break;
  86.231 +				case "\r":
  86.232 +					out.print(escapeColor, "⏎");
  86.233 +					break;
  86.234 +				case "\t":
  86.235 +					out.print(escapeColor, "↹");
  86.236 +					break;
  86.237 +				case NBSP:
  86.238 +					out.print(escapeColor, "⎵");
  86.239 +					break;
  86.240 +				default:
  86.241 +					throw new IllegalStateException("Unexpected whitespace token: „" + m.group() + "“");
  86.242 +			}
  86.243 +
  86.244 +			start = m.end();
  86.245 +		}
  86.246 +
  86.247 +		printColorOrNot(out, basicColor, valueString.substring(start, valueString.length()));
  86.248 +	}
  86.249 +
  86.250 +	private static void printColorOrNot(ColorfulPrintWriter out, ColorfulPrintWriter.TerminalColor color, String text) {
  86.251 +		if (color == null) {
  86.252 +			out.print(text);
  86.253 +		} else {
  86.254 +			out.print(color, text);
  86.255 +		}
  86.256 +	}
  86.257 +}
    87.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    87.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/InfoLister.java	Mon Mar 04 20:15:24 2019 +0100
    87.3 @@ -0,0 +1,673 @@
    87.4 +/**
    87.5 + * SQL-DK
    87.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    87.7 + *
    87.8 + * This program is free software: you can redistribute it and/or modify
    87.9 + * it under the terms of the GNU General Public License as published by
   87.10 + * the Free Software Foundation, either version 3 of the License, or
   87.11 + * (at your option) any later version.
   87.12 + *
   87.13 + * This program is distributed in the hope that it will be useful,
   87.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   87.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   87.16 + * GNU General Public License for more details.
   87.17 + *
   87.18 + * You should have received a copy of the GNU General Public License
   87.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   87.20 + */
   87.21 +package info.globalcode.sql.dk;
   87.22 +
   87.23 +import info.globalcode.sql.dk.configuration.CommandArgument;
   87.24 +import info.globalcode.sql.dk.configuration.Configuration;
   87.25 +import info.globalcode.sql.dk.configuration.ConfigurationException;
   87.26 +import info.globalcode.sql.dk.configuration.ConfigurationProvider;
   87.27 +import info.globalcode.sql.dk.configuration.DatabaseDefinition;
   87.28 +import info.globalcode.sql.dk.configuration.FormatterDefinition;
   87.29 +import info.globalcode.sql.dk.configuration.Properties;
   87.30 +import info.globalcode.sql.dk.configuration.Property;
   87.31 +import info.globalcode.sql.dk.configuration.PropertyDeclaration;
   87.32 +import info.globalcode.sql.dk.configuration.TunnelDefinition;
   87.33 +import info.globalcode.sql.dk.formatting.ColumnsHeader;
   87.34 +import info.globalcode.sql.dk.formatting.CommonProperties;
   87.35 +import info.globalcode.sql.dk.formatting.FakeSqlArray;
   87.36 +import info.globalcode.sql.dk.formatting.Formatter;
   87.37 +import info.globalcode.sql.dk.formatting.FormatterContext;
   87.38 +import info.globalcode.sql.dk.formatting.FormatterException;
   87.39 +import java.io.BufferedReader;
   87.40 +import java.io.ByteArrayOutputStream;
   87.41 +import java.io.InputStreamReader;
   87.42 +import java.io.PrintStream;
   87.43 +import java.sql.Array;
   87.44 +import java.sql.Driver;
   87.45 +import java.sql.DriverManager;
   87.46 +import java.sql.DriverPropertyInfo;
   87.47 +import java.sql.SQLException;
   87.48 +import java.util.ArrayList;
   87.49 +import java.util.Collections;
   87.50 +import java.util.Comparator;
   87.51 +import java.util.EnumSet;
   87.52 +import java.util.HashMap;
   87.53 +import java.util.HashSet;
   87.54 +import java.util.List;
   87.55 +import java.util.Map;
   87.56 +import java.util.Map.Entry;
   87.57 +import java.util.ServiceLoader;
   87.58 +import java.util.Set;
   87.59 +import java.util.concurrent.ExecutorService;
   87.60 +import java.util.concurrent.Executors;
   87.61 +import java.util.concurrent.TimeUnit;
   87.62 +import java.util.logging.Level;
   87.63 +import java.util.logging.LogRecord;
   87.64 +import java.util.logging.Logger;
   87.65 +import javax.sql.rowset.RowSetMetaDataImpl;
   87.66 +
   87.67 +/**
   87.68 + * Displays info like help, version etc.
   87.69 + *
   87.70 + * @author Ing. František Kučera (frantovo.cz)
   87.71 + */
   87.72 +public class InfoLister {
   87.73 +
   87.74 +	private static final Logger log = Logger.getLogger(InfoLister.class.getName());
   87.75 +	/**
   87.76 +	 * Fake database name for output formatting
   87.77 +	 */
   87.78 +	public static final String CONFIG_DB_NAME = "sqldk_configuration";
   87.79 +	private final PrintStream out;
   87.80 +	private final ConfigurationProvider configurationProvider;
   87.81 +	private final CLIOptions options;
   87.82 +	private Formatter formatter;
   87.83 +
   87.84 +	public InfoLister(PrintStream out, ConfigurationProvider configurationProvider, CLIOptions options) {
   87.85 +		this.out = out;
   87.86 +		this.configurationProvider = configurationProvider;
   87.87 +		this.options = options;
   87.88 +	}
   87.89 +
   87.90 +	public void showInfo() throws ConfigurationException, FormatterException {
   87.91 +		EnumSet<InfoType> commands = options.getShowInfo();
   87.92 +
   87.93 +		boolean formattinNeeded = false;
   87.94 +
   87.95 +		for (InfoType infoType : commands) {
   87.96 +			switch (infoType) {
   87.97 +				case CONNECTION:
   87.98 +				case JDBC_DRIVERS:
   87.99 +				case JDBC_PROPERTIES:
  87.100 +				case DATABASES:
  87.101 +				case FORMATTERS:
  87.102 +				case FORMATTER_PROPERTIES:
  87.103 +				case TYPES:
  87.104 +				case JAVA_PROPERTIES:
  87.105 +				case ENVIRONMENT_VARIABLES:
  87.106 +					formattinNeeded = true;
  87.107 +					break;
  87.108 +			}
  87.109 +		}
  87.110 +
  87.111 +		if (formattinNeeded) {
  87.112 +			try (Formatter f = getFormatter()) {
  87.113 +				formatter = f;
  87.114 +				formatter.writeStartBatch();
  87.115 +				DatabaseDefinition dd = new DatabaseDefinition();
  87.116 +				dd.setName(CONFIG_DB_NAME);
  87.117 +				formatter.writeStartDatabase(dd);
  87.118 +				showInfos(commands);
  87.119 +				formatter.writeEndDatabase();
  87.120 +				formatter.writeEndBatch();
  87.121 +				formatter.close();
  87.122 +			}
  87.123 +		} else {
  87.124 +			showInfos(commands);
  87.125 +		}
  87.126 +	}
  87.127 +
  87.128 +	private void showInfos(EnumSet<InfoType> commands) throws ConfigurationException, FormatterException {
  87.129 +		for (InfoType infoType : commands) {
  87.130 +			infoType.showInfo(this);
  87.131 +		}
  87.132 +	}
  87.133 +
  87.134 +	private void listJavaProperties() throws FormatterException, ConfigurationException {
  87.135 +		ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("value", SQLType.VARCHAR));
  87.136 +		List<Object[]> data = new ArrayList<>();
  87.137 +		for (Entry<Object, Object> e : System.getProperties().entrySet()) {
  87.138 +			data.add(new Object[]{e.getKey(), e.getValue()});
  87.139 +		}
  87.140 +		printTable(formatter, header, "-- Java system properties", null, data, 0);
  87.141 +	}
  87.142 +
  87.143 +	private void listEnvironmentVariables() throws FormatterException, ConfigurationException {
  87.144 +		ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("value", SQLType.VARCHAR));
  87.145 +		List<Object[]> data = new ArrayList<>();
  87.146 +		for (Entry<String, String> e : System.getenv().entrySet()) {
  87.147 +			data.add(new Object[]{e.getKey(), e.getValue()});
  87.148 +		}
  87.149 +		printTable(formatter, header, "-- environment variables", null, data, 0);
  87.150 +	}
  87.151 +
  87.152 +	private void listFormatters() throws ConfigurationException, FormatterException {
  87.153 +		ColumnsHeader header = constructHeader(
  87.154 +				new HeaderField("name", SQLType.VARCHAR),
  87.155 +				new HeaderField("built_in", SQLType.BOOLEAN),
  87.156 +				new HeaderField("default", SQLType.BOOLEAN),
  87.157 +				new HeaderField("class_name", SQLType.VARCHAR),
  87.158 +				new HeaderField("valid", SQLType.BOOLEAN));
  87.159 +		List<Object[]> data = new ArrayList<>();
  87.160 +
  87.161 +		String defaultFormatter = configurationProvider.getConfiguration().getDefaultFormatter();
  87.162 +		defaultFormatter = defaultFormatter == null ? Configuration.DEFAULT_FORMATTER : defaultFormatter;
  87.163 +
  87.164 +		for (FormatterDefinition fd : configurationProvider.getConfiguration().getBuildInFormatters()) {
  87.165 +			data.add(new Object[]{fd.getName(), true, defaultFormatter.equals(fd.getName()), fd.getClassName(), isInstantiable(fd)});
  87.166 +		}
  87.167 +
  87.168 +		for (FormatterDefinition fd : configurationProvider.getConfiguration().getFormatters()) {
  87.169 +			data.add(new Object[]{fd.getName(), false, defaultFormatter.equals(fd.getName()), fd.getClassName(), isInstantiable(fd)});
  87.170 +		}
  87.171 +
  87.172 +		printTable(formatter, header, "-- configured and built-in output formatters", null, data);
  87.173 +	}
  87.174 +
  87.175 +	private boolean isInstantiable(FormatterDefinition fd) {
  87.176 +		try {
  87.177 +			try (ByteArrayOutputStream testStream = new ByteArrayOutputStream()) {
  87.178 +				fd.getInstance(new FormatterContext(testStream, new Properties(0)));
  87.179 +				return true;
  87.180 +			}
  87.181 +		} catch (Exception e) {
  87.182 +			log.log(Level.SEVERE, "Unable to create an instance of formatter: " + fd.getName(), e);
  87.183 +			return false;
  87.184 +		}
  87.185 +	}
  87.186 +
  87.187 +	private void listFormatterProperties() throws FormatterException, ConfigurationException {
  87.188 +		for (String formatterName : options.getFormatterNamesToListProperties()) {
  87.189 +			listFormatterProperties(formatterName);
  87.190 +		}
  87.191 +	}
  87.192 +
  87.193 +	private void listFormatterProperties(String formatterName) throws FormatterException, ConfigurationException {
  87.194 +		FormatterDefinition fd = configurationProvider.getConfiguration().getFormatter(formatterName);
  87.195 +		try {
  87.196 +
  87.197 +			// currently only for debugging purposes
  87.198 +			// TODO: introduce --info-lister-property or generic filtering capability in printTable() ?
  87.199 +			boolean printDeclaredIn = options.getFormatterProperties().getBoolean("InfoLister:print:declared_in", false);
  87.200 +
  87.201 +			List<HeaderField> headerFields = new ArrayList<>();
  87.202 +			headerFields.add(new HeaderField("name", SQLType.VARCHAR));
  87.203 +			headerFields.add(new HeaderField("type", SQLType.VARCHAR));
  87.204 +			headerFields.add(new HeaderField("default", SQLType.VARCHAR));
  87.205 +			headerFields.add(new HeaderField("description", SQLType.VARCHAR));
  87.206 +			if (printDeclaredIn) {
  87.207 +				headerFields.add(new HeaderField("declared_in", SQLType.VARCHAR));
  87.208 +			}
  87.209 +
  87.210 +			ColumnsHeader header = constructHeader(headerFields.toArray(new HeaderField[0]));
  87.211 +
  87.212 +			Map<String, Object[]> data = new HashMap<>();
  87.213 +			Class<Formatter> formatterClass = (Class<Formatter>) Class.forName(fd.getClassName());
  87.214 +			List<Class<? extends Formatter>> hierarchy = Functions.getClassHierarchy(formatterClass, Formatter.class);
  87.215 +			Collections.reverse(hierarchy);
  87.216 +			hierarchy.stream().forEach((c) -> {
  87.217 +				for (PropertyDeclaration p : Functions.getPropertyDeclarations(c)) {
  87.218 +					data.put(p.name(), propertyDeclarationToRow(p, c, printDeclaredIn));
  87.219 +				}
  87.220 +			});
  87.221 +
  87.222 +			List<Parameter> parameters = new ArrayList<>();
  87.223 +			parameters.add(new NamedParameter("formatter", formatterName, SQLType.VARCHAR));
  87.224 +
  87.225 +			printTable(formatter, header, "-- formatter properties", parameters, new ArrayList<>(data.values()));
  87.226 +		} catch (ClassNotFoundException e) {
  87.227 +			throw new ConfigurationException("Unable to find class " + fd.getClassName() + " of formatter" + fd.getName(), e);
  87.228 +		}
  87.229 +	}
  87.230 +
  87.231 +	private static Object[] propertyDeclarationToRow(PropertyDeclaration p, Class formatterClass, boolean printDeclaredIn) {
  87.232 +		List list = new ArrayList();
  87.233 +
  87.234 +		list.add(p.name());
  87.235 +		list.add(CommonProperties.getSimpleTypeName(p.type()));
  87.236 +		list.add(p.defaultValue());
  87.237 +		list.add(p.description());
  87.238 +		if (printDeclaredIn) {
  87.239 +			list.add(formatterClass.getName());
  87.240 +		}
  87.241 +
  87.242 +		return list.toArray();
  87.243 +	}
  87.244 +
  87.245 +	private void listTypes() throws FormatterException, ConfigurationException {
  87.246 +		ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("code", SQLType.INTEGER));
  87.247 +		List<Object[]> data = new ArrayList<>();
  87.248 +		for (SQLType sqlType : SQLType.values()) {
  87.249 +			data.add(new Object[]{sqlType.name(), sqlType.getCode()});
  87.250 +		}
  87.251 +		printTable(formatter, header, "-- data types", null, data);
  87.252 +		log.log(Level.INFO, "Type names in --types option are case insensitive");
  87.253 +	}
  87.254 +
  87.255 +	private void listDatabases() throws ConfigurationException, FormatterException {
  87.256 +		ColumnsHeader header = constructHeader(
  87.257 +				new HeaderField("database_name", SQLType.VARCHAR),
  87.258 +				new HeaderField("user_name", SQLType.VARCHAR),
  87.259 +				new HeaderField("database_url", SQLType.VARCHAR));
  87.260 +		List<Object[]> data = new ArrayList<>();
  87.261 +
  87.262 +		final List<DatabaseDefinition> configuredDatabases = configurationProvider.getConfiguration().getDatabases();
  87.263 +		if (configuredDatabases.isEmpty()) {
  87.264 +			log.log(Level.WARNING, "No databases are configured.");
  87.265 +		} else {
  87.266 +			for (DatabaseDefinition dd : configuredDatabases) {
  87.267 +				data.add(new Object[]{dd.getName(), dd.getUserName(), dd.getUrl()});
  87.268 +
  87.269 +				final TunnelDefinition tunnel = dd.getTunnel();
  87.270 +				if (tunnel != null) {
  87.271 +					log.log(Level.INFO, "Tunnel command: {0}", tunnel.getCommand());
  87.272 +					for (CommandArgument ca : Functions.notNull(tunnel.getArguments())) {
  87.273 +						log.log(Level.INFO, "\targument: {0}/{1}", new Object[]{ca.getType(), ca.getValue()});
  87.274 +					}
  87.275 +				}
  87.276 +
  87.277 +			}
  87.278 +		}
  87.279 +
  87.280 +		printTable(formatter, header, "-- configured databases", null, data);
  87.281 +	}
  87.282 +
  87.283 +	private void listJdbcDrivers() throws FormatterException, ConfigurationException {
  87.284 +		ColumnsHeader header = constructHeader(
  87.285 +				new HeaderField("class", SQLType.VARCHAR),
  87.286 +				new HeaderField("version", SQLType.VARCHAR),
  87.287 +				new HeaderField("major", SQLType.INTEGER),
  87.288 +				new HeaderField("minor", SQLType.INTEGER),
  87.289 +				new HeaderField("jdbc_compliant", SQLType.BOOLEAN));
  87.290 +		List<Object[]> data = new ArrayList<>();
  87.291 +
  87.292 +		final ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class);
  87.293 +		for (Driver d : drivers) {
  87.294 +			data.add(new Object[]{
  87.295 +				d.getClass().getName(),
  87.296 +				d.getMajorVersion() + "." + d.getMinorVersion(),
  87.297 +				d.getMajorVersion(),
  87.298 +				d.getMinorVersion(),
  87.299 +				d.jdbcCompliant()
  87.300 +			});
  87.301 +		}
  87.302 +
  87.303 +		printTable(formatter, header, "-- discovered JDBC drivers (available on the CLASSPATH)", null, data);
  87.304 +	}
  87.305 +
  87.306 +	private void listJdbcProperties() throws FormatterException, ConfigurationException {
  87.307 +		for (String dbName : options.getDatabaseNamesToListProperties()) {
  87.308 +			ColumnsHeader header = constructHeader(
  87.309 +					new HeaderField("property_name", SQLType.VARCHAR),
  87.310 +					new HeaderField("required", SQLType.BOOLEAN),
  87.311 +					new HeaderField("choices", SQLType.ARRAY),
  87.312 +					new HeaderField("configured_value", SQLType.VARCHAR),
  87.313 +					new HeaderField("description", SQLType.VARCHAR));
  87.314 +			List<Object[]> data = new ArrayList<>();
  87.315 +
  87.316 +			DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName);
  87.317 +
  87.318 +			Driver driver = findDriver(dd);
  87.319 +
  87.320 +			if (driver == null) {
  87.321 +				log.log(Level.WARNING, "No JDBC driver was found for DB: {0} with URL: {1}", new Object[]{dd.getName(), dd.getUrl()});
  87.322 +			} else {
  87.323 +				log.log(Level.INFO, "For DB: {0} was found JDBC driver: {1}", new Object[]{dd.getName(), driver.getClass().getName()});
  87.324 +
  87.325 +				try {
  87.326 +					DriverPropertyInfo[] propertyInfos = driver.getPropertyInfo(dd.getUrl(), dd.getProperties().getJavaProperties());
  87.327 +
  87.328 +					Set<String> standardProperties = new HashSet<>();
  87.329 +
  87.330 +					for (DriverPropertyInfo pi : propertyInfos) {
  87.331 +						Array choices = new FakeSqlArray(pi.choices, SQLType.VARCHAR);
  87.332 +						data.add(new Object[]{
  87.333 +							pi.name,
  87.334 +							pi.required,
  87.335 +							choices.getArray() == null ? "" : choices,
  87.336 +							pi.value == null ? "" : pi.value,
  87.337 +							pi.description
  87.338 +						});
  87.339 +						standardProperties.add(pi.name);
  87.340 +					}
  87.341 +
  87.342 +					for (Property p : dd.getProperties()) {
  87.343 +						if (!standardProperties.contains(p.getName())) {
  87.344 +							data.add(new Object[]{
  87.345 +								p.getName(),
  87.346 +								"",
  87.347 +								"",
  87.348 +								p.getValue(),
  87.349 +								""
  87.350 +							});
  87.351 +							log.log(Level.WARNING, "Your configuration contains property „{0}“ not declared by the JDBC driver.", p.getName());
  87.352 +						}
  87.353 +					}
  87.354 +
  87.355 +				} catch (SQLException e) {
  87.356 +					log.log(Level.WARNING, "Error during getting property infos.", e);
  87.357 +				}
  87.358 +
  87.359 +				List<Parameter> parameters = new ArrayList<>();
  87.360 +				parameters.add(new NamedParameter("database", dbName, SQLType.VARCHAR));
  87.361 +				parameters.add(new NamedParameter("driver_class", driver.getClass().getName(), SQLType.VARCHAR));
  87.362 +				parameters.add(new NamedParameter("driver_major_version", driver.getMajorVersion(), SQLType.INTEGER));
  87.363 +				parameters.add(new NamedParameter("driver_minor_version", driver.getMinorVersion(), SQLType.INTEGER));
  87.364 +
  87.365 +				printTable(formatter, header, "-- configured and configurable JDBC driver properties", parameters, data);
  87.366 +			}
  87.367 +		}
  87.368 +
  87.369 +	}
  87.370 +
  87.371 +	private Driver findDriver(DatabaseDefinition dd) {
  87.372 +		final ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class);
  87.373 +		for (Driver d : drivers) {
  87.374 +			try {
  87.375 +				if (d.acceptsURL(dd.getUrl())) {
  87.376 +					return d;
  87.377 +				}
  87.378 +			} catch (SQLException e) {
  87.379 +				log.log(Level.WARNING, "Error during finding JDBC driver for: " + dd.getName(), e);
  87.380 +			}
  87.381 +		}
  87.382 +		return null;
  87.383 +	}
  87.384 +
  87.385 +	/**
  87.386 +	 * Parallelism for connection testing – maximum concurrent database connections.
  87.387 +	 */
  87.388 +	private static final int TESTING_THREAD_COUNT = 64;
  87.389 +	/**
  87.390 +	 * Time limit for all connection testing threads – particular timeouts per connection will be
  87.391 +	 * much smaller.
  87.392 +	 */
  87.393 +	private static final long TESTING_AWAIT_LIMIT = 1;
  87.394 +	private static final TimeUnit TESTING_AWAIT_UNIT = TimeUnit.DAYS;
  87.395 +
  87.396 +	private void testConnections() throws FormatterException, ConfigurationException {
  87.397 +		ColumnsHeader header = constructHeader(
  87.398 +				new HeaderField("database_name", SQLType.VARCHAR),
  87.399 +				new HeaderField("configured", SQLType.BOOLEAN),
  87.400 +				new HeaderField("connected", SQLType.BOOLEAN),
  87.401 +				new HeaderField("product_name", SQLType.VARCHAR),
  87.402 +				new HeaderField("product_version", SQLType.VARCHAR));
  87.403 +
  87.404 +		log.log(Level.FINE, "Testing DB connections in {0} threads", TESTING_THREAD_COUNT);
  87.405 +
  87.406 +		ExecutorService es = Executors.newFixedThreadPool(TESTING_THREAD_COUNT);
  87.407 +
  87.408 +		final Formatter currentFormatter = formatter;
  87.409 +
  87.410 +		printHeader(currentFormatter, header, "-- database configuration and connectivity test", null);
  87.411 +
  87.412 +		for (final String dbName : options.getDatabaseNamesToTest()) {
  87.413 +			preloadDriver(dbName);
  87.414 +		}
  87.415 +
  87.416 +		for (final String dbName : options.getDatabaseNamesToTest()) {
  87.417 +			es.submit(() -> {
  87.418 +				final Object[] row = testConnection(dbName);
  87.419 +				synchronized (currentFormatter) {
  87.420 +					printRow(currentFormatter, row);
  87.421 +				}
  87.422 +			}
  87.423 +			);
  87.424 +		}
  87.425 +
  87.426 +		es.shutdown();
  87.427 +
  87.428 +		try {
  87.429 +			log.log(Level.FINEST, "Waiting for test results: {0} {1}", new Object[]{TESTING_AWAIT_LIMIT, TESTING_AWAIT_UNIT.name()});
  87.430 +			boolean finished = es.awaitTermination(TESTING_AWAIT_LIMIT, TESTING_AWAIT_UNIT);
  87.431 +			if (finished) {
  87.432 +				log.log(Level.FINEST, "All testing threads finished in time limit.");
  87.433 +			} else {
  87.434 +				throw new FormatterException("Exceeded total time limit for test threads – this should never happen");
  87.435 +			}
  87.436 +		} catch (InterruptedException e) {
  87.437 +			throw new FormatterException("Interrupted while waiting for test results", e);
  87.438 +		}
  87.439 +
  87.440 +		printFooter(currentFormatter);
  87.441 +	}
  87.442 +
  87.443 +	/**
  87.444 +	 * JDBC driver classes should be preloaded in single thread to avoid deadlocks while doing
  87.445 +	 * {@linkplain DriverManager#registerDriver(java.sql.Driver)} during parallel connections.
  87.446 +	 *
  87.447 +	 * @param dbName
  87.448 +	 */
  87.449 +	private void preloadDriver(String dbName) {
  87.450 +		try {
  87.451 +			DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName);
  87.452 +			Driver driver = findDriver(dd);
  87.453 +			if (driver == null) {
  87.454 +				log.log(Level.WARNING, "No Driver found for DB: {0}", dbName);
  87.455 +			} else {
  87.456 +				log.log(Level.FINEST, "Driver preloading for DB: {0} was successfull", dbName);
  87.457 +			}
  87.458 +		} catch (Exception e) {
  87.459 +			LogRecord r = new LogRecord(Level.WARNING, "Failed to preload the Driver for DB: {0}");
  87.460 +			r.setParameters(new Object[]{dbName});
  87.461 +			r.setThrown(e);
  87.462 +			log.log(r);
  87.463 +		}
  87.464 +	}
  87.465 +
  87.466 +	private Object[] testConnection(String dbName) {
  87.467 +		log.log(Level.FINE, "Testing connection to database: {0}", dbName);
  87.468 +
  87.469 +		boolean succesfullyConnected = false;
  87.470 +		boolean succesfullyConfigured = false;
  87.471 +		String productName = null;
  87.472 +		String productVersion = null;
  87.473 +
  87.474 +		try {
  87.475 +			DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName);
  87.476 +			log.log(Level.FINE, "Database definition was loaded from configuration");
  87.477 +			succesfullyConfigured = true;
  87.478 +			try (DatabaseConnection dc = dd.connect(options.getDatabaseProperties())) {
  87.479 +				succesfullyConnected = dc.test();
  87.480 +				productName = dc.getProductName();
  87.481 +				productVersion = dc.getProductVersion();
  87.482 +			}
  87.483 +			log.log(Level.FINE, "Database connection test was successful");
  87.484 +		} catch (ConfigurationException | SQLException | RuntimeException e) {
  87.485 +			log.log(Level.SEVERE, "Error during testing connection " + dbName, e);
  87.486 +		}
  87.487 +
  87.488 +		return new Object[]{dbName, succesfullyConfigured, succesfullyConnected, productName, productVersion};
  87.489 +	}
  87.490 +
  87.491 +	private void printResource(String fileName) {
  87.492 +		try (BufferedReader reader = new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(fileName)))) {
  87.493 +			while (true) {
  87.494 +				String line = reader.readLine();
  87.495 +				if (line == null) {
  87.496 +					break;
  87.497 +				} else {
  87.498 +					println(line);
  87.499 +				}
  87.500 +			}
  87.501 +		} catch (Exception e) {
  87.502 +			log.log(Level.SEVERE, "Unable to print this info. Please see our website for it: " + Constants.WEBSITE, e);
  87.503 +		}
  87.504 +	}
  87.505 +
  87.506 +	private void println(String line) {
  87.507 +		out.println(line);
  87.508 +	}
  87.509 +
  87.510 +	private void printTable(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters, List<Object[]> data) throws ConfigurationException, FormatterException {
  87.511 +		printTable(formatter, header, sql, parameters, data, null);
  87.512 +	}
  87.513 +
  87.514 +	private void printTable(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters, List<Object[]> data, final Integer sortByColumn) throws ConfigurationException, FormatterException {
  87.515 +		printHeader(formatter, header, sql, parameters);
  87.516 +
  87.517 +		if (sortByColumn != null) {
  87.518 +			Collections.sort(data, new Comparator<Object[]>() {
  87.519 +
  87.520 +				@Override
  87.521 +				public int compare(Object[] o1, Object[] o2) {
  87.522 +					String s1 = String.valueOf(o1[sortByColumn]);
  87.523 +					String s2 = String.valueOf(o2[sortByColumn]);
  87.524 +					return s1.compareTo(s2);
  87.525 +				}
  87.526 +			});
  87.527 +		}
  87.528 +
  87.529 +		for (Object[] row : data) {
  87.530 +			printRow(formatter, row);
  87.531 +		}
  87.532 +
  87.533 +		printFooter(formatter);
  87.534 +	}
  87.535 +
  87.536 +	private void printHeader(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters) {
  87.537 +		formatter.writeStartStatement();
  87.538 +		if (sql != null) {
  87.539 +			formatter.writeQuery(sql);
  87.540 +			if (parameters != null) {
  87.541 +				formatter.writeParameters(parameters);
  87.542 +			}
  87.543 +		}
  87.544 +		formatter.writeStartResultSet(header);
  87.545 +	}
  87.546 +
  87.547 +	private void printRow(Formatter formatter, Object[] row) {
  87.548 +		formatter.writeStartRow();
  87.549 +		for (Object cell : row) {
  87.550 +			formatter.writeColumnValue(cell);
  87.551 +		}
  87.552 +		formatter.writeEndRow();
  87.553 +	}
  87.554 +
  87.555 +	private void printFooter(Formatter formatter) {
  87.556 +		formatter.writeEndResultSet();
  87.557 +		formatter.writeEndStatement();
  87.558 +	}
  87.559 +
  87.560 +	private Formatter getFormatter() throws ConfigurationException, FormatterException {
  87.561 +		String formatterName = options.getFormatterName();
  87.562 +		formatterName = formatterName == null ? Configuration.DEFAULT_FORMATTER_PREFETCHING : formatterName;
  87.563 +		FormatterDefinition fd = configurationProvider.getConfiguration().getFormatter(formatterName);
  87.564 +		FormatterContext context = new FormatterContext(out, options.getFormatterProperties());
  87.565 +		return fd.getInstance(context);
  87.566 +	}
  87.567 +
  87.568 +	private ColumnsHeader constructHeader(HeaderField... fields) throws FormatterException {
  87.569 +		try {
  87.570 +			RowSetMetaDataImpl metaData = new RowSetMetaDataImpl();
  87.571 +			metaData.setColumnCount(fields.length);
  87.572 +
  87.573 +			for (int i = 0; i < fields.length; i++) {
  87.574 +				HeaderField hf = fields[i];
  87.575 +				int sqlIndex = i + 1;
  87.576 +				metaData.setColumnName(sqlIndex, hf.name);
  87.577 +				metaData.setColumnLabel(sqlIndex, hf.name);
  87.578 +				metaData.setColumnType(sqlIndex, hf.type.getCode());
  87.579 +				metaData.setColumnTypeName(sqlIndex, hf.type.name());
  87.580 +			}
  87.581 +
  87.582 +			return new ColumnsHeader(metaData);
  87.583 +		} catch (SQLException e) {
  87.584 +			throw new FormatterException("Error while constructing table headers", e);
  87.585 +		}
  87.586 +	}
  87.587 +
  87.588 +	private static class HeaderField {
  87.589 +
  87.590 +		String name;
  87.591 +		SQLType type;
  87.592 +
  87.593 +		public HeaderField(String name, SQLType type) {
  87.594 +			this.name = name;
  87.595 +			this.type = type;
  87.596 +		}
  87.597 +	}
  87.598 +
  87.599 +	public enum InfoType {
  87.600 +
  87.601 +		HELP {
  87.602 +			@Override
  87.603 +			public void showInfo(InfoLister infoLister) {
  87.604 +				infoLister.printResource(Constants.HELP_FILE);
  87.605 +			}
  87.606 +		},
  87.607 +		VERSION {
  87.608 +			@Override
  87.609 +			public void showInfo(InfoLister infoLister) {
  87.610 +				infoLister.printResource(Constants.VERSION_FILE);
  87.611 +			}
  87.612 +		},
  87.613 +		LICENSE {
  87.614 +			@Override
  87.615 +			public void showInfo(InfoLister infoLister) {
  87.616 +				infoLister.printResource(Constants.LICENSE_FILE);
  87.617 +			}
  87.618 +		},
  87.619 +		JAVA_PROPERTIES {
  87.620 +			@Override
  87.621 +			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  87.622 +				infoLister.listJavaProperties();
  87.623 +			}
  87.624 +		},
  87.625 +		ENVIRONMENT_VARIABLES {
  87.626 +			@Override
  87.627 +			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  87.628 +				infoLister.listEnvironmentVariables();
  87.629 +			}
  87.630 +		},
  87.631 +		FORMATTERS {
  87.632 +			@Override
  87.633 +			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  87.634 +				infoLister.listFormatters();
  87.635 +			}
  87.636 +		},
  87.637 +		FORMATTER_PROPERTIES {
  87.638 +			@Override
  87.639 +			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  87.640 +				infoLister.listFormatterProperties();
  87.641 +			}
  87.642 +		},
  87.643 +		TYPES {
  87.644 +			@Override
  87.645 +			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  87.646 +				infoLister.listTypes();
  87.647 +			}
  87.648 +		},
  87.649 +		JDBC_DRIVERS {
  87.650 +			@Override
  87.651 +			public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException {
  87.652 +				infoLister.listJdbcDrivers();
  87.653 +			}
  87.654 +		},
  87.655 +		JDBC_PROPERTIES {
  87.656 +			@Override
  87.657 +			public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException {
  87.658 +				infoLister.listJdbcProperties();
  87.659 +			}
  87.660 +		},
  87.661 +		DATABASES {
  87.662 +			@Override
  87.663 +			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  87.664 +				infoLister.listDatabases();
  87.665 +			}
  87.666 +		},
  87.667 +		CONNECTION {
  87.668 +			@Override
  87.669 +			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
  87.670 +				infoLister.testConnections();
  87.671 +			}
  87.672 +		};
  87.673 +
  87.674 +		public abstract void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException;
  87.675 +	}
  87.676 +}
    88.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    88.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/InvalidOptionsException.java	Mon Mar 04 20:15:24 2019 +0100
    88.3 @@ -0,0 +1,66 @@
    88.4 +/**
    88.5 + * SQL-DK
    88.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    88.7 + *
    88.8 + * This program is free software: you can redistribute it and/or modify
    88.9 + * it under the terms of the GNU General Public License as published by
   88.10 + * the Free Software Foundation, either version 3 of the License, or
   88.11 + * (at your option) any later version.
   88.12 + *
   88.13 + * This program is distributed in the hope that it will be useful,
   88.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   88.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   88.16 + * GNU General Public License for more details.
   88.17 + *
   88.18 + * You should have received a copy of the GNU General Public License
   88.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   88.20 + */
   88.21 +package info.globalcode.sql.dk;
   88.22 +
   88.23 +import java.util.ArrayList;
   88.24 +import java.util.Collection;
   88.25 +import java.util.Collections;
   88.26 +
   88.27 +/**
   88.28 + *
   88.29 + * @author Ing. František Kučera (frantovo.cz)
   88.30 + */
   88.31 +public class InvalidOptionsException extends Exception {
   88.32 +
   88.33 +	private final Collection<OptionProblem> problems = new ArrayList<>();
   88.34 +
   88.35 +	public Collection<OptionProblem> getProblems() {
   88.36 +		return Collections.unmodifiableCollection(problems);
   88.37 +	}
   88.38 +
   88.39 +	public void addProblem(OptionProblem p) {
   88.40 +		problems.add(p);
   88.41 +	}
   88.42 +
   88.43 +	public boolean hasProblems() {
   88.44 +		return !problems.isEmpty();
   88.45 +	}
   88.46 +
   88.47 +	public static class OptionProblem {
   88.48 +
   88.49 +		private String description;
   88.50 +		private Throwable exception;
   88.51 +
   88.52 +		public OptionProblem(String description) {
   88.53 +			this.description = description;
   88.54 +		}
   88.55 +
   88.56 +		public OptionProblem(String description, Throwable exception) {
   88.57 +			this.description = description;
   88.58 +			this.exception = exception;
   88.59 +		}
   88.60 +
   88.61 +		public String getDescription() {
   88.62 +			return description;
   88.63 +		}
   88.64 +
   88.65 +		public Throwable getException() {
   88.66 +			return exception;
   88.67 +		}
   88.68 +	}
   88.69 +}
    89.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    89.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/NamedParameter.java	Mon Mar 04 20:15:24 2019 +0100
    89.3 @@ -0,0 +1,48 @@
    89.4 +/**
    89.5 + * SQL-DK
    89.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    89.7 + *
    89.8 + * This program is free software: you can redistribute it and/or modify
    89.9 + * it under the terms of the GNU General Public License as published by
   89.10 + * the Free Software Foundation, either version 3 of the License, or
   89.11 + * (at your option) any later version.
   89.12 + *
   89.13 + * This program is distributed in the hope that it will be useful,
   89.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   89.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   89.16 + * GNU General Public License for more details.
   89.17 + *
   89.18 + * You should have received a copy of the GNU General Public License
   89.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   89.20 + */
   89.21 +package info.globalcode.sql.dk;
   89.22 +
   89.23 +import info.globalcode.sql.dk.configuration.NameIdentified;
   89.24 +
   89.25 +/**
   89.26 + *
   89.27 + * @author Ing. František Kučera (frantovo.cz)
   89.28 + */
   89.29 +public class NamedParameter extends Parameter implements NameIdentified {
   89.30 +
   89.31 +	private String name;
   89.32 +
   89.33 +	public NamedParameter(String name, Object value, SQLType type) {
   89.34 +		super(value, type);
   89.35 +		this.name = name;
   89.36 +	}
   89.37 +
   89.38 +	@Override
   89.39 +	public String getName() {
   89.40 +		return name;
   89.41 +	}
   89.42 +
   89.43 +	public void setName(String name) {
   89.44 +		this.name = name;
   89.45 +	}
   89.46 +
   89.47 +	@Override
   89.48 +	public String toString() {
   89.49 +		return "NamedParameter {" + name + " = " + getValue() + "; " + getType() + "}";
   89.50 +	}
   89.51 +}
    90.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    90.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/Parameter.java	Mon Mar 04 20:15:24 2019 +0100
    90.3 @@ -0,0 +1,69 @@
    90.4 +/**
    90.5 + * SQL-DK
    90.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    90.7 + *
    90.8 + * This program is free software: you can redistribute it and/or modify
    90.9 + * it under the terms of the GNU General Public License as published by
   90.10 + * the Free Software Foundation, either version 3 of the License, or
   90.11 + * (at your option) any later version.
   90.12 + *
   90.13 + * This program is distributed in the hope that it will be useful,
   90.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   90.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   90.16 + * GNU General Public License for more details.
   90.17 + *
   90.18 + * You should have received a copy of the GNU General Public License
   90.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   90.20 + */
   90.21 +package info.globalcode.sql.dk;
   90.22 +
   90.23 +import java.sql.Types;
   90.24 +
   90.25 +/**
   90.26 + * Parameter for {@linkplain SQLCommand}
   90.27 + *
   90.28 + * @author Ing. František Kučera (frantovo.cz)
   90.29 + */
   90.30 +public class Parameter {
   90.31 +
   90.32 +	/**
   90.33 +	 * @see Types
   90.34 +	 */
   90.35 +	public static final SQLType DEFAULT_TYPE = SQLType.VARCHAR;
   90.36 +	private Object value;
   90.37 +	private SQLType type;
   90.38 +
   90.39 +	public Parameter() {
   90.40 +	}
   90.41 +
   90.42 +	public Parameter(Object value, SQLType type) {
   90.43 +		this.value = value;
   90.44 +		if (type == null) {
   90.45 +			this.type = DEFAULT_TYPE;
   90.46 +		} else {
   90.47 +			this.type = type;
   90.48 +		}
   90.49 +	}
   90.50 +
   90.51 +	public Object getValue() {
   90.52 +		return value;
   90.53 +	}
   90.54 +
   90.55 +	public void setValue(Object value) {
   90.56 +		this.value = value;
   90.57 +	}
   90.58 +
   90.59 +	/**
   90.60 +	 * @see java.sql.Types
   90.61 +	 */
   90.62 +	public SQLType getType() {
   90.63 +		return type;
   90.64 +	}
   90.65 +
   90.66 +	/**
   90.67 +	 * @see java.sql.Types
   90.68 +	 */
   90.69 +	public void setType(SQLType type) {
   90.70 +		this.type = type;
   90.71 +	}
   90.72 +}
    91.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    91.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommand.java	Mon Mar 04 20:15:24 2019 +0100
    91.3 @@ -0,0 +1,49 @@
    91.4 +/**
    91.5 + * SQL-DK
    91.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    91.7 + *
    91.8 + * This program is free software: you can redistribute it and/or modify
    91.9 + * it under the terms of the GNU General Public License as published by
   91.10 + * the Free Software Foundation, either version 3 of the License, or
   91.11 + * (at your option) any later version.
   91.12 + *
   91.13 + * This program is distributed in the hope that it will be useful,
   91.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   91.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   91.16 + * GNU General Public License for more details.
   91.17 + *
   91.18 + * You should have received a copy of the GNU General Public License
   91.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   91.20 + */
   91.21 +package info.globalcode.sql.dk;
   91.22 +
   91.23 +import java.sql.Connection;
   91.24 +import java.sql.PreparedStatement;
   91.25 +import java.sql.SQLException;
   91.26 +import java.util.List;
   91.27 +
   91.28 +/**
   91.29 + * Represents SQL string and its parameters (if there are any).
   91.30 + *
   91.31 + * @author Ing. František Kučera (frantovo.cz)
   91.32 + */
   91.33 +public abstract class SQLCommand {
   91.34 +
   91.35 +	private String query;
   91.36 +
   91.37 +	public SQLCommand(String query) {
   91.38 +		this.query = query;
   91.39 +	}
   91.40 +
   91.41 +	public PreparedStatement prepareStatement(Connection c) throws SQLException {
   91.42 +		return c.prepareStatement(query);
   91.43 +	}
   91.44 +
   91.45 +	public abstract void parametrize(PreparedStatement ps) throws SQLException;
   91.46 +
   91.47 +	public abstract List<? extends Parameter> getParameters();
   91.48 +
   91.49 +	public String getQuery() {
   91.50 +		return query;
   91.51 +	}
   91.52 +}
    92.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    92.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommandNamed.java	Mon Mar 04 20:15:24 2019 +0100
    92.3 @@ -0,0 +1,155 @@
    92.4 +/**
    92.5 + * SQL-DK
    92.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    92.7 + *
    92.8 + * This program is free software: you can redistribute it and/or modify
    92.9 + * it under the terms of the GNU General Public License as published by
   92.10 + * the Free Software Foundation, either version 3 of the License, or
   92.11 + * (at your option) any later version.
   92.12 + *
   92.13 + * This program is distributed in the hope that it will be useful,
   92.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   92.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   92.16 + * GNU General Public License for more details.
   92.17 + *
   92.18 + * You should have received a copy of the GNU General Public License
   92.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   92.20 + */
   92.21 +package info.globalcode.sql.dk;
   92.22 +
   92.23 +import static info.globalcode.sql.dk.Functions.findByName;
   92.24 +import java.sql.Connection;
   92.25 +import java.sql.PreparedStatement;
   92.26 +import java.sql.SQLException;
   92.27 +import java.util.ArrayList;
   92.28 +import java.util.List;
   92.29 +import java.util.logging.Level;
   92.30 +import java.util.logging.Logger;
   92.31 +import java.util.regex.Matcher;
   92.32 +import java.util.regex.Pattern;
   92.33 +import java.util.regex.PatternSyntaxException;
   92.34 +
   92.35 +/**
   92.36 + * Has named parameters.
   92.37 + *
   92.38 + * @author Ing. František Kučera (frantovo.cz)
   92.39 + */
   92.40 +public class SQLCommandNamed extends SQLCommand {
   92.41 +
   92.42 +	private static final Logger log = Logger.getLogger(SQLCommandNamed.class.getName());
   92.43 +	private String namePrefix;
   92.44 +	private String nameSuffix;
   92.45 +	private List<NamedParameter> parameters;
   92.46 +	private List<NamedParameter> parametersUsed = new ArrayList<>();
   92.47 +	private StringBuilder updatedQuery;
   92.48 +	private Pattern pattern;
   92.49 +	private SQLCommandNumbered numbered;
   92.50 +
   92.51 +	public SQLCommandNamed(String query, List<NamedParameter> parameters, String namePrefix, String nameSuffix) {
   92.52 +		super(query);
   92.53 +		this.updatedQuery = new StringBuilder(query.length());
   92.54 +		this.parameters = parameters;
   92.55 +		this.namePrefix = namePrefix;
   92.56 +		this.nameSuffix = nameSuffix;
   92.57 +	}
   92.58 +
   92.59 +	@Override
   92.60 +	public PreparedStatement prepareStatement(Connection c) throws SQLException {
   92.61 +		return getSQLCommandNumbered().prepareStatement(c);
   92.62 +	}
   92.63 +
   92.64 +	@Override
   92.65 +	public void parametrize(PreparedStatement ps) throws SQLException {
   92.66 +		getSQLCommandNumbered().parametrize(ps);
   92.67 +	}
   92.68 +
   92.69 +	private void prepare() throws SQLException {
   92.70 +		try {
   92.71 +			buildPattern();
   92.72 +			placeParametersAndUpdateQuery();
   92.73 +			logPossiblyMissingParameters();
   92.74 +		} catch (PatternSyntaxException e) {
   92.75 +			throw new SQLException("Name prefix „" + namePrefix + "“ or suffix „" + nameSuffix + "“ contain a wrong regular expression. " + e.getLocalizedMessage(), e);
   92.76 +		}
   92.77 +	}
   92.78 +
   92.79 +	/**
   92.80 +	 * @return SQL command with named parameters converted to SQL command with numbered parameters
   92.81 +	 */
   92.82 +	public SQLCommandNumbered getSQLCommandNumbered() throws SQLException {
   92.83 +		if (numbered == null) {
   92.84 +			prepare();
   92.85 +			numbered = new SQLCommandNumbered(updatedQuery.toString(), parametersUsed);
   92.86 +		}
   92.87 +
   92.88 +		return numbered;
   92.89 +	}
   92.90 +
   92.91 +	/**
   92.92 +	 * Builds a regexp pattern that matches all parameter names (with prefix/suffix) and which has
   92.93 +	 * one group: parameter name (without prefix/suffix)
   92.94 +	 */
   92.95 +	private void buildPattern() throws PatternSyntaxException {
   92.96 +		StringBuilder patternString = new StringBuilder();
   92.97 +
   92.98 +		patternString.append(namePrefix);
   92.99 +		patternString.append("(?<paramName>");
  92.100 +		for (int i = 0; i < parameters.size(); i++) {
  92.101 +			patternString.append(Pattern.quote(parameters.get(i).getName()));
  92.102 +			if (i < parameters.size() - 1) {
  92.103 +				patternString.append("|");
  92.104 +			}
  92.105 +		}
  92.106 +		patternString.append(")");
  92.107 +		patternString.append(nameSuffix);
  92.108 +
  92.109 +		pattern = Pattern.compile(patternString.toString());
  92.110 +	}
  92.111 +
  92.112 +	private void placeParametersAndUpdateQuery() {
  92.113 +		final String originalQuery = getQuery();
  92.114 +		Matcher m = pattern.matcher(originalQuery);
  92.115 +
  92.116 +		int lastPosition = 0;
  92.117 +		while (m.find(lastPosition)) {
  92.118 +			String name = m.group("paramName");
  92.119 +
  92.120 +			updatedQuery.append(originalQuery.substring(lastPosition, m.start()));
  92.121 +			updatedQuery.append("?");
  92.122 +
  92.123 +			parametersUsed.add(findByName(parameters, name));
  92.124 +
  92.125 +			lastPosition = m.end();
  92.126 +		}
  92.127 +		updatedQuery.append(originalQuery.substring(lastPosition, originalQuery.length()));
  92.128 +
  92.129 +		for (NamedParameter definedParameter : parameters) {
  92.130 +			if (findByName(parametersUsed, definedParameter.getName()) == null) {
  92.131 +				/**
  92.132 +				 * User can have predefined set of parameters and use them with different SQL
  92.133 +				 * queries that use only subset of these parameters → just warning, not exception.
  92.134 +				 */
  92.135 +				log.log(Level.WARNING, "Parameter „{0}“ is defined but not used in the query: „{1}“", new Object[]{definedParameter.getName(), originalQuery});
  92.136 +			}
  92.137 +		}
  92.138 +	}
  92.139 +
  92.140 +	private void logPossiblyMissingParameters() {
  92.141 +		Pattern p = Pattern.compile(namePrefix + "(?<paramName>.+?)" + nameSuffix);
  92.142 +		Matcher m = p.matcher(updatedQuery);
  92.143 +		int lastPosition = 0;
  92.144 +		while (m.find(lastPosition)) {
  92.145 +			/**
  92.146 +			 * We have not parsed and understood the SQL query; the parameter-like looking string
  92.147 +			 * could be inside a literal part of the query → just warning, not exception.
  92.148 +			 */
  92.149 +			log.log(Level.WARNING, "Possibly missing parameter „{0}“ in the query: „{1}“", new Object[]{m.group("paramName"), getQuery()});
  92.150 +			lastPosition = m.end();
  92.151 +		}
  92.152 +	}
  92.153 +
  92.154 +	@Override
  92.155 +	public List<NamedParameter> getParameters() {
  92.156 +		return parameters;
  92.157 +	}
  92.158 +}
    93.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    93.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommandNumbered.java	Mon Mar 04 20:15:24 2019 +0100
    93.3 @@ -0,0 +1,51 @@
    93.4 +/**
    93.5 + * SQL-DK
    93.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    93.7 + *
    93.8 + * This program is free software: you can redistribute it and/or modify
    93.9 + * it under the terms of the GNU General Public License as published by
   93.10 + * the Free Software Foundation, either version 3 of the License, or
   93.11 + * (at your option) any later version.
   93.12 + *
   93.13 + * This program is distributed in the hope that it will be useful,
   93.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   93.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   93.16 + * GNU General Public License for more details.
   93.17 + *
   93.18 + * You should have received a copy of the GNU General Public License
   93.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   93.20 + */
   93.21 +package info.globalcode.sql.dk;
   93.22 +
   93.23 +import static info.globalcode.sql.dk.Functions.notNull;
   93.24 +import java.sql.PreparedStatement;
   93.25 +import java.sql.SQLException;
   93.26 +import java.util.List;
   93.27 +
   93.28 +/**
   93.29 + * Has ordinal/numbered parameters.
   93.30 + *
   93.31 + * @author Ing. František Kučera (frantovo.cz)
   93.32 + */
   93.33 +public class SQLCommandNumbered extends SQLCommand {
   93.34 +
   93.35 +	private List<? extends Parameter> parameters;
   93.36 +
   93.37 +	public SQLCommandNumbered(String query, List<? extends Parameter> parameters) {
   93.38 +		super(query);
   93.39 +		this.parameters = parameters;
   93.40 +	}
   93.41 +
   93.42 +	@Override
   93.43 +	public void parametrize(PreparedStatement ps) throws SQLException {
   93.44 +		int i = 1;
   93.45 +		for (Parameter p : notNull(parameters)) {
   93.46 +			ps.setObject(i++, p.getValue(), p.getType().getCode());
   93.47 +		}
   93.48 +	}
   93.49 +
   93.50 +	@Override
   93.51 +	public List<? extends Parameter> getParameters() {
   93.52 +		return parameters;
   93.53 +	}
   93.54 +}
    94.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    94.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLType.java	Mon Mar 04 20:15:24 2019 +0100
    94.3 @@ -0,0 +1,95 @@
    94.4 +/**
    94.5 + * SQL-DK
    94.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    94.7 + *
    94.8 + * This program is free software: you can redistribute it and/or modify
    94.9 + * it under the terms of the GNU General Public License as published by
   94.10 + * the Free Software Foundation, either version 3 of the License, or
   94.11 + * (at your option) any later version.
   94.12 + *
   94.13 + * This program is distributed in the hope that it will be useful,
   94.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   94.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   94.16 + * GNU General Public License for more details.
   94.17 + *
   94.18 + * You should have received a copy of the GNU General Public License
   94.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   94.20 + */
   94.21 +package info.globalcode.sql.dk;
   94.22 +
   94.23 +import java.sql.Types;
   94.24 +
   94.25 +/**
   94.26 + * Data types of SQL parameters.
   94.27 + *
   94.28 + * @author Ing. František Kučera (frantovo.cz)
   94.29 + */
   94.30 +public enum SQLType {
   94.31 +
   94.32 +	/**
   94.33 +	 * Names must be upper case – user input is also converted to upper case → case insensitive
   94.34 +	 */
   94.35 +	BIT(Types.BIT),
   94.36 +	TINYINT(Types.TINYINT),
   94.37 +	SMALLINT(Types.SMALLINT),
   94.38 +	INTEGER(Types.INTEGER),
   94.39 +	BIGINT(Types.BIGINT),
   94.40 +	FLOAT(Types.FLOAT),
   94.41 +	REAL(Types.REAL),
   94.42 +	DOUBLE(Types.DOUBLE),
   94.43 +	NUMERIC(Types.NUMERIC),
   94.44 +	DECIMAL(Types.DECIMAL),
   94.45 +	CHAR(Types.CHAR),
   94.46 +	VARCHAR(Types.VARCHAR),
   94.47 +	LONGVARCHAR(Types.LONGVARCHAR),
   94.48 +	DATE(Types.DATE),
   94.49 +	TIME(Types.TIME),
   94.50 +	TIMESTAMP(Types.TIMESTAMP),
   94.51 +	BINARY(Types.BINARY),
   94.52 +	VARBINARY(Types.VARBINARY),
   94.53 +	LONGVARBINARY(Types.LONGVARBINARY),
   94.54 +	NULL(Types.NULL),
   94.55 +	OTHER(Types.OTHER),
   94.56 +	JAVA_OBJECT(Types.JAVA_OBJECT),
   94.57 +	DISTINCT(Types.DISTINCT),
   94.58 +	STRUCT(Types.STRUCT),
   94.59 +	ARRAY(Types.ARRAY),
   94.60 +	BLOB(Types.BLOB),
   94.61 +	CLOB(Types.CLOB),
   94.62 +	REF(Types.REF),
   94.63 +	DATALINK(Types.DATALINK),
   94.64 +	BOOLEAN(Types.BOOLEAN),
   94.65 +	ROWID(Types.ROWID),
   94.66 +	NCHAR(Types.NCHAR),
   94.67 +	NVARCHAR(Types.NVARCHAR),
   94.68 +	LONGNVARCHAR(Types.LONGNVARCHAR),
   94.69 +	NCLOB(Types.NCLOB),
   94.70 +	SQLXML(Types.SQLXML);
   94.71 +	/** value from java.sql.Types */
   94.72 +	private int code;
   94.73 +
   94.74 +	private SQLType(int code) {
   94.75 +		this.code = code;
   94.76 +	}
   94.77 +
   94.78 +	/**
   94.79 +	 * @see java.sql.Types.Types
   94.80 +	 */
   94.81 +	public int getCode() {
   94.82 +		return code;
   94.83 +	}
   94.84 +
   94.85 +	/**
   94.86 +	 * @param code see {@linkplain java.sql.Types.Types}
   94.87 +	 * @return found SQLType
   94.88 +	 * @throws IllegalArgumentException if no data type has given code
   94.89 +	 */
   94.90 +	public static SQLType valueOf(int code) {
   94.91 +		for (SQLType t : values()) {
   94.92 +			if (t.code == code) {
   94.93 +				return t;
   94.94 +			}
   94.95 +		}
   94.96 +		throw new IllegalArgumentException("No data type has code: " + code);
   94.97 +	}
   94.98 +}
    95.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    95.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/Xmlns.java	Mon Mar 04 20:15:24 2019 +0100
    95.3 @@ -0,0 +1,33 @@
    95.4 +/**
    95.5 + * SQL-DK
    95.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    95.7 + *
    95.8 + * This program is free software: you can redistribute it and/or modify
    95.9 + * it under the terms of the GNU General Public License as published by
   95.10 + * the Free Software Foundation, either version 3 of the License, or
   95.11 + * (at your option) any later version.
   95.12 + *
   95.13 + * This program is distributed in the hope that it will be useful,
   95.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   95.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   95.16 + * GNU General Public License for more details.
   95.17 + *
   95.18 + * You should have received a copy of the GNU General Public License
   95.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   95.20 + */
   95.21 +package info.globalcode.sql.dk;
   95.22 +
   95.23 +/**
   95.24 + * XML namespaces
   95.25 + *
   95.26 + * @author Ing. František Kučera (frantovo.cz)
   95.27 + */
   95.28 +public class Xmlns {
   95.29 +
   95.30 +	public static final String CONFIGURATION = "https://sql-dk.globalcode.info/xmlns/configuration";
   95.31 +	public static final String BATCH_RESULT = "https://sql-dk.globalcode.info/xmlns/batchResult";
   95.32 +	public static final String XHTML = "http://www.w3.org/1999/xhtml";
   95.33 +
   95.34 +	private Xmlns() {
   95.35 +	}
   95.36 +}
    96.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    96.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/Batch.java	Mon Mar 04 20:15:24 2019 +0100
    96.3 @@ -0,0 +1,32 @@
    96.4 +/**
    96.5 + * SQL-DK
    96.6 + * Copyright © 2013 František Kučera (frantovo.cz)
    96.7 + *
    96.8 + * This program is free software: you can redistribute it and/or modify
    96.9 + * it under the terms of the GNU General Public License as published by
   96.10 + * the Free Software Foundation, either version 3 of the License, or
   96.11 + * (at your option) any later version.
   96.12 + *
   96.13 + * This program is distributed in the hope that it will be useful,
   96.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   96.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   96.16 + * GNU General Public License for more details.
   96.17 + *
   96.18 + * You should have received a copy of the GNU General Public License
   96.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   96.20 + */
   96.21 +package info.globalcode.sql.dk.batch;
   96.22 +
   96.23 +import info.globalcode.sql.dk.SQLCommand;
   96.24 +
   96.25 +/**
   96.26 + * Iterator which reads SQL commands from encoded (serialized) batch.
   96.27 + *
   96.28 + * @author Ing. František Kučera (frantovo.cz)
   96.29 + */
   96.30 +public interface Batch {
   96.31 +
   96.32 +	public boolean hasNext() throws BatchException;
   96.33 +
   96.34 +	public SQLCommand next() throws BatchException;
   96.35 +}
    97.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    97.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchConstants.java	Mon Mar 04 20:15:24 2019 +0100
    97.3 @@ -0,0 +1,35 @@
    97.4 +/**
    97.5 + * SQL-DK
    97.6 + * Copyright © 2014 František Kučera (frantovo.cz)
    97.7 + *
    97.8 + * This program is free software: you can redistribute it and/or modify
    97.9 + * it under the terms of the GNU General Public License as published by
   97.10 + * the Free Software Foundation, either version 3 of the License, or
   97.11 + * (at your option) any later version.
   97.12 + *
   97.13 + * This program is distributed in the hope that it will be useful,
   97.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   97.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   97.16 + * GNU General Public License for more details.
   97.17 + *
   97.18 + * You should have received a copy of the GNU General Public License
   97.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   97.20 + */
   97.21 +package info.globalcode.sql.dk.batch;
   97.22 +
   97.23 +import java.nio.charset.Charset;
   97.24 +import java.nio.charset.StandardCharsets;
   97.25 +
   97.26 +/**
   97.27 + *
   97.28 + * @author Ing. František Kučera (frantovo.cz)
   97.29 + */
   97.30 +public class BatchConstants {
   97.31 +
   97.32 +	public static final Charset CHARSET = StandardCharsets.UTF_8;
   97.33 +	public static final byte VERSION = 0x01;
   97.34 +	public static final byte[] BATCH_HEADER = {0x00, 0x53, 0x51, 0x4C, VERSION};
   97.35 +
   97.36 +	private BatchConstants() {
   97.37 +	}
   97.38 +}
    98.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    98.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchDecoder.java	Mon Mar 04 20:15:24 2019 +0100
    98.3 @@ -0,0 +1,108 @@
    98.4 +/**
    98.5 + * SQL-DK
    98.6 + * Copyright © 2014 František Kučera (frantovo.cz)
    98.7 + *
    98.8 + * This program is free software: you can redistribute it and/or modify
    98.9 + * it under the terms of the GNU General Public License as published by
   98.10 + * the Free Software Foundation, either version 3 of the License, or
   98.11 + * (at your option) any later version.
   98.12 + *
   98.13 + * This program is distributed in the hope that it will be useful,
   98.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   98.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   98.16 + * GNU General Public License for more details.
   98.17 + *
   98.18 + * You should have received a copy of the GNU General Public License
   98.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   98.20 + */
   98.21 +package info.globalcode.sql.dk.batch;
   98.22 +
   98.23 +import info.globalcode.sql.dk.Parameter;
   98.24 +import info.globalcode.sql.dk.SQLCommand;
   98.25 +import info.globalcode.sql.dk.SQLCommandNumbered;
   98.26 +import java.io.DataInputStream;
   98.27 +import java.io.InputStream;
   98.28 +import static info.globalcode.sql.dk.batch.BatchConstants.*;
   98.29 +import static info.globalcode.sql.dk.Functions.toHex;
   98.30 +import info.globalcode.sql.dk.SQLType;
   98.31 +import java.io.IOException;
   98.32 +import java.util.ArrayList;
   98.33 +import java.util.Arrays;
   98.34 +import java.util.List;
   98.35 +
   98.36 +/**
   98.37 + *
   98.38 + * @author Ing. František Kučera (frantovo.cz)
   98.39 + */
   98.40 +public class BatchDecoder {
   98.41 +
   98.42 +	public Batch decode(InputStream in) throws BatchException {
   98.43 +		return new BatchFromStream(new DataInputStream(in));
   98.44 +	}
   98.45 +
   98.46 +	private class BatchFromStream implements Batch {
   98.47 +
   98.48 +		private DataInputStream in;
   98.49 +		private boolean hasNext;
   98.50 +
   98.51 +		public BatchFromStream(DataInputStream in) throws BatchException {
   98.52 +			this.in = in;
   98.53 +			hasNext = verifyHeader();
   98.54 +		}
   98.55 +
   98.56 +		@Override
   98.57 +		public boolean hasNext() throws BatchException {
   98.58 +			return hasNext;
   98.59 +		}
   98.60 +
   98.61 +		@Override
   98.62 +		public SQLCommand next() throws BatchException {
   98.63 +			try {
   98.64 +				String sql = readNextString();
   98.65 +
   98.66 +				int paramCount = in.readInt();
   98.67 +				List<Parameter> parameters = new ArrayList<>(paramCount);
   98.68 +
   98.69 +				for (int i = 0; i < paramCount; i++) {
   98.70 +					SQLType type = SQLType.valueOf(in.readInt());
   98.71 +					String value = readNextString();
   98.72 +					parameters.add(new Parameter(value, type));
   98.73 +				}
   98.74 +
   98.75 +				hasNext = verifyHeader();
   98.76 +
   98.77 +				SQLCommand sqlCommand = new SQLCommandNumbered(sql, parameters);
   98.78 +				return sqlCommand;
   98.79 +			} catch (IOException e) {
   98.80 +				throw new BatchException("Unable to read batch", e);
   98.81 +			}
   98.82 +		}
   98.83 +
   98.84 +		private String readNextString() throws IOException {
   98.85 +			byte[] buffer = new byte[in.readInt()];
   98.86 +			in.read(buffer);
   98.87 +			return new String(buffer, CHARSET);
   98.88 +		}
   98.89 +
   98.90 +		/**
   98.91 +		 * @return true if correct batch header was found | false if EOF was found
   98.92 +		 * @throws BatchException if unexpected data was found (not batch header nor EOF)
   98.93 +		 */
   98.94 +		private boolean verifyHeader() throws BatchException {
   98.95 +			try {
   98.96 +				byte[] buffer = new byte[BATCH_HEADER.length];
   98.97 +				int bytesRead = in.read(buffer);
   98.98 +
   98.99 +				if (bytesRead == BATCH_HEADER.length && Arrays.equals(buffer, BATCH_HEADER)) {
  98.100 +					return true;
  98.101 +				} else if (bytesRead == -1) {
  98.102 +					return false;
  98.103 +				} else {
  98.104 +					throw new BatchException("This is not SQL-DK batch: " + toHex(buffer));
  98.105 +				}
  98.106 +			} catch (IOException e) {
  98.107 +				throw new BatchException("Unable to read batch header", e);
  98.108 +			}
  98.109 +		}
  98.110 +	}
  98.111 +}
    99.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    99.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchEncoder.java	Mon Mar 04 20:15:24 2019 +0100
    99.3 @@ -0,0 +1,83 @@
    99.4 +/**
    99.5 + * SQL-DK
    99.6 + * Copyright © 2014 František Kučera (frantovo.cz)
    99.7 + *
    99.8 + * This program is free software: you can redistribute it and/or modify
    99.9 + * it under the terms of the GNU General Public License as published by
   99.10 + * the Free Software Foundation, either version 3 of the License, or
   99.11 + * (at your option) any later version.
   99.12 + *
   99.13 + * This program is distributed in the hope that it will be useful,
   99.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   99.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   99.16 + * GNU General Public License for more details.
   99.17 + *
   99.18 + * You should have received a copy of the GNU General Public License
   99.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
   99.20 + */
   99.21 +package info.globalcode.sql.dk.batch;
   99.22 +
   99.23 +import info.globalcode.sql.dk.Parameter;
   99.24 +import info.globalcode.sql.dk.SQLCommand;
   99.25 +import info.globalcode.sql.dk.SQLCommandNamed;
   99.26 +import java.io.DataOutputStream;
   99.27 +import java.io.IOException;
   99.28 +import java.io.OutputStream;
   99.29 +import static info.globalcode.sql.dk.batch.BatchConstants.*;
   99.30 +import java.io.ByteArrayOutputStream;
   99.31 +import java.sql.SQLException;
   99.32 +import java.util.List;
   99.33 +
   99.34 +/**
   99.35 + *
   99.36 + * @author Ing. František Kučera (frantovo.cz)
   99.37 + */
   99.38 +public class BatchEncoder {
   99.39 +
   99.40 +	public int encode(SQLCommand sqlCommand, OutputStream out) throws BatchException {
   99.41 +		try {
   99.42 +			ByteArrayOutputStream bufferAOS = new ByteArrayOutputStream();
   99.43 +			DataOutputStream buffer = new DataOutputStream(bufferAOS);
   99.44 +
   99.45 +			buffer.write(BATCH_HEADER);
   99.46 +
   99.47 +			if (sqlCommand instanceof SQLCommandNamed) {
   99.48 +				sqlCommand = ((SQLCommandNamed) sqlCommand).getSQLCommandNumbered();
   99.49 +			}
   99.50 +
   99.51 +			writeNextString(sqlCommand.getQuery(), buffer);
   99.52 +
   99.53 +			List<? extends Parameter> parameters = sqlCommand.getParameters();
   99.54 +
   99.55 +			buffer.writeInt(parameters.size());
   99.56 +
   99.57 +			for (Parameter p : parameters) {
   99.58 +				buffer.writeInt(p.getType().getCode());
   99.59 +				writeNextString((String) p.getValue(), buffer); // parameters are encoded before any preprocessing
   99.60 +			}
   99.61 +
   99.62 +			buffer.flush();
   99.63 +			bufferAOS.writeTo(out);
   99.64 +			out.flush();
   99.65 +			return bufferAOS.size();
   99.66 +		} catch (IOException e) {
   99.67 +			throw new BatchException("Unable to write SQL command: " + sqlCommand, e);
   99.68 +		} catch (SQLException e) {
   99.69 +			throw new BatchException("Unable to converd named SQL command to numbered: " + sqlCommand, e);
   99.70 +		}
   99.71 +	}
   99.72 +
   99.73 +	private void writeNextString(String s, DataOutputStream out) throws IOException {
   99.74 +		byte[] bytes = toBytes(s);
   99.75 +		out.writeInt(bytes.length);
   99.76 +		out.write(bytes);
   99.77 +	}
   99.78 +
   99.79 +	private static byte[] toBytes(String s) {
   99.80 +		if (s == null) {
   99.81 +			return new byte[]{};
   99.82 +		} else {
   99.83 +			return s.getBytes(CHARSET);
   99.84 +		}
   99.85 +	}
   99.86 +}
   100.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   100.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchException.java	Mon Mar 04 20:15:24 2019 +0100
   100.3 @@ -0,0 +1,42 @@
   100.4 +/**
   100.5 + * SQL-DK
   100.6 + * Copyright © 2014 František Kučera (frantovo.cz)
   100.7 + *
   100.8 + * This program is free software: you can redistribute it and/or modify
   100.9 + * it under the terms of the GNU General Public License as published by
  100.10 + * the Free Software Foundation, either version 3 of the License, or
  100.11 + * (at your option) any later version.
  100.12 + *
  100.13 + * This program is distributed in the hope that it will be useful,
  100.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  100.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  100.16 + * GNU General Public License for more details.
  100.17 + *
  100.18 + * You should have received a copy of the GNU General Public License
  100.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  100.20 + */
  100.21 +package info.globalcode.sql.dk.batch;
  100.22 +
  100.23 +import info.globalcode.sql.dk.DKException;
  100.24 +
  100.25 +/**
  100.26 + *
  100.27 + * @author Ing. František Kučera (frantovo.cz)
  100.28 + */
  100.29 +public class BatchException extends DKException {
  100.30 +
  100.31 +	public BatchException() {
  100.32 +	}
  100.33 +
  100.34 +	public BatchException(String message) {
  100.35 +		super(message);
  100.36 +	}
  100.37 +
  100.38 +	public BatchException(Throwable cause) {
  100.39 +		super(cause);
  100.40 +	}
  100.41 +
  100.42 +	public BatchException(String message, Throwable cause) {
  100.43 +		super(message, cause);
  100.44 +	}
  100.45 +}
   101.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   101.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/CommandArgument.java	Mon Mar 04 20:15:24 2019 +0100
   101.3 @@ -0,0 +1,82 @@
   101.4 +/**
   101.5 + * SQL-DK
   101.6 + * Copyright © 2015 František Kučera (frantovo.cz)
   101.7 + *
   101.8 + * This program is free software: you can redistribute it and/or modify
   101.9 + * it under the terms of the GNU General Public License as published by
  101.10 + * the Free Software Foundation, either version 3 of the License, or
  101.11 + * (at your option) any later version.
  101.12 + *
  101.13 + * This program is distributed in the hope that it will be useful,
  101.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  101.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  101.16 + * GNU General Public License for more details.
  101.17 + *
  101.18 + * You should have received a copy of the GNU General Public License
  101.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  101.20 + */
  101.21 +package info.globalcode.sql.dk.configuration;
  101.22 +
  101.23 +import javax.xml.bind.annotation.XmlAttribute;
  101.24 +import javax.xml.bind.annotation.XmlEnum;
  101.25 +import javax.xml.bind.annotation.XmlEnumValue;
  101.26 +import javax.xml.bind.annotation.XmlValue;
  101.27 +
  101.28 +/**
  101.29 + *
  101.30 + * @author Ing. František Kučera (frantovo.cz)
  101.31 + */
  101.32 +public class CommandArgument {
  101.33 +
  101.34 +	private String value;
  101.35 +	private TYPE type;
  101.36 +
  101.37 +	@XmlEnum
  101.38 +	public static enum TYPE {
  101.39 +
  101.40 +		/**
  101.41 +		 * value = literal (text) argument
  101.42 +		 */
  101.43 +		@XmlEnumValue("literal")
  101.44 +		LITERAL,
  101.45 +		/**
  101.46 +		 * value will be substituted by hostname or IP address of the DB server
  101.47 +		 */
  101.48 +		@XmlEnumValue("host")
  101.49 +		HOST,
  101.50 +		/**
  101.51 +		 * value will be substituted by the port of the DB server
  101.52 +		 */
  101.53 +		@XmlEnumValue("port")
  101.54 +		PORT,
  101.55 +		/**
  101.56 +		 * value will be substituted by environmental variable of given name
  101.57 +		 */
  101.58 +		@XmlEnumValue("env")
  101.59 +		ENVIRONMENT_VARIABLE,
  101.60 +		/**
  101.61 +		 * value will be substituted by database property of given name
  101.62 +		 */
  101.63 +		@XmlEnumValue("dbProperty")
  101.64 +		DB_PROPERTY;
  101.65 +	}
  101.66 +
  101.67 +	@XmlValue
  101.68 +	public String getValue() {
  101.69 +		return value;
  101.70 +	}
  101.71 +
  101.72 +	public void setValue(String value) {
  101.73 +		this.value = value;
  101.74 +	}
  101.75 +
  101.76 +	@XmlAttribute(name = "type")
  101.77 +	public TYPE getType() {
  101.78 +		return type;
  101.79 +	}
  101.80 +
  101.81 +	public void setType(TYPE type) {
  101.82 +		this.type = type;
  101.83 +	}
  101.84 +
  101.85 +}
   102.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   102.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Configuration.java	Mon Mar 04 20:15:24 2019 +0100
   102.3 @@ -0,0 +1,173 @@
   102.4 +/**
   102.5 + * SQL-DK
   102.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   102.7 + *
   102.8 + * This program is free software: you can redistribute it and/or modify
   102.9 + * it under the terms of the GNU General Public License as published by
  102.10 + * the Free Software Foundation, either version 3 of the License, or
  102.11 + * (at your option) any later version.
  102.12 + *
  102.13 + * This program is distributed in the hope that it will be useful,
  102.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  102.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  102.16 + * GNU General Public License for more details.
  102.17 + *
  102.18 + * You should have received a copy of the GNU General Public License
  102.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  102.20 + */
  102.21 +package info.globalcode.sql.dk.configuration;
  102.22 +
  102.23 +import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
  102.24 +import static info.globalcode.sql.dk.Functions.findByName;
  102.25 +import info.globalcode.sql.dk.formatting.BarChartFormatter;
  102.26 +import info.globalcode.sql.dk.formatting.SilentFormatter;
  102.27 +import info.globalcode.sql.dk.formatting.SingleRecordFormatter;
  102.28 +import info.globalcode.sql.dk.formatting.SingleValueFormatter;
  102.29 +import info.globalcode.sql.dk.formatting.TabularFormatter;
  102.30 +import info.globalcode.sql.dk.formatting.TabularPrefetchingFormatter;
  102.31 +import info.globalcode.sql.dk.formatting.TabularWrappingFormatter;
  102.32 +import info.globalcode.sql.dk.formatting.TeXFormatter;
  102.33 +import info.globalcode.sql.dk.formatting.XhtmlFormatter;
  102.34 +import info.globalcode.sql.dk.formatting.XmlFormatter;
  102.35 +import java.util.ArrayList;
  102.36 +import java.util.Collection;
  102.37 +import java.util.Collections;
  102.38 +import java.util.List;
  102.39 +import javax.xml.bind.annotation.XmlElement;
  102.40 +import javax.xml.bind.annotation.XmlRootElement;
  102.41 +import javax.xml.bind.annotation.XmlTransient;
  102.42 +
  102.43 +/**
  102.44 + * Object representation of user configuration loaded from XML.
  102.45 + *
  102.46 + * @author Ing. František Kučera (frantovo.cz)
  102.47 + */
  102.48 +@XmlRootElement(name = "configuration", namespace = CONFIGURATION)
  102.49 +public class Configuration {
  102.50 +
  102.51 +	private List<DatabaseDefinition> databases = new ArrayList<>();
  102.52 +	private List<FormatterDefinition> formatters = new ArrayList<>();
  102.53 +	/**
  102.54 +	 * is used if no formatter is specified on CLI nor in user configuration
  102.55 +	 */
  102.56 +	public static final String DEFAULT_FORMATTER = TabularFormatter.NAME;
  102.57 +	/**
  102.58 +	 * Can be used as default if prefetching is ok – for configuration listings (config is alread in
  102.59 +	 * memory, so this does not matter)
  102.60 +	 */
  102.61 +	public static final String DEFAULT_FORMATTER_PREFETCHING = TabularPrefetchingFormatter.NAME;
  102.62 +	private String defaultFormatter;
  102.63 +	/**
  102.64 +	 * Default list of formatters. Is used if particular name is not found in user configuration.
  102.65 +	 */
  102.66 +	private static final Collection<FormatterDefinition> buildInFormatters;
  102.67 +
  102.68 +	static {
  102.69 +		Collection<FormatterDefinition> l = new ArrayList<>();
  102.70 +		l.add(new FormatterDefinition(SilentFormatter.NAME, SilentFormatter.class.getName()));
  102.71 +		l.add(new FormatterDefinition(SingleValueFormatter.NAME, SingleValueFormatter.class.getName()));
  102.72 +		l.add(new FormatterDefinition(SingleRecordFormatter.NAME, SingleRecordFormatter.class.getName()));
  102.73 +		l.add(new FormatterDefinition(XmlFormatter.NAME, XmlFormatter.class.getName()));
  102.74 +		l.add(new FormatterDefinition(XhtmlFormatter.NAME, XhtmlFormatter.class.getName()));
  102.75 +		l.add(new FormatterDefinition(TabularFormatter.NAME, TabularFormatter.class.getName()));
  102.76 +		l.add(new FormatterDefinition(TabularPrefetchingFormatter.NAME, TabularPrefetchingFormatter.class.getName()));
  102.77 +		l.add(new FormatterDefinition(TabularWrappingFormatter.NAME, TabularWrappingFormatter.class.getName()));
  102.78 +		l.add(new FormatterDefinition(TeXFormatter.NAME, TeXFormatter.class.getName()));
  102.79 +		//l.add(new FormatterDefinition(DsvFormatter.NAME, DsvFormatter.class.getName()));
  102.80 +		//l.add(new FormatterDefinition(SystemCommandExecutor.NAME, SystemCommandExecutor.class.getName()));
  102.81 +		l.add(new FormatterDefinition(BarChartFormatter.NAME, BarChartFormatter.class.getName()));
  102.82 +		buildInFormatters = Collections.unmodifiableCollection(l);
  102.83 +	}
  102.84 +
  102.85 +	@XmlElement(name = "database", namespace = CONFIGURATION)
  102.86 +	public List<DatabaseDefinition> getDatabases() {
  102.87 +		return databases;
  102.88 +	}
  102.89 +
  102.90 +	public void setDatabases(List<DatabaseDefinition> databases) {
  102.91 +		this.databases = databases;
  102.92 +	}
  102.93 +
  102.94 +	/**
  102.95 +	 * @param name
  102.96 +	 * @return
  102.97 +	 * @throws ConfigurationException if no database with this name is configured
  102.98 +	 */
  102.99 +	public DatabaseDefinition getDatabase(String name) throws ConfigurationException {
 102.100 +		DatabaseDefinition dd = findByName(databases, name);
 102.101 +		if (dd == null) {
 102.102 +			throw new ConfigurationException("Database is not configured: " + name);
 102.103 +		} else {
 102.104 +			return dd;
 102.105 +		}
 102.106 +	}
 102.107 +
 102.108 +	/**
 102.109 +	 * @return only configured formatters
 102.110 +	 * @see #getBuildInFormatters()
 102.111 +	 * @see #getAllFormatters()
 102.112 +	 */
 102.113 +	@XmlElement(name = "formatter", namespace = CONFIGURATION)
 102.114 +	public List<FormatterDefinition> getFormatters() {
 102.115 +		return formatters;
 102.116 +	}
 102.117 +
 102.118 +	public void setFormatters(List<FormatterDefinition> formatters) {
 102.119 +		this.formatters = formatters;
 102.120 +	}
 102.121 +
 102.122 +	/**
 102.123 +	 * @param name name of desired formatter. Looking for this name in user configuration, then in
 102.124 +	 * buil-in formatters. If null, default from configuration or (if not configured) built-in
 102.125 +	 * default is used.
 102.126 +	 * @return formatter definition
 102.127 +	 * @throws ConfigurationException if no formatter with this name was found
 102.128 +	 */
 102.129 +	public FormatterDefinition getFormatter(String name) throws ConfigurationException {
 102.130 +		if (name == null) {
 102.131 +			return defaultFormatter == null ? getFormatter(DEFAULT_FORMATTER) : getFormatter(defaultFormatter);
 102.132 +		} else {
 102.133 +			FormatterDefinition fd = findByName(formatters, name);
 102.134 +			fd = fd == null ? findByName(buildInFormatters, name) : fd;
 102.135 +			if (fd == null) {
 102.136 +				throw new ConfigurationException("Formatter is not configured: " + name);
 102.137 +			} else {
 102.138 +				return fd;
 102.139 +			}
 102.140 +		}
 102.141 +	}
 102.142 +
 102.143 +	/**
 102.144 +	 * @return only built-in formatters
 102.145 +	 * @see #getAllFormatters()
 102.146 +	 * @see #getFormatters()
 102.147 +	 */
 102.148 +	@XmlTransient
 102.149 +	public Collection<FormatterDefinition> getBuildInFormatters() {
 102.150 +		return buildInFormatters;
 102.151 +	}
 102.152 +
 102.153 +	/**
 102.154 +	 * @return built-in + configured formatters
 102.155 +	 * @see #getFormatters()
 102.156 +	 */
 102.157 +	@XmlTransient
 102.158 +	public Collection<FormatterDefinition> getAllFormatters() {
 102.159 +		Collection<FormatterDefinition> allFormatters = new ArrayList<>();
 102.160 +		allFormatters.addAll(buildInFormatters);
 102.161 +		allFormatters.addAll(formatters);
 102.162 +		return allFormatters;
 102.163 +	}
 102.164 +
 102.165 +	/**
 102.166 +	 * @return name of default formatter, is used if name is not specified on CLI
 102.167 +	 */
 102.168 +	@XmlElement(name = "defaultFormatter", namespace = CONFIGURATION)
 102.169 +	public String getDefaultFormatter() {
 102.170 +		return defaultFormatter;
 102.171 +	}
 102.172 +
 102.173 +	public void setDefaultFormatter(String defaultFormatter) {
 102.174 +		this.defaultFormatter = defaultFormatter;
 102.175 +	}
 102.176 +}
   103.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   103.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/ConfigurationException.java	Mon Mar 04 20:15:24 2019 +0100
   103.3 @@ -0,0 +1,42 @@
   103.4 +/**
   103.5 + * SQL-DK
   103.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   103.7 + *
   103.8 + * This program is free software: you can redistribute it and/or modify
   103.9 + * it under the terms of the GNU General Public License as published by
  103.10 + * the Free Software Foundation, either version 3 of the License, or
  103.11 + * (at your option) any later version.
  103.12 + *
  103.13 + * This program is distributed in the hope that it will be useful,
  103.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  103.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  103.16 + * GNU General Public License for more details.
  103.17 + *
  103.18 + * You should have received a copy of the GNU General Public License
  103.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  103.20 + */
  103.21 +package info.globalcode.sql.dk.configuration;
  103.22 +
  103.23 +import info.globalcode.sql.dk.DKException;
  103.24 +
  103.25 +/**
  103.26 + *
  103.27 + * @author Ing. František Kučera (frantovo.cz)
  103.28 + */
  103.29 +public class ConfigurationException extends DKException {
  103.30 +
  103.31 +	public ConfigurationException() {
  103.32 +	}
  103.33 +
  103.34 +	public ConfigurationException(String message) {
  103.35 +		super(message);
  103.36 +	}
  103.37 +
  103.38 +	public ConfigurationException(Throwable cause) {
  103.39 +		super(cause);
  103.40 +	}
  103.41 +
  103.42 +	public ConfigurationException(String message, Throwable cause) {
  103.43 +		super(message, cause);
  103.44 +	}
  103.45 +}
   104.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   104.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/ConfigurationProvider.java	Mon Mar 04 20:15:24 2019 +0100
   104.3 @@ -0,0 +1,28 @@
   104.4 +/**
   104.5 + * SQL-DK
   104.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   104.7 + *
   104.8 + * This program is free software: you can redistribute it and/or modify
   104.9 + * it under the terms of the GNU General Public License as published by
  104.10 + * the Free Software Foundation, either version 3 of the License, or
  104.11 + * (at your option) any later version.
  104.12 + *
  104.13 + * This program is distributed in the hope that it will be useful,
  104.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  104.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  104.16 + * GNU General Public License for more details.
  104.17 + *
  104.18 + * You should have received a copy of the GNU General Public License
  104.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  104.20 + */
  104.21 +package info.globalcode.sql.dk.configuration;
  104.22 +
  104.23 +/**
  104.24 + * Use for lazy-loading of the configuration.
  104.25 + *
  104.26 + * @author Ing. František Kučera (frantovo.cz)
  104.27 + */
  104.28 +public interface ConfigurationProvider {
  104.29 +
  104.30 +	public Configuration getConfiguration() throws ConfigurationException;
  104.31 +}
   105.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   105.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/DatabaseDefinition.java	Mon Mar 04 20:15:24 2019 +0100
   105.3 @@ -0,0 +1,147 @@
   105.4 +/**
   105.5 + * SQL-DK
   105.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   105.7 + *
   105.8 + * This program is free software: you can redistribute it and/or modify
   105.9 + * it under the terms of the GNU General Public License as published by
  105.10 + * the Free Software Foundation, either version 3 of the License, or
  105.11 + * (at your option) any later version.
  105.12 + *
  105.13 + * This program is distributed in the hope that it will be useful,
  105.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  105.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  105.16 + * GNU General Public License for more details.
  105.17 + *
  105.18 + * You should have received a copy of the GNU General Public License
  105.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  105.20 + */
  105.21 +package info.globalcode.sql.dk.configuration;
  105.22 +
  105.23 +import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
  105.24 +import info.globalcode.sql.dk.DatabaseConnection;
  105.25 +import info.globalcode.sql.dk.jmx.ConnectionManagement;
  105.26 +import java.sql.SQLException;
  105.27 +import java.util.logging.Logger;
  105.28 +import javax.xml.bind.annotation.XmlElement;
  105.29 +
  105.30 +/**
  105.31 + * Configured (but not yet connected) database connection.
  105.32 + *
  105.33 + * @author Ing. František Kučera (frantovo.cz)
  105.34 + */
  105.35 +public class DatabaseDefinition implements NameIdentified {
  105.36 +
  105.37 +	private static final Logger log = Logger.getLogger(DatabaseDefinition.class.getName());
  105.38 +	/**
  105.39 +	 * database name in SQL-DK configuration
  105.40 +	 */
  105.41 +	private String name;
  105.42 +	/**
  105.43 +	 * JDBC URL
  105.44 +	 */
  105.45 +	private String url;
  105.46 +	/**
  105.47 +	 * JDBC user name
  105.48 +	 */
  105.49 +	private String userName;
  105.50 +	/**
  105.51 +	 * JDBC password
  105.52 +	 */
  105.53 +	private String password;
  105.54 +	/**
  105.55 +	 * optional JDBC driver – if empty, the DriverManager is used to lookup specific Driver for
  105.56 +	 * given URL
  105.57 +	 */
  105.58 +	private String driver;
  105.59 +	/**
  105.60 +	 * JDBC properties
  105.61 +	 */
  105.62 +	private Properties properties = new Properties();
  105.63 +	/**
  105.64 +	 * optional definition of tunnel to the remote database
  105.65 +	 */
  105.66 +	private TunnelDefinition tunnel;
  105.67 +
  105.68 +	@XmlElement(name = "name", namespace = CONFIGURATION)
  105.69 +	@Override
  105.70 +	public String getName() {
  105.71 +		return name;
  105.72 +	}
  105.73 +
  105.74 +	public void setName(String name) {
  105.75 +		this.name = name;
  105.76 +	}
  105.77 +
  105.78 +	@XmlElement(name = "url", namespace = CONFIGURATION)
  105.79 +	public String getUrl() {
  105.80 +		return url;
  105.81 +	}
  105.82 +
  105.83 +	public void setUrl(String url) {
  105.84 +		this.url = url;
  105.85 +	}
  105.86 +
  105.87 +	@XmlElement(name = "userName", namespace = CONFIGURATION)
  105.88 +	public String getUserName() {
  105.89 +		return userName;
  105.90 +	}
  105.91 +
  105.92 +	public void setUserName(String userName) {
  105.93 +		this.userName = userName;
  105.94 +	}
  105.95 +
  105.96 +	@XmlElement(name = "password", namespace = CONFIGURATION)
  105.97 +	public String getPassword() {
  105.98 +		return password;
  105.99 +	}
 105.100 +
 105.101 +	public void setPassword(String password) {
 105.102 +		this.password = password;
 105.103 +	}
 105.104 +
 105.105 +	public String getDriver() {
 105.106 +		return driver;
 105.107 +	}
 105.108 +
 105.109 +	public void setDriver(String driver) {
 105.110 +		this.driver = driver;
 105.111 +	}
 105.112 +
 105.113 +	@XmlElement(name = "property", namespace = CONFIGURATION)
 105.114 +	public Properties getProperties() {
 105.115 +		return properties;
 105.116 +	}
 105.117 +
 105.118 +	public void setProperties(Properties properties) {
 105.119 +		this.properties = properties;
 105.120 +	}
 105.121 +
 105.122 +	public TunnelDefinition getTunnel() {
 105.123 +		return tunnel;
 105.124 +	}
 105.125 +
 105.126 +	public void setTunnel(TunnelDefinition tunnel) {
 105.127 +		this.tunnel = tunnel;
 105.128 +	}
 105.129 +
 105.130 +	/**
 105.131 +	 * @param properties ad-hoc properties from CLI options (for the JDBC driver)
 105.132 +	 * @param jmxBean JMX management bean for progress reporting | null = disable JMX
 105.133 +	 * @return
 105.134 +	 * @throws java.sql.SQLException
 105.135 +	 */
 105.136 +	public DatabaseConnection connect(Properties properties, ConnectionManagement jmxBean) throws SQLException {
 105.137 +		return new DatabaseConnection(this, properties, jmxBean);
 105.138 +	}
 105.139 +
 105.140 +	/**
 105.141 +	 * @param properties
 105.142 +	 * @return
 105.143 +	 * @throws java.sql.SQLException
 105.144 +	 * @see #connect(info.globalcode.sql.dk.configuration.Properties, java.lang.String)
 105.145 +	 * With disabled JMX reporting.
 105.146 +	 */
 105.147 +	public DatabaseConnection connect(Properties properties) throws SQLException {
 105.148 +		return new DatabaseConnection(this, properties, null);
 105.149 +	}
 105.150 +}
   106.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   106.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/FormatterDefinition.java	Mon Mar 04 20:15:24 2019 +0100
   106.3 @@ -0,0 +1,114 @@
   106.4 +/**
   106.5 + * SQL-DK
   106.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   106.7 + *
   106.8 + * This program is free software: you can redistribute it and/or modify
   106.9 + * it under the terms of the GNU General Public License as published by
  106.10 + * the Free Software Foundation, either version 3 of the License, or
  106.11 + * (at your option) any later version.
  106.12 + *
  106.13 + * This program is distributed in the hope that it will be useful,
  106.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  106.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  106.16 + * GNU General Public License for more details.
  106.17 + *
  106.18 + * You should have received a copy of the GNU General Public License
  106.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  106.20 + */
  106.21 +package info.globalcode.sql.dk.configuration;
  106.22 +
  106.23 +import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
  106.24 +import info.globalcode.sql.dk.formatting.Formatter;
  106.25 +import info.globalcode.sql.dk.formatting.FormatterContext;
  106.26 +import info.globalcode.sql.dk.formatting.FormatterException;
  106.27 +import java.lang.reflect.Constructor;
  106.28 +import java.lang.reflect.InvocationTargetException;
  106.29 +import javax.xml.bind.annotation.XmlElement;
  106.30 +
  106.31 +/**
  106.32 + * Configured (but not yet instantiated) formatter.
  106.33 + *
  106.34 + * @author Ing. František Kučera (frantovo.cz)
  106.35 + */
  106.36 +public class FormatterDefinition implements NameIdentified {
  106.37 +
  106.38 +	private String name;
  106.39 +	private String className;
  106.40 +	private Properties properties = new Properties();
  106.41 +
  106.42 +	public FormatterDefinition() {
  106.43 +	}
  106.44 +
  106.45 +	public FormatterDefinition(String name, String className) {
  106.46 +		this.name = name;
  106.47 +		this.className = className;
  106.48 +	}
  106.49 +
  106.50 +	public FormatterDefinition(String name, String className, Properties properties) {
  106.51 +		this(name, className);
  106.52 +		this.properties = properties;
  106.53 +	}
  106.54 +
  106.55 +	@XmlElement(name = "name", namespace = CONFIGURATION)
  106.56 +	@Override
  106.57 +	public String getName() {
  106.58 +		return name;
  106.59 +	}
  106.60 +
  106.61 +	public void setName(String name) {
  106.62 +		this.name = name;
  106.63 +	}
  106.64 +
  106.65 +	/**
  106.66 +	 * Filter's class. Must implement the
  106.67 +	 * <code>info.globalcode.sql.dk.formatting.Formatter</code> interface.
  106.68 +	 * Subclassing the
  106.69 +	 * <code>info.globalcode.sql.dk.formatting.AbstractFormatter</code> is strongly recommended.
  106.70 +	 * The constructor must accept one parameter:
  106.71 +	 * <code>info.globalcode.sql.dk.formatting.FormatterContext</code>
  106.72 +	 *
  106.73 +	 * @return fully qualified class name
  106.74 +	 */
  106.75 +	@XmlElement(name = "class", namespace = CONFIGURATION)
  106.76 +	public String getClassName() {
  106.77 +		return className;
  106.78 +	}
  106.79 +
  106.80 +	public void setClassName(String className) {
  106.81 +		this.className = className;
  106.82 +	}
  106.83 +
  106.84 +	@XmlElement(name = "property", namespace = CONFIGURATION)
  106.85 +	public Properties getProperties() {
  106.86 +		return properties;
  106.87 +	}
  106.88 +
  106.89 +	public void setProperties(Properties properties) {
  106.90 +		this.properties = properties;
  106.91 +	}
  106.92 +
  106.93 +	/**
  106.94 +	 * @param context
  106.95 +	 * @return
  106.96 +	 * @throws FormatterException
  106.97 +	 */
  106.98 +	public Formatter getInstance(FormatterContext context) throws FormatterException {
  106.99 +		context.getProperties().setDefaults(properties);
 106.100 +		try {
 106.101 +			Constructor constructor = Class.forName(className).getConstructor(context.getClass());
 106.102 +
 106.103 +			Object instance = constructor.newInstance(context);
 106.104 +			if (instance instanceof Formatter) {
 106.105 +				return (Formatter) instance;
 106.106 +			} else {
 106.107 +				throw new FormatterException("Formatter " + instance + " does not implement the " + Formatter.class.getName() + " interface");
 106.108 +			}
 106.109 +		} catch (ClassNotFoundException e) {
 106.110 +			throw new FormatterException("Formatter class does not exist: " + className, e);
 106.111 +		} catch (NoSuchMethodException e) {
 106.112 +			throw new FormatterException("Formatter class with no valid constructor: " + className, e);
 106.113 +		} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
 106.114 +			throw new FormatterException("Formatter's constructor caused an error: " + className, e);
 106.115 +		}
 106.116 +	}
 106.117 +}
   107.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   107.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Loader.java	Mon Mar 04 20:15:24 2019 +0100
   107.3 @@ -0,0 +1,101 @@
   107.4 +/**
   107.5 + * SQL-DK
   107.6 + * Copyright © 2015 František Kučera (frantovo.cz)
   107.7 + *
   107.8 + * This program is free software: you can redistribute it and/or modify
   107.9 + * it under the terms of the GNU General Public License as published by
  107.10 + * the Free Software Foundation, either version 3 of the License, or
  107.11 + * (at your option) any later version.
  107.12 + *
  107.13 + * This program is distributed in the hope that it will be useful,
  107.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  107.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  107.16 + * GNU General Public License for more details.
  107.17 + *
  107.18 + * You should have received a copy of the GNU General Public License
  107.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  107.20 + */
  107.21 +package info.globalcode.sql.dk.configuration;
  107.22 +
  107.23 +import info.globalcode.sql.dk.*;
  107.24 +import static info.globalcode.sql.dk.DatabaseConnection.JDBC_PROPERTY_USER;
  107.25 +import static info.globalcode.sql.dk.DatabaseConnection.JDBC_PROPERTY_PASSWORD;
  107.26 +import java.sql.Connection;
  107.27 +import java.sql.Driver;
  107.28 +import java.sql.DriverManager;
  107.29 +import java.sql.SQLException;
  107.30 +import java.util.logging.Level;
  107.31 +import java.util.logging.Logger;
  107.32 +import javax.xml.bind.JAXBContext;
  107.33 +import javax.xml.bind.Unmarshaller;
  107.34 +
  107.35 +/**
  107.36 + * Configuration loader – deserializes Configuration from the XML file.
  107.37 + *
  107.38 + * @author Ing. František Kučera (frantovo.cz)
  107.39 + */
  107.40 +public class Loader {
  107.41 +
  107.42 +	private static final Logger log = Logger.getLogger(Loader.class.getName());
  107.43 +
  107.44 +	public Configuration loadConfiguration() throws ConfigurationException {
  107.45 +		try {
  107.46 +			JAXBContext jaxb = JAXBContext.newInstance(Configuration.class.getPackage().getName(), Configuration.class.getClassLoader());
  107.47 +			Unmarshaller u = jaxb.createUnmarshaller();
  107.48 +			return (Configuration) u.unmarshal(Constants.CONFIG_FILE);
  107.49 +		} catch (Exception e) {
  107.50 +			throw new ConfigurationException("Unable to load configuration from " + Constants.CONFIG_FILE, e);
  107.51 +		}
  107.52 +	}
  107.53 +
  107.54 +	/**
  107.55 +	 * JDBC connection should not be used directly in SQL-DK.
  107.56 +	 *
  107.57 +	 * @see DatabaseDefinition#connect(info.globalcode.sql.dk.configuration.Properties)
  107.58 +	 * @param properties
  107.59 +	 * @param databaseDefinition
  107.60 +	 * @return
  107.61 +	 * @throws java.sql.SQLException
  107.62 +	 */
  107.63 +	public static Connection jdbcConnect(DatabaseDefinition databaseDefinition, Properties properties) throws SQLException {
  107.64 +		synchronized (properties) {
  107.65 +			/**
  107.66 +			 * Avoid rewriting the properties. Usually, the connection is created only once, but
  107.67 +			 * with --test-connection and with SQL-DK JDBC driver, it might be reused.
  107.68 +			 */
  107.69 +			properties = properties.clone();
  107.70 +		}
  107.71 +		if (properties.hasProperty(JDBC_PROPERTY_PASSWORD)) {
  107.72 +			log.log(Level.WARNING, "Passing DB password as CLI parameter is insecure!");
  107.73 +		}
  107.74 +		Properties credentials = new Properties();
  107.75 +		credentials.add(new Property(JDBC_PROPERTY_USER, databaseDefinition.getUserName()));
  107.76 +		credentials.add(new Property(JDBC_PROPERTY_PASSWORD, databaseDefinition.getPassword()));
  107.77 +		credentials.setDefaults(databaseDefinition.getProperties());
  107.78 +		properties.setDefaults(credentials);
  107.79 +		java.util.Properties javaProperties = properties.getJavaProperties();
  107.80 +
  107.81 +		String driverClassName = databaseDefinition.getDriver();
  107.82 +		final String url = databaseDefinition.getUrl();
  107.83 +		if (driverClassName == null) {
  107.84 +			log.log(Level.FINE, "Using DriverManager to create connection for „{0}“", url);
  107.85 +			return DriverManager.getConnection(url, javaProperties);
  107.86 +		} else {
  107.87 +			log.log(Level.FINE, "Using custom Driver „{0}“ to create connection for „{1}“", new Object[]{driverClassName, url});
  107.88 +			try {
  107.89 +				Class<Driver> driverClass = (Class<Driver>) Class.forName(driverClassName);
  107.90 +				Driver driver = driverClass.newInstance();
  107.91 +				Connection connection = driver.connect(url, javaProperties);
  107.92 +				if (connection == null) {
  107.93 +					log.log(Level.SEVERE, "Driver „{0}“ returend null → it does not accept the URL: „{1}“", new Object[]{driverClassName, url});
  107.94 +					throw new SQLException("Unable to connect: driver returned null.");
  107.95 +				} else {
  107.96 +					return connection;
  107.97 +				}
  107.98 +			} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | SQLException e) {
  107.99 +				throw new SQLException("Unable to connect usig specific driver: " + driverClassName, e);
 107.100 +			}
 107.101 +		}
 107.102 +	}
 107.103 +
 107.104 +}
   108.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   108.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/NameIdentified.java	Mon Mar 04 20:15:24 2019 +0100
   108.3 @@ -0,0 +1,27 @@
   108.4 +/**
   108.5 + * SQL-DK
   108.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   108.7 + *
   108.8 + * This program is free software: you can redistribute it and/or modify
   108.9 + * it under the terms of the GNU General Public License as published by
  108.10 + * the Free Software Foundation, either version 3 of the License, or
  108.11 + * (at your option) any later version.
  108.12 + *
  108.13 + * This program is distributed in the hope that it will be useful,
  108.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  108.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  108.16 + * GNU General Public License for more details.
  108.17 + *
  108.18 + * You should have received a copy of the GNU General Public License
  108.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  108.20 + */
  108.21 +package info.globalcode.sql.dk.configuration;
  108.22 +
  108.23 +/**
  108.24 + *
  108.25 + * @author Ing. František Kučera (frantovo.cz)
  108.26 + */
  108.27 +public interface NameIdentified {
  108.28 +
  108.29 +	public String getName();
  108.30 +}
   109.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   109.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Properties.java	Mon Mar 04 20:15:24 2019 +0100
   109.3 @@ -0,0 +1,129 @@
   109.4 +/**
   109.5 + * SQL-DK
   109.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   109.7 + *
   109.8 + * This program is free software: you can redistribute it and/or modify
   109.9 + * it under the terms of the GNU General Public License as published by
  109.10 + * the Free Software Foundation, either version 3 of the License, or
  109.11 + * (at your option) any later version.
  109.12 + *
  109.13 + * This program is distributed in the hope that it will be useful,
  109.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  109.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  109.16 + * GNU General Public License for more details.
  109.17 + *
  109.18 + * You should have received a copy of the GNU General Public License
  109.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  109.20 + */
  109.21 +package info.globalcode.sql.dk.configuration;
  109.22 +
  109.23 +import java.util.ArrayList;
  109.24 +import javax.xml.bind.annotation.XmlTransient;
  109.25 +import static info.globalcode.sql.dk.Functions.findByName;
  109.26 +import java.util.Collections;
  109.27 +
  109.28 +/**
  109.29 + * <p>
  109.30 + * List of configurables.</p>
  109.31 + *
  109.32 + * <p>
  109.33 + * Can be backed by defaults – if value for given name is nof found in this instance, we will
  109.34 + * look into defaults. Methods also accept defaultValue parameter – is used if property is nof found
  109.35 + * even in default properties.</p>
  109.36 + *
  109.37 + * <p>
  109.38 + * Typical use: </p>
  109.39 + * <ul>
  109.40 + * <li>this instance – ad-hoc properties from CLI options</li>
  109.41 + * <li>default properties – from config file</li>
  109.42 + * <li>defaultValue – hardcoded default</li>
  109.43 + * </ul>
  109.44 + *
  109.45 + * @author Ing. František Kučera (frantovo.cz)
  109.46 + */
  109.47 +public class Properties extends ArrayList<Property> implements Cloneable {
  109.48 +
  109.49 +	private Properties defaults;
  109.50 +
  109.51 +	public Properties() {
  109.52 +	}
  109.53 +
  109.54 +	public Properties(int initialCapacity) {
  109.55 +		super(initialCapacity);
  109.56 +	}
  109.57 +
  109.58 +	@XmlTransient
  109.59 +	public Properties getDefaults() {
  109.60 +		return defaults;
  109.61 +	}
  109.62 +
  109.63 +	public void setDefaults(Properties defaults) {
  109.64 +		this.defaults = defaults;
  109.65 +	}
  109.66 +
  109.67 +	/**
  109.68 +	 * @param defaults the last/deepest defaults
  109.69 +	 */
  109.70 +	public void setLastDefaults(Properties defaults) {
  109.71 +		if (this.defaults == null) {
  109.72 +			this.defaults = defaults;
  109.73 +		} else {
  109.74 +			this.defaults.setLastDefaults(defaults);
  109.75 +		}
  109.76 +	}
  109.77 +
  109.78 +	private Property findProperty(String name) {
  109.79 +		Property p = findByName(this, name);
  109.80 +		if (p == null && defaults != null) {
  109.81 +			p = defaults.findProperty(name);
  109.82 +		}
  109.83 +		return p;
  109.84 +	}
  109.85 +
  109.86 +	public String getString(String name, String defaultValue) {
  109.87 +		Property p = findProperty(name);
  109.88 +		return p == null ? defaultValue : p.getValue();
  109.89 +	}
  109.90 +
  109.91 +	public boolean getBoolean(String name, boolean defaultValue) {
  109.92 +		Property p = findProperty(name);
  109.93 +		return p == null ? defaultValue : Boolean.valueOf(p.getValue());
  109.94 +	}
  109.95 +
  109.96 +	public int getInteger(String name, int defaultValue) {
  109.97 +		Property p = findProperty(name);
  109.98 +		return p == null ? defaultValue : Integer.valueOf(p.getValue());
  109.99 +	}
 109.100 +
 109.101 +	public boolean hasProperty(String name) {
 109.102 +		return findByName(this, name) != null;
 109.103 +	}
 109.104 +
 109.105 +	@Override
 109.106 +	public Properties clone() {
 109.107 +		Properties clone = new Properties(size());
 109.108 +		Collections.copy(clone, this);
 109.109 +		return clone;
 109.110 +	}
 109.111 +
 109.112 +	/**
 109.113 +	 * @return merged this and backing defaults as Java Properties
 109.114 +	 */
 109.115 +	public java.util.Properties getJavaProperties() {
 109.116 +		java.util.Properties javaProperties = new java.util.Properties();
 109.117 +		duplicateTo(javaProperties);
 109.118 +		return javaProperties;
 109.119 +	}
 109.120 +
 109.121 +	private void duplicateTo(java.util.Properties javaProperties) {
 109.122 +		if (defaults != null) {
 109.123 +			defaults.duplicateTo(javaProperties);
 109.124 +		}
 109.125 +		for (Property p : this) {
 109.126 +			String value = p.getValue();
 109.127 +			if (value != null) {
 109.128 +				javaProperties.setProperty(p.getName(), value);
 109.129 +			}
 109.130 +		}
 109.131 +	}
 109.132 +}
   110.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   110.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Property.java	Mon Mar 04 20:15:24 2019 +0100
   110.3 @@ -0,0 +1,69 @@
   110.4 +/**
   110.5 + * SQL-DK
   110.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   110.7 + *
   110.8 + * This program is free software: you can redistribute it and/or modify
   110.9 + * it under the terms of the GNU General Public License as published by
  110.10 + * the Free Software Foundation, either version 3 of the License, or
  110.11 + * (at your option) any later version.
  110.12 + *
  110.13 + * This program is distributed in the hope that it will be useful,
  110.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  110.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  110.16 + * GNU General Public License for more details.
  110.17 + *
  110.18 + * You should have received a copy of the GNU General Public License
  110.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  110.20 + */
  110.21 +package info.globalcode.sql.dk.configuration;
  110.22 +
  110.23 +import javax.xml.bind.annotation.XmlAttribute;
  110.24 +import javax.xml.bind.annotation.XmlValue;
  110.25 +
  110.26 +/**
  110.27 + * One configurable
  110.28 + *
  110.29 + * @author Ing. František Kučera (frantovo.cz)
  110.30 + */
  110.31 +public class Property implements NameIdentified, Cloneable {
  110.32 +
  110.33 +	private String name;
  110.34 +	private String value;
  110.35 +
  110.36 +	public Property() {
  110.37 +	}
  110.38 +
  110.39 +	public Property(String name, String value) {
  110.40 +		this.name = name;
  110.41 +		this.value = value;
  110.42 +	}
  110.43 +
  110.44 +	@XmlAttribute(name = "name")
  110.45 +	@Override
  110.46 +	public String getName() {
  110.47 +		return name;
  110.48 +	}
  110.49 +
  110.50 +	public void setName(String name) {
  110.51 +		this.name = name;
  110.52 +	}
  110.53 +
  110.54 +	@XmlValue
  110.55 +	public String getValue() {
  110.56 +		return value;
  110.57 +	}
  110.58 +
  110.59 +	public void setValue(String value) {
  110.60 +		this.value = value;
  110.61 +	}
  110.62 +
  110.63 +	@Override
  110.64 +	public String toString() {
  110.65 +		return name + "='" + value + "'";
  110.66 +	}
  110.67 +
  110.68 +	@Override
  110.69 +	protected Property clone() {
  110.70 +		return new Property(name, value);
  110.71 +	}
  110.72 +}
   111.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   111.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/PropertyDeclaration.java	Mon Mar 04 20:15:24 2019 +0100
   111.3 @@ -0,0 +1,57 @@
   111.4 +/**
   111.5 + * SQL-DK
   111.6 + * Copyright © 2015 František Kučera (frantovo.cz)
   111.7 + *
   111.8 + * This program is free software: you can redistribute it and/or modify
   111.9 + * it under the terms of the GNU General Public License as published by
  111.10 + * the Free Software Foundation, either version 3 of the License, or
  111.11 + * (at your option) any later version.
  111.12 + *
  111.13 + * This program is distributed in the hope that it will be useful,
  111.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  111.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  111.16 + * GNU General Public License for more details.
  111.17 + *
  111.18 + * You should have received a copy of the GNU General Public License
  111.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  111.20 + */
  111.21 +package info.globalcode.sql.dk.configuration;
  111.22 +
  111.23 +import java.lang.annotation.ElementType;
  111.24 +import java.lang.annotation.Repeatable;
  111.25 +import java.lang.annotation.Retention;
  111.26 +import static java.lang.annotation.RetentionPolicy.RUNTIME;
  111.27 +import java.lang.annotation.Target;
  111.28 +
  111.29 +/**
  111.30 + * Declaration of the (formatter) properties – for documentation purposes.
  111.31 + *
  111.32 + * TODO: automatically inject properties (configured, ad-hoc, default ones) to the formatters
  111.33 + *
  111.34 + * @author Ing. František Kučera (frantovo.cz)
  111.35 + */
  111.36 +@Retention(RUNTIME)
  111.37 +@Target({ElementType.TYPE})
  111.38 +@Repeatable(PropertyDeclarations.class)
  111.39 +public @interface PropertyDeclaration {
  111.40 +
  111.41 +	/**
  111.42 +	 * @return name of the property
  111.43 +	 */
  111.44 +	String name();
  111.45 +
  111.46 +	/**
  111.47 +	 * @return data type of the value: String, numbers, Boolean or Enum
  111.48 +	 */
  111.49 +	Class type();
  111.50 +	
  111.51 +	/**
  111.52 +	 * @return documentation for the users
  111.53 +	 */
  111.54 +	String description();
  111.55 +
  111.56 +	/**
  111.57 +	 * @return default value of this property
  111.58 +	 */
  111.59 +	String defaultValue();
  111.60 +}
   112.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   112.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/PropertyDeclarations.java	Mon Mar 04 20:15:24 2019 +0100
   112.3 @@ -0,0 +1,35 @@
   112.4 +/**
   112.5 + * SQL-DK
   112.6 + * Copyright © 2015 František Kučera (frantovo.cz)
   112.7 + *
   112.8 + * This program is free software: you can redistribute it and/or modify
   112.9 + * it under the terms of the GNU General Public License as published by
  112.10 + * the Free Software Foundation, either version 3 of the License, or
  112.11 + * (at your option) any later version.
  112.12 + *
  112.13 + * This program is distributed in the hope that it will be useful,
  112.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  112.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  112.16 + * GNU General Public License for more details.
  112.17 + *
  112.18 + * You should have received a copy of the GNU General Public License
  112.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  112.20 + */
  112.21 +package info.globalcode.sql.dk.configuration;
  112.22 +
  112.23 +import java.lang.annotation.ElementType;
  112.24 +import java.lang.annotation.Retention;
  112.25 +import static java.lang.annotation.RetentionPolicy.RUNTIME;
  112.26 +import java.lang.annotation.Target;
  112.27 +
  112.28 +/**
  112.29 + *
  112.30 + * @author Ing. František Kučera (frantovo.cz)
  112.31 + */
  112.32 +@Retention(RUNTIME)
  112.33 +@Target({ElementType.TYPE})
  112.34 +public @interface PropertyDeclarations {
  112.35 +
  112.36 +	PropertyDeclaration[] value();
  112.37 +
  112.38 +}
   113.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   113.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/TunnelDefinition.java	Mon Mar 04 20:15:24 2019 +0100
   113.3 @@ -0,0 +1,51 @@
   113.4 +/**
   113.5 + * SQL-DK
   113.6 + * Copyright © 2015 František Kučera (frantovo.cz)
   113.7 + *
   113.8 + * This program is free software: you can redistribute it and/or modify
   113.9 + * it under the terms of the GNU General Public License as published by
  113.10 + * the Free Software Foundation, either version 3 of the License, or
  113.11 + * (at your option) any later version.
  113.12 + *
  113.13 + * This program is distributed in the hope that it will be useful,
  113.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  113.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  113.16 + * GNU General Public License for more details.
  113.17 + *
  113.18 + * You should have received a copy of the GNU General Public License
  113.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  113.20 + */
  113.21 +package info.globalcode.sql.dk.configuration;
  113.22 +
  113.23 +import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
  113.24 +import java.util.List;
  113.25 +import javax.xml.bind.annotation.XmlElement;
  113.26 +
  113.27 +/**
  113.28 + *
  113.29 + * @author Ing. František Kučera (frantovo.cz)
  113.30 + */
  113.31 +public class TunnelDefinition {
  113.32 +
  113.33 +	private String command;
  113.34 +	private List<CommandArgument> arguments;
  113.35 +
  113.36 +	@XmlElement(name = "command", namespace = CONFIGURATION)
  113.37 +	public String getCommand() {
  113.38 +		return command;
  113.39 +	}
  113.40 +
  113.41 +	public void setCommand(String command) {
  113.42 +		this.command = command;
  113.43 +	}
  113.44 +
  113.45 +	@XmlElement(name = "argument", namespace = CONFIGURATION)
  113.46 +	public List<CommandArgument> getArguments() {
  113.47 +		return arguments;
  113.48 +	}
  113.49 +
  113.50 +	public void setArguments(List<CommandArgument> arguments) {
  113.51 +		this.arguments = arguments;
  113.52 +	}
  113.53 +
  113.54 +}
   114.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   114.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractFormatter.java	Mon Mar 04 20:15:24 2019 +0100
   114.3 @@ -0,0 +1,254 @@
   114.4 +/**
   114.5 + * SQL-DK
   114.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   114.7 + *
   114.8 + * This program is free software: you can redistribute it and/or modify
   114.9 + * it under the terms of the GNU General Public License as published by
  114.10 + * the Free Software Foundation, either version 3 of the License, or
  114.11 + * (at your option) any later version.
  114.12 + *
  114.13 + * This program is distributed in the hope that it will be useful,
  114.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  114.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  114.16 + * GNU General Public License for more details.
  114.17 + *
  114.18 + * You should have received a copy of the GNU General Public License
  114.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  114.20 + */
  114.21 +package info.globalcode.sql.dk.formatting;
  114.22 +
  114.23 +import info.globalcode.sql.dk.Parameter;
  114.24 +import info.globalcode.sql.dk.configuration.DatabaseDefinition;
  114.25 +import java.util.EmptyStackException;
  114.26 +import java.util.EnumSet;
  114.27 +import java.util.List;
  114.28 +import java.util.Stack;
  114.29 +
  114.30 +/**
  114.31 + * <ol>
  114.32 + * <li>ensures integrity – if methods are called in correct order and context</li>
  114.33 + * <li>provides default implmentations of methods that does not produce any output for given
  114.34 + * events</li>
  114.35 + * </ol>
  114.36 + *
  114.37 + * @author Ing. František Kučera (frantovo.cz)
  114.38 + */
  114.39 +public abstract class AbstractFormatter implements Formatter {
  114.40 +
  114.41 +	private Stack<State> state = new Stack<>();
  114.42 +	private FormatterContext formatterContext;
  114.43 +	private ColumnsHeader currentColumnsHeader;
  114.44 +	private String currentQuery;
  114.45 +	private int currentColumnsCount;
  114.46 +	private int currentRowCount;
  114.47 +
  114.48 +	public AbstractFormatter(FormatterContext formatterContext) {
  114.49 +		this.formatterContext = formatterContext;
  114.50 +		state.push(State.ROOT);
  114.51 +	}
  114.52 +
  114.53 +	/*
  114.54 +	 * root
  114.55 +	 * .batch
  114.56 +	 * ..database
  114.57 +	 * ...statement
  114.58 +	 * ....@query
  114.59 +	 * ....@parameters
  114.60 +	 * ....resultSet
  114.61 +	 * .....row
  114.62 +	 * ......@columnValue
  114.63 +	 * ....@updatesResult
  114.64 +	 */
  114.65 +	protected enum State {
  114.66 +
  114.67 +		ROOT,
  114.68 +		BATCH,
  114.69 +		DATABASE,
  114.70 +		STATEMENT,
  114.71 +		RESULT_SET,
  114.72 +		ROW
  114.73 +	}
  114.74 +
  114.75 +	/**
  114.76 +	 * Go down in hierarchy.
  114.77 +	 * Pushes new state and verifies the old one.
  114.78 +	 *
  114.79 +	 * @param current the new state – currently entering
  114.80 +	 * @param expected expected previous states (any of them is valid)
  114.81 +	 * @return previous state
  114.82 +	 * @throws IllegalStateException if previous state was not one from expected
  114.83 +	 */
  114.84 +	private State pushState(State current, EnumSet expected) {
  114.85 +		State previous = state.peek();
  114.86 +
  114.87 +		if (expected.contains(previous)) {
  114.88 +			state.push(current);
  114.89 +			return previous;
  114.90 +		} else {
  114.91 +			throw new IllegalStateException("Formatter was in wrong state: " + previous + " when it should be in one of: " + expected);
  114.92 +		}
  114.93 +	}
  114.94 +
  114.95 +	protected State peekState(EnumSet expected) {
  114.96 +		State current = state.peek();
  114.97 +
  114.98 +		if (expected.contains(current)) {
  114.99 +			return current;
 114.100 +		} else {
 114.101 +			throw new IllegalStateException("Formatter is in wrong state: " + current + " when it should be in one of: " + expected);
 114.102 +		}
 114.103 +
 114.104 +	}
 114.105 +
 114.106 +	/**
 114.107 +	 * Go up in hierarchy.
 114.108 +	 * Pops the superior state/branch.
 114.109 +	 *
 114.110 +	 * @param expected expected superior state
 114.111 +	 * @return the superior state
 114.112 +	 * @throws IllegalStateException if superior state was not one from expected or if there is no
 114.113 +	 * more superior state (we are at root level)
 114.114 +	 */
 114.115 +	private State popState(EnumSet expected) {
 114.116 +		try {
 114.117 +			state.pop();
 114.118 +			State superior = state.peek();
 114.119 +			if (expected.contains(superior)) {
 114.120 +				return superior;
 114.121 +			} else {
 114.122 +				throw new IllegalStateException("Formatter had wrong superior state: " + superior + " when it should be in one of: " + expected);
 114.123 +			}
 114.124 +		} catch (EmptyStackException e) {
 114.125 +			throw new IllegalStateException("Formatter was already at root level – there is nothing above that.", e);
 114.126 +		}
 114.127 +	}
 114.128 +
 114.129 +	@Override
 114.130 +	public void writeStartBatch() {
 114.131 +		pushState(State.BATCH, EnumSet.of(State.ROOT));
 114.132 +	}
 114.133 +
 114.134 +	@Override
 114.135 +	public void writeEndBatch() {
 114.136 +		popState(EnumSet.of(State.ROOT));
 114.137 +	}
 114.138 +
 114.139 +	@Override
 114.140 +	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
 114.141 +		pushState(State.DATABASE, EnumSet.of(State.BATCH));
 114.142 +	}
 114.143 +
 114.144 +	@Override
 114.145 +	public void writeEndDatabase() {
 114.146 +		popState(EnumSet.of(State.BATCH));
 114.147 +	}
 114.148 +
 114.149 +	@Override
 114.150 +	public void writeStartStatement() {
 114.151 +		pushState(State.STATEMENT, EnumSet.of(State.DATABASE));
 114.152 +	}
 114.153 +
 114.154 +	@Override
 114.155 +	public void writeEndStatement() {
 114.156 +		popState(EnumSet.of(State.DATABASE));
 114.157 +	}
 114.158 +
 114.159 +	@Override
 114.160 +	public void writeStartResultSet(ColumnsHeader header) {
 114.161 +		pushState(State.RESULT_SET, EnumSet.of(State.STATEMENT));
 114.162 +		currentRowCount = 0;
 114.163 +		currentColumnsHeader = header;
 114.164 +	}
 114.165 +
 114.166 +	@Override
 114.167 +	public void writeEndResultSet() {
 114.168 +		popState(EnumSet.of(State.STATEMENT));
 114.169 +		currentColumnsHeader = null;
 114.170 +	}
 114.171 +
 114.172 +	@Override
 114.173 +	public void writeQuery(String sql) {
 114.174 +		peekState(EnumSet.of(State.STATEMENT));
 114.175 +
 114.176 +		if (currentColumnsHeader == null) {
 114.177 +			currentQuery = sql;
 114.178 +		} else {
 114.179 +			throw new IllegalStateException("Query string '" + sql + "' must be set before columns header – was already set: " + currentColumnsHeader);
 114.180 +		}
 114.181 +	}
 114.182 +
 114.183 +	@Override
 114.184 +	public void writeParameters(List<? extends Parameter> parameters) {
 114.185 +		peekState(EnumSet.of(State.STATEMENT));
 114.186 +
 114.187 +		if (currentColumnsHeader != null) {
 114.188 +			throw new IllegalStateException("Parameters '" + parameters + "' must be set before columns header – was already set: " + currentColumnsHeader);
 114.189 +		}
 114.190 +
 114.191 +		if (currentQuery == null && parameters != null) {
 114.192 +			throw new IllegalStateException("Parameters '" + parameters + "' must be set after query – was not yet set.");
 114.193 +		}
 114.194 +	}
 114.195 +
 114.196 +	@Override
 114.197 +	public void writeStartRow() {
 114.198 +		pushState(State.ROW, EnumSet.of(State.RESULT_SET));
 114.199 +		currentColumnsCount = 0;
 114.200 +		currentRowCount++;
 114.201 +	}
 114.202 +
 114.203 +	@Override
 114.204 +	public void writeEndRow() {
 114.205 +		popState(EnumSet.of(State.RESULT_SET));
 114.206 +	}
 114.207 +
 114.208 +	@Override
 114.209 +	public void writeColumnValue(Object value) {
 114.210 +		peekState(EnumSet.of(State.ROW));
 114.211 +		currentColumnsCount++;
 114.212 +
 114.213 +		int declaredCount = currentColumnsHeader.getColumnCount();
 114.214 +		if (currentColumnsCount > declaredCount) {
 114.215 +			throw new IllegalStateException("Current columns count is " + currentColumnsCount + " which is more than declared " + declaredCount + " in header.");
 114.216 +		}
 114.217 +	}
 114.218 +
 114.219 +	@Override
 114.220 +	public void writeUpdatesResult(int updatedRowsCount) {
 114.221 +		peekState(EnumSet.of(State.STATEMENT));
 114.222 +	}
 114.223 +
 114.224 +	@Override
 114.225 +	public void close() throws FormatterException {
 114.226 +	}
 114.227 +
 114.228 +	public FormatterContext getFormatterContext() {
 114.229 +		return formatterContext;
 114.230 +	}
 114.231 +
 114.232 +	protected ColumnsHeader getCurrentColumnsHeader() {
 114.233 +		return currentColumnsHeader;
 114.234 +	}
 114.235 +
 114.236 +	/**
 114.237 +	 * @return column number, 1 = first
 114.238 +	 */
 114.239 +	protected int getCurrentColumnsCount() {
 114.240 +		return currentColumnsCount;
 114.241 +	}
 114.242 +
 114.243 +	protected boolean isCurrentColumnFirst() {
 114.244 +		return currentColumnsCount == 1;
 114.245 +	}
 114.246 +
 114.247 +	protected boolean isCurrentColumnLast() {
 114.248 +		return currentColumnsCount == currentColumnsHeader.getColumnCount();
 114.249 +	}
 114.250 +
 114.251 +	/**
 114.252 +	 * @return row number, 1 = first
 114.253 +	 */
 114.254 +	protected int getCurrentRowCount() {
 114.255 +		return currentRowCount;
 114.256 +	}
 114.257 +}
   115.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   115.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java	Mon Mar 04 20:15:24 2019 +0100
   115.3 @@ -0,0 +1,241 @@
   115.4 +/**
   115.5 + * SQL-DK
   115.6 + * Copyright © 2014 František Kučera (frantovo.cz)
   115.7 + *
   115.8 + * This program is free software: you can redistribute it and/or modify
   115.9 + * it under the terms of the GNU General Public License as published by
  115.10 + * the Free Software Foundation, either version 3 of the License, or
  115.11 + * (at your option) any later version.
  115.12 + *
  115.13 + * This program is distributed in the hope that it will be useful,
  115.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  115.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  115.16 + * GNU General Public License for more details.
  115.17 + *
  115.18 + * You should have received a copy of the GNU General Public License
  115.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  115.20 + */
  115.21 +package info.globalcode.sql.dk.formatting;
  115.22 +
  115.23 +import info.globalcode.sql.dk.ColorfulPrintWriter;
  115.24 +import info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
  115.25 +import java.util.Stack;
  115.26 +import javax.xml.namespace.QName;
  115.27 +import static info.globalcode.sql.dk.Functions.isEmpty;
  115.28 +import static info.globalcode.sql.dk.Functions.toHex;
  115.29 +import info.globalcode.sql.dk.configuration.PropertyDeclaration;
  115.30 +import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
  115.31 +import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
  115.32 +import java.nio.charset.Charset;
  115.33 +import java.util.EmptyStackException;
  115.34 +import java.util.HashMap;
  115.35 +import java.util.LinkedHashMap;
  115.36 +import java.util.Map;
  115.37 +import java.util.Map.Entry;
  115.38 +import java.util.logging.Level;
  115.39 +import java.util.logging.Logger;
  115.40 +
  115.41 +/**
  115.42 + * <p>
  115.43 + * Provides helper methods for printing pretty intended and optionally colorful (syntax highlighted)
  115.44 + * XML output.
  115.45 + * </p>
  115.46 + *
  115.47 + * <p>
  115.48 + * Must be used with care – bad usage can lead to invalid XML (e.g. using undeclared namespaces).
  115.49 + * </p>
  115.50 + *
  115.51 + * @author Ing. František Kučera (frantovo.cz)
  115.52 + */
  115.53 +@PropertyDeclaration(name = COLORFUL, defaultValue = "false", type = Boolean.class, description = COLORFUL_DESCRIPTION)
  115.54 +@PropertyDeclaration(name = AbstractXmlFormatter.PROPERTY_INDENT, defaultValue = AbstractXmlFormatter.PROPERTY_INDENT_DEFAULT, type = String.class, description = "tab or sequence of spaces used for indentation of nested elements")
  115.55 +@PropertyDeclaration(name = AbstractXmlFormatter.PROPERTY_INDENT_TEXT, defaultValue = "true", type = Boolean.class, description = "whether text with line breaks should be indented; if not original whitespace will be preserved.")
  115.56 +public abstract class AbstractXmlFormatter extends AbstractFormatter {
  115.57 +
  115.58 +	private static final Logger log = Logger.getLogger(AbstractXmlFormatter.class.getName());
  115.59 +	public static final String PROPERTY_INDENT = "indent";
  115.60 +	protected static final String PROPERTY_INDENT_DEFAULT = "\t";
  115.61 +	public static final String PROPERTY_INDENT_TEXT = "indentText";
  115.62 +	private static final TerminalColor ELEMENT_COLOR = TerminalColor.Magenta;
  115.63 +	private static final TerminalColor ATTRIBUTE_NAME_COLOR = TerminalColor.Green;
  115.64 +	private static final TerminalColor ATTRIBUTE_VALUE_COLOR = TerminalColor.Yellow;
  115.65 +	private static final TerminalColor XML_DECLARATION_COLOR = TerminalColor.Red;
  115.66 +	private static final TerminalColor XML_DOCTYPE_COLOR = TerminalColor.Cyan;
  115.67 +	private Stack<QName> treePosition = new Stack<>();
  115.68 +	private final ColorfulPrintWriter out;
  115.69 +	private final String indent;
  115.70 +	private final boolean indentText;
  115.71 +
  115.72 +	public AbstractXmlFormatter(FormatterContext formatterContext) {
  115.73 +		super(formatterContext);
  115.74 +		boolean colorful = formatterContext.getProperties().getBoolean(COLORFUL, false);
  115.75 +		out = new ColorfulPrintWriter(formatterContext.getOutputStream(), false, colorful);
  115.76 +		indent = formatterContext.getProperties().getString(PROPERTY_INDENT, PROPERTY_INDENT_DEFAULT);
  115.77 +		indentText = formatterContext.getProperties().getBoolean(PROPERTY_INDENT_TEXT, true);
  115.78 +
  115.79 +		if (!indent.matches("\\s*")) {
  115.80 +			log.log(Level.WARNING, "Setting indent to „{0}“ is weird & freaky; in hex: {1}", new Object[]{indent, toHex(indent.getBytes())});
  115.81 +		}
  115.82 +
  115.83 +	}
  115.84 +
  115.85 +	protected void printStartDocument() {
  115.86 +		out.print(XML_DECLARATION_COLOR, "<?xml version=\"1.0\" encoding=\"" + Charset.defaultCharset().name() + "\"?>");
  115.87 +	}
  115.88 +
  115.89 +	protected void printDoctype(String doctype) {
  115.90 +		out.print(XML_DOCTYPE_COLOR, "\n<!DOCTYPE " + doctype + ">");
  115.91 +	}
  115.92 +
  115.93 +	protected void printEndDocument() {
  115.94 +		out.println();
  115.95 +		out.flush();
  115.96 +		if (!treePosition.empty()) {
  115.97 +			throw new IllegalStateException("Some elements are not closed: " + treePosition);
  115.98 +		}
  115.99 +	}
 115.100 +
 115.101 +	protected void printStartElement(QName element) {
 115.102 +		printStartElement(element, null);
 115.103 +	}
 115.104 +
 115.105 +	protected Map<QName, String> singleAttribute(QName name, String value) {
 115.106 +		Map<QName, String> attributes = new HashMap<>(2);
 115.107 +		attributes.put(name, value);
 115.108 +		return attributes;
 115.109 +	}
 115.110 +
 115.111 +	protected void printStartElement(QName element, Map<QName, String> attributes) {
 115.112 +		printStartElement(element, attributes, false);
 115.113 +	}
 115.114 +
 115.115 +	/**
 115.116 +	 * @param empty whether element should be closed <codfe>… /&gt;</code> (has no content, do not
 115.117 +	 * call {@linkplain #printEndElement()})
 115.118 +	 */
 115.119 +	private void printStartElement(QName element, Map<QName, String> attributes, boolean empty) {
 115.120 +		printIndent();
 115.121 +
 115.122 +		out.print(ELEMENT_COLOR, "<" + toString(element));
 115.123 +
 115.124 +		if (attributes != null) {
 115.125 +			for (Entry<QName, String> attribute : attributes.entrySet()) {
 115.126 +				out.print(" ");
 115.127 +				out.print(ATTRIBUTE_NAME_COLOR, toString(attribute.getKey()));
 115.128 +				out.print("=");
 115.129 +				out.print(ATTRIBUTE_VALUE_COLOR, '"' + escapeXmlAttribute(attribute.getValue()) + '"');
 115.130 +			}
 115.131 +		}
 115.132 +
 115.133 +		if (empty) {
 115.134 +			out.print(ELEMENT_COLOR, "/>");
 115.135 +		} else {
 115.136 +			out.print(ELEMENT_COLOR, ">");
 115.137 +			treePosition.add(element);
 115.138 +		}
 115.139 +
 115.140 +		out.flush();
 115.141 +	}
 115.142 +
 115.143 +	/**
 115.144 +	 * Prints text node wrapped in given element without indenting the text and adding line breaks
 115.145 +	 * (useful for short texts).
 115.146 +	 *
 115.147 +	 * @param attributes use {@linkplain  LinkedHashMap} to preserve attributes order
 115.148 +	 */
 115.149 +	protected void printTextElement(QName element, Map<QName, String> attributes, String text) {
 115.150 +		printStartElement(element, attributes);
 115.151 +
 115.152 +		String[] lines = text.split("\\n");
 115.153 +
 115.154 +		if (indentText && lines.length > 1) {
 115.155 +			for (String line : lines) {
 115.156 +				printText(line, true);
 115.157 +			}
 115.158 +			printEndElement(true);
 115.159 +		} else {
 115.160 +			/*
 115.161 +			 * line breaks at the end of the text will be eaten – if you need them, use indentText = false
 115.162 +			 */
 115.163 +			if (lines.length == 1 && text.endsWith("\n")) {
 115.164 +				text = text.substring(0, text.length() - 1);
 115.165 +			}
 115.166 +
 115.167 +			printText(text, false);
 115.168 +			printEndElement(false);
 115.169 +		}
 115.170 +	}
 115.171 +
 115.172 +	protected void printEmptyElement(QName element, Map<QName, String> attributes) {
 115.173 +		printStartElement(element, attributes, true);
 115.174 +	}
 115.175 +
 115.176 +	protected void printEndElement() {
 115.177 +		printEndElement(true);
 115.178 +	}
 115.179 +
 115.180 +	private void printEndElement(boolean indent) {
 115.181 +		try {
 115.182 +			QName name = treePosition.pop();
 115.183 +
 115.184 +			if (indent) {
 115.185 +				printIndent();
 115.186 +			}
 115.187 +
 115.188 +			out.print(ELEMENT_COLOR, "</" + toString(name) + ">");
 115.189 +			out.flush();
 115.190 +
 115.191 +		} catch (EmptyStackException e) {
 115.192 +			throw new IllegalStateException("No more elements to end.", e);
 115.193 +		}
 115.194 +	}
 115.195 +
 115.196 +	protected void printText(String s, boolean indent) {
 115.197 +		if (indent) {
 115.198 +			printIndent();
 115.199 +		}
 115.200 +		out.print(escapeXmlText(s));
 115.201 +		out.flush();
 115.202 +	}
 115.203 +
 115.204 +	protected void printIndent() {
 115.205 +		out.println();
 115.206 +		for (int i = 0; i < treePosition.size(); i++) {
 115.207 +			out.print(indent);
 115.208 +		}
 115.209 +	}
 115.210 +
 115.211 +	protected static QName qname(String name) {
 115.212 +		return new QName(name);
 115.213 +	}
 115.214 +
 115.215 +	protected static QName qname(String prefix, String name) {
 115.216 +		return new QName(null, name, prefix);
 115.217 +	}
 115.218 +
 115.219 +	private String toString(QName name) {
 115.220 +		if (isEmpty(name.getPrefix(), true)) {
 115.221 +			return escapeName(name.getLocalPart());
 115.222 +		} else {
 115.223 +			return escapeName(name.getPrefix()) + ":" + escapeName(name.getLocalPart());
 115.224 +		}
 115.225 +	}
 115.226 +
 115.227 +	private String escapeName(String s) {
 115.228 +		// TODO: avoid ugly values in <name name="…"/>		
 115.229 +		return s;
 115.230 +	}
 115.231 +
 115.232 +	private static String escapeXmlText(String s) {
 115.233 +		return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
 115.234 +		// Not needed:
 115.235 +		// return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
 115.236 +	}
 115.237 +
 115.238 +	/**
 115.239 +	 * Expects attribute values enclosed in "quotes" not 'apostrophes'.
 115.240 +	 */
 115.241 +	private static String escapeXmlAttribute(String s) {
 115.242 +		return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
 115.243 +	}
 115.244 +}
   116.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   116.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/BarChartFormatter.java	Mon Mar 04 20:15:24 2019 +0100
   116.3 @@ -0,0 +1,104 @@
   116.4 +/**
   116.5 + * SQL-DK
   116.6 + * Copyright © 2015 František Kučera (frantovo.cz)
   116.7 + *
   116.8 + * This program is free software: you can redistribute it and/or modify
   116.9 + * it under the terms of the GNU General Public License as published by
  116.10 + * the Free Software Foundation, either version 3 of the License, or
  116.11 + * (at your option) any later version.
  116.12 + *
  116.13 + * This program is distributed in the hope that it will be useful,
  116.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  116.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  116.16 + * GNU General Public License for more details.
  116.17 + *
  116.18 + * You should have received a copy of the GNU General Public License
  116.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  116.20 + */
  116.21 +package info.globalcode.sql.dk.formatting;
  116.22 +
  116.23 +import info.globalcode.sql.dk.Functions;
  116.24 +import info.globalcode.sql.dk.configuration.PropertyDeclaration;
  116.25 +import info.globalcode.sql.dk.logging.LoggerProducer;
  116.26 +import java.math.BigDecimal;
  116.27 +import java.math.MathContext;
  116.28 +import java.math.RoundingMode;
  116.29 +import java.util.List;
  116.30 +import java.util.logging.Level;
  116.31 +import java.util.logging.Logger;
  116.32 +
  116.33 +/**
  116.34 + * TODO: min/max values – range for case that no value is 100 %
  116.35 + *
  116.36 + * TODO: multiple barcharts in same table (last column is still default) + multiple resultsets
  116.37 + *
  116.38 + * TODO: negative values - bar starting from the middle, not always from the left
  116.39 + *
  116.40 + * @author Ing. František Kučera (frantovo.cz)
  116.41 + */
  116.42 +@PropertyDeclaration(name = BarChartFormatter.PROPERTY_PRECISION, type = Integer.class, defaultValue = BarChartFormatter.PROPERTY_PRECISION_DEFAULT, description = "number of characters representing 100 % in the bar chart")
  116.43 +public class BarChartFormatter extends TabularPrefetchingFormatter {
  116.44 +
  116.45 +	public static final String NAME = "barchart"; // bash-completion:formatter
  116.46 +	public static final String PROPERTY_PRECISION = "precision";
  116.47 +	protected static final String PROPERTY_PRECISION_DEFAULT = "100";
  116.48 +	private static final MathContext mathContext = MathContext.DECIMAL128;
  116.49 +	public static final Logger log = LoggerProducer.getLogger();
  116.50 +	private final BigDecimal chartPrecision;
  116.51 +	private final char chartFull;
  116.52 +	private final char chartEmpty;
  116.53 +
  116.54 +	public BarChartFormatter(FormatterContext formatterContext) {
  116.55 +		super(formatterContext);
  116.56 +		chartPrecision = BigDecimal.valueOf(formatterContext.getProperties().getInteger(PROPERTY_PRECISION, Integer.parseInt(PROPERTY_PRECISION_DEFAULT)));
  116.57 +		chartFull = isAsciiNostalgia() ? '#' : '█';
  116.58 +		chartEmpty = isAsciiNostalgia() ? '~' : '░';
  116.59 +		// TODO: consider using partial blocks for more precision: https://en.wikipedia.org/wiki/Block_Elements
  116.60 +	}
  116.61 +
  116.62 +	@Override
  116.63 +	protected void postprocessPrefetchedResultSet(ColumnsHeader currentHeader, List<Object[]> currentResultSet) {
  116.64 +		super.postprocessPrefetchedResultSet(currentHeader, currentResultSet);
  116.65 +
  116.66 +		updateColumnWidth(currentHeader.getColumnCount(), chartPrecision.intValue());
  116.67 +
  116.68 +		BigDecimal maximum = BigDecimal.ZERO;
  116.69 +		BigDecimal minimum = BigDecimal.ZERO;
  116.70 +		int lastIndex = currentHeader.getColumnCount() - 1;
  116.71 +
  116.72 +		Object valueObject = null;
  116.73 +		try {
  116.74 +			for (Object[] row : currentResultSet) {
  116.75 +				valueObject = row[lastIndex];
  116.76 +				if (valueObject != null) {
  116.77 +					BigDecimal value = new BigDecimal(valueObject.toString());
  116.78 +					maximum = maximum.max(value);
  116.79 +					minimum = minimum.min(value);
  116.80 +				}
  116.81 +			}
  116.82 +
  116.83 +			BigDecimal range = maximum.subtract(minimum);
  116.84 +
  116.85 +			for (Object[] row : currentResultSet) {
  116.86 +				valueObject = row[lastIndex];
  116.87 +				if (valueObject == null) {
  116.88 +					row[lastIndex] = "";
  116.89 +				} else {
  116.90 +					BigDecimal value = new BigDecimal(valueObject.toString());
  116.91 +					BigDecimal valueFromMinimum = value.subtract(minimum);
  116.92 +
  116.93 +					BigDecimal points = chartPrecision.divide(range, mathContext).multiply(valueFromMinimum, mathContext);
  116.94 +					int pointsRounded = points.setScale(0, RoundingMode.HALF_UP).intValue();
  116.95 +					row[lastIndex] = Functions.repeat(chartFull, pointsRounded) + Functions.repeat(chartEmpty, chartPrecision.intValue() - pointsRounded);
  116.96 +				}
  116.97 +			}
  116.98 +
  116.99 +		} catch (NumberFormatException e) {
 116.100 +			// https://en.wiktionary.org/wiki/parsable
 116.101 +			log.log(Level.SEVERE, "Last column must be number or an object with toString() value parsable to a number. But was „{0}“", valueObject);
 116.102 +			// FIXME: throw FormatterException
 116.103 +			throw e;
 116.104 +		}
 116.105 +	}
 116.106 +
 116.107 +}
   117.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   117.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnDescriptor.java	Mon Mar 04 20:15:24 2019 +0100
   117.3 @@ -0,0 +1,122 @@
   117.4 +/**
   117.5 + * SQL-DK
   117.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   117.7 + *
   117.8 + * This program is free software: you can redistribute it and/or modify
   117.9 + * it under the terms of the GNU General Public License as published by
  117.10 + * the Free Software Foundation, either version 3 of the License, or
  117.11 + * (at your option) any later version.
  117.12 + *
  117.13 + * This program is distributed in the hope that it will be useful,
  117.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  117.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  117.16 + * GNU General Public License for more details.
  117.17 + *
  117.18 + * You should have received a copy of the GNU General Public License
  117.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  117.20 + */
  117.21 +package info.globalcode.sql.dk.formatting;
  117.22 +
  117.23 +import java.sql.Types;
  117.24 +
  117.25 +/**
  117.26 + *
  117.27 + * @author Ing. František Kučera (frantovo.cz)
  117.28 + */
  117.29 +public class ColumnDescriptor {
  117.30 +
  117.31 +	private String name;
  117.32 +	private String label;
  117.33 +	private int type;
  117.34 +	private String typeName;
  117.35 +	private boolean firstColumn;
  117.36 +	private boolean lastColumn;
  117.37 +	private int columnNumber;
  117.38 +
  117.39 +	/**
  117.40 +	 * @return column name
  117.41 +	 * @see #getLabel()
  117.42 +	 */
  117.43 +	public String getName() {
  117.44 +		return name;
  117.45 +	}
  117.46 +
  117.47 +	public void setName(String name) {
  117.48 +		this.name = name;
  117.49 +	}
  117.50 +
  117.51 +	/**
  117.52 +	 * @return label specified by the SQL AS clause
  117.53 +	 */
  117.54 +	public String getLabel() {
  117.55 +		return label;
  117.56 +	}
  117.57 +
  117.58 +	public void setLabel(String label) {
  117.59 +		this.label = label;
  117.60 +	}
  117.61 +
  117.62 +	public int getType() {
  117.63 +		return type;
  117.64 +	}
  117.65 +
  117.66 +	public void setType(int type) {
  117.67 +		this.type = type;
  117.68 +	}
  117.69 +
  117.70 +	public String getTypeName() {
  117.71 +		return typeName;
  117.72 +	}
  117.73 +
  117.74 +	public void setTypeName(String typeName) {
  117.75 +		this.typeName = typeName;
  117.76 +	}
  117.77 +
  117.78 +	public boolean isFirstColumn() {
  117.79 +		return firstColumn;
  117.80 +	}
  117.81 +
  117.82 +	public void setFirstColumn(boolean firstColumn) {
  117.83 +		this.firstColumn = firstColumn;
  117.84 +	}
  117.85 +
  117.86 +	public boolean isLastColumn() {
  117.87 +		return lastColumn;
  117.88 +	}
  117.89 +
  117.90 +	public void setLastColumn(boolean lastColumn) {
  117.91 +		this.lastColumn = lastColumn;
  117.92 +	}
  117.93 +
  117.94 +	/**
  117.95 +	 * @return number of this column, 1 = first
  117.96 +	 */
  117.97 +	public int getColumnNumber() {
  117.98 +		return columnNumber;
  117.99 +	}
 117.100 +
 117.101 +	public void setColumnNumber(int columnNumber) {
 117.102 +		this.columnNumber = columnNumber;
 117.103 +	}
 117.104 +
 117.105 +	public boolean isBoolean() {
 117.106 +		return type == Types.BOOLEAN;
 117.107 +	}
 117.108 +
 117.109 +	public boolean isNumeric() {
 117.110 +		switch (type) {
 117.111 +			case Types.BIGINT:
 117.112 +			case Types.DECIMAL:
 117.113 +			case Types.DOUBLE:
 117.114 +			case Types.FLOAT:
 117.115 +			case Types.INTEGER:
 117.116 +			case Types.NUMERIC:
 117.117 +			case Types.REAL:
 117.118 +			case Types.SMALLINT:
 117.119 +			case Types.TINYINT:
 117.120 +				return true;
 117.121 +			default:
 117.122 +				return false;
 117.123 +		}
 117.124 +	}
 117.125 +}
   118.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   118.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnsHeader.java	Mon Mar 04 20:15:24 2019 +0100
   118.3 @@ -0,0 +1,70 @@
   118.4 +/**
   118.5 + * SQL-DK
   118.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   118.7 + *
   118.8 + * This program is free software: you can redistribute it and/or modify
   118.9 + * it under the terms of the GNU General Public License as published by
  118.10 + * the Free Software Foundation, either version 3 of the License, or
  118.11 + * (at your option) any later version.
  118.12 + *
  118.13 + * This program is distributed in the hope that it will be useful,
  118.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  118.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  118.16 + * GNU General Public License for more details.
  118.17 + *
  118.18 + * You should have received a copy of the GNU General Public License
  118.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  118.20 + */
  118.21 +package info.globalcode.sql.dk.formatting;
  118.22 +
  118.23 +import java.sql.ResultSetMetaData;
  118.24 +import java.sql.SQLException;
  118.25 +import java.util.ArrayList;
  118.26 +import java.util.List;
  118.27 +
  118.28 +/**
  118.29 + *
  118.30 + * @author Ing. František Kučera (frantovo.cz)
  118.31 + */
  118.32 +public class ColumnsHeader {
  118.33 +	
  118.34 +	private ResultSetMetaData metaData;
  118.35 +	
  118.36 +	public ColumnsHeader(ResultSetMetaData metaData) {
  118.37 +		this.metaData = metaData;
  118.38 +	}
  118.39 +	
  118.40 +	public int getColumnCount() {
  118.41 +		try {
  118.42 +			return metaData.getColumnCount();
  118.43 +		} catch (SQLException e) {
  118.44 +			throw new IllegalStateException("Error during getting column count.", e);
  118.45 +		}
  118.46 +	}
  118.47 +	
  118.48 +	public List<ColumnDescriptor> getColumnDescriptors() {
  118.49 +		try {
  118.50 +			int count = metaData.getColumnCount();
  118.51 +			List<ColumnDescriptor> list = new ArrayList<>(count);
  118.52 +			
  118.53 +			for (int i = 1; i <= count; i++) {
  118.54 +				ColumnDescriptor cd = new ColumnDescriptor();
  118.55 +				
  118.56 +				cd.setFirstColumn(i == 1);
  118.57 +				cd.setLastColumn(i == count);
  118.58 +				cd.setColumnNumber(i);
  118.59 +				
  118.60 +				cd.setLabel(metaData.getColumnLabel(i));
  118.61 +				cd.setName(metaData.getColumnName(i));
  118.62 +				cd.setType(metaData.getColumnType(i));
  118.63 +				cd.setTypeName(metaData.getColumnTypeName(i));
  118.64 +				/** TODO: more properties */
  118.65 +				list.add(cd);
  118.66 +			}
  118.67 +			
  118.68 +			return list;
  118.69 +		} catch (SQLException e) {
  118.70 +			throw new IllegalStateException("Error during building column descriptors.", e);
  118.71 +		}
  118.72 +	}
  118.73 +}
   119.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   119.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/CommonProperties.java	Mon Mar 04 20:15:24 2019 +0100
   119.3 @@ -0,0 +1,50 @@
   119.4 +/**
   119.5 + * SQL-DK
   119.6 + * Copyright © 2015 František Kučera (frantovo.cz)
   119.7 + *
   119.8 + * This program is free software: you can redistribute it and/or modify
   119.9 + * it under the terms of the GNU General Public License as published by
  119.10 + * the Free Software Foundation, either version 3 of the License, or
  119.11 + * (at your option) any later version.
  119.12 + *
  119.13 + * This program is distributed in the hope that it will be useful,
  119.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  119.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  119.16 + * GNU General Public License for more details.
  119.17 + *
  119.18 + * You should have received a copy of the GNU General Public License
  119.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  119.20 + */
  119.21 +package info.globalcode.sql.dk.formatting;
  119.22 +
  119.23 +import java.util.Collections;
  119.24 +import java.util.HashMap;
  119.25 +import java.util.Map;
  119.26 +
  119.27 +/**
  119.28 + *
  119.29 + * @author Ing. František Kučera (frantovo.cz)
  119.30 + */
  119.31 +public class CommonProperties {
  119.32 +
  119.33 +	private static final Map<Class, String> TYPE_SIMPLE_NAMES;
  119.34 +
  119.35 +	static {
  119.36 +		Map<Class, String> m = new HashMap<>();
  119.37 +		m.put(Boolean.class, "boolean");
  119.38 +		m.put(String.class, "String");
  119.39 +		m.put(Character.class, "char");
  119.40 +		m.put(Integer.class, "int");
  119.41 +		m.put(Long.class, "long");
  119.42 +		m.put(Double.class, "double");
  119.43 +		TYPE_SIMPLE_NAMES = Collections.unmodifiableMap(m);
  119.44 +	}
  119.45 +
  119.46 +	public static String getSimpleTypeName(Class type) {
  119.47 +		String name = TYPE_SIMPLE_NAMES.get(type);
  119.48 +		return name == null ? type.getName() : name;
  119.49 +	}
  119.50 +
  119.51 +	public static final String COLORFUL = "color";
  119.52 +	public static final String COLORFUL_DESCRIPTION = "whether the output should be printed in color (ANSI Escape Sequences)";
  119.53 +}
   120.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   120.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FakeSqlArray.java	Mon Mar 04 20:15:24 2019 +0100
   120.3 @@ -0,0 +1,106 @@
   120.4 +/**
   120.5 + * SQL-DK
   120.6 + * Copyright © 2014 František Kučera (frantovo.cz)
   120.7 + *
   120.8 + * This program is free software: you can redistribute it and/or modify
   120.9 + * it under the terms of the GNU General Public License as published by
  120.10 + * the Free Software Foundation, either version 3 of the License, or
  120.11 + * (at your option) any later version.
  120.12 + *
  120.13 + * This program is distributed in the hope that it will be useful,
  120.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  120.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  120.16 + * GNU General Public License for more details.
  120.17 + *
  120.18 + * You should have received a copy of the GNU General Public License
  120.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  120.20 + */
  120.21 +package info.globalcode.sql.dk.formatting;
  120.22 +
  120.23 +import info.globalcode.sql.dk.SQLType;
  120.24 +import java.sql.Array;
  120.25 +import java.sql.ResultSet;
  120.26 +import java.sql.SQLException;
  120.27 +import java.util.Map;
  120.28 +
  120.29 +/**
  120.30 + * Fake SQL array, for formatting purposes only
  120.31 + *
  120.32 + * @author Ing. František Kučera (frantovo.cz)
  120.33 + */
  120.34 +public class FakeSqlArray implements Array {
  120.35 +
  120.36 +	private static final UnsupportedOperationException exception = new UnsupportedOperationException("This is just a fake SQL array.");
  120.37 +	private final Object[] data;
  120.38 +	private final SQLType baseType;
  120.39 +
  120.40 +	public FakeSqlArray(Object[] data, SQLType baseType) {
  120.41 +		this.data = data;
  120.42 +		this.baseType = baseType;
  120.43 +	}
  120.44 +
  120.45 +	@Override
  120.46 +	public String toString() {
  120.47 +		StringBuilder string = new StringBuilder();
  120.48 +		for (Object o : data) {
  120.49 +			string.append(o);
  120.50 +			string.append("\n");
  120.51 +		}
  120.52 +		return string.toString();
  120.53 +	}
  120.54 +
  120.55 +	@Override
  120.56 +	public String getBaseTypeName() throws SQLException {
  120.57 +		return baseType.name();
  120.58 +	}
  120.59 +
  120.60 +	@Override
  120.61 +	public int getBaseType() throws SQLException {
  120.62 +		return baseType.getCode();
  120.63 +	}
  120.64 +
  120.65 +	@Override
  120.66 +	public Object getArray() throws SQLException {
  120.67 +		return data;
  120.68 +	}
  120.69 +
  120.70 +	@Override
  120.71 +	public Object getArray(Map<String, Class<?>> map) throws SQLException {
  120.72 +		throw exception;
  120.73 +	}
  120.74 +
  120.75 +	@Override
  120.76 +	public Object getArray(long index, int count) throws SQLException {
  120.77 +		throw exception;
  120.78 +	}
  120.79 +
  120.80 +	@Override
  120.81 +	public Object getArray(long index, int count, Map<String, Class<?>> map) throws SQLException {
  120.82 +		throw exception;
  120.83 +	}
  120.84 +
  120.85 +	@Override
  120.86 +	public ResultSet getResultSet() throws SQLException {
  120.87 +		throw exception;
  120.88 +	}
  120.89 +
  120.90 +	@Override
  120.91 +	public ResultSet getResultSet(Map<String, Class<?>> map) throws SQLException {
  120.92 +		throw exception;
  120.93 +	}
  120.94 +
  120.95 +	@Override
  120.96 +	public ResultSet getResultSet(long index, int count) throws SQLException {
  120.97 +		throw exception;
  120.98 +	}
  120.99 +
 120.100 +	@Override
 120.101 +	public ResultSet getResultSet(long index, int count, Map<String, Class<?>> map) throws SQLException {
 120.102 +		throw exception;
 120.103 +	}
 120.104 +
 120.105 +	@Override
 120.106 +	public void free() throws SQLException {
 120.107 +		throw exception;
 120.108 +	}
 120.109 +}
   121.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   121.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/Formatter.java	Mon Mar 04 20:15:24 2019 +0100
   121.3 @@ -0,0 +1,67 @@
   121.4 +/**
   121.5 + * SQL-DK
   121.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   121.7 + *
   121.8 + * This program is free software: you can redistribute it and/or modify
   121.9 + * it under the terms of the GNU General Public License as published by
  121.10 + * the Free Software Foundation, either version 3 of the License, or
  121.11 + * (at your option) any later version.
  121.12 + *
  121.13 + * This program is distributed in the hope that it will be useful,
  121.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  121.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  121.16 + * GNU General Public License for more details.
  121.17 + *
  121.18 + * You should have received a copy of the GNU General Public License
  121.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  121.20 + */
  121.21 +package info.globalcode.sql.dk.formatting;
  121.22 +
  121.23 +import info.globalcode.sql.dk.Parameter;
  121.24 +import info.globalcode.sql.dk.configuration.DatabaseDefinition;
  121.25 +import java.util.List;
  121.26 +
  121.27 +/**
  121.28 + * The formatter is responsible for printing the result sets and/or updates result (count of
  121.29 + * inserted/updated rows). The formatter can produce output in arbitrary format – text, some markup
  121.30 + * or even binary data.
  121.31 + *
  121.32 + * @author Ing. František Kučera (frantovo.cz)
  121.33 + */
  121.34 +public interface Formatter extends AutoCloseable {
  121.35 +
  121.36 +	void writeStartBatch();
  121.37 +
  121.38 +	void writeStartDatabase(DatabaseDefinition databaseDefinition);
  121.39 +
  121.40 +	void writeEndDatabase();
  121.41 +
  121.42 +	void writeStartStatement();
  121.43 +
  121.44 +	void writeEndStatement();
  121.45 +
  121.46 +	void writeQuery(String sql);
  121.47 +
  121.48 +	void writeParameters(List<? extends Parameter> parameters);
  121.49 +
  121.50 +	void writeStartResultSet(ColumnsHeader header);
  121.51 +
  121.52 +	void writeEndResultSet();
  121.53 +
  121.54 +	void writeStartRow();
  121.55 +
  121.56 +	void writeColumnValue(Object value);
  121.57 +
  121.58 +	void writeEndRow();
  121.59 +
  121.60 +	void writeUpdatesResult(int updatedRowsCount);
  121.61 +
  121.62 +	void writeEndBatch();
  121.63 +
  121.64 +	/**
  121.65 +	 * If an error occurs (e.g. lost connection during result set reading) this method will be
  121.66 +	 * called even if there was no {@linkplain #writeEndBach()}.
  121.67 +	 */
  121.68 +	@Override
  121.69 +	void close() throws FormatterException;
  121.70 +}
   122.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   122.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FormatterContext.java	Mon Mar 04 20:15:24 2019 +0100
   122.3 @@ -0,0 +1,49 @@
   122.4 +/**
   122.5 + * SQL-DK
   122.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   122.7 + *
   122.8 + * This program is free software: you can redistribute it and/or modify
   122.9 + * it under the terms of the GNU General Public License as published by
  122.10 + * the Free Software Foundation, either version 3 of the License, or
  122.11 + * (at your option) any later version.
  122.12 + *
  122.13 + * This program is distributed in the hope that it will be useful,
  122.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  122.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  122.16 + * GNU General Public License for more details.
  122.17 + *
  122.18 + * You should have received a copy of the GNU General Public License
  122.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  122.20 + */
  122.21 +package info.globalcode.sql.dk.formatting;
  122.22 +
  122.23 +import info.globalcode.sql.dk.configuration.Properties;
  122.24 +import java.io.OutputStream;
  122.25 +
  122.26 +/**
  122.27 + * To be passed from the SQL-DK core to the formatter.
  122.28 + *
  122.29 + * @author Ing. František Kučera (frantovo.cz)
  122.30 + */
  122.31 +public class FormatterContext {
  122.32 +
  122.33 +	private OutputStream outputStream;
  122.34 +	private Properties properties;
  122.35 +
  122.36 +	public FormatterContext(OutputStream outputStream, Properties properties) {
  122.37 +		this.outputStream = outputStream;
  122.38 +		this.properties = properties;
  122.39 +	}
  122.40 +
  122.41 +	public OutputStream getOutputStream() {
  122.42 +		return outputStream;
  122.43 +	}
  122.44 +
  122.45 +	public Properties getProperties() {
  122.46 +		return properties;
  122.47 +	}
  122.48 +
  122.49 +	public void setProperties(Properties properties) {
  122.50 +		this.properties = properties;
  122.51 +	}
  122.52 +}
   123.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   123.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FormatterException.java	Mon Mar 04 20:15:24 2019 +0100
   123.3 @@ -0,0 +1,42 @@
   123.4 +/**
   123.5 + * SQL-DK
   123.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   123.7 + *
   123.8 + * This program is free software: you can redistribute it and/or modify
   123.9 + * it under the terms of the GNU General Public License as published by
  123.10 + * the Free Software Foundation, either version 3 of the License, or
  123.11 + * (at your option) any later version.
  123.12 + *
  123.13 + * This program is distributed in the hope that it will be useful,
  123.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  123.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  123.16 + * GNU General Public License for more details.
  123.17 + *
  123.18 + * You should have received a copy of the GNU General Public License
  123.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  123.20 + */
  123.21 +package info.globalcode.sql.dk.formatting;
  123.22 +
  123.23 +import info.globalcode.sql.dk.DKException;
  123.24 +
  123.25 +/**
  123.26 + *
  123.27 + * @author Ing. František Kučera (frantovo.cz)
  123.28 + */
  123.29 +public class FormatterException extends DKException {
  123.30 +
  123.31 +	public FormatterException() {
  123.32 +	}
  123.33 +
  123.34 +	public FormatterException(String message) {
  123.35 +		super(message);
  123.36 +	}
  123.37 +
  123.38 +	public FormatterException(Throwable cause) {
  123.39 +		super(cause);
  123.40 +	}
  123.41 +
  123.42 +	public FormatterException(String message, Throwable cause) {
  123.43 +		super(message, cause);
  123.44 +	}
  123.45 +}
   124.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   124.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SilentFormatter.java	Mon Mar 04 20:15:24 2019 +0100
   124.3 @@ -0,0 +1,33 @@
   124.4 +/**
   124.5 + * SQL-DK
   124.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   124.7 + *
   124.8 + * This program is free software: you can redistribute it and/or modify
   124.9 + * it under the terms of the GNU General Public License as published by
  124.10 + * the Free Software Foundation, either version 3 of the License, or
  124.11 + * (at your option) any later version.
  124.12 + *
  124.13 + * This program is distributed in the hope that it will be useful,
  124.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  124.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  124.16 + * GNU General Public License for more details.
  124.17 + *
  124.18 + * You should have received a copy of the GNU General Public License
  124.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  124.20 + */
  124.21 +package info.globalcode.sql.dk.formatting;
  124.22 +
  124.23 +/**
  124.24 + * Does not output anything, can be used instead of
  124.25 + * <code>/dev/null</code>.
  124.26 + *
  124.27 + * @author Ing. František Kučera (frantovo.cz)
  124.28 + */
  124.29 +public class SilentFormatter extends AbstractFormatter {
  124.30 +	
  124.31 +	public static final String NAME = "silent"; // bash-completion:formatter
  124.32 +
  124.33 +	public SilentFormatter(FormatterContext formatterContext) {
  124.34 +		super(formatterContext);
  124.35 +	}
  124.36 +}
   125.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   125.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SingleRecordFormatter.java	Mon Mar 04 20:15:24 2019 +0100
   125.3 @@ -0,0 +1,105 @@
   125.4 +/**
   125.5 + * SQL-DK
   125.6 + * Copyright © 2015 František Kučera (frantovo.cz)
   125.7 + *
   125.8 + * This program is free software: you can redistribute it and/or modify
   125.9 + * it under the terms of the GNU General Public License as published by
  125.10 + * the Free Software Foundation, either version 3 of the License, or
  125.11 + * (at your option) any later version.
  125.12 + *
  125.13 + * This program is distributed in the hope that it will be useful,
  125.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  125.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  125.16 + * GNU General Public License for more details.
  125.17 + *
  125.18 + * You should have received a copy of the GNU General Public License
  125.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  125.20 + */
  125.21 +package info.globalcode.sql.dk.formatting;
  125.22 +
  125.23 +import info.globalcode.sql.dk.ColorfulPrintWriter;
  125.24 +import info.globalcode.sql.dk.Functions;
  125.25 +import info.globalcode.sql.dk.configuration.PropertyDeclaration;
  125.26 +import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
  125.27 +import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
  125.28 +
  125.29 +/**
  125.30 + * Formatter intended for printing one record (or few records) with many columns.
  125.31 + * Prints each colum name and its value on separate line.
  125.32 + *
  125.33 + * @author Ing. František Kučera (frantovo.cz)
  125.34 + */
  125.35 +@PropertyDeclaration(name = COLORFUL, defaultValue = "true", type = Boolean.class, description = COLORFUL_DESCRIPTION)
  125.36 +public class SingleRecordFormatter extends AbstractFormatter {
  125.37 +
  125.38 +	public static final String NAME = "record"; // bash-completion:formatter
  125.39 +	private final ColorfulPrintWriter out;
  125.40 +	private boolean firstResult = true;
  125.41 +
  125.42 +	public SingleRecordFormatter(FormatterContext formatterContext) {
  125.43 +		super(formatterContext);
  125.44 +		out = new ColorfulPrintWriter(formatterContext.getOutputStream());
  125.45 +		out.setColorful(formatterContext.getProperties().getBoolean(COLORFUL, true));
  125.46 +	}
  125.47 +
  125.48 +	@Override
  125.49 +	public void writeStartResultSet(ColumnsHeader header) {
  125.50 +		super.writeStartResultSet(header);
  125.51 +		printResultSeparator();
  125.52 +	}
  125.53 +
  125.54 +	@Override
  125.55 +	public void writeStartRow() {
  125.56 +		super.writeStartRow();
  125.57 +		printRecordSeparator();
  125.58 +		out.print(ColorfulPrintWriter.TerminalColor.Red, "Record: ");
  125.59 +		out.print(getCurrentRowCount());
  125.60 +		println();
  125.61 +	}
  125.62 +
  125.63 +	@Override
  125.64 +	public void writeColumnValue(Object value) {
  125.65 +		super.writeColumnValue(value);
  125.66 +		String columnName = getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getLabel();
  125.67 +		out.print(ColorfulPrintWriter.TerminalColor.Green, columnName + ": ");
  125.68 +		Functions.printValueWithWhitespaceReplaced(out, toString(value), null, ColorfulPrintWriter.TerminalColor.Red);
  125.69 +		println();
  125.70 +	}
  125.71 +
  125.72 +	private static String toString(Object value) {
  125.73 +		return String.valueOf(value);
  125.74 +	}
  125.75 +
  125.76 +	@Override
  125.77 +	public void writeUpdatesResult(int updatedRowsCount) {
  125.78 +		super.writeUpdatesResult(updatedRowsCount);
  125.79 +		printResultSeparator();
  125.80 +		out.print(ColorfulPrintWriter.TerminalColor.Red, "Updated records: ");
  125.81 +		out.println(updatedRowsCount);
  125.82 +		printBellAndFlush();
  125.83 +	}
  125.84 +
  125.85 +	private void printBellAndFlush() {
  125.86 +		out.bell();
  125.87 +		out.flush();
  125.88 +	}
  125.89 +
  125.90 +	private void println() {
  125.91 +		out.println();
  125.92 +		printBellAndFlush();
  125.93 +	}
  125.94 +
  125.95 +	private void printRecordSeparator() {
  125.96 +		if (getCurrentRowCount() > 1) {
  125.97 +			println();
  125.98 +		}
  125.99 +	}
 125.100 +
 125.101 +	private void printResultSeparator() {
 125.102 +		if (firstResult) {
 125.103 +			firstResult = false;
 125.104 +		} else {
 125.105 +			println();
 125.106 +		}
 125.107 +	}
 125.108 +}
   126.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   126.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SingleValueFormatter.java	Mon Mar 04 20:15:24 2019 +0100
   126.3 @@ -0,0 +1,52 @@
   126.4 +/**
   126.5 + * SQL-DK
   126.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   126.7 + *
   126.8 + * This program is free software: you can redistribute it and/or modify
   126.9 + * it under the terms of the GNU General Public License as published by
  126.10 + * the Free Software Foundation, either version 3 of the License, or
  126.11 + * (at your option) any later version.
  126.12 + *
  126.13 + * This program is distributed in the hope that it will be useful,
  126.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  126.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  126.16 + * GNU General Public License for more details.
  126.17 + *
  126.18 + * You should have received a copy of the GNU General Public License
  126.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  126.20 + */
  126.21 +package info.globalcode.sql.dk.formatting;
  126.22 +
  126.23 +import java.io.PrintWriter;
  126.24 +
  126.25 +/**
  126.26 + * Prints just the value without any formatting. If the result set contains multiple records or
  126.27 + * columns, the values are simply concatenate without any separators. If updates result is returned,
  126.28 + * the updated records count is printed.
  126.29 + *
  126.30 + * @author Ing. František Kučera (frantovo.cz)
  126.31 + */
  126.32 +public class SingleValueFormatter extends AbstractFormatter {
  126.33 +
  126.34 +	public static final String NAME = "single"; // bash-completion:formatter
  126.35 +	private PrintWriter out;
  126.36 +
  126.37 +	public SingleValueFormatter(FormatterContext formatterContext) {
  126.38 +		super(formatterContext);
  126.39 +		this.out = new PrintWriter(formatterContext.getOutputStream());
  126.40 +	}
  126.41 +
  126.42 +	@Override
  126.43 +	public void writeColumnValue(Object value) {
  126.44 +		super.writeColumnValue(value);
  126.45 +		out.print(String.valueOf(value));
  126.46 +		out.flush();
  126.47 +	}
  126.48 +
  126.49 +	@Override
  126.50 +	public void writeUpdatesResult(int updatedRowsCount) {
  126.51 +		super.writeUpdatesResult(updatedRowsCount);
  126.52 +		out.print(updatedRowsCount);
  126.53 +		out.flush();
  126.54 +	}
  126.55 +}
   127.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   127.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularFormatter.java	Mon Mar 04 20:15:24 2019 +0100
   127.3 @@ -0,0 +1,308 @@
   127.4 +/**
   127.5 + * SQL-DK
   127.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   127.7 + *
   127.8 + * This program is free software: you can redistribute it and/or modify
   127.9 + * it under the terms of the GNU General Public License as published by
  127.10 + * the Free Software Foundation, either version 3 of the License, or
  127.11 + * (at your option) any later version.
  127.12 + *
  127.13 + * This program is distributed in the hope that it will be useful,
  127.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  127.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  127.16 + * GNU General Public License for more details.
  127.17 + *
  127.18 + * You should have received a copy of the GNU General Public License
  127.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  127.20 + */
  127.21 +package info.globalcode.sql.dk.formatting;
  127.22 +
  127.23 +import info.globalcode.sql.dk.ColorfulPrintWriter;
  127.24 +import static info.globalcode.sql.dk.ColorfulPrintWriter.*;
  127.25 +import info.globalcode.sql.dk.Functions;
  127.26 +import static info.globalcode.sql.dk.Functions.lpad;
  127.27 +import static info.globalcode.sql.dk.Functions.rpad;
  127.28 +import static info.globalcode.sql.dk.Functions.repeat;
  127.29 +import info.globalcode.sql.dk.configuration.PropertyDeclaration;
  127.30 +import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
  127.31 +import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
  127.32 +import java.sql.SQLException;
  127.33 +import java.sql.SQLXML;
  127.34 +import java.util.List;
  127.35 +import java.util.logging.Level;
  127.36 +import java.util.logging.Logger;
  127.37 +
  127.38 +/**
  127.39 + * <p>
  127.40 + * Prints human-readable output – tables of result sets and text messages with update counts.
  127.41 + * </p>
  127.42 + *
  127.43 + * <p>
  127.44 + * Longer values might break the table – overflow the cells – see alternative tabular formatters and
  127.45 + * the {@linkplain #PROPERTY_TRIM} property.
  127.46 + * </p>
  127.47 + *
  127.48 + * @author Ing. František Kučera (frantovo.cz)
  127.49 + * @see TabularPrefetchingFormatter
  127.50 + * @see TabularWrappingFormatter
  127.51 + */
  127.52 +@PropertyDeclaration(name = COLORFUL, defaultValue = "true", type = Boolean.class, description = COLORFUL_DESCRIPTION)
  127.53 +@PropertyDeclaration(name = TabularFormatter.PROPERTY_ASCII, defaultValue = "false", type = Boolean.class, description = "whether to use ASCII table borders instead of unicode ones")
  127.54 +@PropertyDeclaration(name = TabularFormatter.PROPERTY_TRIM, defaultValue = "false", type = Boolean.class, description = "whether to trim the values to fit the column width")
  127.55 +@PropertyDeclaration(name = TabularFormatter.PROPERTY_HEADER_TYPE, defaultValue = "true", type = Boolean.class, description = "whether to print data types in column headers")
  127.56 +public class TabularFormatter extends AbstractFormatter {
  127.57 +
  127.58 +	private static final Logger log = Logger.getLogger(TabularFormatter.class.getName());
  127.59 +	public static final String NAME = "tabular"; // bash-completion:formatter
  127.60 +	private static final String HEADER_TYPE_PREFIX = " (";
  127.61 +	private static final String HEADER_TYPE_SUFFIX = ")";
  127.62 +	public static final String PROPERTY_ASCII = "ascii";
  127.63 +	public static final String PROPERTY_TRIM = "trim";
  127.64 +	public static final String PROPERTY_HEADER_TYPE = "headerTypes";
  127.65 +	protected ColorfulPrintWriter out;
  127.66 +	private boolean firstResult = true;
  127.67 +	private int[] columnWidth;
  127.68 +	/**
  127.69 +	 * use ASCII borders instead of unicode ones
  127.70 +	 */
  127.71 +	private final boolean asciiNostalgia;
  127.72 +	/**
  127.73 +	 * Trim values if they are longer than cell size
  127.74 +	 */
  127.75 +	private final boolean trimValues;
  127.76 +	/**
  127.77 +	 * Print data type of each column in the header
  127.78 +	 */
  127.79 +	private final boolean printHeaderTypes;
  127.80 +
  127.81 +	public TabularFormatter(FormatterContext formatterContext) {
  127.82 +		super(formatterContext);
  127.83 +		out = new ColorfulPrintWriter(formatterContext.getOutputStream());
  127.84 +		asciiNostalgia = formatterContext.getProperties().getBoolean(PROPERTY_ASCII, false);
  127.85 +		trimValues = formatterContext.getProperties().getBoolean(PROPERTY_TRIM, false);
  127.86 +		printHeaderTypes = formatterContext.getProperties().getBoolean(PROPERTY_HEADER_TYPE, true);
  127.87 +		out.setColorful(formatterContext.getProperties().getBoolean(COLORFUL, true));
  127.88 +	}
  127.89 +
  127.90 +	@Override
  127.91 +	public void writeStartResultSet(ColumnsHeader header) {
  127.92 +		super.writeStartResultSet(header);
  127.93 +		printResultSeparator();
  127.94 +
  127.95 +		initColumnWidths(header.getColumnCount());
  127.96 +
  127.97 +		printTableIndent();
  127.98 +		printTableBorder("╭");
  127.99 +
 127.100 +		List<ColumnDescriptor> columnDescriptors = header.getColumnDescriptors();
 127.101 +
 127.102 +		for (ColumnDescriptor cd : columnDescriptors) {
 127.103 +			// padding: make header cell at least same width as data cells in this column
 127.104 +			int typeWidth = printHeaderTypes ? cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length() : 0;
 127.105 +			cd.setLabel(rpad(cd.getLabel(), getColumnWidth(cd.getColumnNumber()) - typeWidth));
 127.106 +			updateColumnWidth(cd.getColumnNumber(), cd.getLabel().length() + typeWidth);
 127.107 +
 127.108 +			if (!cd.isFirstColumn()) {
 127.109 +				printTableBorder("┬");
 127.110 +			}
 127.111 +			printTableBorder(repeat('─', getColumnWidth(cd.getColumnNumber()) + 2));
 127.112 +		}
 127.113 +		printTableBorder("╮");
 127.114 +		out.println();
 127.115 +
 127.116 +		for (ColumnDescriptor cd : columnDescriptors) {
 127.117 +			if (cd.isFirstColumn()) {
 127.118 +				printTableIndent();
 127.119 +				printTableBorder("│ ");
 127.120 +			} else {
 127.121 +				printTableBorder(" │ ");
 127.122 +			}
 127.123 +			out.print(TerminalStyle.Bright, cd.getLabel());
 127.124 +			if (printHeaderTypes) {
 127.125 +				out.print(HEADER_TYPE_PREFIX);
 127.126 +				out.print(cd.getTypeName());
 127.127 +				out.print(HEADER_TYPE_SUFFIX);
 127.128 +			}
 127.129 +			if (cd.isLastColumn()) {
 127.130 +				printTableBorder(" │");
 127.131 +			}
 127.132 +		}
 127.133 +		out.println();
 127.134 +
 127.135 +		printTableIndent();
 127.136 +		printTableBorder("├");
 127.137 +		for (int i = 1; i <= header.getColumnCount(); i++) {
 127.138 +			if (i > 1) {
 127.139 +				printTableBorder("┼");
 127.140 +			}
 127.141 +			printTableBorder(repeat('─', getColumnWidth(i) + 2));
 127.142 +		}
 127.143 +		printTableBorder("┤");
 127.144 +		out.println();
 127.145 +
 127.146 +		out.flush();
 127.147 +	}
 127.148 +
 127.149 +	/**
 127.150 +	 * Must be called before {@linkplain #updateColumnWidth(int, int)} and
 127.151 +	 * {@linkplain #getColumnWidth(int)} for each result set.
 127.152 +	 *
 127.153 +	 * @param columnCount number of columns in current result set
 127.154 +	 */
 127.155 +	protected void initColumnWidths(int columnCount) {
 127.156 +		if (columnWidth == null) {
 127.157 +			columnWidth = new int[columnCount];
 127.158 +		}
 127.159 +	}
 127.160 +
 127.161 +	protected void cleanColumnWidths() {
 127.162 +		columnWidth = null;
 127.163 +	}
 127.164 +
 127.165 +	@Override
 127.166 +	public void writeColumnValue(Object value) {
 127.167 +		super.writeColumnValue(value);
 127.168 +		writeColumnValueInternal(value);
 127.169 +	}
 127.170 +
 127.171 +	protected void writeColumnValueInternal(Object value) {
 127.172 +
 127.173 +		if (isCurrentColumnFirst()) {
 127.174 +			printTableIndent();
 127.175 +			printTableBorder("│ ");
 127.176 +		} else {
 127.177 +			printTableBorder(" │ ");
 127.178 +		}
 127.179 +
 127.180 +		printValueWithWhitespaceReplaced(toString(value));
 127.181 +
 127.182 +		if (isCurrentColumnLast()) {
 127.183 +			printTableBorder(" │");
 127.184 +		}
 127.185 +
 127.186 +	}
 127.187 +
 127.188 +	protected void printValueWithWhitespaceReplaced(String text) {
 127.189 +		Functions.printValueWithWhitespaceReplaced(out, text, TerminalColor.Cyan, TerminalColor.Red);
 127.190 +	}
 127.191 +
 127.192 +	protected int getColumnWidth(int columnNumber) {
 127.193 +		return columnWidth[columnNumber - 1];
 127.194 +	}
 127.195 +
 127.196 +	private void setColumnWidth(int columnNumber, int width) {
 127.197 +		columnWidth[columnNumber - 1] = width;
 127.198 +	}
 127.199 +
 127.200 +	protected void updateColumnWidth(int columnNumber, int width) {
 127.201 +		int oldWidth = getColumnWidth(columnNumber);
 127.202 +		setColumnWidth(columnNumber, Math.max(width, oldWidth));
 127.203 +
 127.204 +	}
 127.205 +
 127.206 +	protected String toString(Object value) {
 127.207 +		final int width = getColumnWidth(getCurrentColumnsCount());
 127.208 +		String result;
 127.209 +		if (value instanceof Number || value instanceof Boolean) {
 127.210 +			result = lpad(String.valueOf(value), width);
 127.211 +		} else {
 127.212 +			if (value instanceof SQLXML) {
 127.213 +				// TODO: move to a common method, share with other formatters
 127.214 +				try {
 127.215 +					value = ((SQLXML) value).getString();
 127.216 +				} catch (SQLException e) {
 127.217 +					log.log(Level.SEVERE, "Unable to format XML", e);
 127.218 +				}
 127.219 +			}
 127.220 +
 127.221 +			result = rpad(String.valueOf(value), width);
 127.222 +		}
 127.223 +		// ?	value = (boolean) value ? "✔" : "✗";
 127.224 +
 127.225 +		if (trimValues && result.length() > width) {
 127.226 +			result = result.substring(0, width - 1) + "…";
 127.227 +		}
 127.228 +
 127.229 +		return result;
 127.230 +	}
 127.231 +
 127.232 +	@Override
 127.233 +	public void writeEndRow() {
 127.234 +		super.writeEndRow();
 127.235 +		writeEndRowInternal();
 127.236 +	}
 127.237 +
 127.238 +	public void writeEndRowInternal() {
 127.239 +		out.println();
 127.240 +		out.flush();
 127.241 +	}
 127.242 +
 127.243 +	@Override
 127.244 +	public void writeEndResultSet() {
 127.245 +		int columnCount = getCurrentColumnsHeader().getColumnCount();
 127.246 +		super.writeEndResultSet();
 127.247 +
 127.248 +		printTableIndent();
 127.249 +		printTableBorder("╰");
 127.250 +		for (int i = 1; i <= columnCount; i++) {
 127.251 +			if (i > 1) {
 127.252 +				printTableBorder("┴");
 127.253 +			}
 127.254 +			printTableBorder(repeat('─', getColumnWidth(i) + 2));
 127.255 +		}
 127.256 +		printTableBorder("╯");
 127.257 +		out.println();
 127.258 +
 127.259 +		cleanColumnWidths();
 127.260 +
 127.261 +		out.print(TerminalColor.Yellow, "Record count: ");
 127.262 +		out.println(getCurrentRowCount());
 127.263 +		out.bell();
 127.264 +		out.flush();
 127.265 +	}
 127.266 +
 127.267 +	@Override
 127.268 +	public void writeUpdatesResult(int updatedRowsCount) {
 127.269 +		super.writeUpdatesResult(updatedRowsCount);
 127.270 +		printResultSeparator();
 127.271 +		out.print(TerminalColor.Red, "Updated records: ");
 127.272 +		out.println(updatedRowsCount);
 127.273 +		out.bell();
 127.274 +		out.flush();
 127.275 +	}
 127.276 +
 127.277 +	@Override
 127.278 +	public void writeEndDatabase() {
 127.279 +		super.writeEndDatabase();
 127.280 +		out.flush();
 127.281 +	}
 127.282 +
 127.283 +	private void printResultSeparator() {
 127.284 +		if (firstResult) {
 127.285 +			firstResult = false;
 127.286 +		} else {
 127.287 +			out.println();
 127.288 +		}
 127.289 +	}
 127.290 +
 127.291 +	protected void printTableBorder(String border) {
 127.292 +		if (asciiNostalgia) {
 127.293 +			border = border.replaceAll("─", "-");
 127.294 +			border = border.replaceAll("│", "|");
 127.295 +			border = border.replaceAll("[╭┬╮├┼┤╰┴╯]", "+");
 127.296 +		}
 127.297 +
 127.298 +		out.print(TerminalColor.Green, border);
 127.299 +	}
 127.300 +
 127.301 +	protected void printTableIndent() {
 127.302 +		out.print(" ");
 127.303 +	}
 127.304 +
 127.305 +	/**
 127.306 +	 * @return whether should print only ASCII characters instead of unlimited Unicode.
 127.307 +	 */
 127.308 +	protected boolean isAsciiNostalgia() {
 127.309 +		return asciiNostalgia;
 127.310 +	}
 127.311 +}
   128.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   128.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java	Mon Mar 04 20:15:24 2019 +0100
   128.3 @@ -0,0 +1,119 @@
   128.4 +/**
   128.5 + * SQL-DK
   128.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   128.7 + *
   128.8 + * This program is free software: you can redistribute it and/or modify
   128.9 + * it under the terms of the GNU General Public License as published by
  128.10 + * the Free Software Foundation, either version 3 of the License, or
  128.11 + * (at your option) any later version.
  128.12 + *
  128.13 + * This program is distributed in the hope that it will be useful,
  128.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  128.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  128.16 + * GNU General Public License for more details.
  128.17 + *
  128.18 + * You should have received a copy of the GNU General Public License
  128.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  128.20 + */
  128.21 +package info.globalcode.sql.dk.formatting;
  128.22 +
  128.23 +import java.util.ArrayList;
  128.24 +import java.util.List;
  128.25 +
  128.26 +/**
  128.27 + * <p>
  128.28 + * Prefetches whole result set and computes column widths. Whole table is flushed at once in
  128.29 + * {@linkplain #writeEndResultSet()}.
  128.30 + * </p>
  128.31 + *
  128.32 + * <p>
  128.33 + * Long values will not overflow the cells, but whole result set must be loaded into the memory.
  128.34 + * </p>
  128.35 + *
  128.36 + * @author Ing. František Kučera (frantovo.cz)
  128.37 + */
  128.38 +public class TabularPrefetchingFormatter extends TabularFormatter {
  128.39 +
  128.40 +	public static final String NAME = "tabular-prefetching"; // bash-completion:formatter
  128.41 +	private ColumnsHeader currentHeader;
  128.42 +	private List<Object[]> currentResultSet;
  128.43 +	private Object[] currentRow;
  128.44 +	private int currentColumnsCount;
  128.45 +	private boolean prefetchDone = false;
  128.46 +
  128.47 +	public TabularPrefetchingFormatter(FormatterContext formatterContext) {
  128.48 +		super(formatterContext);
  128.49 +	}
  128.50 +
  128.51 +	@Override
  128.52 +	protected int getCurrentColumnsCount() {
  128.53 +		if (prefetchDone) {
  128.54 +			return super.getCurrentColumnsCount();
  128.55 +		} else {
  128.56 +			return currentColumnsCount;
  128.57 +		}
  128.58 +	}
  128.59 +
  128.60 +	@Override
  128.61 +	public void writeStartResultSet(ColumnsHeader header) {
  128.62 +		currentResultSet = new ArrayList<>();
  128.63 +		currentHeader = header;
  128.64 +		initColumnWidths(header.getColumnCount());
  128.65 +	}
  128.66 +
  128.67 +	@Override
  128.68 +	public void writeStartRow() {
  128.69 +		currentRow = new Object[currentHeader.getColumnCount()];
  128.70 +		currentResultSet.add(currentRow);
  128.71 +		currentColumnsCount = 0;
  128.72 +	}
  128.73 +
  128.74 +	@Override
  128.75 +	public void writeColumnValue(Object value) {
  128.76 +		currentRow[currentColumnsCount] = value;
  128.77 +		currentColumnsCount++;
  128.78 +		String textRepresentation = toString(value);
  128.79 +		/** TODO: count only printable characters (currently not an issue) */
  128.80 +		updateColumnWidth(currentColumnsCount, textRepresentation.length());
  128.81 +	}
  128.82 +
  128.83 +	@Override
  128.84 +	public void writeEndRow() {
  128.85 +		// do nothing
  128.86 +	}
  128.87 +
  128.88 +	@Override
  128.89 +	public void writeEndResultSet() {
  128.90 +		prefetchDone = true;
  128.91 +
  128.92 +		postprocessPrefetchedResultSet(currentHeader, currentResultSet);
  128.93 +
  128.94 +		super.writeStartResultSet(currentHeader);
  128.95 +
  128.96 +		for (Object[] row : currentResultSet) {
  128.97 +			super.writeStartRow();
  128.98 +			for (Object cell : row) {
  128.99 +				super.writeColumnValue(cell);
 128.100 +			}
 128.101 +			super.writeEndRow();
 128.102 +		}
 128.103 +
 128.104 +		currentColumnsCount = 0;
 128.105 +		currentHeader = null;
 128.106 +		currentRow = null;
 128.107 +		currentResultSet = null;
 128.108 +		super.writeEndResultSet();
 128.109 +		prefetchDone = false;
 128.110 +	}
 128.111 +
 128.112 +	/**
 128.113 +	 * Optional post-processing – override in sub-classes if needed.
 128.114 +	 * Don't forget to {@linkplain #updateColumnWidth(int, int)}
 128.115 +	 *
 128.116 +	 * @param currentHeader
 128.117 +	 * @param currentResultSet
 128.118 +	 */
 128.119 +	protected void postprocessPrefetchedResultSet(ColumnsHeader currentHeader, List<Object[]> currentResultSet) {
 128.120 +	}
 128.121 +
 128.122 +}
   129.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   129.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularWrappingFormatter.java	Mon Mar 04 20:15:24 2019 +0100
   129.3 @@ -0,0 +1,116 @@
   129.4 +/**
   129.5 + * SQL-DK
   129.6 + * Copyright © 2014 František Kučera (frantovo.cz)
   129.7 + *
   129.8 + * This program is free software: you can redistribute it and/or modify
   129.9 + * it under the terms of the GNU General Public License as published by
  129.10 + * the Free Software Foundation, either version 3 of the License, or
  129.11 + * (at your option) any later version.
  129.12 + *
  129.13 + * This program is distributed in the hope that it will be useful,
  129.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  129.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  129.16 + * GNU General Public License for more details.
  129.17 + *
  129.18 + * You should have received a copy of the GNU General Public License
  129.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  129.20 + */
  129.21 +package info.globalcode.sql.dk.formatting;
  129.22 +
  129.23 +import info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
  129.24 +import java.util.ArrayList;
  129.25 +import java.util.List;
  129.26 +import static info.globalcode.sql.dk.Functions.lpad;
  129.27 +import static info.globalcode.sql.dk.Functions.rpad;
  129.28 +import static info.globalcode.sql.dk.Functions.repeat;
  129.29 +
  129.30 +/**
  129.31 + * Longer values are line-wrapped – the cell then contains multiple lines. Marks are added to
  129.32 + * signalize forced line ends (not present in original data).
  129.33 + *
  129.34 + * @author Ing. František Kučera (frantovo.cz)
  129.35 + */
  129.36 +public class TabularWrappingFormatter extends TabularFormatter {
  129.37 +
  129.38 +	public static final String NAME = "tabular-wrapping"; // bash-completion:formatter
  129.39 +	private List<String[]> currentRow;
  129.40 +
  129.41 +	public TabularWrappingFormatter(FormatterContext formatterContext) {
  129.42 +		super(formatterContext);
  129.43 +	}
  129.44 +
  129.45 +	@Override
  129.46 +	public void writeStartResultSet(ColumnsHeader header) {
  129.47 +		super.writeStartResultSet(header);
  129.48 +		currentRow = new ArrayList<>(header.getColumnCount());
  129.49 +	}
  129.50 +
  129.51 +	@Override
  129.52 +	protected void writeColumnValueInternal(Object value) {
  129.53 +		boolean rightAlign = value instanceof Number || value instanceof Boolean;
  129.54 +		String valueString = String.valueOf(value);
  129.55 +		int columnWidth = getColumnWidth(getCurrentColumnsCount()) - 1;  // -1 = space for new line symbol
  129.56 +		currentRow.add(wrapLines(valueString, columnWidth, rightAlign));
  129.57 +	}
  129.58 +
  129.59 +	@Override
  129.60 +	public void writeEndRow() {
  129.61 +		super.writeEndRow();
  129.62 +
  129.63 +		int wrappedLine = 0;
  129.64 +		boolean hasMoreWrappedLines;
  129.65 +
  129.66 +		do {
  129.67 +			hasMoreWrappedLines = false;
  129.68 +			for (int i = 0; i < currentRow.size(); i++) {
  129.69 +				if (i == 0) {
  129.70 +					printTableIndent();
  129.71 +					printTableBorder("│ ");
  129.72 +				} else {
  129.73 +					printTableBorder(" │ ");
  129.74 +				}
  129.75 +				String[] columnArray = currentRow.get(i);
  129.76 +				if (wrappedLine < columnArray.length) {
  129.77 +					printValueWithWhitespaceReplaced(columnArray[wrappedLine]);
  129.78 +
  129.79 +					if (wrappedLine < columnArray.length - 1) {
  129.80 +						out.print(TerminalColor.Red, "↩");
  129.81 +						hasMoreWrappedLines = true;
  129.82 +					} else {
  129.83 +						out.print(" ");
  129.84 +					}
  129.85 +
  129.86 +				} else {
  129.87 +					out.print(repeat(' ', getColumnWidth(i + 1)));
  129.88 +				}
  129.89 +
  129.90 +				if (i == (currentRow.size() - 1)) {
  129.91 +					printTableBorder(" │");
  129.92 +				}
  129.93 +			}
  129.94 +			out.println();
  129.95 +			out.flush();
  129.96 +			wrappedLine++;
  129.97 +		} while (hasMoreWrappedLines);
  129.98 +
  129.99 +		currentRow.clear();
 129.100 +	}
 129.101 +
 129.102 +	@Override
 129.103 +	public void writeEndRowInternal() {
 129.104 +		// already done – wrapped row ends
 129.105 +	}
 129.106 +
 129.107 +	private static String[] wrapLines(String s, int width, boolean rightAlign) {
 129.108 +		String[] array = new String[(s.length() - 1) / width + 1];
 129.109 +		for (int i = 0; i < array.length; i++) {
 129.110 +			if (i == array.length - 1) {
 129.111 +				String part = s.substring(i * width, s.length());
 129.112 +				array[i] = rightAlign ? lpad(part, width) : rpad(part, width);
 129.113 +			} else {
 129.114 +				array[i] = s.substring(i * width, (i + 1) * width);
 129.115 +			}
 129.116 +		}
 129.117 +		return array;
 129.118 +	}
 129.119 +}
   130.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   130.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TeXFormatter.java	Mon Mar 04 20:15:24 2019 +0100
   130.3 @@ -0,0 +1,208 @@
   130.4 +/**
   130.5 + * SQL-DK
   130.6 + * Copyright © 2014 František Kučera (frantovo.cz)
   130.7 + *
   130.8 + * This program is free software: you can redistribute it and/or modify
   130.9 + * it under the terms of the GNU General Public License as published by
  130.10 + * the Free Software Foundation, either version 3 of the License, or
  130.11 + * (at your option) any later version.
  130.12 + *
  130.13 + * This program is distributed in the hope that it will be useful,
  130.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  130.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  130.16 + * GNU General Public License for more details.
  130.17 + *
  130.18 + * You should have received a copy of the GNU General Public License
  130.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  130.20 + */
  130.21 +package info.globalcode.sql.dk.formatting;
  130.22 +
  130.23 +import info.globalcode.sql.dk.ColorfulPrintWriter;
  130.24 +import info.globalcode.sql.dk.Constants;
  130.25 +import info.globalcode.sql.dk.configuration.PropertyDeclaration;
  130.26 +import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
  130.27 +import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
  130.28 +import java.util.Collections;
  130.29 +import java.util.HashMap;
  130.30 +import java.util.List;
  130.31 +import java.util.Map;
  130.32 +
  130.33 +/**
  130.34 + * Outputs result sets in (La)TeX format.
  130.35 + *
  130.36 + * @author Ing. František Kučera (frantovo.cz)
  130.37 + */
  130.38 +@PropertyDeclaration(name = COLORFUL, defaultValue = "false", type = Boolean.class, description = COLORFUL_DESCRIPTION)
  130.39 +public class TeXFormatter extends AbstractFormatter {
  130.40 +
  130.41 +	public static final String NAME = "tex"; // bash-completion:formatter
  130.42 +	private static final ColorfulPrintWriter.TerminalColor COMMAND_COLOR = ColorfulPrintWriter.TerminalColor.Magenta;
  130.43 +	private static final ColorfulPrintWriter.TerminalColor OPTIONS_COLOR = ColorfulPrintWriter.TerminalColor.Yellow;
  130.44 +	private static final Map<Character, String> TEX_ESCAPE_MAP;
  130.45 +	private final ColorfulPrintWriter out;
  130.46 +
  130.47 +	static {
  130.48 +		Map<Character, String> replacements = new HashMap<>();
  130.49 +
  130.50 +		replacements.put('\\', "\\textbackslash{}");
  130.51 +		replacements.put('{', "\\{{}");
  130.52 +		replacements.put('}', "\\}{}");
  130.53 +		replacements.put('_', "\\_{}");
  130.54 +		replacements.put('^', "\\textasciicircum{}");
  130.55 +		replacements.put('#', "\\#{}");
  130.56 +		replacements.put('&', "\\&{}");
  130.57 +		replacements.put('$', "\\${}");
  130.58 +		replacements.put('%', "\\%{}");
  130.59 +		replacements.put('~', "\\textasciitilde{}");
  130.60 +		replacements.put('-', "{-}");
  130.61 +
  130.62 +		TEX_ESCAPE_MAP = Collections.unmodifiableMap(replacements);
  130.63 +	}
  130.64 +
  130.65 +	public TeXFormatter(FormatterContext formatterContext) {
  130.66 +		super(formatterContext);
  130.67 +		boolean colorful = formatterContext.getProperties().getBoolean(COLORFUL, false);
  130.68 +		out = new ColorfulPrintWriter(formatterContext.getOutputStream(), false, colorful);
  130.69 +	}
  130.70 +
  130.71 +	@Override
  130.72 +	public void writeStartBatch() {
  130.73 +		super.writeStartBatch();
  130.74 +
  130.75 +		printCommand("documentclass", "a4paper,twoside", "article", true);
  130.76 +		printCommand("usepackage", "T1", "fontenc", true);
  130.77 +		printCommand("usepackage", "utf8x", "inputenc", true);
  130.78 +		printCommand("usepackage", "pdfauthor={" + Constants.WEBSITE + "}, bookmarks=true,unicode,colorlinks=true,linkcolor=black,urlcolor=blue,citecolor=blue", "hyperref", true);
  130.79 +		printBegin("document");
  130.80 +	}
  130.81 +
  130.82 +	@Override
  130.83 +	public void writeEndBatch() {
  130.84 +		super.writeEndBatch();
  130.85 +		printEnd("document");
  130.86 +	}
  130.87 +
  130.88 +	@Override
  130.89 +	public void writeColumnValue(Object value) {
  130.90 +		super.writeColumnValue(value);
  130.91 +		// TODO: arrays, numbers, booleans, nulls etc.:
  130.92 +		out.print(escapeTex(toString(value)));
  130.93 +
  130.94 +		if (!isCurrentColumnLast()) {
  130.95 +			printColumnSeparator();
  130.96 +		}
  130.97 +	}
  130.98 +
  130.99 +	@Override
 130.100 +	public void writeEndRow() {
 130.101 +		super.writeEndRow();
 130.102 +		printEndRow();
 130.103 +	}
 130.104 +
 130.105 +	@Override
 130.106 +	public void writeStartResultSet(ColumnsHeader header) {
 130.107 +		super.writeStartResultSet(header);
 130.108 +		printCommand("begin", null, "tabular", false);
 130.109 +
 130.110 +		List<ColumnDescriptor> columnDescriptors = header.getColumnDescriptors();
 130.111 +
 130.112 +		StringBuilder columnAlignments = new StringBuilder();
 130.113 +		for (ColumnDescriptor cd : columnDescriptors) {
 130.114 +			if (cd.isNumeric() || cd.isBoolean()) {
 130.115 +				columnAlignments.append('r');
 130.116 +			} else {
 130.117 +				columnAlignments.append('l');
 130.118 +			}
 130.119 +		}
 130.120 +
 130.121 +		printCommand(null, null, columnAlignments.toString(), true);
 130.122 +		printCommand("hline", null, null, true);
 130.123 +
 130.124 +		for (ColumnDescriptor cd : columnDescriptors) {
 130.125 +			printCommand("textbf", null, cd.getLabel(), false);
 130.126 +			if (cd.isLastColumn()) {
 130.127 +				printEndRow();
 130.128 +			} else {
 130.129 +				printColumnSeparator();
 130.130 +			}
 130.131 +		}
 130.132 +
 130.133 +		printCommand("hline", null, null, true);
 130.134 +	}
 130.135 +
 130.136 +	@Override
 130.137 +	public void writeEndResultSet() {
 130.138 +		super.writeEndResultSet();
 130.139 +		printCommand("hline", null, null, true);
 130.140 +		printEnd("tabular");
 130.141 +	}
 130.142 +
 130.143 +	private String escapeTex(String text) {
 130.144 +		if (text == null) {
 130.145 +			return null;
 130.146 +		} else {
 130.147 +			StringBuilder result = new StringBuilder(text.length() * 2);
 130.148 +
 130.149 +			for (char ch : text.toCharArray()) {
 130.150 +				String replacement = TEX_ESCAPE_MAP.get(ch);
 130.151 +				result.append(replacement == null ? ch : replacement);
 130.152 +			}
 130.153 +
 130.154 +			return result.toString();
 130.155 +		}
 130.156 +	}
 130.157 +
 130.158 +	protected String toString(Object value) {
 130.159 +		return String.valueOf(value);
 130.160 +	}
 130.161 +
 130.162 +	private void printColumnSeparator() {
 130.163 +		out.print(COMMAND_COLOR, " & ");
 130.164 +	}
 130.165 +
 130.166 +	private void printEndRow() {
 130.167 +		out.println(COMMAND_COLOR, " \\\\");
 130.168 +		out.flush();
 130.169 +	}
 130.170 +
 130.171 +	/**
 130.172 +	 *
 130.173 +	 * @param command will not be escaped – should contain just a valid TeX command name
 130.174 +	 * @param options will not be escaped – should be properly formatted to be printed inside [
 130.175 +	 * and ]
 130.176 +	 * @param value will be escaped
 130.177 +	 * @param println whether to print line end and flush
 130.178 +	 */
 130.179 +	private void printCommand(String command, String options, String value, boolean println) {
 130.180 +
 130.181 +		if (command != null) {
 130.182 +			out.print(COMMAND_COLOR, "\\" + command);
 130.183 +		}
 130.184 +
 130.185 +		if (options != null) {
 130.186 +			out.print(COMMAND_COLOR, "[");
 130.187 +			out.print(OPTIONS_COLOR, options);
 130.188 +			out.print(COMMAND_COLOR, "]");
 130.189 +		}
 130.190 +
 130.191 +		if (value != null) {
 130.192 +			out.print(COMMAND_COLOR, "{");
 130.193 +			out.print(escapeTex(value));
 130.194 +			out.print(COMMAND_COLOR, "}");
 130.195 +		}
 130.196 +
 130.197 +		if (println) {
 130.198 +			out.println();
 130.199 +			out.flush();
 130.200 +		}
 130.201 +	}
 130.202 +
 130.203 +	private void printBegin(String environment) {
 130.204 +		printCommand("begin", null, environment, true);
 130.205 +	}
 130.206 +
 130.207 +	private void printEnd(String environment) {
 130.208 +		printCommand("end", null, environment, true);
 130.209 +	}
 130.210 +
 130.211 +}
   131.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   131.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XhtmlFormatter.java	Mon Mar 04 20:15:24 2019 +0100
   131.3 @@ -0,0 +1,262 @@
   131.4 +/**
   131.5 + * SQL-DK
   131.6 + * Copyright © 2014 František Kučera (frantovo.cz)
   131.7 + *
   131.8 + * This program is free software: you can redistribute it and/or modify
   131.9 + * it under the terms of the GNU General Public License as published by
  131.10 + * the Free Software Foundation, either version 3 of the License, or
  131.11 + * (at your option) any later version.
  131.12 + *
  131.13 + * This program is distributed in the hope that it will be useful,
  131.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  131.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  131.16 + * GNU General Public License for more details.
  131.17 + *
  131.18 + * You should have received a copy of the GNU General Public License
  131.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  131.20 + */
  131.21 +package info.globalcode.sql.dk.formatting;
  131.22 +
  131.23 +import info.globalcode.sql.dk.Constants;
  131.24 +import info.globalcode.sql.dk.NamedParameter;
  131.25 +import info.globalcode.sql.dk.Parameter;
  131.26 +import info.globalcode.sql.dk.Xmlns;
  131.27 +import info.globalcode.sql.dk.configuration.DatabaseDefinition;
  131.28 +import info.globalcode.sql.dk.configuration.Properties;
  131.29 +import info.globalcode.sql.dk.configuration.Property;
  131.30 +import static info.globalcode.sql.dk.formatting.AbstractXmlFormatter.qname;
  131.31 +import java.sql.Array;
  131.32 +import java.sql.SQLException;
  131.33 +import java.util.Date;
  131.34 +import java.util.List;
  131.35 +import java.util.Map;
  131.36 +import java.util.Scanner;
  131.37 +import java.util.logging.Level;
  131.38 +import java.util.logging.Logger;
  131.39 +import javax.xml.namespace.QName;
  131.40 +
  131.41 +/**
  131.42 + * Prints result sets and parameters as tables, SQL as preformatted and updates counts as
  131.43 + * paragraphs. You can pick XHTML fragments (usually tabular data) and use it on your website or use
  131.44 + * whole output as preview or report.
  131.45 + *
  131.46 + * @author Ing. František Kučera (frantovo.cz)
  131.47 + */
  131.48 +public class XhtmlFormatter extends AbstractXmlFormatter {
  131.49 +
  131.50 +	private static final Logger log = Logger.getLogger(XhtmlFormatter.class.getName());
  131.51 +	public static final String NAME = "xhtml"; // bash-completion:formatter
  131.52 +	private static final String DOCTYPE = "html PUBLIC \"-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN\" \"http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd\"";
  131.53 +	private static final String CSS_FILE = "info/globalcode/sql/dk/formatter/XhtmlFormatter.css";
  131.54 +	private int statementCounter = 0;
  131.55 +	private int resultSetCounter = 0;
  131.56 +	private int updatesResultCounter = 0;
  131.57 +
  131.58 +	public XhtmlFormatter(FormatterContext formatterContext) {
  131.59 +		super(addDefaults(formatterContext));
  131.60 +	}
  131.61 +
  131.62 +	/**
  131.63 +	 * Do not indent text – preserve whitespace for pre elements
  131.64 +	 */
  131.65 +	private static FormatterContext addDefaults(FormatterContext formatterContext) {
  131.66 +		Properties defaults = new Properties(1);
  131.67 +		defaults.add(new Property(PROPERTY_INDENT_TEXT, "false"));
  131.68 +		formatterContext.getProperties().setLastDefaults(defaults);
  131.69 +		return formatterContext;
  131.70 +	}
  131.71 +
  131.72 +	@Override
  131.73 +	public void writeStartBatch() {
  131.74 +		super.writeStartBatch();
  131.75 +		printStartDocument();
  131.76 +		printDoctype(DOCTYPE);
  131.77 +		printStartElement(qname("html"), singleAttribute(qname("xmlns"), Xmlns.XHTML));
  131.78 +
  131.79 +		printStartElement(qname("head"));
  131.80 +		printTextElement(qname("title"), null, Constants.PROGRAM_NAME + ": batch results");
  131.81 +		printCss();
  131.82 +		printEndElement();
  131.83 +
  131.84 +		printStartElement(qname("body"));
  131.85 +	}
  131.86 +
  131.87 +	private void printCss() {
  131.88 +
  131.89 +		try (Scanner css = new Scanner(getClass().getClassLoader().getResourceAsStream(CSS_FILE))) {
  131.90 +			printStartElement(qname("style"), singleAttribute(qname("type"), "text/css"));
  131.91 +			while (css.hasNext()) {
  131.92 +				printText(css.nextLine(), true);
  131.93 +			}
  131.94 +			printEndElement();
  131.95 +		}
  131.96 +	}
  131.97 +
  131.98 +	@Override
  131.99 +	public void writeEndBatch() {
 131.100 +		super.writeEndBatch();
 131.101 +		printEndElement();
 131.102 +		printEndElement();
 131.103 +		printEndDocument();
 131.104 +	}
 131.105 +
 131.106 +	@Override
 131.107 +	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
 131.108 +		super.writeStartDatabase(databaseDefinition);
 131.109 +		printTextElement(qname("h1"), null, "Database: " + databaseDefinition.getName());
 131.110 +
 131.111 +		printStartElement(qname("p"));
 131.112 +		printText("This is XHTML output of batch executed at: ", true);
 131.113 +		printText(new Date().toString(), true);
 131.114 +		printEndElement();
 131.115 +	}
 131.116 +
 131.117 +	@Override
 131.118 +	public void writeQuery(String sql) {
 131.119 +		super.writeQuery(sql);
 131.120 +		printTextElement(qname("pre"), null, sql);
 131.121 +	}
 131.122 +
 131.123 +	@Override
 131.124 +	public void writeParameters(List<? extends Parameter> parameters) {
 131.125 +		super.writeParameters(parameters);
 131.126 +
 131.127 +		if (parameters == null || parameters.isEmpty()) {
 131.128 +			printTextElement(qname("p"), null, "(this query has no parameters)");
 131.129 +		} else {
 131.130 +			printTextElement(qname("h3"), null, "Parameters:");
 131.131 +
 131.132 +			printStartElement(qname("table"));
 131.133 +
 131.134 +			printStartElement(qname("thead"));
 131.135 +			printStartElement(qname("tr"));
 131.136 +			printTextElement(qname("td"), null, "id");
 131.137 +			printTextElement(qname("td"), null, "type");
 131.138 +			printTextElement(qname("td"), null, "value");
 131.139 +			printEndElement();
 131.140 +			printEndElement();
 131.141 +
 131.142 +			printStartElement(qname("tbody"));
 131.143 +			for (int i = 0; i < parameters.size(); i++) {
 131.144 +				Parameter p = parameters.get(i);
 131.145 +				printStartElement(qname("tr"));
 131.146 +				String numberOrName;
 131.147 +				if (p instanceof NamedParameter) {
 131.148 +					numberOrName = ((NamedParameter) p).getName();
 131.149 +				} else {
 131.150 +					numberOrName = String.valueOf(i + 1);
 131.151 +				}
 131.152 +				printTextElement(qname("td"), null, numberOrName);
 131.153 +				printTextElement(qname("td"), null, p.getType().name());
 131.154 +				printTableData(p.getValue());
 131.155 +				printEndElement();
 131.156 +			}
 131.157 +			printEndElement();
 131.158 +
 131.159 +			printEndElement();
 131.160 +		}
 131.161 +	}
 131.162 +
 131.163 +	private void printTableData(Object value) {
 131.164 +
 131.165 +		if (value instanceof Array) {
 131.166 +			Array sqlArray = (Array) value;
 131.167 +			try {
 131.168 +				Object[] array = (Object[]) sqlArray.getArray();
 131.169 +				printStartElement(qname("td"));
 131.170 +				printArray(array);
 131.171 +				printEndElement();
 131.172 +			} catch (SQLException e) {
 131.173 +				log.log(Level.SEVERE, "Unable to format array", e);
 131.174 +				printTableData(String.valueOf(value));
 131.175 +			}
 131.176 +		} else {
 131.177 +			Map<QName, String> attributes = null;
 131.178 +			if (value instanceof Number) {
 131.179 +				attributes = singleAttribute(qname("class"), "number");
 131.180 +			} else if (value instanceof Boolean) {
 131.181 +				attributes = singleAttribute(qname("class"), "boolean");
 131.182 +			}
 131.183 +			printTextElement(qname("td"), attributes, String.valueOf(value));
 131.184 +		}
 131.185 +	}
 131.186 +
 131.187 +	private void printArray(Object[] array) {
 131.188 +		printStartElement(qname("ul"));
 131.189 +		for (Object o : array) {
 131.190 +			if (o instanceof Object[]) {
 131.191 +				printStartElement(qname("li"));
 131.192 +				printTextElement(qname("p"), null, "nested array:");
 131.193 +				printArray((Object[]) o);
 131.194 +				printEndElement();
 131.195 +			} else {
 131.196 +				printTextElement(qname("li"), null, String.valueOf(o));
 131.197 +			}
 131.198 +		}
 131.199 +		printEndElement();
 131.200 +	}
 131.201 +
 131.202 +	@Override
 131.203 +	public void writeStartResultSet(ColumnsHeader header) {
 131.204 +		super.writeStartResultSet(header);
 131.205 +		resultSetCounter++;
 131.206 +		printEmptyElement(qname("hr"), null);
 131.207 +		printTextElement(qname("h3"), null, "Result set #" + resultSetCounter);
 131.208 +		printStartElement(qname("table"));
 131.209 +		printStartElement(qname("thead"));
 131.210 +		printStartElement(qname("tr"));
 131.211 +		for (ColumnDescriptor cd : header.getColumnDescriptors()) {
 131.212 +			// TODO: type
 131.213 +			printTextElement(qname("td"), null, cd.getLabel());
 131.214 +		}
 131.215 +		printEndElement();
 131.216 +		printEndElement();
 131.217 +
 131.218 +		printStartElement(qname("tbody"));
 131.219 +	}
 131.220 +
 131.221 +	@Override
 131.222 +	public void writeEndResultSet() {
 131.223 +		super.writeEndResultSet();
 131.224 +		printEndElement();
 131.225 +		printEndElement();
 131.226 +		printTextElement(qname("p"), null, "Record count: " + getCurrentRowCount());
 131.227 +	}
 131.228 +
 131.229 +	@Override
 131.230 +	public void writeStartRow() {
 131.231 +		super.writeStartRow();
 131.232 +		printStartElement(qname("tr"));
 131.233 +	}
 131.234 +
 131.235 +	@Override
 131.236 +	public void writeColumnValue(Object value) {
 131.237 +		super.writeColumnValue(value);
 131.238 +		printTableData(value);
 131.239 +	}
 131.240 +
 131.241 +	@Override
 131.242 +	public void writeEndRow() {
 131.243 +		super.writeEndRow();
 131.244 +		printEndElement();
 131.245 +	}
 131.246 +
 131.247 +	@Override
 131.248 +	public void writeStartStatement() {
 131.249 +		super.writeStartStatement();
 131.250 +		statementCounter++;
 131.251 +		printEmptyElement(qname("hr"), null);
 131.252 +		printTextElement(qname("h2"), null, "SQL statement #" + statementCounter);
 131.253 +		resultSetCounter = 0;
 131.254 +		updatesResultCounter = 0;
 131.255 +	}
 131.256 +
 131.257 +	@Override
 131.258 +	public void writeUpdatesResult(int updatedRowsCount) {
 131.259 +		super.writeUpdatesResult(updatedRowsCount);
 131.260 +		updatesResultCounter++;
 131.261 +		printEmptyElement(qname("hr"), null);
 131.262 +		printTextElement(qname("h3"), null, "Updates result #" + updatesResultCounter);
 131.263 +		printTextElement(qname("p"), null, "Updated rows: " + updatedRowsCount);
 131.264 +	}
 131.265 +}
   132.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   132.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XmlFormatter.java	Mon Mar 04 20:15:24 2019 +0100
   132.3 @@ -0,0 +1,245 @@
   132.4 +/**
   132.5 + * SQL-DK
   132.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   132.7 + *
   132.8 + * This program is free software: you can redistribute it and/or modify
   132.9 + * it under the terms of the GNU General Public License as published by
  132.10 + * the Free Software Foundation, either version 3 of the License, or
  132.11 + * (at your option) any later version.
  132.12 + *
  132.13 + * This program is distributed in the hope that it will be useful,
  132.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  132.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  132.16 + * GNU General Public License for more details.
  132.17 + *
  132.18 + * You should have received a copy of the GNU General Public License
  132.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  132.20 + */
  132.21 +package info.globalcode.sql.dk.formatting;
  132.22 +
  132.23 +import info.globalcode.sql.dk.Parameter;
  132.24 +import info.globalcode.sql.dk.Xmlns;
  132.25 +import info.globalcode.sql.dk.configuration.DatabaseDefinition;
  132.26 +import static info.globalcode.sql.dk.Functions.notNull;
  132.27 +import info.globalcode.sql.dk.NamedParameter;
  132.28 +import info.globalcode.sql.dk.configuration.PropertyDeclaration;
  132.29 +import static info.globalcode.sql.dk.formatting.AbstractXmlFormatter.qname;
  132.30 +import java.sql.Array;
  132.31 +import java.sql.ResultSet;
  132.32 +import java.sql.SQLException;
  132.33 +import java.sql.SQLXML;
  132.34 +import java.util.ArrayList;
  132.35 +import java.util.LinkedHashMap;
  132.36 +import java.util.List;
  132.37 +import java.util.Map;
  132.38 +import java.util.logging.Level;
  132.39 +import java.util.logging.Logger;
  132.40 +import javax.xml.namespace.QName;
  132.41 +
  132.42 +/**
  132.43 + * <p>
  132.44 + * Prints machine-readable output – XML document containing resultsets and updates count. Good
  132.45 + * choice for further processing – e.g. XSL transformation.</p>
  132.46 + *
  132.47 + * <p>
  132.48 + * TODO: XSD</p>
  132.49 + *
  132.50 + * @author Ing. František Kučera (frantovo.cz)
  132.51 + */
  132.52 +@PropertyDeclaration(name = XmlFormatter.PROPERTY_LABELED_COLUMNS, defaultValue = "false", type = Boolean.class, description = "whether to add 'label' attribute to each 'column' element")
  132.53 +public class XmlFormatter extends AbstractXmlFormatter {
  132.54 +
  132.55 +	public static final String NAME = "xml"; // bash-completion:formatter
  132.56 +	public static final String PROPERTY_LABELED_COLUMNS = "labeledColumns";
  132.57 +	private static final Logger log = Logger.getLogger(XmlFormatter.class.getName());
  132.58 +	private final boolean labeledColumns;
  132.59 +
  132.60 +	public XmlFormatter(FormatterContext formatterContext) {
  132.61 +		super(formatterContext);
  132.62 +		labeledColumns = formatterContext.getProperties().getBoolean(PROPERTY_LABELED_COLUMNS, false);
  132.63 +	}
  132.64 +
  132.65 +	@Override
  132.66 +	public void writeStartBatch() {
  132.67 +		super.writeStartBatch();
  132.68 +		printStartDocument();
  132.69 +		printStartElement(qname("batchResult"), singleAttribute(qname("xmlns"), Xmlns.BATCH_RESULT));
  132.70 +	}
  132.71 +
  132.72 +	@Override
  132.73 +	public void writeEndBatch() {
  132.74 +		super.writeEndBatch();
  132.75 +		printEndElement();
  132.76 +		printEndDocument();
  132.77 +	}
  132.78 +
  132.79 +	@Override
  132.80 +	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
  132.81 +		super.writeStartDatabase(databaseDefinition);
  132.82 +		Map<QName, String> attributes = databaseDefinition.getName() == null ? null : singleAttribute(qname("name"), databaseDefinition.getName());
  132.83 +		printStartElement(qname("database"), attributes);
  132.84 +	}
  132.85 +
  132.86 +	@Override
  132.87 +	public void writeEndDatabase() {
  132.88 +		super.writeEndDatabase();
  132.89 +		printEndElement();
  132.90 +	}
  132.91 +
  132.92 +	@Override
  132.93 +	public void writeStartStatement() {
  132.94 +		super.writeStartStatement();
  132.95 +		printStartElement(qname("statement"));
  132.96 +	}
  132.97 +
  132.98 +	@Override
  132.99 +	public void writeEndStatement() {
 132.100 +		super.writeEndStatement();
 132.101 +		printEndElement();
 132.102 +	}
 132.103 +
 132.104 +	@Override
 132.105 +	public void writeQuery(String sql) {
 132.106 +		super.writeQuery(sql);
 132.107 +		printTextElement(qname("sql"), null, sql);
 132.108 +	}
 132.109 +
 132.110 +	@Override
 132.111 +	public void writeParameters(List<? extends Parameter> parameters) {
 132.112 +		super.writeParameters(parameters);
 132.113 +
 132.114 +		for (Parameter p : notNull(parameters)) {
 132.115 +
 132.116 +			Map<QName, String> attributes = new LinkedHashMap<>(2);
 132.117 +			if (p instanceof NamedParameter) {
 132.118 +				attributes.put(qname("name"), ((NamedParameter) p).getName());
 132.119 +			}
 132.120 +			attributes.put(qname("type"), p.getType().name());
 132.121 +
 132.122 +			printTextElement(qname("parameter"), attributes, String.valueOf(p.getValue()));
 132.123 +		}
 132.124 +
 132.125 +	}
 132.126 +
 132.127 +	@Override
 132.128 +	public void writeStartResultSet(ColumnsHeader header) {
 132.129 +		super.writeStartResultSet(header);
 132.130 +		printStartElement(qname("resultSet"));
 132.131 +
 132.132 +		for (ColumnDescriptor cd : header.getColumnDescriptors()) {
 132.133 +			Map<QName, String> attributes = new LinkedHashMap<>(4);
 132.134 +			attributes.put(qname("label"), cd.getLabel());
 132.135 +			attributes.put(qname("name"), cd.getName());
 132.136 +			attributes.put(qname("typeName"), cd.getTypeName());
 132.137 +			attributes.put(qname("type"), String.valueOf(cd.getType()));
 132.138 +			printEmptyElement(qname("columnHeader"), attributes);
 132.139 +		}
 132.140 +	}
 132.141 +
 132.142 +	@Override
 132.143 +	public void writeEndResultSet() {
 132.144 +		super.writeEndResultSet();
 132.145 +		printEndElement();
 132.146 +	}
 132.147 +
 132.148 +	@Override
 132.149 +	public void writeStartRow() {
 132.150 +		super.writeStartRow();
 132.151 +		printStartElement(qname("row"));
 132.152 +	}
 132.153 +
 132.154 +	@Override
 132.155 +	public void writeColumnValue(Object value) {
 132.156 +		super.writeColumnValue(value);
 132.157 +
 132.158 +		Map<QName, String> attributes = null;
 132.159 +		if (labeledColumns) {
 132.160 +			attributes = new LinkedHashMap<>(2);
 132.161 +			attributes.put(qname("label"), getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getLabel());
 132.162 +		}
 132.163 +
 132.164 +		if (value == null) {
 132.165 +			if (attributes == null) {
 132.166 +				attributes = new LinkedHashMap<>(2);
 132.167 +			}
 132.168 +			attributes.put(qname("null"), "true");
 132.169 +			printEmptyElement(qname("column"), attributes);
 132.170 +		} else if (value instanceof Array) {
 132.171 +
 132.172 +			Array sqlArray = (Array) value;
 132.173 +			try {
 132.174 +				Object[] array = (Object[]) sqlArray.getArray();
 132.175 +				printStartElement(qname("column"), attributes);
 132.176 +				printArray(array);
 132.177 +				printEndElement();
 132.178 +			} catch (SQLException e) {
 132.179 +				// FIXME: rewrite array formatting, remember array mode, don't try sqlArray.getArray() again and again if it has failed
 132.180 +				log.log(Level.SEVERE, "Unable to format array", e);
 132.181 +				try {
 132.182 +					ResultSet arrayResultSet = sqlArray.getResultSet();
 132.183 +					//int columnCount = arrayResultSet.getMetaData().getColumnCount();
 132.184 +					ArrayList<Object> arrayList = new ArrayList<>();
 132.185 +					while (arrayResultSet.next()) {
 132.186 +						arrayList.add(arrayResultSet.getObject(2));
 132.187 +						// for (int i = 1; i <= columnCount; i++) {
 132.188 +						// 	log.log(Level.INFO, "Array column {0} = {1}", new Object[]{i, arrayResultSet.getObject(i)});
 132.189 +						// }
 132.190 +					}
 132.191 +
 132.192 +					printStartElement(qname("column"), attributes);
 132.193 +					// FIXME: instanceof SQLXML, see below
 132.194 +					printArray(arrayList.toArray());
 132.195 +					printEndElement();
 132.196 +
 132.197 +				} catch (SQLException e2) {
 132.198 +					// FIXME: fix logging, error recovery
 132.199 +					log.log(Level.SEVERE, "Second level fuck up !!!", e2);
 132.200 +				}
 132.201 +
 132.202 +				writeColumnValue(String.valueOf(value));
 132.203 +			}
 132.204 +
 132.205 +		} else if (value instanceof SQLXML) { // FIXME: move to separate method, to AbstractFormatter?
 132.206 +			SQLXML xml = (SQLXML) value;
 132.207 +			// TODO: parse DOM/SAX and transplant XML, don't escape (optional)
 132.208 +			try {
 132.209 +				printTextElement(qname("column"), attributes, xml.getString());
 132.210 +			} catch (SQLException e) {
 132.211 +				log.log(Level.SEVERE, "Unable to format XML", e);
 132.212 +				writeColumnValue(String.valueOf(value));
 132.213 +			}
 132.214 +		} else {
 132.215 +			printTextElement(qname("column"), attributes, toString(value));
 132.216 +		}
 132.217 +	}
 132.218 +
 132.219 +	private void printArray(Object[] array) {
 132.220 +		printStartElement(qname("array"));
 132.221 +		for (Object o : array) {
 132.222 +			if (o instanceof Object[]) {
 132.223 +				printStartElement(qname("item"));
 132.224 +				printArray((Object[]) o);
 132.225 +				printEndElement();
 132.226 +			} else {
 132.227 +				printTextElement(qname("item"), null, String.valueOf(o));
 132.228 +			}
 132.229 +		}
 132.230 +		printEndElement();
 132.231 +	}
 132.232 +
 132.233 +	@Override
 132.234 +	public void writeEndRow() {
 132.235 +		super.writeEndRow();
 132.236 +		printEndElement();
 132.237 +	}
 132.238 +
 132.239 +	@Override
 132.240 +	public void writeUpdatesResult(int updatedRowsCount) {
 132.241 +		super.writeUpdatesResult(updatedRowsCount);
 132.242 +		printTextElement(qname("updatedRows"), null, String.valueOf(updatedRowsCount));
 132.243 +	}
 132.244 +
 132.245 +	protected String toString(Object value) {
 132.246 +		return String.valueOf(value);
 132.247 +	}
 132.248 +}
   133.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   133.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ConnectionManagement.java	Mon Mar 04 20:15:24 2019 +0100
   133.3 @@ -0,0 +1,97 @@
   133.4 +/**
   133.5 + * SQL-DK
   133.6 + * Copyright © 2014 František Kučera (frantovo.cz)
   133.7 + *
   133.8 + * This program is free software: you can redistribute it and/or modify
   133.9 + * it under the terms of the GNU General Public License as published by
  133.10 + * the Free Software Foundation, either version 3 of the License, or
  133.11 + * (at your option) any later version.
  133.12 + *
  133.13 + * This program is distributed in the hope that it will be useful,
  133.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  133.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  133.16 + * GNU General Public License for more details.
  133.17 + *
  133.18 + * You should have received a copy of the GNU General Public License
  133.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  133.20 + */
  133.21 +package info.globalcode.sql.dk.jmx;
  133.22 +
  133.23 +import java.util.EnumMap;
  133.24 +import java.util.Map;
  133.25 +
  133.26 +/**
  133.27 + * JMX management bean for progress reporting.
  133.28 + *
  133.29 + * @author Ing. František Kučera (frantovo.cz)
  133.30 + */
  133.31 +public class ConnectionManagement implements ConnectionManagementMBean {
  133.32 +
  133.33 +	private final String databaseName;
  133.34 +	private final Map<COUNTER, Integer> counters = new EnumMap(COUNTER.class);
  133.35 +
  133.36 +	public ConnectionManagement(String databaseName) {
  133.37 +		this.databaseName = databaseName;
  133.38 +		for (COUNTER c : COUNTER.values()) {
  133.39 +			counters.put(c, 0);
  133.40 +		}
  133.41 +	}
  133.42 +
  133.43 +	public enum COUNTER {
  133.44 +
  133.45 +		COMMAND,
  133.46 +		RECORD_CURRENT,
  133.47 +		RECORD_TOTAL
  133.48 +	};
  133.49 +
  133.50 +	public void incrementCounter(COUNTER counter) {
  133.51 +		synchronized (counters) {
  133.52 +			int old = counters.get(counter);
  133.53 +			counters.put(counter, old + 1);
  133.54 +		}
  133.55 +	}
  133.56 +
  133.57 +	public void resetCounter(COUNTER counter) {
  133.58 +		synchronized (counters) {
  133.59 +			counters.put(counter, 0);
  133.60 +		}
  133.61 +	}
  133.62 +
  133.63 +	public static void incrementCounter(ConnectionManagement mbean, COUNTER counter) {
  133.64 +		if (mbean != null) {
  133.65 +			mbean.incrementCounter(counter);
  133.66 +		}
  133.67 +	}
  133.68 +
  133.69 +	public static void resetCounter(ConnectionManagement mbean, COUNTER counter) {
  133.70 +		if (mbean != null) {
  133.71 +			mbean.resetCounter(counter);
  133.72 +		}
  133.73 +	}
  133.74 +
  133.75 +	@Override
  133.76 +	public String getDatabaseName() {
  133.77 +		return databaseName;
  133.78 +	}
  133.79 +
  133.80 +	@Override
  133.81 +	public int getCommandCount() {
  133.82 +		synchronized (counters) {
  133.83 +			return counters.get(COUNTER.COMMAND);
  133.84 +		}
  133.85 +	}
  133.86 +
  133.87 +	@Override
  133.88 +	public int getCurrentRecordCount() {
  133.89 +		synchronized (counters) {
  133.90 +			return counters.get(COUNTER.RECORD_CURRENT);
  133.91 +		}
  133.92 +	}
  133.93 +	
  133.94 +	@Override
  133.95 +	public int getTotalRecordCount() {
  133.96 +		synchronized (counters) {
  133.97 +			return counters.get(COUNTER.RECORD_TOTAL);
  133.98 +		}
  133.99 +	}
 133.100 +}
   134.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   134.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ConnectionManagementMBean.java	Mon Mar 04 20:15:24 2019 +0100
   134.3 @@ -0,0 +1,34 @@
   134.4 +/**
   134.5 + * SQL-DK
   134.6 + * Copyright © 2014 František Kučera (frantovo.cz)
   134.7 + *
   134.8 + * This program is free software: you can redistribute it and/or modify
   134.9 + * it under the terms of the GNU General Public License as published by
  134.10 + * the Free Software Foundation, either version 3 of the License, or
  134.11 + * (at your option) any later version.
  134.12 + *
  134.13 + * This program is distributed in the hope that it will be useful,
  134.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  134.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  134.16 + * GNU General Public License for more details.
  134.17 + *
  134.18 + * You should have received a copy of the GNU General Public License
  134.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  134.20 + */
  134.21 +package info.globalcode.sql.dk.jmx;
  134.22 +
  134.23 +/**
  134.24 + *
  134.25 + * @author Ing. František Kučera (frantovo.cz)
  134.26 + */
  134.27 +public interface ConnectionManagementMBean {
  134.28 +
  134.29 +	public String getDatabaseName();
  134.30 +
  134.31 +	public int getCommandCount();
  134.32 +
  134.33 +	public int getCurrentRecordCount();
  134.34 +
  134.35 +	public int getTotalRecordCount();
  134.36 +
  134.37 +}
   135.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   135.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ManagementUtils.java	Mon Mar 04 20:15:24 2019 +0100
   135.3 @@ -0,0 +1,68 @@
   135.4 +/**
   135.5 + * SQL-DK
   135.6 + * Copyright © 2014 František Kučera (frantovo.cz)
   135.7 + *
   135.8 + * This program is free software: you can redistribute it and/or modify
   135.9 + * it under the terms of the GNU General Public License as published by
  135.10 + * the Free Software Foundation, either version 3 of the License, or
  135.11 + * (at your option) any later version.
  135.12 + *
  135.13 + * This program is distributed in the hope that it will be useful,
  135.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  135.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  135.16 + * GNU General Public License for more details.
  135.17 + *
  135.18 + * You should have received a copy of the GNU General Public License
  135.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  135.20 + */
  135.21 +package info.globalcode.sql.dk.jmx;
  135.22 +
  135.23 +import java.lang.management.ManagementFactory;
  135.24 +import java.util.Hashtable;
  135.25 +import java.util.logging.Level;
  135.26 +import java.util.logging.Logger;
  135.27 +import javax.management.MBeanServer;
  135.28 +import javax.management.ObjectName;
  135.29 +
  135.30 +/**
  135.31 + *
  135.32 + * @author Ing. František Kučera (frantovo.cz)
  135.33 + */
  135.34 +public class ManagementUtils {
  135.35 +
  135.36 +	private static final Logger log = Logger.getLogger(ManagementUtils.class.getName());
  135.37 +	public static final String DEFAULT_CONNECTION_JMX_NAME = "main";
  135.38 +
  135.39 +	/**
  135.40 +	 * @see #registerMBean(java.lang.String, java.lang.String) with default JMX name
  135.41 +	 */
  135.42 +	public static ConnectionManagement registerMBean(String dbName) {
  135.43 +		return registerMBean(dbName, DEFAULT_CONNECTION_JMX_NAME);
  135.44 +	}
  135.45 +
  135.46 +	/**
  135.47 +	 *
  135.48 +	 * @param dbName database name
  135.49 +	 * @param jmxName name of JMX bean
  135.50 +	 * @return registered JMX bean | or null if registration fails (should not)
  135.51 +	 */
  135.52 +	public static ConnectionManagement registerMBean(String dbName, String jmxName) {
  135.53 +		try {
  135.54 +			ConnectionManagement mbean = new ConnectionManagement(dbName);
  135.55 +			MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
  135.56 +			Hashtable<String, String> objectProperties = new Hashtable<>();
  135.57 +			objectProperties.put("type", "Connection");
  135.58 +			objectProperties.put("name", jmxName);
  135.59 +			ObjectName objectName = new ObjectName("info.globalcode.sql.dk", objectProperties);
  135.60 +			mbs.registerMBean(mbean, objectName);
  135.61 +			log.log(Level.FINE, "JMX MBean was registered as: {0}", objectName);
  135.62 +			return mbean;
  135.63 +		} catch (Exception e) {
  135.64 +			log.log(Level.WARNING, "Unable to register JMX MBean", e);
  135.65 +			return null;
  135.66 +		}
  135.67 +	}
  135.68 +
  135.69 +	private ManagementUtils() {
  135.70 +	}
  135.71 +}
   136.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   136.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/ColorfulConsoleFormatter.java	Mon Mar 04 20:15:24 2019 +0100
   136.3 @@ -0,0 +1,97 @@
   136.4 +/**
   136.5 + * SQL-DK
   136.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   136.7 + *
   136.8 + * This program is free software: you can redistribute it and/or modify
   136.9 + * it under the terms of the GNU General Public License as published by
  136.10 + * the Free Software Foundation, either version 3 of the License, or
  136.11 + * (at your option) any later version.
  136.12 + *
  136.13 + * This program is distributed in the hope that it will be useful,
  136.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  136.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  136.16 + * GNU General Public License for more details.
  136.17 + *
  136.18 + * You should have received a copy of the GNU General Public License
  136.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  136.20 + */
  136.21 +package info.globalcode.sql.dk.logging;
  136.22 +
  136.23 +import info.globalcode.sql.dk.ColorfulPrintWriter;
  136.24 +import static info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
  136.25 +import static info.globalcode.sql.dk.ColorfulPrintWriter.TerminalStyle;
  136.26 +import static info.globalcode.sql.dk.Functions.rpad;
  136.27 +import java.io.StringWriter;
  136.28 +import java.util.logging.Formatter;
  136.29 +import java.util.logging.Level;
  136.30 +import java.util.logging.LogRecord;
  136.31 +
  136.32 +/**
  136.33 + * For console/terminal log output. Log messages are printed in brief and colorful form.
  136.34 + *
  136.35 + * @author Ing. František Kučera (frantovo.cz)
  136.36 + */
  136.37 +public class ColorfulConsoleFormatter extends Formatter {
  136.38 +
  136.39 +	private boolean printStacktrace = false;
  136.40 +
  136.41 +	@Override
  136.42 +	public String format(LogRecord r) {
  136.43 +		StringWriter sw = new StringWriter();
  136.44 +		try (ColorfulPrintWriter out = new ColorfulPrintWriter(sw)) {
  136.45 +			printLevel(out, r.getLevel());
  136.46 +			printMessage(out, r);
  136.47 +			printThrowable(out, r);
  136.48 +			out.println();
  136.49 +		}
  136.50 +		return sw.toString();
  136.51 +	}
  136.52 +
  136.53 +	private void printLevel(ColorfulPrintWriter out, Level l) {
  136.54 +		TerminalColor color = TerminalColor.Magenta;
  136.55 +
  136.56 +		if (l == Level.SEVERE) {
  136.57 +			color = TerminalColor.Red;
  136.58 +		} else if (l == Level.WARNING) {
  136.59 +			color = TerminalColor.Yellow;
  136.60 +		}
  136.61 +
  136.62 +		out.print(color, rpad(l.getLocalizedName() + ": ", 10));
  136.63 +	}
  136.64 +
  136.65 +	private void printMessage(ColorfulPrintWriter out, LogRecord r) {
  136.66 +		out.print(formatMessage(r));
  136.67 +	}
  136.68 +
  136.69 +	private void printThrowable(ColorfulPrintWriter out, LogRecord r) {
  136.70 +		Throwable t = r.getThrown();
  136.71 +		if (t != null) {
  136.72 +			out.print(": ");
  136.73 +			out.print(TerminalColor.Red, t.getClass().getSimpleName());
  136.74 +			String message = t.getLocalizedMessage();
  136.75 +			if (message != null) {
  136.76 +				out.print(": ");
  136.77 +				if (printStacktrace) {
  136.78 +					out.print(message);
  136.79 +				} else {
  136.80 +					out.print(message.replaceAll("\\n", " "));
  136.81 +				}
  136.82 +			}
  136.83 +			if (printStacktrace) {
  136.84 +				out.println();
  136.85 +				out.setForegroundColor(TerminalColor.Yellow);
  136.86 +				out.setStyle(TerminalStyle.Dim);
  136.87 +				t.printStackTrace(out);
  136.88 +				out.resetAll();
  136.89 +			}
  136.90 +		}
  136.91 +	}
  136.92 +
  136.93 +	public boolean isPrintStacktrace() {
  136.94 +		return printStacktrace;
  136.95 +	}
  136.96 +
  136.97 +	public void setPrintStacktrace(boolean printStacktrace) {
  136.98 +		this.printStacktrace = printStacktrace;
  136.99 +	}
 136.100 +}
   137.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   137.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/LoggerInitializer.java	Mon Mar 04 20:15:24 2019 +0100
   137.3 @@ -0,0 +1,78 @@
   137.4 +/**
   137.5 + * SQL-DK
   137.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   137.7 + *
   137.8 + * This program is free software: you can redistribute it and/or modify
   137.9 + * it under the terms of the GNU General Public License as published by
  137.10 + * the Free Software Foundation, either version 3 of the License, or
  137.11 + * (at your option) any later version.
  137.12 + *
  137.13 + * This program is distributed in the hope that it will be useful,
  137.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  137.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  137.16 + * GNU General Public License for more details.
  137.17 + *
  137.18 + * You should have received a copy of the GNU General Public License
  137.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  137.20 + */
  137.21 +package info.globalcode.sql.dk.logging;
  137.22 +
  137.23 +import info.globalcode.sql.dk.Constants;
  137.24 +import java.util.logging.ConsoleHandler;
  137.25 +import java.util.logging.Handler;
  137.26 +import java.util.logging.Level;
  137.27 +import java.util.logging.Logger;
  137.28 +
  137.29 +/**
  137.30 + * Configures logging subsystem.
  137.31 + * Usage: java -Djava.util.logging.config.class=info.globalcode.sql.dk.logging.LoggerInitializer …
  137.32 + *
  137.33 + * @author Ing. František Kučera (frantovo.cz)
  137.34 + */
  137.35 +public class LoggerInitializer {
  137.36 +
  137.37 +	private static final Logger log = Logger.getLogger(LoggerInitializer.class.getName());
  137.38 +	public static final String LEVEL_PROPERTY = LoggerInitializer.class.getName() + ".level";
  137.39 +	private static final Level DEFAULT_LEVEL = Level.INFO;
  137.40 +
  137.41 +	public LoggerInitializer() {
  137.42 +		Logger logger = Logger.getLogger(Constants.JAVA_PACKAGE);
  137.43 +		ConsoleHandler handler = new ConsoleHandler();
  137.44 +		ColorfulConsoleFormatter formatter = new ColorfulConsoleFormatter();
  137.45 +
  137.46 +		logger.addHandler(handler);
  137.47 +		handler.setFormatter(formatter);
  137.48 +
  137.49 +		setLevel(logger, handler, formatter);
  137.50 +
  137.51 +
  137.52 +		/**
  137.53 +		 * TODO: optional FileHandler – detailed logs in file in ~/sql-dk/log/…
  137.54 +		 */
  137.55 +	}
  137.56 +
  137.57 +	private void setLevel(Logger logger, Handler handler, ColorfulConsoleFormatter formatter) {
  137.58 +		boolean levelParseError = false;
  137.59 +		Level level;
  137.60 +		String cliLevel = System.getProperty(LEVEL_PROPERTY);
  137.61 +		if (cliLevel == null) {
  137.62 +			level = DEFAULT_LEVEL;
  137.63 +		} else {
  137.64 +			try {
  137.65 +				level = Level.parse(cliLevel);
  137.66 +			} catch (IllegalArgumentException e) {
  137.67 +				level = DEFAULT_LEVEL;
  137.68 +				levelParseError = true;
  137.69 +			}
  137.70 +		}
  137.71 +
  137.72 +		handler.setLevel(level);
  137.73 +		logger.setLevel(level);
  137.74 +
  137.75 +		if (levelParseError) {
  137.76 +			log.log(Level.WARNING, "Invalid logging level „{0}“ specified in „{1}“ → using default level „{2}“", new Object[]{cliLevel, LEVEL_PROPERTY, DEFAULT_LEVEL});
  137.77 +		}
  137.78 +
  137.79 +		formatter.setPrintStacktrace(level.intValue() < Level.INFO.intValue());
  137.80 +	}
  137.81 +}
   138.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   138.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/LoggerProducer.java	Mon Mar 04 20:15:24 2019 +0100
   138.3 @@ -0,0 +1,36 @@
   138.4 +/**
   138.5 + * SQL-DK
   138.6 + * Copyright © 2015 František Kučera (frantovo.cz)
   138.7 + *
   138.8 + * This program is free software: you can redistribute it and/or modify
   138.9 + * it under the terms of the GNU General Public License as published by
  138.10 + * the Free Software Foundation, either version 3 of the License, or
  138.11 + * (at your option) any later version.
  138.12 + *
  138.13 + * This program is distributed in the hope that it will be useful,
  138.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  138.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  138.16 + * GNU General Public License for more details.
  138.17 + *
  138.18 + * You should have received a copy of the GNU General Public License
  138.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  138.20 + */
  138.21 +package info.globalcode.sql.dk.logging;
  138.22 +
  138.23 +import java.util.logging.Logger;
  138.24 +
  138.25 +/**
  138.26 + *
  138.27 + * @author Ing. František Kučera (frantovo.cz)
  138.28 + */
  138.29 +public class LoggerProducer {
  138.30 +
  138.31 +	/**
  138.32 +	 * @return created logger for the caller class
  138.33 +	 */
  138.34 +	public static Logger getLogger() {
  138.35 +		String className = Thread.currentThread().getStackTrace()[2].getClassName();
  138.36 +		return Logger.getLogger(className);
  138.37 +	}
  138.38 +
  138.39 +}
   139.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   139.2 +++ b/java/sql-dk/src/main/resources/info/globalcode/sql/dk/configuration/jaxb.index	Mon Mar 04 20:15:24 2019 +0100
   139.3 @@ -0,0 +1,1 @@
   139.4 +Configuration
   139.5 \ No newline at end of file
   140.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   140.2 +++ b/java/sql-dk/src/main/resources/info/globalcode/sql/dk/example-config.xml	Mon Mar 04 20:15:24 2019 +0100
   140.3 @@ -0,0 +1,1 @@
   140.4 +../../../../../../../../../xml/config.xml
   140.5 \ No newline at end of file
   141.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   141.2 +++ b/java/sql-dk/src/main/resources/info/globalcode/sql/dk/formatter/XhtmlFormatter.css	Mon Mar 04 20:15:24 2019 +0100
   141.3 @@ -0,0 +1,54 @@
   141.4 +body {
   141.5 +	font-family: sans-serif;
   141.6 +	font-size: 16px;
   141.7 +	padding-left: 16px;
   141.8 +	padding-right: 16px;
   141.9 +}
  141.10 +
  141.11 +pre {
  141.12 +	background-color: #ddd;
  141.13 +	padding: 6px;
  141.14 +	border-radius: 4px;
  141.15 +	overflow: auto;
  141.16 +
  141.17 +	-moz-tab-size: 4;
  141.18 +	-o-tab-size: 4;
  141.19 +	tab-size: 4;
  141.20 +}
  141.21 +
  141.22 +table {
  141.23 +	border-collapse:collapse;
  141.24 +	box-shadow: 3px 3px 3px grey;
  141.25 +	margin-top: 10px;
  141.26 +	margin-bottom: 20px;
  141.27 +}
  141.28 +td, th {
  141.29 +	border: 1px solid black;
  141.30 +	padding-top: 4px;
  141.31 +	padding-bottom: 4px;
  141.32 +	padding-left: 6px;
  141.33 +	padding-right: 6px;
  141.34 +	font-weight: normal;
  141.35 +}
  141.36 +td.number {
  141.37 +	text-align: right;
  141.38 +}
  141.39 +td.boolean {
  141.40 +	text-align: right;
  141.41 +}
  141.42 +thead tr {
  141.43 +	background: #ddd;
  141.44 +	color:black;
  141.45 +}
  141.46 +tbody tr:hover {
  141.47 +	background-color: #eee;
  141.48 +	color:black;
  141.49 +}
  141.50 +
  141.51 +table ul {
  141.52 +	margin: 0px;
  141.53 +}
  141.54 +
  141.55 +table li {
  141.56 +	padding-right: 10px;
  141.57 +}
   142.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   142.2 +++ b/java/sql-dk/src/main/resources/info/globalcode/sql/dk/license.txt	Mon Mar 04 20:15:24 2019 +0100
   142.3 @@ -0,0 +1,1 @@
   142.4 +../../../../../../../../../license/gpl.txt
   142.5 \ No newline at end of file
   143.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   143.2 +++ b/java/sql-dk/src/test/java/info/globalcode/sql/dk/CLIParserTest.java	Mon Mar 04 20:15:24 2019 +0100
   143.3 @@ -0,0 +1,195 @@
   143.4 +/**
   143.5 + * SQL-DK
   143.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   143.7 + *
   143.8 + * This program is free software: you can redistribute it and/or modify
   143.9 + * it under the terms of the GNU General Public License as published by
  143.10 + * the Free Software Foundation, either version 3 of the License, or
  143.11 + * (at your option) any later version.
  143.12 + *
  143.13 + * This program is distributed in the hope that it will be useful,
  143.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  143.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  143.16 + * GNU General Public License for more details.
  143.17 + *
  143.18 + * You should have received a copy of the GNU General Public License
  143.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  143.20 + */
  143.21 +package info.globalcode.sql.dk;
  143.22 +
  143.23 +import info.globalcode.sql.dk.CLIParser.Tokens;
  143.24 +import static info.globalcode.sql.dk.CLIParser.TYPE_NAME_SEPARATOR;
  143.25 +import info.globalcode.sql.dk.InfoLister.InfoType;
  143.26 +import java.io.ByteArrayInputStream;
  143.27 +import java.util.Collection;
  143.28 +import static org.testng.Assert.*;
  143.29 +import org.testng.annotations.BeforeMethod;
  143.30 +import org.testng.annotations.Test;
  143.31 +
  143.32 +/**
  143.33 + *
  143.34 + * @author Ing. František Kučera (frantovo.cz)
  143.35 + */
  143.36 +public class CLIParserTest {
  143.37 +
  143.38 +	private static final String DATABASE_NAME_1 = "some database 1";
  143.39 +	private static final String SQL_1 = "SELECT * FROM table1";
  143.40 +	private static final String DATA_1 = "aaa";
  143.41 +	private static final String DATA_2 = "bbb";
  143.42 +	private static final String DATA_3 = "ccc";
  143.43 +	private static final String NAME_1 = "param1";
  143.44 +	private static final String NAME_2 = "param2";
  143.45 +	private static final String NAME_3 = "param3";
  143.46 +	private CLIParser parser;
  143.47 +
  143.48 +	@BeforeMethod
  143.49 +	public void setUpMethod() throws Exception {
  143.50 +		parser = new CLIParser();
  143.51 +	}
  143.52 +
  143.53 +	private CLIOptions parseOptions(String[] args) throws CLIParserException {
  143.54 +		return parser.parseOptions(args, new ByteArrayInputStream("".getBytes()));
  143.55 +	}
  143.56 +
  143.57 +	@Test
  143.58 +	public void testParseOptions_QueryNow_NoParams() throws InvalidOptionsException, CLIParserException {
  143.59 +		String[] args = new String[]{
  143.60 +			Tokens.DB, DATABASE_NAME_1,
  143.61 +			Tokens.SQL, SQL_1};
  143.62 +		CLIOptions options = parseOptions(args);
  143.63 +		options.validate();
  143.64 +
  143.65 +		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
  143.66 +		assertEquals(options.getSql(), SQL_1);
  143.67 +		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
  143.68 +		assertTrue(options.getNamedParameters().isEmpty(), "Named parameters should be empty.");
  143.69 +		assertTrue(options.getNumberedParameters().isEmpty(), "Numbered parameters should be empty.");
  143.70 +	}
  143.71 +
  143.72 +	@Test
  143.73 +	public void testParseOptions_QueryNow_Numbered() throws InvalidOptionsException, CLIParserException {
  143.74 +		String[] args = new String[]{
  143.75 +			Tokens.DB, DATABASE_NAME_1,
  143.76 +			Tokens.SQL, SQL_1,
  143.77 +			Tokens.DATA, DATA_1, DATA_2, DATA_3};
  143.78 +		CLIOptions options = parseOptions(args);
  143.79 +		options.validate();
  143.80 +
  143.81 +		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
  143.82 +		assertEquals(options.getSql(), SQL_1);
  143.83 +		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
  143.84 +		assertEquals(options.getNumberedParameters().size(), 3);
  143.85 +		assertEquals(options.getNumberedParameters().get(0).getValue(), DATA_1);
  143.86 +		assertEquals(options.getNumberedParameters().get(1).getValue(), DATA_2);
  143.87 +		assertEquals(options.getNumberedParameters().get(2).getValue(), DATA_3);
  143.88 +		assertEquals(options.getNumberedParameters().get(0).getType(), Parameter.DEFAULT_TYPE);
  143.89 +		assertEquals(options.getNumberedParameters().get(1).getType(), Parameter.DEFAULT_TYPE);
  143.90 +		assertEquals(options.getNumberedParameters().get(2).getType(), Parameter.DEFAULT_TYPE);
  143.91 +	}
  143.92 +
  143.93 +	@Test
  143.94 +	public void testParseOptions_QueryNow_Numbered_withTypes() throws InvalidOptionsException, CLIParserException {
  143.95 +		String[] args = new String[]{
  143.96 +			Tokens.DB, DATABASE_NAME_1,
  143.97 +			Tokens.SQL, SQL_1,
  143.98 +			Tokens.TYPES, " INTEGER,VARCHAR, BOOLEAN",
  143.99 +			Tokens.DATA, DATA_1, DATA_2, DATA_3};
 143.100 +		CLIOptions options = parseOptions(args);
 143.101 +		options.validate();
 143.102 +
 143.103 +		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
 143.104 +		assertEquals(options.getSql(), SQL_1);
 143.105 +		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
 143.106 +		assertEquals(options.getNumberedParameters().size(), 3);
 143.107 +		assertEquals(options.getNumberedParameters().get(0).getValue(), DATA_1);
 143.108 +		assertEquals(options.getNumberedParameters().get(1).getValue(), DATA_2);
 143.109 +		assertEquals(options.getNumberedParameters().get(2).getValue(), DATA_3);
 143.110 +		assertEquals(options.getNumberedParameters().get(0).getType(), SQLType.INTEGER);
 143.111 +		assertEquals(options.getNumberedParameters().get(1).getType(), SQLType.VARCHAR);
 143.112 +		assertEquals(options.getNumberedParameters().get(2).getType(), SQLType.BOOLEAN);
 143.113 +	}
 143.114 +
 143.115 +	@Test
 143.116 +	public void testParseOptions_QueryNow_Named() throws InvalidOptionsException, CLIParserException {
 143.117 +		String[] args = new String[]{
 143.118 +			Tokens.DB, DATABASE_NAME_1,
 143.119 +			Tokens.SQL, SQL_1,
 143.120 +			Tokens.DATA_NAMED, NAME_1, DATA_1, NAME_2, DATA_2, NAME_3, DATA_3};
 143.121 +		CLIOptions options = parseOptions(args);
 143.122 +		options.validate();
 143.123 +
 143.124 +		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
 143.125 +		assertEquals(options.getSql(), SQL_1);
 143.126 +		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
 143.127 +		assertEquals(options.getNamedParameters().size(), 3);
 143.128 +		assertNamedParameter(options.getNamedParameters(), NAME_1, DATA_1, Parameter.DEFAULT_TYPE);
 143.129 +		assertNamedParameter(options.getNamedParameters(), NAME_2, DATA_2, Parameter.DEFAULT_TYPE);
 143.130 +		assertNamedParameter(options.getNamedParameters(), NAME_3, DATA_3, Parameter.DEFAULT_TYPE);
 143.131 +	}
 143.132 +
 143.133 +	@Test
 143.134 +	public void testParseOptions_QueryNow_Named_withTypes() throws InvalidOptionsException, CLIParserException {
 143.135 +		String[] args = new String[]{
 143.136 +			Tokens.DB, DATABASE_NAME_1,
 143.137 +			Tokens.SQL, SQL_1,
 143.138 +			Tokens.NAME_PREFIX, "$",
 143.139 +			Tokens.TYPES, " " + NAME_1 + TYPE_NAME_SEPARATOR + "INTEGER" + "," + NAME_3 + TYPE_NAME_SEPARATOR + "BOOLEAN",
 143.140 +			Tokens.DATA_NAMED, NAME_1, DATA_1, NAME_2, DATA_2, NAME_3, DATA_3};
 143.141 +		CLIOptions options = parseOptions(args);
 143.142 +		options.validate();
 143.143 +
 143.144 +		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
 143.145 +		assertEquals(options.getSql(), SQL_1);
 143.146 +		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
 143.147 +		assertEquals(options.getNamedParameters().size(), 3);
 143.148 +		assertNamedParameter(options.getNamedParameters(), NAME_1, DATA_1, SQLType.INTEGER);
 143.149 +		assertNamedParameter(options.getNamedParameters(), NAME_2, DATA_2, Parameter.DEFAULT_TYPE);
 143.150 +		assertNamedParameter(options.getNamedParameters(), NAME_3, DATA_3, SQLType.BOOLEAN);
 143.151 +	}
 143.152 +
 143.153 +	private void assertNamedParameter(Collection<NamedParameter> params, String name, Object value, SQLType type) {
 143.154 +		for (NamedParameter p : params) {
 143.155 +			if (name.equals(p.getName())) {
 143.156 +				assertEquals(p.getValue(), value, "value does not match – name: " + name);
 143.157 +				assertEquals(p.getType(), type, "value does not match – name: " + name);
 143.158 +				return;
 143.159 +			}
 143.160 +		}
 143.161 +		fail("Named parameter not found: " + name);
 143.162 +	}
 143.163 +
 143.164 +	@Test
 143.165 +	public void testParseOptions_PrepareBatch() throws InvalidOptionsException, CLIParserException {
 143.166 +		String[] args = new String[]{
 143.167 +			Tokens.BATCH,
 143.168 +			Tokens.SQL, SQL_1};
 143.169 +		CLIOptions options = parseOptions(args);
 143.170 +		options.validate();
 143.171 +
 143.172 +		assertEquals(options.getSql(), SQL_1);
 143.173 +		assertEquals(options.getMode(), CLIOptions.MODE.PREPARE_BATCH);
 143.174 +	}
 143.175 +
 143.176 +	@Test
 143.177 +	public void testParseOptions_ExecuteBatch() throws InvalidOptionsException, CLIParserException {
 143.178 +		String[] args = new String[]{
 143.179 +			Tokens.BATCH,
 143.180 +			Tokens.DB, DATABASE_NAME_1};
 143.181 +		CLIOptions options = parseOptions(args);
 143.182 +		options.validate();
 143.183 +
 143.184 +		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
 143.185 +		assertEquals(options.getMode(), CLIOptions.MODE.EXECUTE_BATCH);
 143.186 +	}
 143.187 +
 143.188 +	@Test
 143.189 +	public void testParseOptions_ShowInfo_Help() throws InvalidOptionsException, CLIParserException {
 143.190 +		String[] args = new String[]{Tokens.INFO_HELP};
 143.191 +		CLIOptions options = parseOptions(args);
 143.192 +		options.validate();
 143.193 +
 143.194 +		assertEquals(options.getMode(), CLIOptions.MODE.JUST_SHOW_INFO);
 143.195 +		assertEquals(options.getShowInfo().size(), 1);
 143.196 +		assertTrue(options.getShowInfo().contains(InfoType.HELP));
 143.197 +	}
 143.198 +}
 143.199 \ No newline at end of file
   144.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   144.2 +++ b/java/sql-dk/src/test/java/info/globalcode/sql/dk/FunctionsTest.java	Mon Mar 04 20:15:24 2019 +0100
   144.3 @@ -0,0 +1,96 @@
   144.4 +/**
   144.5 + * SQL-DK
   144.6 + * Copyright © 2013 František Kučera (frantovo.cz)
   144.7 + *
   144.8 + * This program is free software: you can redistribute it and/or modify
   144.9 + * it under the terms of the GNU General Public License as published by
  144.10 + * the Free Software Foundation, either version 3 of the License, or
  144.11 + * (at your option) any later version.
  144.12 + *
  144.13 + * This program is distributed in the hope that it will be useful,
  144.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  144.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  144.16 + * GNU General Public License for more details.
  144.17 + *
  144.18 + * You should have received a copy of the GNU General Public License
  144.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
  144.20 + */
  144.21 +package info.globalcode.sql.dk;
  144.22 +
  144.23 +import java.util.ArrayList;
  144.24 +import java.util.Collection;
  144.25 +import java.util.List;
  144.26 +import static org.testng.Assert.*;
  144.27 +import org.testng.annotations.*;
  144.28 +
  144.29 +/**
  144.30 + *
  144.31 + * @author Ing. František Kučera (frantovo.cz)
  144.32 + */
  144.33 +public class FunctionsTest {
  144.34 +
  144.35 +	@Test
  144.36 +	public void testNotNull() {
  144.37 +		Collection<String> c = null;
  144.38 +		for (String s : Functions.notNull(c)) {
  144.39 +			fail("Should not iterate through null collection");
  144.40 +		}
  144.41 +
  144.42 +		c = new ArrayList<>();
  144.43 +		c.add("ahoj");
  144.44 +		int count = 0;
  144.45 +		for (String s : Functions.notNull(c)) {
  144.46 +			assertEquals(s, "ahoj", "Wrong item in collection");
  144.47 +			count++;
  144.48 +		}
  144.49 +		assertEquals(count, 1, "Wrong number of iterations");
  144.50 +	}
  144.51 +
  144.52 +	@Test
  144.53 +	public void testLpad() {
  144.54 +		String original = "abc";
  144.55 +		String padded;
  144.56 +
  144.57 +		padded = Functions.lpad(original, 5);
  144.58 +		assertEquals(padded, "  abc");
  144.59 +
  144.60 +		padded = Functions.lpad(original, 2);
  144.61 +		assertEquals(padded, original);
  144.62 +	}
  144.63 +
  144.64 +	@Test
  144.65 +	public void testRpad() {
  144.66 +		String original = "abc";
  144.67 +		String padded;
  144.68 +
  144.69 +		padded = Functions.rpad(original, 5);
  144.70 +		assertEquals(padded, "abc  ");
  144.71 +
  144.72 +		padded = Functions.rpad(original, 2);
  144.73 +		assertEquals(padded, original);
  144.74 +	}
  144.75 +
  144.76 +	@Test
  144.77 +	public void testRepeat() {
  144.78 +		assertEquals(Functions.repeat('f', 0), "");
  144.79 +		assertEquals(Functions.repeat('f', 3), "fff");
  144.80 +	}
  144.81 +
  144.82 +	@Test
  144.83 +	public void testGetClassHierarchy() {
  144.84 +		List<Class<? extends HierarchyMockClass2>> hierarchy = Functions.getClassHierarchy(HierarchyMockClass0.class, HierarchyMockClass2.class);
  144.85 +		assertEquals(hierarchy.size(), 3, "invalid number of classes in the hierarchy");
  144.86 +		assertEquals(hierarchy.get(0), HierarchyMockClass0.class);
  144.87 +		assertEquals(hierarchy.get(1), HierarchyMockClass1.class);
  144.88 +		assertEquals(hierarchy.get(2), HierarchyMockClass2.class);
  144.89 +	}
  144.90 +
  144.91 +	private static class HierarchyMockClass0 extends HierarchyMockClass1 {
  144.92 +	}
  144.93 +
  144.94 +	private static class HierarchyMockClass1 extends HierarchyMockClass2 {
  144.95 +	}
  144.96 +
  144.97 +	private static class HierarchyMockClass2 {
  144.98 +	}
  144.99 +}
   145.1 --- a/java/sql-dk/test/info/globalcode/sql/dk/CLIParserTest.java	Mon Mar 04 17:06:42 2019 +0100
   145.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
   145.3 @@ -1,195 +0,0 @@
   145.4 -/**
   145.5 - * SQL-DK
   145.6 - * Copyright © 2013 František Kučera (frantovo.cz)
   145.7 - *
   145.8 - * This program is free software: you can redistribute it and/or modify
   145.9 - * it under the terms of the GNU General Public License as published by
  145.10 - * the Free Software Foundation, either version 3 of the License, or
  145.11 - * (at your option) any later version.
  145.12 - *
  145.13 - * This program is distributed in the hope that it will be useful,
  145.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
  145.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  145.16 - * GNU General Public License for more details.
  145.17 - *
  145.18 - * You should have received a copy of the GNU General Public License
  145.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
  145.20 - */
  145.21 -package info.globalcode.sql.dk;
  145.22 -
  145.23 -import info.globalcode.sql.dk.CLIParser.Tokens;
  145.24 -import static info.globalcode.sql.dk.CLIParser.TYPE_NAME_SEPARATOR;
  145.25 -import info.globalcode.sql.dk.InfoLister.InfoType;
  145.26 -import java.io.ByteArrayInputStream;
  145.27 -import java.util.Collection;
  145.28 -import static org.testng.Assert.*;
  145.29 -import org.testng.annotations.BeforeMethod;
  145.30 -import org.testng.annotations.Test;
  145.31 -
  145.32 -/**
  145.33 - *
  145.34 - * @author Ing. František Kučera (frantovo.cz)
  145.35 - */
  145.36 -public class CLIParserTest {
  145.37 -
  145.38 -	private static final String DATABASE_NAME_1 = "some database 1";
  145.39 -	private static final String SQL_1 = "SELECT * FROM table1";
  145.40 -	private static final String DATA_1 = "aaa";
  145.41 -	private static final String DATA_2 = "bbb";
  145.42 -	private static final String DATA_3 = "ccc";
  145.43 -	private static final String NAME_1 = "param1";
  145.44 -	private static final String NAME_2 = "param2";
  145.45 -	private static final String NAME_3 = "param3";
  145.46 -	private CLIParser parser;
  145.47 -
  145.48 -	@BeforeMethod
  145.49 -	public void setUpMethod() throws Exception {
  145.50 -		parser = new CLIParser();
  145.51 -	}
  145.52 -
  145.53 -	private CLIOptions parseOptions(String[] args) throws CLIParserException {
  145.54 -		return parser.parseOptions(args, new ByteArrayInputStream("".getBytes()));
  145.55 -	}
  145.56 -
  145.57 -	@Test
  145.58 -	public void testParseOptions_QueryNow_NoParams() throws InvalidOptionsException, CLIParserException {
  145.59 -		String[] args = new String[]{
  145.60 -			Tokens.DB, DATABASE_NAME_1,
  145.61 -			Tokens.SQL, SQL_1};
  145.62 -		CLIOptions options = parseOptions(args);
  145.63 -		options.validate();
  145.64 -
  145.65 -		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
  145.66 -		assertEquals(options.getSql(), SQL_1);
  145.67 -		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
  145.68 -		assertTrue(options.getNamedParameters().isEmpty(), "Named parameters should be empty.");
  145.69 -		assertTrue(options.getNumberedParameters().isEmpty(), "Numbered parameters should be empty.");
  145.70 -	}
  145.71 -
  145.72 -	@Test
  145.73 -	public void testParseOptions_QueryNow_Numbered() throws InvalidOptionsException, CLIParserException {
  145.74 -		String[] args = new String[]{
  145.75 -			Tokens.DB, DATABASE_NAME_1,
  145.76 -			Tokens.SQL, SQL_1,
  145.77 -			Tokens.DATA, DATA_1, DATA_2, DATA_3};
  145.78 -		CLIOptions options = parseOptions(args);
  145.79 -		options.validate();
  145.80 -
  145.81 -		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
  145.82 -		assertEquals(options.getSql(), SQL_1);
  145.83 -		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
  145.84 -		assertEquals(options.getNumberedParameters().size(), 3);
  145.85 -		assertEquals(options.getNumberedParameters().get(0).getValue(), DATA_1);
  145.86 -		assertEquals(options.getNumberedParameters().get(1).getValue(), DATA_2);
  145.87 -		assertEquals(options.getNumberedParameters().get(2).getValue(), DATA_3);
  145.88 -		assertEquals(options.getNumberedParameters().get(0).getType(), Parameter.DEFAULT_TYPE);
  145.89 -		assertEquals(options.getNumberedParameters().get(1).getType(), Parameter.DEFAULT_TYPE);
  145.90 -		assertEquals(options.getNumberedParameters().get(2).getType(), Parameter.DEFAULT_TYPE);
  145.91 -	}
  145.92 -
  145.93 -	@Test
  145.94 -	public void testParseOptions_QueryNow_Numbered_withTypes() throws InvalidOptionsException, CLIParserException {
  145.95 -		String[] args = new String[]{
  145.96 -			Tokens.DB, DATABASE_NAME_1,
  145.97 -			Tokens.SQL, SQL_1,
  145.98 -			Tokens.TYPES, " INTEGER,VARCHAR, BOOLEAN",
  145.99 -			Tokens.DATA, DATA_1, DATA_2, DATA_3};
 145.100 -		CLIOptions options = parseOptions(args);
 145.101 -		options.validate();
 145.102 -
 145.103 -		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
 145.104 -		assertEquals(options.getSql(), SQL_1);
 145.105 -		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
 145.106 -		assertEquals(options.getNumberedParameters().size(), 3);
 145.107 -		assertEquals(options.getNumberedParameters().get(0).getValue(), DATA_1);
 145.108 -		assertEquals(options.getNumberedParameters().get(1).getValue(), DATA_2);
 145.109 -		assertEquals(options.getNumberedParameters().get(2).getValue(), DATA_3);
 145.110 -		assertEquals(options.getNumberedParameters().get(0).getType(), SQLType.INTEGER);
 145.111 -		assertEquals(options.getNumberedParameters().get(1).getType(), SQLType.VARCHAR);
 145.112 -		assertEquals(options.getNumberedParameters().get(2).getType(), SQLType.BOOLEAN);
 145.113 -	}
 145.114 -
 145.115 -	@Test
 145.116 -	public void testParseOptions_QueryNow_Named() throws InvalidOptionsException, CLIParserException {
 145.117 -		String[] args = new String[]{
 145.118 -			Tokens.DB, DATABASE_NAME_1,
 145.119 -			Tokens.SQL, SQL_1,
 145.120 -			Tokens.DATA_NAMED, NAME_1, DATA_1, NAME_2, DATA_2, NAME_3, DATA_3};
 145.121 -		CLIOptions options = parseOptions(args);
 145.122 -		options.validate();
 145.123 -
 145.124 -		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
 145.125 -		assertEquals(options.getSql(), SQL_1);
 145.126 -		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
 145.127 -		assertEquals(options.getNamedParameters().size(), 3);
 145.128 -		assertNamedParameter(options.getNamedParameters(), NAME_1, DATA_1, Parameter.DEFAULT_TYPE);
 145.129 -		assertNamedParameter(options.getNamedParameters(), NAME_2, DATA_2, Parameter.DEFAULT_TYPE);
 145.130 -		assertNamedParameter(options.getNamedParameters(), NAME_3, DATA_3, Parameter.DEFAULT_TYPE);
 145.131 -	}
 145.132 -
 145.133 -	@Test
 145.134 -	public void testParseOptions_QueryNow_Named_withTypes() throws InvalidOptionsException, CLIParserException {
 145.135 -		String[] args = new String[]{
 145.136 -			Tokens.DB, DATABASE_NAME_1,
 145.137 -			Tokens.SQL, SQL_1,
 145.138 -			Tokens.NAME_PREFIX, "$",
 145.139 -			Tokens.TYPES, " " + NAME_1 + TYPE_NAME_SEPARATOR + "INTEGER" + "," + NAME_3 + TYPE_NAME_SEPARATOR + "BOOLEAN",
 145.140 -			Tokens.DATA_NAMED, NAME_1, DATA_1, NAME_2, DATA_2, NAME_3, DATA_3};
 145.141 -		CLIOptions options = parseOptions(args);
 145.142 -		options.validate();
 145.143 -
 145.144 -		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
 145.145 -		assertEquals(options.getSql(), SQL_1);
 145.146 -		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
 145.147 -		assertEquals(options.getNamedParameters().size(), 3);
 145.148 -		assertNamedParameter(options.getNamedParameters(), NAME_1, DATA_1, SQLType.INTEGER);
 145.149 -		assertNamedParameter(options.getNamedParameters(), NAME_2, DATA_2, Parameter.DEFAULT_TYPE);
 145.150 -		assertNamedParameter(options.getNamedParameters(), NAME_3, DATA_3, SQLType.BOOLEAN);
 145.151 -	}
 145.152 -
 145.153 -	private void assertNamedParameter(Collection<NamedParameter> params, String name, Object value, SQLType type) {
 145.154 -		for (NamedParameter p : params) {
 145.155 -			if (name.equals(p.getName())) {
 145.156 -				assertEquals(p.getValue(), value, "value does not match – name: " + name);
 145.157 -				assertEquals(p.getType(), type, "value does not match – name: " + name);
 145.158 -				return;
 145.159 -			}
 145.160 -		}
 145.161 -		fail("Named parameter not found: " + name);
 145.162 -	}
 145.163 -
 145.164 -	@Test
 145.165 -	public void testParseOptions_PrepareBatch() throws InvalidOptionsException, CLIParserException {
 145.166 -		String[] args = new String[]{
 145.167 -			Tokens.BATCH,
 145.168 -			Tokens.SQL, SQL_1};
 145.169 -		CLIOptions options = parseOptions(args);
 145.170 -		options.validate();
 145.171 -
 145.172 -		assertEquals(options.getSql(), SQL_1);
 145.173 -		assertEquals(options.getMode(), CLIOptions.MODE.PREPARE_BATCH);
 145.174 -	}
 145.175 -
 145.176 -	@Test
 145.177 -	public void testParseOptions_ExecuteBatch() throws InvalidOptionsException, CLIParserException {
 145.178 -		String[] args = new String[]{
 145.179 -			Tokens.BATCH,
 145.180 -			Tokens.DB, DATABASE_NAME_1};
 145.181 -		CLIOptions options = parseOptions(args);
 145.182 -		options.validate();
 145.183 -
 145.184 -		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
 145.185 -		assertEquals(options.getMode(), CLIOptions.MODE.EXECUTE_BATCH);
 145.186 -	}
 145.187 -
 145.188 -	@Test
 145.189 -	public void testParseOptions_ShowInfo_Help() throws InvalidOptionsException, CLIParserException {
 145.190 -		String[] args = new String[]{Tokens.INFO_HELP};
 145.191 -		CLIOptions options = parseOptions(args);
 145.192 -		options.validate();
 145.193 -
 145.194 -		assertEquals(options.getMode(), CLIOptions.MODE.JUST_SHOW_INFO);
 145.195 -		assertEquals(options.getShowInfo().size(), 1);
 145.196 -		assertTrue(options.getShowInfo().contains(InfoType.HELP));
 145.197 -	}
 145.198 -}
 145.199 \ No newline at end of file
   146.1 --- a/java/sql-dk/test/info/globalcode/sql/dk/FunctionsTest.java	Mon Mar 04 17:06:42 2019 +0100
   146.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
   146.3 @@ -1,96 +0,0 @@
   146.4 -/**
   146.5 - * SQL-DK
   146.6 - * Copyright © 2013 František Kučera (frantovo.cz)
   146.7 - *
   146.8 - * This program is free software: you can redistribute it and/or modify
   146.9 - * it under the terms of the GNU General Public License as published by
  146.10 - * the Free Software Foundation, either version 3 of the License, or
  146.11 - * (at your option) any later version.
  146.12 - *
  146.13 - * This program is distributed in the hope that it will be useful,
  146.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
  146.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  146.16 - * GNU General Public License for more details.
  146.17 - *
  146.18 - * You should have received a copy of the GNU General Public License
  146.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
  146.20 - */
  146.21 -package info.globalcode.sql.dk;
  146.22 -
  146.23 -import java.util.ArrayList;
  146.24 -import java.util.Collection;
  146.25 -import java.util.List;
  146.26 -import static org.testng.Assert.*;
  146.27 -import org.testng.annotations.*;
  146.28 -
  146.29 -/**
  146.30 - *
  146.31 - * @author Ing. František Kučera (frantovo.cz)
  146.32 - */
  146.33 -public class FunctionsTest {
  146.34 -
  146.35 -	@Test
  146.36 -	public void testNotNull() {
  146.37 -		Collection<String> c = null;
  146.38 -		for (String s : Functions.notNull(c)) {
  146.39 -			fail("Should not iterate through null collection");
  146.40 -		}
  146.41 -
  146.42 -		c = new ArrayList<>();
  146.43 -		c.add("ahoj");
  146.44 -		int count = 0;
  146.45 -		for (String s : Functions.notNull(c)) {
  146.46 -			assertEquals(s, "ahoj", "Wrong item in collection");
  146.47 -			count++;
  146.48 -		}
  146.49 -		assertEquals(count, 1, "Wrong number of iterations");
  146.50 -	}
  146.51 -
  146.52 -	@Test
  146.53 -	public void testLpad() {
  146.54 -		String original = "abc";
  146.55 -		String padded;
  146.56 -
  146.57 -		padded = Functions.lpad(original, 5);
  146.58 -		assertEquals(padded, "  abc");
  146.59 -
  146.60 -		padded = Functions.lpad(original, 2);
  146.61 -		assertEquals(padded, original);
  146.62 -	}
  146.63 -
  146.64 -	@Test
  146.65 -	public void testRpad() {
  146.66 -		String original = "abc";
  146.67 -		String padded;
  146.68 -
  146.69 -		padded = Functions.rpad(original, 5);
  146.70 -		assertEquals(padded, "abc  ");
  146.71 -
  146.72 -		padded = Functions.rpad(original, 2);
  146.73 -		assertEquals(padded, original);
  146.74 -	}
  146.75 -
  146.76 -	@Test
  146.77 -	public void testRepeat() {
  146.78 -		assertEquals(Functions.repeat('f', 0), "");
  146.79 -		assertEquals(Functions.repeat('f', 3), "fff");
  146.80 -	}
  146.81 -
  146.82 -	@Test
  146.83 -	public void testGetClassHierarchy() {
  146.84 -		List<Class<? extends HierarchyMockClass2>> hierarchy = Functions.getClassHierarchy(HierarchyMockClass0.class, HierarchyMockClass2.class);
  146.85 -		assertEquals(hierarchy.size(), 3, "invalid number of classes in the hierarchy");
  146.86 -		assertEquals(hierarchy.get(0), HierarchyMockClass0.class);
  146.87 -		assertEquals(hierarchy.get(1), HierarchyMockClass1.class);
  146.88 -		assertEquals(hierarchy.get(2), HierarchyMockClass2.class);
  146.89 -	}
  146.90 -
  146.91 -	private static class HierarchyMockClass0 extends HierarchyMockClass1 {
  146.92 -	}
  146.93 -
  146.94 -	private static class HierarchyMockClass1 extends HierarchyMockClass2 {
  146.95 -	}
  146.96 -
  146.97 -	private static class HierarchyMockClass2 {
  146.98 -	}
  146.99 -}