136 Commits

Author SHA1 Message Date
9c1b110bfd Merge pull request #28 from frosch95/total_number_of_lines
total number of lines
2022-10-15 13:47:55 +02:00
398219a897 total number of lines 2022-10-15 13:41:31 +02:00
Andreas Billmann
a79dde5421 Merge pull request #24 from frosch95/switch_to_fastcsv
switch to fastcsv
2021-12-10 00:54:12 +01:00
fb234fa50e fix tests 2021-12-10 00:51:38 +01:00
43856dffab switch to fastcsv as supercsv is not maintaned anymore 2021-12-10 00:30:59 +01:00
8cecca2172 update the thanks section 2021-11-23 06:19:33 +01:00
Andreas Billmann
35f395d44a Update README.md 2021-11-22 22:22:55 +01:00
Andreas Billmann
9961373d76 Update version 2021-11-22 22:11:04 +01:00
Andreas Billmann
fd6938fa80 Merge pull request #23 from apixandru/loader_exceptions
#22 don't ignore loading exceptions
2021-11-22 22:08:58 +01:00
Alexandru
78f3f5909e #22 don't ignore loading exceptions 2021-10-29 05:05:13 -07:00
Andreas Billmann
d572dcdeed JDK17 2021-10-02 09:26:59 +02:00
af6ce56f71 changed to jdk 17 LTS 2021-10-02 07:28:16 +02:00
2f692352b7 remove travis badge 2020-11-02 22:02:45 +01:00
5b59c76200 try to build on ubuntu 2020-11-02 22:00:20 +01:00
7e40daa1e5 remove branch dependencies 2020-11-02 21:54:59 +01:00
2da843e1e2 added gthub action badge 2020-11-02 21:45:28 +01:00
d1ffd09941 use github action 2020-11-02 21:42:57 +01:00
Andreas Billmann
4c29d29bf5 Update README.md 2020-10-06 02:19:28 +02:00
Andreas Billmann
c4f91d94f1 Merge pull request #18 from frosch95/update-dependencies
update the dependencies
2020-10-02 23:43:52 +02:00
8c43c5a1de update the dependencies 2020-10-02 23:40:31 +02:00
187002997c version 1.0.0 2019-10-24 21:55:05 +02:00
aa7e38c613 closes #10
logo for the app
2019-10-24 21:51:42 +02:00
13a41468d4 removed unused property 2019-10-23 06:03:55 +02:00
f41979960b migrated all test to junit 5 2019-10-23 05:57:06 +02:00
a7fce854ca file storage test 2019-10-23 05:08:49 +02:00
4f40418aea Merge remote-tracking branch 'origin/master' 2019-10-23 02:05:44 +02:00
d7c821d1fe removed some code smells 2019-10-23 02:04:57 +02:00
Andreas Billmann
49f8003f97 Update README.md 2019-10-23 01:23:20 +02:00
Andreas Billmann
5f8383c6a1 Update README.md 2019-10-23 01:09:46 +02:00
03dcf20229 permission access for travis 2019-10-23 00:55:21 +02:00
543125812a enable travis-ci 2019-10-23 00:50:43 +02:00
c52f6c0bc8 upgraded the test libraries 2019-10-23 00:36:08 +02:00
422bd73bad Merge branch 'master' of https://github.com/frosch95/SmartCSV.fx 2019-10-23 00:20:49 +02:00
e6672834fb allow redordering of columns 2019-10-23 00:10:19 +02:00
8e0ea66552 build for java 11 and updated license text 2019-10-22 23:19:51 +02:00
Andreas Billmann
bf476308a2 Update README.md 2019-10-22 23:11:48 +02:00
Andreas Billmann
cf9203549b Merge pull request #17 from aoetk/fileencoding
Add the setting changes encoding for CSV file to the preference dialog
2019-10-22 22:36:26 +02:00
AOE Takashi
02b102cfb2 Add the setting changes encoding for CSV file to the preference dialog
(cherry picked from commit 11210b6d8b4584621ef02e7356854ffa9f7f2647)
2019-10-19 23:16:09 +09:00
8e0e1c41c3 update the README.md with the 0.9.2 download link 2019-10-03 21:18:16 +02:00
3ce9ae8edd fixed the main javafx application problem 2019-10-03 21:08:29 +02:00
7c12b7506f upgrade to javafx 13 2019-10-03 18:03:01 +02:00
6fc62006fa upgrade to javafx 12 2019-07-08 20:11:50 +02:00
fdc4f99fd5 updated dependencies 2018-07-08 14:01:13 +02:00
e29d60b9b8 upgraded gradle wrapper 2018-07-08 13:42:43 +02:00
0a4e86c160 fixed the markdown 2018-04-22 09:27:20 +02:00
c07dde8e19 updated the version number and link to the distribution 2018-04-22 09:25:50 +02:00
9b0e3707b7 updated libs and removed fxlauncher as it is not used by customers 2018-04-22 09:18:01 +02:00
a1f6c24bae updatet libraries to the newest versions 2017-03-22 12:00:35 +01:00
c1a671504e added fxlauncher to about dialog 2016-10-06 21:27:09 +02:00
7c3f140491 Merge remote-tracking branch 'remotes/origin/fxlauncher' 2016-10-06 21:07:42 +02:00
16373a033a change updating text in fxlauncher 2016-10-06 21:07:01 +02:00
e9e71b5474 gradle task to rename the fxlauncher.jar and launcher without own ui 2016-10-06 15:33:00 +02:00
48c657b648 fxlauncher without customization 2016-10-06 14:01:52 +02:00
8bd90b47e1 renamed license file 2016-10-06 07:09:50 +02:00
44f36759a6 thx for contribution: Hasan Kara 2016-10-05 15:43:36 +02:00
Andreas Billmann
326e4d954b Merge pull request #14 from haisi/master
Fix typo
2016-10-05 15:35:56 +02:00
Hasan Kara
96446e4e75 Fix typo 2016-10-05 14:51:05 +02:00
Andreas Billmann
5eaf021632 fixed broken tests 2016-09-23 11:14:45 +02:00
Andreas Billmann
ef376d6c61 set constraints when created 2016-09-22 22:09:56 +02:00
Andreas Billmann
2676b523d3 clear groovy field if there is some text in it 2016-09-22 21:49:30 +02:00
Andreas Billmann
7e34ff8c59 fixed NPE when string has no format 2016-09-22 21:35:40 +02:00
Andreas Billmann
a37a84bd09 Merge remote-tracking branch 'remotes/origin/json_table_schema' 2016-09-22 20:48:57 +02:00
Andreas Billmann
5100c668ab version 0.8 based on JSON Table Schema 2016-09-22 20:46:30 +02:00
Andreas Billmann
90bda028ec finding enum based on external value should use external value as compare value 2016-09-18 15:26:59 +02:00
Andreas Billmann
5e31f834ac fixed the imports of the new validation classes 2016-09-18 15:18:48 +02:00
Andreas Billmann
d689423c61 added 2016 to the license text 2016-09-18 14:58:38 +02:00
Andreas Billmann
9e8d71ca1d implemented string format validations 2016-09-18 14:58:37 +02:00
Andreas Billmann
70855e614a the editor can handle the support subset of JSON Table Schema 2016-09-18 14:58:37 +02:00
4354afdf1d handle defaults 2016-09-18 14:58:36 +02:00
4002219dfe reorganize validation package 2016-09-18 14:58:35 +02:00
7723906935 write values back to config 2016-09-18 14:58:34 +02:00
5694273c51 read constraints into an object instead of some map structure and change form init of validation editor 2016-09-18 14:58:33 +02:00
04a54da798 editor should support new config ... first steps 2016-09-18 14:58:32 +02:00
6f635f5e75 ignore log file 2016-09-18 14:58:31 +02:00
742f129ea9 change editor to the new config
The editor should handle the new configuration
2016-09-18 14:58:31 +02:00
0c391e292e first quick implementation of to support json table schema 2016-09-18 14:58:30 +02:00
cd0ca0ed11 set preferences on csv file reader and writer after load and changing through dialog 2016-09-14 19:23:36 +02:00
e799f0ac45 min and max value spinner are dependent 2016-08-11 13:25:15 +02:00
3664a4e8a7 the most senseless combination of rules are not possible any more 2016-08-11 13:11:23 +02:00
e87eec2162 no need for 2 checkboxes when it is a boolean rule 2016-08-11 12:55:09 +02:00
3adfd566bc error export implemented and message on error side bar includes column name 2016-08-10 23:45:47 +02:00
e6742a4e30 error export implemented and message on error side bar includes column name 2016-08-10 23:41:47 +02:00
7472c66823 fixed uniqueness validation and renamed the abstract class when empty values are valid 2016-08-09 21:17:29 +02:00
c7873ef0ba unique validation has to work on the model to have the correct snapshot of the data 2016-08-09 20:59:29 +02:00
b79a20201e removed the special treatment of NotEmptyValidation 2016-08-09 20:59:29 +02:00
Andreas Billmann
3ebf78805c Update README.md 2016-08-08 17:23:02 +02:00
dcdea4236f version 0.6 2016-08-08 17:14:51 +02:00
11358c5bec Merge remote-tracking branch 'remotes/origin/validation_algorithm_revisited' 2016-08-08 17:12:25 +02:00
9c21357059 the error color should be more red 2016-08-08 17:11:11 +02:00
909617d9b7 Merge remote-tracking branch 'remotes/origin/validation_algorithm_revisited' 2016-08-07 23:41:26 +02:00
ab952aa98f optimized imports based on intellij IDEA 2016-08-07 23:13:47 +02:00
a7bd8d5a3e revisted the model to speed up the validation and to have a better control how much revalitaions have to be done when something changes 2016-08-07 23:13:02 +02:00
bc59f08bbb first version of new validation algorithm, that is a lot faster 2016-08-07 16:57:55 +02:00
4c4d25f3b5 little performance optimization (less method calls) 2016-08-06 21:41:14 +02:00
6af20575e8 uniqueness validation is now also supported by the validation rules editor 2016-08-05 23:45:23 +02:00
b6731f7641 - added unique validation
- show line number
- goto line action
2016-08-05 23:09:47 +02:00
d2f81d7d3e added introduction talk 2016-08-03 10:58:01 +02:00
59f38b9abb version 0.4 2016-07-30 17:31:40 +02:00
82c9c336f8 clear script cache before revalidation, otherwise a script change in the edit dialog does not work 2016-07-30 17:12:34 +02:00
f173089b14 added exception handling 2016-07-24 11:21:45 +02:00
4801203378 support of RichTextFX to have syntax highlighting in the groovy edit field of validation editor 2016-07-23 16:56:48 +02:00
68a68a51a9 updated look of validation rule editor 2016-07-23 10:01:27 +02:00
e5b19d4ad9 upgrade gradle to 2.14.1 2016-07-23 09:36:40 +02:00
7f8977f399 updated video link 2016-07-23 00:47:59 +02:00
0214744184 NPE when new validation is created 2016-07-23 00:40:41 +02:00
9fe9246c55 version 0.4 2016-07-23 00:04:48 +02:00
9570730478 Merge remote-tracking branch 'remotes/origin/create_validation_config_based_on_csv' 2016-07-23 00:04:01 +02:00
63b05c2307 version 0.4 2016-07-23 00:03:37 +02:00
328c1b0b88 use of better icons, create a new validation file for csv 2016-07-22 23:51:08 +02:00
3aa9303b3e handle validation in own thread 2016-07-22 21:41:39 +02:00
c2c29433b7 added log4j2 to the 3rd party section 2016-07-22 21:17:58 +02:00
83638d918f found rounding bug in ErrorSideBar when the number of rows is greater than height of application 2016-07-22 21:11:59 +02:00
64d5000b62 reorganized storage of models 2016-07-22 20:45:26 +02:00
6e7c3226ab removed unused import 2016-07-20 17:09:32 +02:00
8e7731f554 updated the about dialog and add thanks to AOE Takashi 2016-07-09 22:19:23 +02:00
7dc15760d5 update of the dependencies 2016-07-09 21:55:40 +02:00
d48c6ddeb8 Merge pull request #3 from aoetk/macmenubar
Supported the Mac OS menubar.
2016-07-09 21:39:18 +02:00
AOE Takashi
dce228123a Supported the Mac OS menubar. 2016-07-09 22:46:06 +09:00
Andreas Billmann
3b220c3fd5 set the some default preferences if file not found 2016-02-16 17:18:37 +01:00
Andreas Billmann
028af43510 set "\n" as default end of line 2016-02-16 17:09:28 +01:00
Andreas Billmann
a4a9e9914b fixed growing and prefered size of the config file name 2016-02-12 19:33:25 +01:00
Andreas Billmann
ecdb4837d4 deleted unused import 2016-02-12 18:28:05 +01:00
Andreas Billmann
e29bbf4008 added missing properties 2016-02-12 16:03:23 +01:00
Andreas Billmann
41a8fb41b1 added a status color block and changed the error color 2016-02-12 13:39:41 +01:00
Andreas Billmann
ab6d99815e Merge branch 'editable_column_rules' 2016-02-05 09:44:51 +01:00
Andreas Billmann
744d86fea4 null check to prevent NPE 2016-02-05 09:43:24 +01:00
Andreas Billmann
fc26dcc9aa validation rules are now editable through context menu 2016-02-05 08:01:13 +01:00
Andreas Billmann
10c2592510 switched from config to Gson for better json support as the validation config will be editable in the application directly 2016-02-02 03:20:14 +01:00
Andreas Billmann
d9405eb536 formal revision 2016-01-27 23:17:31 +01:00
Andreas Billmann
4c9f468e08 use regions instead of drawing into canvas. thx to Dirk Lemmermann 2016-01-27 23:11:39 +01:00
Andreas Billmann
c996ce660d added controlsfx project to about dialog 2016-01-27 10:05:47 +01:00
Andreas Billmann
ef1ad63b3f improved side bar popup 2016-01-27 09:59:12 +01:00
Andreas Billmann
26c06a0908 removed the error list and added a clickable sidaebar wih error markers to have more editing space 2016-01-27 04:39:53 +01:00
Andreas Billmann
b4cc3f9922 bugfix: NPE when adding new row as the setValue has to be called at the end, after all other properties are set 2016-01-14 10:43:11 +01:00
Andreas Billmann
16b848adc9 add and remove rows 2016-01-13 23:07:29 +01:00
Andreas Billmann
4e94cf2091 added toolbar 2016-01-13 11:50:54 +01:00
97 changed files with 4947 additions and 1175 deletions

21
.github/workflows/build-and-test.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Build and Test with Gradle
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 17
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Test with Gradle
run: ./gradlew test

1
.gitignore vendored
View File

@@ -74,3 +74,4 @@ crashlytics.properties
crashlytics-build.properties crashlytics-build.properties
smartcsv.log

View File

@@ -1,7 +1,7 @@
The MIT License (MIT) The MIT License (MIT)
------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------
Copyright (c) 2015 Andreas Billmann <andreas.billmann@javafx.ninja> Copyright (c) 2015-2021 Andreas Billmann <andreas.billmann@javafx.ninja>

View File

@@ -1,19 +1,30 @@
# SmartCSV.fx # SmartCSV.fx
![Build and Test](https://github.com/frosch95/SmartCSV.fx/workflows/Build%20and%20Test%20with%20Gradle/badge.svg) ![GitHub All Releases](https://img.shields.io/github/downloads/frosch95/SmartCSV.fx/total) ![GitHub](https://img.shields.io/github/license/frosch95/SmartCSV.fx) ![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/frosch95/SmartCSV.fx?label=version) ![GitHub Release Date](https://img.shields.io/github/release-date/frosch95/SmartCSV.fx)
## Description ## Description
A simple JavaFX application to load, save and edit a CSV file and provide a JSON configuration for columns to check the values in the columns. A simple JavaFX application to load, save and edit a CSV file and provide a [JSON Table Schema](http://specs.frictionlessdata.io/json-table-schema/) for columns to check the values in the columns.
## Motivation ## Motivation
At work I have the need to fix wrong CSV files from customers. It is hard to find the errors and fix them in a texteditor, At work I have the need to fix wrong CSV files from customers. It is hard to find the errors and fix them in a texteditor,
even in a "normal" CSV editor. So I decided to write this simple JavaFX application. even in a "normal" CSV editor. So I decided to write this simple JavaFX application.
## Video ## Video
[![short video about the application](http://img.youtube.com/vi/SH0UAtPG6Eg/0.jpg)](https://youtu.be/SH0UAtPG6Eg) [![short video about the application](http://img.youtube.com/vi/eUh_WLx1OwI/0.jpg)](https://youtu.be/eUh_WLx1OwI)
## Links
[GitHub Page](http://frosch95.github.io/SmartCSV.fx/)
[Wiki & Documentation](https://github.com/frosch95/SmartCSV.fx/wiki)
binary distribution of the [latest release (1.1.0)](https://github.com/frosch95/SmartCSV.fx/releases/download/1.1.0/SmartCSV.fx-1.0.1.zip)
## Talks
[Introduction](http://javafx.ninja/talks/introduction/)
## License ## License
### The MIT License (MIT) ### The MIT License (MIT)
Copyright (c) 2015 Andreas Billmann <andreas.billmann@javafx.ninja> Copyright (c) 2015-2020 Andreas Billmann <andreas.billmann@javafx.ninja>

View File

@@ -1,27 +1,52 @@
group 'ninja.javafx' plugins {
version '0.3-SNAPSHOT' id 'idea'
id 'java'
apply plugin: 'java' id 'groovy'
apply plugin: 'groovy' id 'application'
apply plugin:'application' id 'org.openjfx.javafxplugin' version '0.0.10'
apply plugin: 'idea' }
sourceCompatibility = 1.8
mainClassName = 'ninja.javafx.smartcsv.fx.SmartCSV'
repositories { repositories {
mavenCentral() mavenCentral()
jcenter() jcenter()
} }
dependencies { apply plugin: 'java'
testCompile group: 'junit', name: 'junit', version: '4.12' sourceCompatibility = 17
testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '1.3' targetCompatibility = 17
testCompile group: 'org.mockito', name: 'mockito-all', version: '1.10.19'
compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.5' javafx {
compile group: 'org.springframework', name:'spring-context', version: '4.2.4.RELEASE' version = "17"
compile group: 'net.sf.supercsv', name: 'super-csv', version: '2.4.0' modules = [ 'javafx.controls', 'javafx.fxml' ]
compile group: 'com.typesafe', name: 'config', version: '1.3.0'
compile group: 'commons-validator', name: 'commons-validator', version: '1.5.0'
compile group: 'de.jensd', name: 'fontawesomefx', version: '8.8'
} }
dependencies {
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.8.2'
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.2'
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.8.2'
testImplementation group: 'org.hamcrest', name: 'hamcrest-library', version: '2.2'
testImplementation group: 'org.mockito', name: 'mockito-core', version:'3.1.0'
implementation group: 'org.apache.groovy', name: 'groovy', version: '4.0.0-rc-1'
implementation group: 'org.springframework', name:'spring-context', version: '5.3.13'
implementation group: 'de.siegmar', name: 'fastcsv', version: '2.1.0'
implementation group: 'commons-validator', name: 'commons-validator', version: '1.7'
implementation group: 'de.jensd', name: 'fontawesomefx-commons', version: '11.0'
implementation group: 'de.jensd', name: 'fontawesomefx-fontawesome', version: '4.7.0-11'
implementation group: 'de.jensd', name: 'fontawesomefx-materialdesignfont', version: '1.7.22-11'
implementation group: 'de.jensd', name: 'fontawesomefx-materialicons', version: '2.2.0-11'
implementation group: 'org.controlsfx', name: 'controlsfx', version: '11.1.0'
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.9'
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.14.1'
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.14.1'
implementation group: 'org.fxmisc.richtext', name: 'richtextfx', version: '0.10.7'
}
test {
useJUnitPlatform()
}
group 'ninja.javafx'
version '1.1.0'
mainClassName = 'ninja.javafx.smartcsv.Main'

Binary file not shown.

View File

@@ -1,6 +1,5 @@
#Thu Oct 01 19:02:27 CEST 2015
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2-all.zip

74
gradlew vendored Normal file → Executable file
View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env sh
############################################################################## ##############################################################################
## ##
@@ -6,12 +6,30 @@
## ##
############################################################################## ##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Attempt to set APP_HOME
DEFAULT_JVM_OPTS="" # Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD="maximum"
@@ -30,6 +48,7 @@ die ( ) {
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false
case "`uname`" in case "`uname`" in
CYGWIN* ) CYGWIN* )
cygwin=true cygwin=true
@@ -40,31 +59,11 @@ case "`uname`" in
MINGW* ) MINGW* )
msys=true msys=true
;; ;;
NONSTOP* )
nonstop=true
;;
esac esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -90,7 +89,7 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n` MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -114,6 +113,7 @@ fi
if $cygwin ; then if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath # We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
@@ -154,11 +154,19 @@ if $cygwin ; then
esac esac
fi fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules # Escape application args
function splitJvmOpts() { save () {
JVM_OPTS=("$@") for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
} }
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS APP_ARGS=$(save "$@")
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" # Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

14
gradlew.bat vendored
View File

@@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +46,9 @@ echo location of your Java installation.
goto fail goto fail
:init :init
@rem Get command-line arguments, handling Windowz variants @rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args :win9xME_args
@rem Slurp the command line arguments. @rem Slurp the command line arguments.
@@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%* set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute :execute
@rem Setup the command line @rem Setup the command line

View File

@@ -1,2 +1,2 @@
rootProject.name = 'SmartCSV.ninja.javafx.smartcsv.fx' rootProject.name = 'SmartCSV.fx'

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -32,6 +32,7 @@ import java.io.IOException;
/** /**
* read some file * read some file
*/ */
public interface FileReader { public interface FileReader<E> {
E getContent();
void read(File filename) throws IOException; void read(File filename) throws IOException;
} }

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -32,6 +32,7 @@ import java.io.IOException;
/** /**
* write some file * write some file
*/ */
public interface FileWriter { public interface FileWriter<E> {
void setContent(E content);
void write(File filename) throws IOException; void write(File filename) throws IOException;
} }

View File

@@ -0,0 +1,36 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv;
import ninja.javafx.smartcsv.fx.SmartCSV;
public class Main {
public static void main(String[] args) {
// workaround for module problem
SmartCSV.main(args);
}
}

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -26,20 +26,29 @@
package ninja.javafx.smartcsv.csv; package ninja.javafx.smartcsv.csv;
import org.supercsv.prefs.CsvPreference; import ninja.javafx.smartcsv.preferences.Preferences;
import static ninja.javafx.smartcsv.preferences.Preferences.defaultPreferences;
/** /**
* *
*/ */
public class CSVConfigurable { public class CSVConfigurable {
protected CsvPreference csvPreference; protected Preferences csvPreference;
protected String fileEncoding;
public CSVConfigurable() { public CSVConfigurable() {
csvPreference = CsvPreference.EXCEL_NORTH_EUROPE_PREFERENCE; csvPreference = defaultPreferences();
} }
public void setCsvPreference(CsvPreference csvPreference) { public void setCsvPreference(Preferences csvPreference) {
this.csvPreference = csvPreference; this.csvPreference = csvPreference;
} }
public void setFileEncoding(String fileEncoding) {
this.fileEncoding = fileEncoding;
}
} }

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -26,53 +26,57 @@
package ninja.javafx.smartcsv.csv; package ninja.javafx.smartcsv.csv;
import de.siegmar.fastcsv.reader.NamedCsvReader;
import ninja.javafx.smartcsv.FileReader; import ninja.javafx.smartcsv.FileReader;
import ninja.javafx.smartcsv.fx.table.model.CSVModel; import ninja.javafx.smartcsv.fx.table.model.CSVModel;
import ninja.javafx.smartcsv.fx.table.model.CSVRow;
import org.springframework.stereotype.Service;
import org.supercsv.io.CsvMapReader;
import org.supercsv.io.ICsvMapReader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.nio.charset.Charset;
/** /**
* reads the csv file and stores the values in csv model * reads the csv file and stores the values in csv model
*/ */
@Service public class CSVFileReader extends CSVConfigurable implements FileReader<CSVModel> {
public class CSVFileReader extends CSVConfigurable implements FileReader {
private CSVModel model; private CSVModel model;
@Override @Override
public void read(File file) throws IOException { public void read(File file) throws IOException {
ICsvMapReader mapReader = null; System.out.println(csvPreference);
try { try (var csv = getNamedCsvReader(file)) {
mapReader = new CsvMapReader(new java.io.FileReader(file.getAbsoluteFile()), csvPreference);
model = new CSVModel(); model = new CSVModel();
// the header columns are used as the keys to the Map // the header columns are used as the keys to the Map
String[] header = mapReader.getHeader(true); var header = csv.getHeader().toArray(new String[csv.getHeader().size()]);
model.setHeader(header); model.setHeader(header);
Map<String, String> customerMap; csv.forEach(csvRow -> {
while ((customerMap = mapReader.read(header)) != null) { var row = model.addRow();
CSVRow row = model.addRow();
for (String column : header) { for (String column : header) {
row.addValue(column, customerMap.get(column)); model.addValue(row, column, csvRow.getField(column));
}
});
} catch (IOException ex) {
// TODO perhaps a custom NinjaException that can properly identify and localize the exception message
// is this a file not found? is this a corrupt csv? etc
throw new IOException("Failed to read " + file + ": " + ex.getMessage(), ex);
} }
} }
} finally { private NamedCsvReader getNamedCsvReader(File file) throws IOException {
if (mapReader != null) { var builder = NamedCsvReader.builder()
mapReader.close(); .fieldSeparator(csvPreference.delimiterChar());
} if (csvPreference.quoteChar() != null) {
} builder.quoteCharacter(csvPreference.quoteChar());
} }
public CSVModel getData() { return builder.build(file.toPath(), Charset.forName(fileEncoding));
}
public CSVModel getContent() {
return model; return model;
} }

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -26,48 +26,47 @@
package ninja.javafx.smartcsv.csv; package ninja.javafx.smartcsv.csv;
import de.siegmar.fastcsv.writer.CsvWriter;
import de.siegmar.fastcsv.writer.QuoteStrategy;
import ninja.javafx.smartcsv.fx.table.model.CSVModel; import ninja.javafx.smartcsv.fx.table.model.CSVModel;
import ninja.javafx.smartcsv.fx.table.model.CSVRow; import ninja.javafx.smartcsv.fx.table.model.CSVRow;
import org.springframework.stereotype.Service;
import org.supercsv.io.CsvMapWriter;
import org.supercsv.io.ICsvMapWriter;
import java.io.File; import java.io.File;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.nio.charset.Charset;
import java.util.List;
import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toList;
/** /**
* filewriter for the csv * filewriter for the csv
*/ */
@Service public class CSVFileWriter extends CSVConfigurable implements ninja.javafx.smartcsv.FileWriter<CSVModel> {
public class CSVFileWriter extends CSVConfigurable implements ninja.javafx.smartcsv.FileWriter {
private CSVModel model; private CSVModel model;
public void setModel(CSVModel model) { public void setContent(CSVModel model) {
this.model = model; this.model = model;
} }
@Override @Override
public void write(File filename) throws IOException { public void write(File filename) throws IOException {
ICsvMapWriter mapWriter = null; try (var writer = getCsvWriter(filename)){
try { writer.writeRow(model.getHeader());
mapWriter = new CsvMapWriter(new FileWriter(filename.getAbsolutePath()), csvPreference);
mapWriter.writeHeader(model.getHeader());
for(CSVRow row: model.getRows()) { for(CSVRow row: model.getRows()) {
Map<String, String> columns = convertMapFromModel(row); writer.writeRow(convertMapFromModel(row));
mapWriter.write(columns, model.getHeader());
} }
} }
finally {
if( mapWriter != null ) {
mapWriter.close();
} }
private CsvWriter getCsvWriter(File filename) throws IOException {
var writer = CsvWriter.builder().fieldSeparator(csvPreference.delimiterChar());
if (csvPreference.quoteChar() != null) {
writer.quoteCharacter(csvPreference.quoteChar());
writer.quoteStrategy(QuoteStrategy.ALWAYS);
} }
return writer.build(filename.toPath(), Charset.forName(fileEncoding));
} }
/** /**
@@ -75,13 +74,8 @@ public class CSVFileWriter extends CSVConfigurable implements ninja.javafx.smart
* @param row the row to convert * @param row the row to convert
* @return a simple map for the supercvs writer * @return a simple map for the supercvs writer
*/ */
private Map<String, String> convertMapFromModel(CSVRow row) { private List<String> convertMapFromModel(CSVRow row) {
return row.getColumns().entrySet().stream() return row.getColumns().values().stream().map(v -> v.get().getValue())
.collect( .collect(toList());
toMap(
Map.Entry::getKey,
e -> e.getValue().getValue().getValue() != null ? e.getValue().getValue().getValue() : ""
)
);
} }
} }

View File

@@ -0,0 +1,96 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.export;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import ninja.javafx.smartcsv.fx.table.model.CSVModel;
import java.io.File;
import java.io.StringWriter;
import java.nio.file.Files;
import java.util.ResourceBundle;
import static java.text.MessageFormat.format;
import static ninja.javafx.smartcsv.fx.util.I18nValidationUtil.getI18nValidatioMessage;
/**
* this class exports the error messages into a log file
*/
@org.springframework.stereotype.Service
public class ErrorExport extends Service {
private CSVModel model;
private File file;
private ResourceBundle resourceBundle;
private String csvFilename;
public void setCsvFilename(String csvFilename) {
this.csvFilename = csvFilename;
}
public void setResourceBundle(ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
}
public void setModel(CSVModel model) {
this.model = model;
}
public void setFile(File file) {
this.file = file;
}
@Override
protected Task createTask() {
return new Task() {
@Override
protected Void call() throws Exception {
try {
StringWriter log = new StringWriter();
log.append(
format(resourceBundle.getString("log.header.message"),
csvFilename,
Integer.toString(model.getValidationError().size()))).append("\n\n");
model.getValidationError().forEach(error ->
log.append(
format(resourceBundle.getString("log.message"),
error.getLineNumber().toString(),
error.getColumn(),
getI18nValidatioMessage(resourceBundle, error))).append("\n")
);
Files.write(file.toPath(), log.toString().getBytes());
} catch (Throwable ex) {
ex.printStackTrace();
}
return null;
}
};
}
}

View File

@@ -0,0 +1,104 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.files;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import ninja.javafx.smartcsv.FileReader;
import ninja.javafx.smartcsv.FileWriter;
import java.io.File;
import java.io.IOException;
/**
* This class stores files and their state
* @author abi
*/
public class FileStorage<E> {
private FileReader<E> reader;
private FileWriter<E> writer;
public FileStorage(FileReader<E> reader, FileWriter<E> writer) {
this.reader = reader;
this.writer = writer;
}
private BooleanProperty fileChanged = new SimpleBooleanProperty(true);
private ObjectProperty<File> file = new SimpleObjectProperty<>();
private ObjectProperty<E> content = new SimpleObjectProperty<>();
public boolean isFileChanged() {
return fileChanged.get();
}
public BooleanProperty fileChangedProperty() {
return fileChanged;
}
public void setFileChanged(boolean fileChanged) {
this.fileChanged.set(fileChanged);
}
public File getFile() {
return file.get();
}
public ObjectProperty<File> fileProperty() {
return file;
}
public void setFile(File file) {
this.file.set(file);
}
public E getContent() {
return content.get();
}
public ObjectProperty<E> contentProperty() {
return content;
}
public void setContent(E content) {
this.content.set(content);
}
public void load() throws IOException {
reader.read(file.get());
setContent(reader.getContent());
setFileChanged(false);
}
public void save() throws IOException {
writer.setContent(content.get());
writer.write(file.get());
setFileChanged(false);
}
}

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -28,11 +28,10 @@ package ninja.javafx.smartcsv.fx;
import javafx.application.Application; import javafx.application.Application;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import ninja.javafx.smartcsv.fx.about.AboutController; import ninja.javafx.smartcsv.fx.about.AboutController;
import org.springframework.context.annotation.*; import org.springframework.context.annotation.*;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
@@ -86,9 +85,12 @@ public class SmartCSV extends Application {
private void showUI(Stage primaryStage, String name, String version) { private void showUI(Stage primaryStage, String name, String version) {
SmartCSVController smartCVSController = appContext.getBean(SmartCSVController.class); SmartCSVController smartCVSController = appContext.getBean(SmartCSVController.class);
Scene scene = new Scene((Parent) smartCVSController.getView()); Scene scene = new Scene((Parent) smartCVSController.getView());
var defaultThemeCss = getClass().getResource("/ninja/javafx/smartcsv/fx/smartcsv.css").toExternalForm();
scene.getRoot().getStylesheets().add(defaultThemeCss);
primaryStage.setScene(scene); primaryStage.setScene(scene);
primaryStage.setTitle(String.format("%s %s", name, version)); primaryStage.setTitle(String.format("%s %s", name, version));
primaryStage.getIcons().add(new Image(SmartCSV.class.getResourceAsStream("/ninja/javafx/smartcsv/icon/logo.png")));
primaryStage.show(); primaryStage.show();
primaryStage.setMaximized(true); primaryStage.setMaximized(true);

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -26,8 +26,11 @@
package ninja.javafx.smartcsv.fx; package ninja.javafx.smartcsv.fx;
import javafx.beans.property.BooleanProperty; import javafx.beans.InvalidationListener;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.binding.Bindings;
import javafx.collections.ListChangeListener;
import javafx.collections.WeakListChangeListener;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@@ -36,39 +39,46 @@ import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import ninja.javafx.smartcsv.FileReader;
import ninja.javafx.smartcsv.FileWriter;
import ninja.javafx.smartcsv.csv.CSVFileReader; import ninja.javafx.smartcsv.csv.CSVFileReader;
import ninja.javafx.smartcsv.csv.CSVFileWriter; import ninja.javafx.smartcsv.csv.CSVFileWriter;
import ninja.javafx.smartcsv.export.ErrorExport;
import ninja.javafx.smartcsv.files.FileStorage;
import ninja.javafx.smartcsv.fx.about.AboutController; import ninja.javafx.smartcsv.fx.about.AboutController;
import ninja.javafx.smartcsv.fx.list.ValidationErrorListCell; import ninja.javafx.smartcsv.fx.list.ErrorSideBar;
import ninja.javafx.smartcsv.fx.list.GotoLineDialog;
import ninja.javafx.smartcsv.fx.preferences.PreferencesController; import ninja.javafx.smartcsv.fx.preferences.PreferencesController;
import ninja.javafx.smartcsv.fx.table.ObservableMapValueFactory; import ninja.javafx.smartcsv.fx.table.ObservableMapValueFactory;
import ninja.javafx.smartcsv.fx.table.ValidationCellFactory; import ninja.javafx.smartcsv.fx.table.ValidationCellFactory;
import ninja.javafx.smartcsv.fx.table.model.CSVModel; import ninja.javafx.smartcsv.fx.table.model.CSVModel;
import ninja.javafx.smartcsv.fx.table.model.CSVRow; import ninja.javafx.smartcsv.fx.table.model.CSVRow;
import ninja.javafx.smartcsv.fx.table.model.CSVValue; import ninja.javafx.smartcsv.fx.table.model.CSVValue;
import ninja.javafx.smartcsv.fx.util.JavaFxUtils;
import ninja.javafx.smartcsv.fx.util.LoadFileService; import ninja.javafx.smartcsv.fx.util.LoadFileService;
import ninja.javafx.smartcsv.fx.util.SaveFileService; import ninja.javafx.smartcsv.fx.util.SaveFileService;
import ninja.javafx.smartcsv.preferences.PreferencesFileReader; import ninja.javafx.smartcsv.fx.validation.ValidationEditorController;
import ninja.javafx.smartcsv.preferences.PreferencesFileWriter; import ninja.javafx.smartcsv.preferences.*;
import ninja.javafx.smartcsv.validation.ValidationError; import ninja.javafx.smartcsv.validation.ValidationError;
import ninja.javafx.smartcsv.validation.ValidationFileReader; import ninja.javafx.smartcsv.validation.ValidationFileReader;
import ninja.javafx.smartcsv.validation.ValidationFileWriter;
import ninja.javafx.smartcsv.validation.configuration.ValidationConfiguration;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.supercsv.prefs.CsvPreference;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.text.MessageFormat.format;
import static javafx.application.Platform.exit; import static javafx.application.Platform.exit;
import static javafx.application.Platform.runLater; import static javafx.application.Platform.runLater;
import static javafx.beans.binding.Bindings.*;
import static javafx.scene.layout.AnchorPane.*; import static javafx.scene.layout.AnchorPane.*;
import static ninja.javafx.smartcsv.preferences.Preferences.defaultPreferences;
/** /**
* main controller of the application * main controller of the application
@@ -76,6 +86,7 @@ import static javafx.scene.layout.AnchorPane.*;
@Component @Component
public class SmartCSVController extends FXMLController { public class SmartCSVController extends FXMLController {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// constants // constants
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -86,36 +97,41 @@ public class SmartCSVController extends FXMLController {
File.separator + "" + File.separator + "" +
"preferences.json"); "preferences.json");
private static final File ENCODING_FILE = new File(System.getProperty("user.home") +
File.separator +
".SmartCSV.fx" +
File.separator +
"encoding.json");
public static final String CSV_FILTER_TEXT = "CSV files (*.csv)";
public static final String CSV_FILTER_EXTENSION = "*.csv";
public static final String JSON_FILTER_TEXT = "JSON files (*.json)";
public static final String JSON_FILTER_EXTENSION = "*.json";
public static final String EXPORT_LOG_FILTER_TEXT = "Error log files (*.log)";
public static final String EXPORT_LOG_FILTER_EXTENSION = "*.log";
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// injections // injections
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Autowired
private PreferencesFileReader preferencesLoader;
@Autowired
private PreferencesFileWriter preferencesWriter;
@Autowired
private CSVFileReader csvLoader;
@Autowired
private ValidationFileReader validationLoader;
@Autowired
private CSVFileWriter csvFileWriter;
@Autowired @Autowired
private AboutController aboutController; private AboutController aboutController;
@Autowired @Autowired
private PreferencesController preferencesController; private PreferencesController preferencesController;
@Autowired
private ValidationEditorController validationEditorController;
@Autowired @Autowired
private LoadFileService loadFileService; private LoadFileService loadFileService;
@Autowired @Autowired
private SaveFileService saveFileService;; private SaveFileService saveFileService;
@Autowired
private ErrorExport errorExport;
@FXML @FXML
private BorderPane applicationPane; private BorderPane applicationPane;
@@ -129,9 +145,6 @@ public class SmartCSVController extends FXMLController {
@FXML @FXML
private Label stateName; private Label stateName;
@FXML
private ListView errorList;
@FXML @FXML
private AnchorPane tableWrapper; private AnchorPane tableWrapper;
@@ -141,6 +154,62 @@ public class SmartCSVController extends FXMLController {
@FXML @FXML
private MenuItem saveAsMenuItem; private MenuItem saveAsMenuItem;
@FXML
private MenuItem createConfigMenuItem;
@FXML
private MenuItem loadConfigMenuItem;
@FXML
private MenuItem saveConfigMenuItem;
@FXML
private MenuItem saveAsConfigMenuItem;
@FXML
private MenuItem deleteRowMenuItem;
@FXML
private MenuItem addRowMenuItem;
@FXML
private MenuItem gotoLineMenuItem;
@FXML
private MenuItem exportMenuItem;
@FXML
private Button saveButton;
@FXML
private Button saveAsButton;
@FXML
private Button createConfigButton;
@FXML
private Button loadConfigButton;
@FXML
private Button saveConfigButton;
@FXML
private Button saveAsConfigButton;
@FXML
private Button deleteRowButton;
@FXML
private Button addRowButton;
@FXML
private Button exportButton;
@FXML
private Label currentLineNumber;
@FXML
private Label totalNumberOfLines;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// members // members
@@ -148,13 +217,19 @@ public class SmartCSVController extends FXMLController {
private ValidationCellFactory cellFactory; private ValidationCellFactory cellFactory;
private CSVModel model;
private TableView<CSVRow> tableView; private TableView<CSVRow> tableView;
private BooleanProperty fileChanged = new SimpleBooleanProperty(true); private ErrorSideBar errorSideBar;
private ResourceBundle resourceBundle; private ResourceBundle resourceBundle;
private File currentCsvFile; private CSVFileReader csvFileReader = new CSVFileReader();
private File currentConfigFile; private CSVFileWriter csvFileWriter = new CSVFileWriter();
private FileStorage<CSVModel> currentCsvFile = new FileStorage<>(csvFileReader, csvFileWriter);
private FileStorage<ValidationConfiguration> currentConfigFile = new FileStorage<>(new ValidationFileReader(), new ValidationFileWriter());
private FileStorage<Preferences> csvPreferenceFile = new FileStorage<>(new PreferencesFileReader(), new PreferencesFileWriter());
private FileStorage<String> fileEncodingFile = new FileStorage<>(new EncodingFileReader(), new EncodingFileWriter());
private ListChangeListener<ValidationError> errorListListener = c -> tableView.refresh();
private WeakListChangeListener<ValidationError> weakErrorListListener = new WeakListChangeListener<>(errorListListener);
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// init // init
@@ -163,16 +238,50 @@ public class SmartCSVController extends FXMLController {
@Override @Override
public void initialize(URL location, ResourceBundle resourceBundle) { public void initialize(URL location, ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
saveFileService.setWriter(csvFileWriter);
cellFactory = new ValidationCellFactory(resourceBundle); setupTableCellFactory();
errorList.setCellFactory(param -> new ValidationErrorListCell(resourceBundle)); setupErrorSideBar(resourceBundle);
errorList.getSelectionModel().selectedItemProperty().addListener(observable -> scrollToError());
fileChanged.addListener(observable -> setStateName()); bindMenuItemsToContentExistence(currentCsvFile, saveMenuItem, saveAsMenuItem, addRowMenuItem, gotoLineMenuItem, createConfigMenuItem, loadConfigMenuItem);
setStateName(); bindButtonsToContentExistence(currentCsvFile, saveButton, saveAsButton, addRowButton, createConfigButton, loadConfigButton);
loadCsvPreferences();
bindMenuItemsToContentExistence(currentConfigFile, saveConfigMenuItem, saveAsConfigMenuItem);
bindButtonsToContentExistence(currentConfigFile, saveAsConfigButton, saveConfigButton);
bindCsvFileName();
bindConfigFileName();
csvPreferenceFile.setFile(PREFERENCES_FILE);
fileEncodingFile.setFile(ENCODING_FILE);
loadCsvPreferencesFromFile();
} }
private void loadEncodingFromFile() {
if (fileEncodingFile.getFile().exists()) {
useLoadFileService(fileEncodingFile, event -> setFileEncoding(fileEncodingFile.getContent()));
} else {
setFileEncoding(Charset.defaultCharset().name());
}
}
private void setFileEncoding(String content) {
preferencesController.setFileEncoding(content);
csvFileReader.setFileEncoding(content);
csvFileWriter.setFileEncoding(content);
}
private void setupErrorSideBar(ResourceBundle resourceBundle) {
errorSideBar = new ErrorSideBar(resourceBundle);
errorSideBar.selectedValidationErrorProperty().addListener((observable, oldValue, newValue) -> {
scrollToError(newValue);
});
applicationPane.setRight(errorSideBar);
}
private void setupTableCellFactory() {
cellFactory = new ValidationCellFactory(resourceBundle);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -192,39 +301,44 @@ public class SmartCSVController extends FXMLController {
@FXML @FXML
public void openCsv(ActionEvent actionEvent) { public void openCsv(ActionEvent actionEvent) {
currentCsvFile = loadFile(csvLoader, loadFile(CSV_FILTER_TEXT, CSV_FILTER_EXTENSION, "Open CSV", currentCsvFile);
"CSV files (*.csv)",
"*.csv",
"Open CSV",
currentCsvFile);
enableSaveMenuItems();
setCsvFileName();
} }
@FXML @FXML
public void openConfig(ActionEvent actionEvent) { public void openConfig(ActionEvent actionEvent) {
currentConfigFile = loadFile(validationLoader, loadFile(JSON_FILTER_TEXT, JSON_FILTER_EXTENSION, "Open Validation Configuration", currentConfigFile);
"JSON files (*.json)", }
"*.json",
"Open Validation Configuration", @FXML
currentConfigFile); public void createConfig(ActionEvent actionEvent) {
setConfigFileName(); currentConfigFile.setContent(currentCsvFile.getContent().createValidationConfiguration());
currentConfigFile.setFile(null);
currentConfigFile.setFileChanged(true);
resetContent();
} }
@FXML @FXML
public void saveCsv(ActionEvent actionEvent) { public void saveCsv(ActionEvent actionEvent) {
csvFileWriter.setModel(model); useSaveFileService(currentCsvFile);
useSaveFileService(csvFileWriter, currentCsvFile);
} }
@FXML @FXML
public void saveAsCsv(ActionEvent actionEvent) { public void saveAsCsv(ActionEvent actionEvent) {
csvFileWriter.setModel(model); saveFile(CSV_FILTER_TEXT, CSV_FILTER_EXTENSION, currentCsvFile);
currentCsvFile = saveFile(csvFileWriter, }
"CSV files (*.csv)",
"*.csv", @FXML
currentCsvFile); public void saveConfig(ActionEvent actionEvent) {
setCsvFileName(); if (currentConfigFile.getFile() == null) {
saveAsConfig(actionEvent);
} else {
useSaveFileService(currentConfigFile);
}
}
@FXML
public void saveAsConfig(ActionEvent actionEvent) {
saveFile(JSON_FILTER_TEXT, JSON_FILTER_EXTENSION, currentConfigFile);
} }
@FXML @FXML
@@ -257,17 +371,94 @@ public class SmartCSVController extends FXMLController {
Optional<ButtonType> result = alert.showAndWait(); Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK){ if (result.get() == ButtonType.OK){
CsvPreference csvPreference = preferencesController.getCsvPreference(); Preferences csvPreference = preferencesController.getCsvPreference();
setCsvPreference(csvPreference); setCsvPreference(csvPreference);
saveCsvPreferences(csvPreference); saveCsvPreferences(csvPreference);
String fileEncoding = CharsetHelper.getCharsetName(preferencesController.getFileEncoding());
setFileEncoding(fileEncoding);
saveFileEncoding(fileEncoding);
} else { } else {
preferencesController.setCsvPreference(preferencesLoader.getCSVpreference()); preferencesController.setCsvPreference(csvPreferenceFile.getContent());
preferencesController.setFileEncoding(fileEncodingFile.getContent());
}
}
private void saveFileEncoding(String fileEncoding) {
try {
createFileEncodingFile();
fileEncodingFile.setContent(fileEncoding);
useSaveFileService(fileEncodingFile);
} catch (IOException e) {
e.printStackTrace();
}
}
private void createFileEncodingFile() throws IOException {
if (!fileEncodingFile.getFile().exists()) {
createPreferencesFileFolder();
fileEncodingFile.getFile().createNewFile();
}
}
@FXML
public void deleteRow(ActionEvent actionEvent) {
currentCsvFile.getContent().getRows().removeAll(tableView.getSelectionModel().getSelectedItems());
currentCsvFile.setFileChanged(true);
resetContent();
}
@FXML
public void addRow(ActionEvent actionEvent) {
CSVRow row = currentCsvFile.getContent().addRow();
for (String column : currentCsvFile.getContent().getHeader()) {
currentCsvFile.getContent().addValue(row, column, "");
}
currentCsvFile.setFileChanged(true);
resetContent();
selectNewRow();
}
@FXML
public void gotoLine(ActionEvent actionEvent) {
int maxLineNumber = currentCsvFile.getContent().getRows().size();
GotoLineDialog dialog = new GotoLineDialog(maxLineNumber);
dialog.setTitle(resourceBundle.getString("dialog.goto.line.title"));
dialog.setHeaderText(format(resourceBundle.getString("dialog.goto.line.header.text"), maxLineNumber));
dialog.setContentText(resourceBundle.getString("dialog.goto.line.label"));
Optional<Integer> result = dialog.showAndWait();
if (result.isPresent()){
Integer lineNumber = result.get();
if (lineNumber != null) {
tableView.scrollTo(max(0, lineNumber - 2));
tableView.getSelectionModel().select(lineNumber - 1);
}
}
}
@FXML
public void export(ActionEvent actionEvent) {
final FileChooser fileChooser = new FileChooser();
//Set extension filter
final FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter(EXPORT_LOG_FILTER_TEXT, EXPORT_LOG_FILTER_EXTENSION);
fileChooser.getExtensionFilters().add(extFilter);
fileChooser.setTitle("Save");
//Show open file dialog
File file = fileChooser.showSaveDialog(applicationPane.getScene().getWindow());
if (file != null) {
errorExport.setCsvFilename(currentCsvFile.getFile().getName());
errorExport.setModel(currentCsvFile.getContent());
errorExport.setFile(file);
errorExport.setResourceBundle(resourceBundle);
errorExport.restart();
} }
} }
public boolean canExit() { public boolean canExit() {
boolean canExit = true; boolean canExit = true;
if (model != null && fileChanged.get()) { if (currentCsvFile.getContent() != null && currentCsvFile.isFileChanged()) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION); Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle(resourceBundle.getString("dialog.exit.title")); alert.setTitle(resourceBundle.getString("dialog.exit.title"));
alert.setHeaderText(resourceBundle.getString("dialog.exit.header.text")); alert.setHeaderText(resourceBundle.getString("dialog.exit.header.text"));
@@ -282,74 +473,119 @@ public class SmartCSVController extends FXMLController {
return canExit; return canExit;
} }
public void showValidationEditor(String column) {
validationEditorController.setSelectedColumn(column);
validationEditorController.updateForm();
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setGraphic(null);
alert.setTitle(resourceBundle.getString("dialog.validation.rules.title"));
alert.setHeaderText(format(resourceBundle.getString("dialog.validation.rules.header"), column));
alert.getDialogPane().setContent(validationEditorController.getView());
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK){
runLater(() -> {
validationEditorController.updateConfiguration();
currentCsvFile.setFileChanged(true);
currentCsvFile.getContent().revalidate(column);
});
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// private methods // private methods
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void enableSaveMenuItems() { private void selectNewRow() {
if (currentCsvFile != null) { int lastRow = tableView.getItems().size()-1;
saveMenuItem.setDisable(false); tableView.scrollTo(lastRow);
saveAsMenuItem.setDisable(false); tableView.requestFocus();
tableView.getSelectionModel().select(lastRow);
}
private void bindMenuItemsToContentExistence(FileStorage file, MenuItem... items) {
for (MenuItem item: items) {
item.disableProperty().bind(isNull(file.contentProperty()));
} }
} }
private void setCsvFileName() { private void bindButtonsToContentExistence(FileStorage file, Button... items) {
if (currentCsvFile != null) { for (Button item: items) {
csvName.setText(currentCsvFile.getName()); item.disableProperty().bind(isNull(file.contentProperty()));
}
}
private void bindMenuItemsToTableSelection(MenuItem... items) {
for (MenuItem item: items) {
item.disableProperty().bind(lessThan(tableView.getSelectionModel().selectedIndexProperty(), 0));
}
}
private void bindButtonsToTableSelection(Button... items) {
for (Button item: items) {
item.disableProperty().bind(lessThan(tableView.getSelectionModel().selectedIndexProperty(), 0));
}
}
private void bindCsvFileName() {
csvName.textProperty().bind(selectString(currentCsvFile.fileProperty(), "name"));
}
private void bindConfigFileName() {
configurationName.textProperty().bind(selectString(currentConfigFile.fileProperty(), "name"));
}
private void bindLineNumber() {
currentLineNumber.textProperty().bind(tableView.getSelectionModel().selectedIndexProperty().add(1).asString());
}
private void loadCsvPreferencesFromFile() {
if (csvPreferenceFile.getFile().exists()) {
useLoadFileService(csvPreferenceFile, event -> {
setCsvPreference(csvPreferenceFile.getContent());
loadEncodingFromFile();
});
} else { } else {
csvName.setText(""); setCsvPreference(defaultPreferences());
loadEncodingFromFile();
} }
} }
private void setConfigFileName() { private void saveCsvPreferences(Preferences csvPreference) {
if (currentConfigFile != null) {
configurationName.setText(currentConfigFile.getName());
} else {
configurationName.setText("");
}
}
private void loadCsvPreferences() {
if (PREFERENCES_FILE.exists()) {
useLoadFileService(preferencesLoader, PREFERENCES_FILE);
}
setCsvPreference(preferencesLoader.getCSVpreference());
}
private void saveCsvPreferences(CsvPreference csvPreference) {
try { try {
createPreferenceFile(); createPreferenceFile();
preferencesWriter.setCsvPreference(csvPreference); csvPreferenceFile.setContent(csvPreference);
useSaveFileService(preferencesWriter, PREFERENCES_FILE); useSaveFileService(csvPreferenceFile);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
private void createPreferenceFile() throws IOException { private void createPreferenceFile() throws IOException {
if (!PREFERENCES_FILE.exists()) { if (!csvPreferenceFile.getFile().exists()) {
createPreferencesFileFolder(); createPreferencesFileFolder();
PREFERENCES_FILE.createNewFile(); csvPreferenceFile.getFile().createNewFile();
} }
} }
private void createPreferencesFileFolder() { private void createPreferencesFileFolder() {
if (!PREFERENCES_FILE.getParentFile().exists()) { if (!csvPreferenceFile.getFile().getParentFile().exists()) {
PREFERENCES_FILE.getParentFile().mkdir(); csvPreferenceFile.getFile().getParentFile().mkdir();
} }
} }
private void setCsvPreference(CsvPreference csvPreference) { private void setCsvPreference(Preferences csvPreference) {
csvLoader.setCsvPreference(csvPreference);
csvFileWriter.setCsvPreference(csvPreference);
preferencesController.setCsvPreference(csvPreference); preferencesController.setCsvPreference(csvPreference);
csvFileReader.setCsvPreference(csvPreference);
csvFileWriter.setCsvPreference(csvPreference);
} }
private File loadFile(FileReader fileReader, private void loadFile(String filterText,
String filterText,
String filter, String filter,
String title, String title,
File initChildFile) { FileStorage storageFile) {
final FileChooser fileChooser = new FileChooser(); final FileChooser fileChooser = new FileChooser();
//Set extension filter //Set extension filter
@@ -357,107 +593,136 @@ public class SmartCSVController extends FXMLController {
fileChooser.getExtensionFilters().add(extFilter); fileChooser.getExtensionFilters().add(extFilter);
fileChooser.setTitle(title); fileChooser.setTitle(title);
if (initChildFile != null) { if (storageFile.getFile() != null) {
fileChooser.setInitialDirectory(initChildFile.getParentFile()); fileChooser.setInitialDirectory(storageFile.getFile().getParentFile());
} }
//Show open file dialog //Show open file dialog
File file = fileChooser.showOpenDialog(applicationPane.getScene().getWindow()); File file = fileChooser.showOpenDialog(applicationPane.getScene().getWindow());
if (file != null) { if (file != null) {
useLoadFileService(fileReader, file); File previousFile = storageFile.getFile();
return file; storageFile.setFile(file);
} else { useLoadFileService(storageFile, t -> resetContent(), () -> storageFile.setFile(previousFile));
return initChildFile;
} }
} }
private File saveFile(FileWriter writer, String filterText, String filter, File initFile) { private File saveFile(String filterText, String filter, FileStorage fileStorage) {
File file = initFile; File file = fileStorage.getFile();
if (model != null) { if (fileStorage.getContent() != null) {
final FileChooser fileChooser = new FileChooser(); final FileChooser fileChooser = new FileChooser();
//Set extension filter //Set extension filter
final FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter(filterText, filter); final FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter(filterText, filter);
fileChooser.getExtensionFilters().add(extFilter); fileChooser.getExtensionFilters().add(extFilter);
if (initFile != null) { if (fileStorage.getFile() != null) {
fileChooser.setInitialDirectory(initFile.getParentFile()); fileChooser.setInitialDirectory(fileStorage.getFile().getParentFile());
fileChooser.setInitialFileName(initFile.getName()); fileChooser.setInitialFileName(fileStorage.getFile().getName());
} }
fileChooser.setTitle("Save File"); fileChooser.setTitle("Save File");
//Show open file dialog //Show open file dialog
file = fileChooser.showSaveDialog(applicationPane.getScene().getWindow()); file = fileChooser.showSaveDialog(applicationPane.getScene().getWindow());
if (file != null) { if (file != null) {
useSaveFileService(writer, file); fileStorage.setFile(file);
useSaveFileService(fileStorage);
} }
} }
return file; return file;
} }
private void useLoadFileService(FileReader fileReader, File file) { private void useLoadFileService(FileStorage<?> fileStorage, EventHandler<WorkerStateEvent> onSucceededHandler) {
loadFileService.setFile(file); useLoadFileService(fileStorage, onSucceededHandler, () -> {
loadFileService.setFileReader(fileReader); // nothing to rollback
loadFileService.restart(); });
loadFileService.setOnSucceeded(event -> runLater(() -> {
resetContent();
fileChanged.setValue(false);
}));
} }
private void useSaveFileService(FileWriter writer, File file) { private void useLoadFileService(FileStorage<?> fileStorage, EventHandler<WorkerStateEvent> onSucceededHandler, Runnable rollbackAction) {
saveFileService.setFile(file); loadFileService.setFileStorage(fileStorage);
saveFileService.setWriter(writer); loadFileService.restart();
loadFileService.setOnSucceeded(onSucceededHandler);
loadFileService.setOnFailed(event -> JavaFxUtils.onServiceError(event, "LoadFileService Error", "Failed to load the file.", rollbackAction));
}
private void useSaveFileService(FileStorage<?> fileStorage) {
saveFileService.setFileStorage(fileStorage);
saveFileService.restart(); saveFileService.restart();
saveFileService.setOnSucceeded(event -> runLater(() -> { saveFileService.setOnSucceeded(t -> resetContent());
resetContent();
fileChanged.setValue(false);
}));
} }
/** /**
* Creates new table view and add the new content * Creates new table view and add the new content
*/ */
private void resetContent() { private void resetContent() {
model = csvLoader.getData(); resetExportButtons();
if (model != null) {
model.setValidator(validationLoader.getValidator());
tableView = new TableView<>();
for (String column : model.getHeader()) { if (currentCsvFile.getContent() != null) {
currentCsvFile.getContent().getValidationError().addListener(weakErrorListListener);
currentCsvFile.getContent().setValidationConfiguration(currentConfigFile.getContent());
validationEditorController.setValidationConfiguration(currentConfigFile.getContent());
tableView = new TableView<>();
bindLineNumber();
bindMenuItemsToTableSelection(deleteRowMenuItem);
bindButtonsToTableSelection(deleteRowButton);
for (String column : currentCsvFile.getContent().getHeader()) {
addColumn(column, tableView); addColumn(column, tableView);
} }
tableView.getItems().setAll(model.getRows());
tableView.getColumns().addListener(
(InvalidationListener) observable -> currentCsvFile.getContent().setHeader(
tableView.getColumns().stream().map((x) -> x.getText()).toArray(String[]::new)));
tableView.getItems().setAll(currentCsvFile.getContent().getRows());
tableView.setEditable(true); tableView.setEditable(true);
totalNumberOfLines.textProperty().setValue("" + currentCsvFile.getContent().getRows().size());
setBottomAnchor(tableView, 0.0); setBottomAnchor(tableView, 0.0);
setTopAnchor(tableView, 0.0); setTopAnchor(tableView, 0.0);
setLeftAnchor(tableView, 0.0); setLeftAnchor(tableView, 0.0);
setRightAnchor(tableView, 0.0); setRightAnchor(tableView, 0.0);
tableWrapper.getChildren().setAll(tableView); tableWrapper.getChildren().setAll(tableView);
errorSideBar.setModel(currentCsvFile.getContent());
errorList.setItems(model.getValidationError()); binExportButtons();
} }
} }
private void binExportButtons() {
exportButton.disableProperty().bind(Bindings.isEmpty(currentCsvFile.getContent().getValidationError()));
exportMenuItem.disableProperty().bind(Bindings.isEmpty(currentCsvFile.getContent().getValidationError()));
}
private void resetExportButtons() {
exportButton.disableProperty().unbind();
exportMenuItem.disableProperty().unbind();
exportButton.disableProperty().setValue(true);
exportMenuItem.disableProperty().setValue(true);
}
/** /**
* Adds a column with the given name to the tableview * Adds a column with the given name to the tableview
* @param header name of the column header * @param header name of the column header
* @param tableView the tableview * @param tableView the tableview
*/ */
private void addColumn(String header, TableView tableView) { private void addColumn(final String header, TableView tableView) {
TableColumn column = new TableColumn(header); TableColumn column = new TableColumn(header);
column.setCellValueFactory(new ObservableMapValueFactory(header)); column.setCellValueFactory(new ObservableMapValueFactory(header));
column.setCellFactory(cellFactory); column.setCellFactory(cellFactory);
column.setEditable(true); column.setEditable(true);
column.setSortable(false);
ContextMenu contextMenu = contextMenuForColumn(header);
column.setContextMenu(contextMenu);
column.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<CSVRow, CSVValue>>() { column.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<CSVRow, CSVValue>>() {
@Override @Override
public void handle(TableColumn.CellEditEvent<CSVRow, CSVValue> event) { public void handle(TableColumn.CellEditEvent<CSVRow, CSVValue> event) {
event.getTableView().getItems().get(event.getTablePosition().getRow()). event.getTableView().getItems().get(event.getTablePosition().getRow()).
getColumns().get(header).setValue(event.getNewValue()); getColumns().get(header).setValue(event.getNewValue());
runLater(() -> { runLater(() -> {
fileChanged.setValue(true); currentCsvFile.setFileChanged(true);
model.revalidate();
}); });
} }
}); });
@@ -465,8 +730,16 @@ public class SmartCSVController extends FXMLController {
tableView.getColumns().add(column); tableView.getColumns().add(column);
} }
private void scrollToError() { private ContextMenu contextMenuForColumn(String header) {
ValidationError entry = (ValidationError)errorList.getSelectionModel().getSelectedItem(); ContextMenu contextMenu = new ContextMenu();
MenuItem editColumnRulesMenuItem = new MenuItem(resourceBundle.getString("context.menu.edit.column.rules"));
bindMenuItemsToContentExistence(currentConfigFile, editColumnRulesMenuItem);
editColumnRulesMenuItem.setOnAction(e -> showValidationEditor(header));
contextMenu.getItems().addAll(editColumnRulesMenuItem);
return contextMenu;
}
private void scrollToError(ValidationError entry) {
if (entry != null) { if (entry != null) {
if (entry.getLineNumber() != null) { if (entry.getLineNumber() != null) {
tableView.scrollTo(max(0, entry.getLineNumber() - 1)); tableView.scrollTo(max(0, entry.getLineNumber() - 1));
@@ -476,16 +749,4 @@ public class SmartCSVController extends FXMLController {
} }
} }
} }
private void setStateName() {
if (model != null) {
if (fileChanged.get()) {
stateName.setText(resourceBundle.getString("state.changed"));
} else {
stateName.setText(resourceBundle.getString("state.unchanged"));
}
} else {
stateName.setText("");
}
}
} }

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -0,0 +1,173 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.fx.list;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.WeakListChangeListener;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import ninja.javafx.smartcsv.fx.table.model.CSVModel;
import ninja.javafx.smartcsv.validation.ValidationError;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.controlsfx.control.PopOver;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import static javafx.geometry.Pos.CENTER;
import static ninja.javafx.smartcsv.fx.util.ColorConstants.ERROR_COLOR;
import static ninja.javafx.smartcsv.fx.util.ColorConstants.OK_COLOR;
import static ninja.javafx.smartcsv.fx.util.I18nValidationUtil.getI18nValidatioMessageWithColumn;
/**
* clickable side bar with error markers
*/
public class ErrorSideBar extends Region {
private static final Logger logger = LogManager.getLogger(ErrorSideBar.class);
private static final double WIDTH = 20.0;
private static final int BORDER = 8;
private static final double STATUS_BLOCK_HEIGHT = WIDTH - BORDER;
private static final double STATUS_BLOCK_WIDTH = WIDTH - BORDER;
private static final double STATUS_BLOCK_OFFSET = WIDTH + BORDER / 2;
private ListChangeListener<ValidationError> errorListListener = c -> setErrorMarker();
private WeakListChangeListener<ValidationError> weakErrorListListener = new WeakListChangeListener<>(errorListListener);
private ObjectProperty<CSVModel> model = new SimpleObjectProperty<>();
private ObjectProperty<ValidationError> selectedValidationError = new SimpleObjectProperty<>();
private PopOver popOver = new PopOver();
private ResourceBundle resourceBundle;
private Region statusBlock;
public ErrorSideBar(ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
initPopOver();
setFixWidth();
addModelListener();
statusBlock = new Region();
statusBlock.setPrefSize(STATUS_BLOCK_WIDTH, STATUS_BLOCK_HEIGHT);
statusBlock.setLayoutY(BORDER / 2);
statusBlock.setLayoutX(BORDER / 2);
}
private void initPopOver() {
popOver.setAutoHide(true);
popOver.setArrowLocation(PopOver.ArrowLocation.RIGHT_CENTER);
}
public void setModel(CSVModel model) {
this.model.set(model);
}
public CSVModel getModel() {
return model.get();
}
public ObjectProperty<CSVModel> modelProperty() {
return model;
}
public ValidationError getSelectedValidationError() {
return selectedValidationError.get();
}
public ObjectProperty<ValidationError> selectedValidationErrorProperty() {
return selectedValidationError;
}
public void setSelectedValidationError(ValidationError selectedValidationError) {
this.selectedValidationError.set(selectedValidationError);
}
private void addModelListener() {
model.addListener((observable, oldValue, newValue) -> {
newValue.getValidationError().addListener(weakErrorListListener);
setErrorMarker();
});
}
private void setFixWidth() {
setMinWidth(WIDTH);
setPrefWidth(WIDTH);
setMaxWidth(WIDTH);
}
private void setErrorMarker() {
List<Region> errorMarkerList = new ArrayList<>();
errorMarkerList.add(statusBlock);
statusBlock.setStyle("-fx-background-color: " + OK_COLOR);
if (model.get() != null) {
List<ValidationError> errorList = model.get().getValidationError();
if (errorList != null && !errorList.isEmpty()) {
statusBlock.setStyle("-fx-background-color: " + ERROR_COLOR);
int rows = model.get().getRows().size();
double space = (double)heightWithoutStatusBlock() / rows;
errorList.forEach(error -> errorMarkerList.add(generateErrorMarker(space, error)));
}
}
getChildren().setAll(errorMarkerList);
}
private int heightWithoutStatusBlock() {
return (int)(getHeight() - STATUS_BLOCK_OFFSET);
}
private Region generateErrorMarker(double space, ValidationError error) {
logger.info("generate error marker for {} errors in line {}", error.getMessages().size(), error.getLineNumber());
logger.info("layout y is set to {}", (space * error.getLineNumber() + STATUS_BLOCK_OFFSET));
Region errorMarker = new Region();
errorMarker.setLayoutY(space * error.getLineNumber() + STATUS_BLOCK_OFFSET);
errorMarker.setPrefSize(WIDTH, 2);
errorMarker.setStyle("-fx-background-color: " + ERROR_COLOR);
errorMarker.setOnMouseClicked(event -> selectedValidationError.setValue(error));
errorMarker.setOnMouseEntered(event -> {
popOver.setContentNode(popupContent(getI18nValidatioMessageWithColumn(resourceBundle, error)));
popOver.show(errorMarker, -16);
});
return errorMarker;
}
private Node popupContent(String text) {
VBox vBox = new VBox();
vBox.setPadding(new Insets(10,10,10,10));
vBox.getChildren().add(new Text(text));
vBox.setAlignment(CENTER);
return vBox;
}
}

View File

@@ -0,0 +1,113 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.fx.list;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import java.util.function.UnaryOperator;
public class GotoLineDialog extends Dialog<Integer> {
private final GridPane grid;
private final Label label;
private final TextField textField;
public GotoLineDialog(int maxLineNumber) {
final DialogPane dialogPane = getDialogPane();
this.textField = new TextField("");
this.textField.setMaxWidth(Double.MAX_VALUE);
UnaryOperator<TextFormatter.Change> filter = change -> {
String text = change.getText();
if (text.matches("[0-9]*")) {
try {
int lineNumber = Integer.parseInt(textField.getText() + text);
if (lineNumber <= maxLineNumber) {
return change;
}
} catch (NumberFormatException e) {
// this happens when focusing textfield or press special keys like DEL
return change;
}
}
return null;
};
TextFormatter<String> textFormatter = new TextFormatter<>(filter);
textField.setTextFormatter(textFormatter);
GridPane.setHgrow(textField, Priority.ALWAYS);
GridPane.setFillWidth(textField, true);
label = createContentLabel(dialogPane.getContentText());
label.setPrefWidth(Region.USE_COMPUTED_SIZE);
label.textProperty().bind(dialogPane.contentTextProperty());
this.grid = new GridPane();
this.grid.setHgap(10);
this.grid.setMaxWidth(Double.MAX_VALUE);
this.grid.setAlignment(Pos.CENTER_LEFT);
dialogPane.contentTextProperty().addListener(o -> updateGrid());
dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
updateGrid();
setResultConverter((dialogButton) -> {
ButtonBar.ButtonData data = dialogButton == null ? null : dialogButton.getButtonData();
return data == ButtonBar.ButtonData.OK_DONE ? Integer.parseInt(textField.getText()) : null;
});
}
private Label createContentLabel(String text) {
Label label = new Label(text);
label.setMaxWidth(Double.MAX_VALUE);
label.setMaxHeight(Double.MAX_VALUE);
label.getStyleClass().add("content");
label.setWrapText(true);
label.setPrefWidth(360);
return label;
}
private void updateGrid() {
grid.getChildren().clear();
grid.add(label, 0, 0);
grid.add(textField, 1, 0);
getDialogPane().setContent(grid);
Platform.runLater(() -> textField.requestFocus());
}
}

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -26,29 +26,23 @@
package ninja.javafx.smartcsv.fx.preferences; package ninja.javafx.smartcsv.fx.preferences;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter; import javafx.scene.control.TextFormatter;
import ninja.javafx.smartcsv.fx.FXMLController; import ninja.javafx.smartcsv.fx.FXMLController;
import ninja.javafx.smartcsv.preferences.Preferences;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.supercsv.prefs.CsvPreference;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
import static ninja.javafx.smartcsv.preferences.QuoteModeHelper.getQuoteMode;
import static ninja.javafx.smartcsv.preferences.QuoteModeHelper.getQuoteModeName;
/** /**
* controller for preferences * controller for preferences
*/ */
@@ -61,14 +55,11 @@ public class PreferencesController extends FXMLController {
@FXML @FXML
private TextField delimiterChar; private TextField delimiterChar;
@FXML
private CheckBox surroundingSpacesNeedQuotes;
@FXML @FXML
private CheckBox ignoreEmptyLines; private CheckBox ignoreEmptyLines;
@FXML @FXML
private ComboBox<String> quoteMode; private ComboBox<String> fileEncoding;
private String endOfLineSymbols; private String endOfLineSymbols;
@@ -83,7 +74,7 @@ public class PreferencesController extends FXMLController {
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
quoteMode.getItems().addAll("normal", "always", "column"); fileEncoding.getItems().addAll(Charset.availableCharsets().keySet());
UnaryOperator<TextFormatter.Change> allowOnlyOneCharacter = change -> { UnaryOperator<TextFormatter.Change> allowOnlyOneCharacter = change -> {
if (change.isContentChange()) { if (change.isContentChange()) {
@@ -101,24 +92,31 @@ public class PreferencesController extends FXMLController {
} }
private void revalidate() { private void revalidate() {
valid.setValue(quoteChar.getText().length() == 1 && delimiterChar.getText().length() == 1); valid.setValue(quoteChar.getText().length() <= 1 && delimiterChar.getText().length() == 1);
} }
public void setCsvPreference(CsvPreference csvPreference) { public void setCsvPreference(Preferences csvPreference) {
quoteChar.setText(Character.toString(csvPreference.getQuoteChar())); if (csvPreference.quoteChar() != null) {
delimiterChar.setText(Character.toString((char)csvPreference.getDelimiterChar())); quoteChar.setText(csvPreference.quoteChar().toString());
surroundingSpacesNeedQuotes.setSelected(csvPreference.isSurroundingSpacesNeedQuotes()); } else {
ignoreEmptyLines.setSelected(csvPreference.isIgnoreEmptyLines()); quoteChar.setText("");
quoteMode.getSelectionModel().select(getQuoteModeName(csvPreference.getQuoteMode())); }
endOfLineSymbols = csvPreference.getEndOfLineSymbols(); delimiterChar.setText(Character.toString(csvPreference.delimiterChar()));
ignoreEmptyLines.setSelected(csvPreference.ignoreEmptyLines());
endOfLineSymbols = csvPreference.endOfLineSymbols();
} }
public CsvPreference getCsvPreference() { public Preferences getCsvPreference() {
return new CsvPreference.Builder(quoteChar.getText().charAt(0), delimiterChar.getText().charAt(0), endOfLineSymbols) var quote = quoteChar.getText().length() == 0 ? null : quoteChar.getText().charAt(0);
.useQuoteMode(getQuoteMode(quoteMode.getSelectionModel().getSelectedItem())) return new Preferences(quote, delimiterChar.getText().charAt(0), endOfLineSymbols, ignoreEmptyLines.isSelected());
.surroundingSpacesNeedQuotes(surroundingSpacesNeedQuotes.isSelected()) }
.ignoreEmptyLines(ignoreEmptyLines.isSelected())
.build(); public void setFileEncoding(String fileEncoding) {
this.fileEncoding.setValue(fileEncoding);
}
public String getFileEncoding() {
return fileEncoding.getValue();
} }
public boolean getValid() { public boolean getValid() {

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -37,6 +37,7 @@ import ninja.javafx.smartcsv.fx.table.model.CSVValue;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import static javafx.application.Platform.runLater; import static javafx.application.Platform.runLater;
import static ninja.javafx.smartcsv.fx.util.ColorConstants.ERROR_COLOR;
import static ninja.javafx.smartcsv.fx.util.I18nValidationUtil.getI18nValidatioMessage; import static ninja.javafx.smartcsv.fx.util.I18nValidationUtil.getI18nValidatioMessage;
/** /**
@@ -77,7 +78,7 @@ public class EditableValidationCell extends TableCell<CSVRow, CSVValue> {
setStyle(""); setStyle("");
setTooltip(null); setTooltip(null);
} else if (item.getValidationError() != null) { } else if (item.getValidationError() != null) {
setStyle("-fx-background-color: #ff8888"); setStyle("-fx-background-color: derive("+ ERROR_COLOR +", 30%)");
setTooltip(new Tooltip(getI18nValidatioMessage(resourceBundle, item.getValidationError()))); setTooltip(new Tooltip(getI18nValidatioMessage(resourceBundle, item.getValidationError())));
} }
@@ -124,7 +125,7 @@ public class EditableValidationCell extends TableCell<CSVRow, CSVValue> {
}); });
} }
private class ValueTextField extends TextField { private static class ValueTextField extends TextField {
private CSVValue value; private CSVValue value;
public ValueTextField(CSVValue value) { public ValueTextField(CSVValue value) {

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -47,7 +47,6 @@ public class ObservableMapValueFactory implements
@Override @Override
public ObjectProperty<CSVValue> call(TableColumn.CellDataFeatures<CSVRow, CSVValue> features) { public ObjectProperty<CSVValue> call(TableColumn.CellDataFeatures<CSVRow, CSVValue> features) {
CSVRow row = features.getValue(); CSVRow row = features.getValue();
ObjectProperty<CSVValue> value = row.getColumns().get(key); return row.getColumns().get(key);
return value;
} }
} }

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -28,31 +28,40 @@ package ninja.javafx.smartcsv.fx.table.model;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import ninja.javafx.smartcsv.validation.RevalidationService;
import ninja.javafx.smartcsv.validation.configuration.ValidationConfiguration;
import ninja.javafx.smartcsv.validation.ValidationError; import ninja.javafx.smartcsv.validation.ValidationError;
import ninja.javafx.smartcsv.validation.Validator; import ninja.javafx.smartcsv.validation.Validator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/** /**
* The CSVModel is the client representation for the csv filepath. * The CSVModel is the client representation for the csv filepath.
* It holds the data in rows, stores the header and manages the validator. * It holds the data in rows, stores the header and manages the validator.
*/ */
public class CSVModel { public final class CSVModel implements ColumnValueProvider {
private static final Logger logger = LogManager.getLogger(CSVModel.class);
private Validator validator; private Validator validator;
private ObservableList<CSVRow> rows = FXCollections.observableArrayList(); private ObservableList<CSVRow> rows = FXCollections.observableArrayList();
private String[] header; private String[] header;
private ObservableList<ValidationError> validationError = FXCollections.observableArrayList(); private ObservableList<ValidationError> validationError = FXCollections.observableArrayList();
private RevalidationService revalidationService = new RevalidationService();
/** /**
* sets the validator for the data revalidates * sets the validator configuration for the data revalidates
* @param validator the validator for this data *
* @param validationConfiguration the validator configuration for this data
*/ */
public void setValidator(Validator validator) { public void setValidationConfiguration(ValidationConfiguration validationConfiguration) {
this.validator = validator; this.validator = new Validator(validationConfiguration, this);
revalidate(); revalidate();
} }
/** /**
* returns the data as a list of rows of the * returns the data as a list of rows of the
*
* @return list of rows * @return list of rows
*/ */
public ObservableList<CSVRow> getRows() { public ObservableList<CSVRow> getRows() {
@@ -65,18 +74,30 @@ public class CSVModel {
/** /**
* adds a new and empty row * adds a new and empty row
*
* @return the new row * @return the new row
*/ */
public CSVRow addRow() { public CSVRow addRow() {
CSVRow row = new CSVRow(); CSVRow row = new CSVRow();
row.setValidator(validator);
row.setRowNumber(rows.size()); row.setRowNumber(rows.size());
rows.add(row); rows.add(row);
return row; return row;
} }
public void addValue(final CSVRow row, final String column, final String value) {
final CSVValue csvValue = row.addValue(column, value);
csvValue.valueProperty().addListener(observable -> {
if (validator.needsColumnValidation(column)) {
revalidate();
} else {
csvValue.setValidationError(validator.isValid(row.getRowNumber(), column, csvValue.getValue()));
}
});
}
/** /**
* sets the column headers as string array * sets the column headers as string array
*
* @param header the headers of the columns * @param header the headers of the columns
*/ */
public void setHeader(String[] header) { public void setHeader(String[] header) {
@@ -86,6 +107,7 @@ public class CSVModel {
/** /**
* returns the column headers * returns the column headers
*
* @return the column headers * @return the column headers
*/ */
public String[] getHeader() { public String[] getHeader() {
@@ -93,41 +115,49 @@ public class CSVModel {
} }
public void revalidate(String column) {
if (!hasValidator()) return;
validator.reinitializeColumn(column);
revalidate();
}
/** /**
* walks through the data and validates each value * walks through the data and validates each value
*/ */
public void revalidate() { public void revalidate() {
validationError.clear(); validationError.clear();
if (header != null && validator != null) { logger.info("revalidate: hasValidator -> {}", hasValidator());
addValidationError(validator.isHeaderValid(header));
if (!hasValidator()) return;
revalidationService.setHeader(header);
revalidationService.setRows(rows);
revalidationService.setValidator(validator);
revalidationService.setOnSucceeded(t -> validationError.setAll(revalidationService.getValue()));
revalidationService.setOnFailed(t -> logger.error("revalidation service failed!"));
revalidationService.restart();
} }
for (int lineNumber = 0; lineNumber < rows.size(); lineNumber++) { private boolean hasValidator() {
CSVRow row = rows.get(lineNumber); return validator != null && validator.hasConfig();
row.setValidator(validator);
for (String column: row.getColumns().keySet()) {
CSVValue value = row.getColumns().get(column).getValue();
value.setValidator(validator);
if (validator != null) {
ValidationError validationError = validator.isValid(column, value.getValue(), lineNumber);
if (validationError != null) {
addValidationError(validationError);
value.setValidationError(validationError);
} else {
value.setValidationError(null);
}
} else {
value.setValidationError(null);
}
}
}
} }
private void addValidationError(ValidationError validationError) { public ValidationConfiguration createValidationConfiguration() {
if (validationError != null) { ValidationConfiguration newValidationConfiguration = new ValidationConfiguration();
this.validationError.add(validationError); newValidationConfiguration.setHeaderNames(this.header);
} this.validator = new Validator(newValidationConfiguration, this);
this.revalidate();
return newValidationConfiguration;
} }
@Override
public String getValue(int row, String column) {
return rows.get(row).getColumns().get(column).getValue().getValue();
}
@Override
public int getNumberOfRows() {
return rows.size();
}
} }

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -30,24 +30,14 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableMap; import javafx.collections.ObservableMap;
import ninja.javafx.smartcsv.validation.Validator;
/** /**
* This class represents a single row in the csv file. * This class represents a single row in the csv file.
*/ */
public class CSVRow { public class CSVRow {
private Validator validator;
private ObservableMap<String, ObjectProperty<CSVValue>> columns = FXCollections.observableHashMap(); private ObservableMap<String, ObjectProperty<CSVValue>> columns = FXCollections.observableHashMap();
private int rowNumber; private int rowNumber;
/**
* single row
* @param validator the reference to the validator
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
/** /**
* sets the row number * sets the row number
* @param rowNumber * @param rowNumber
@@ -78,13 +68,11 @@ public class CSVRow {
* @param column column name * @param column column name
* @param value the value to store * @param value the value to store
*/ */
public void addValue(String column, String value) { CSVValue addValue(String column, String value) {
CSVValue v = new CSVValue(); CSVValue v = new CSVValue();
v.setValidator(validator);
v.setValue(value); v.setValue(value);
v.setColumn(column);
v.setRowNumber(rowNumber);
columns.put(column, new SimpleObjectProperty<>(v)); columns.put(column, new SimpleObjectProperty<>(v));
return v;
} }
} }

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -29,7 +29,6 @@ package ninja.javafx.smartcsv.fx.table.model;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import ninja.javafx.smartcsv.validation.ValidationError; import ninja.javafx.smartcsv.validation.ValidationError;
import ninja.javafx.smartcsv.validation.Validator;
/** /**
* The csv value represents the value of a single cell. * The csv value represents the value of a single cell.
@@ -37,36 +36,9 @@ import ninja.javafx.smartcsv.validation.Validator;
* and if the value is valid based on the validator. * and if the value is valid based on the validator.
*/ */
public class CSVValue { public class CSVValue {
private Validator validator;
private int rowNumber;
private String column;
private StringProperty value = new SimpleStringProperty(); private StringProperty value = new SimpleStringProperty();
private ValidationError valid; private ValidationError valid;
/**
* single value of a cell
* @param validator the reference to the validator
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
/**
* the row number this value is stored in
* @param row row number
*/
public void setRowNumber(int row) {
this.rowNumber = row;
}
/**
* the column this value is stored in
* @param column header name of the column
*/
public void setColumn(String column) {
this.column = column;
}
/** /**
* returns the real value * returns the real value
* @return the real value * @return the real value
@@ -88,9 +60,6 @@ public class CSVValue {
* @param value the real value * @param value the real value
*/ */
public void setValue(String value) { public void setValue(String value) {
if (validator != null) {
valid = validator.isValid(column, value, rowNumber);
}
this.value.set(value); this.value.set(value);
} }
@@ -106,7 +75,7 @@ public class CSVValue {
* sets the state if a value is valid or not * sets the state if a value is valid or not
* @param valid the validation state * @param valid the validation state
*/ */
protected void setValidationError(ValidationError valid) { public void setValidationError(ValidationError valid) {
this.valid = valid; this.valid = valid;
} }
} }

View File

@@ -0,0 +1,37 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.fx.table.model;
/**
* interface for easier access to values in a column
*/
public interface ColumnValueProvider {
String getValue(int row, String column);
int getNumberOfRows();
}

View File

@@ -0,0 +1,35 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.fx.util;
/**
* Collection of all the important colors
*/
public class ColorConstants {
public static final String OK_COLOR = "#22aa22";
public static final String ERROR_COLOR = "#ff3333";
}

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -40,22 +40,44 @@ import static java.text.MessageFormat.format;
*/ */
public class I18nValidationUtil { public class I18nValidationUtil {
public static String getI18nValidatioMessage(ResourceBundle resourceBundle, List<ValidationError> errors) {
StringWriter message = new StringWriter();
errors.forEach(error -> message.append(getI18nValidatioMessage(resourceBundle, error)).append("\n"));
if (message.toString().length() != 0) {
return cutOffLastLineBreak(message.toString());
}
return "";
}
public static String getI18nValidatioMessageWithColumn(ResourceBundle resourceBundle, ValidationError error) {
return getI18nValidatioMessage(resourceBundle, error, resourceBundle.getString("column") + " " + error.getColumn() + " : ");
}
public static String getI18nValidatioMessage(ResourceBundle resourceBundle, ValidationError error) { public static String getI18nValidatioMessage(ResourceBundle resourceBundle, ValidationError error) {
return getI18nValidatioMessage(resourceBundle, error, "");
}
private static String getI18nValidatioMessage(ResourceBundle resourceBundle, ValidationError error, String prefix) {
List<ValidationMessage> validationMessages = error.getMessages(); List<ValidationMessage> validationMessages = error.getMessages();
StringWriter message = new StringWriter(); StringWriter message = new StringWriter();
for (ValidationMessage validationMessage: validationMessages) { validationMessages.forEach(validationMessage -> {
message.append(prefix);
if (resourceBundle.containsKey(validationMessage.getKey())) { if (resourceBundle.containsKey(validationMessage.getKey())) {
String resourceText = resourceBundle.getString(validationMessage.getKey()); String resourceText = resourceBundle.getString(validationMessage.getKey());
if (validationMessage.getParameters().length > 0) { if (validationMessage.getParameters().length > 0) {
message.append(format(resourceText, validationMessage.getParameters())).append("\n"); message.append(format(resourceText, (Object[]) validationMessage.getParameters())).append("\n");
} else { } else {
message.append(resourceText).append("\n"); message.append(resourceText).append("\n");
} }
} else { } else {
message.append(validationMessage.getKey()).append("\n"); message.append(validationMessage.getKey()).append("\n");
} }
} });
if (!validationMessages.isEmpty()) { if (!validationMessages.isEmpty()) {
return cutOffLastLineBreak(message.toString()); return cutOffLastLineBreak(message.toString());

View File

@@ -0,0 +1,73 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.fx.util;
import javafx.concurrent.Worker;
import javafx.concurrent.WorkerStateEvent;
import javafx.scene.control.Alert;
import ninja.javafx.smartcsv.fx.SmartCSVController;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* This class helps extract useful messages from exceptions
* TODO: make I18n maybe?
*/
public final class JavaFxUtils {
private static final Logger logger = LogManager.getLogger(SmartCSVController.class);
private JavaFxUtils() {
}
public static void onServiceError(WorkerStateEvent event, String windowTitle, String errorHeader) {
onServiceError(event, windowTitle, errorHeader, () -> {
});
}
public static void onServiceError(WorkerStateEvent event, String windowTitle, String errorHeader, Runnable rollbackAction) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle(windowTitle);
alert.setHeaderText(errorHeader);
alert.setContentText(findExceptionMessage(event.getSource()));
alert.showAndWait();
}
private static String findExceptionMessage(Worker<?> source) {
if (source == null) {
return "Cannot identify the source of the event!";
} else if (source.getException() == null) {
return "The event did not have an exception?";
}
Throwable exception = source.getException();
logger.error("Error in " + source.getClass().getSimpleName(), exception);
if (exception.getMessage() == null) {
return "Exception of type " + exception.getClass() + " had no message, check the logs.";
}
return exception.getMessage();
}
}

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -28,33 +28,27 @@ package ninja.javafx.smartcsv.fx.util;
import javafx.concurrent.Service; import javafx.concurrent.Service;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import ninja.javafx.smartcsv.FileReader; import ninja.javafx.smartcsv.files.FileStorage;
import java.io.File;
/** /**
* Service class for async load of a csv file * Service class for async load of a csv file
*/ */
@org.springframework.stereotype.Service @org.springframework.stereotype.Service
public class LoadFileService extends Service { public class LoadFileService extends Service<Void> {
private File file; private FileStorage<?> file;
private FileReader fileReader;
public void setFile(File value) { public void setFileStorage(FileStorage<?> file) {
file = value; this.file = file;
}
public void setFileReader(FileReader fileReader) {
this.fileReader = fileReader;
} }
@Override @Override
protected Task createTask() { protected Task<Void> createTask() {
return new Task() { return new Task<>() {
@Override @Override
protected Void call() throws Exception { protected Void call() throws Exception {
if (file != null) { if (file != null) {
fileReader.read(file); file.load();
} }
return null; return null;
} }

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -28,37 +28,27 @@ package ninja.javafx.smartcsv.fx.util;
import javafx.concurrent.Service; import javafx.concurrent.Service;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import ninja.javafx.smartcsv.FileWriter; import ninja.javafx.smartcsv.files.FileStorage;
import ninja.javafx.smartcsv.csv.CSVFileWriter;
import java.io.File;
import static javafx.application.Platform.runLater;
/** /**
* Service class for async load of a csv file * Service class for async save of a csv file
*/ */
@org.springframework.stereotype.Service @org.springframework.stereotype.Service
public class SaveFileService extends Service { public class SaveFileService extends Service<Void> {
private File file; private FileStorage<?> file;
private FileWriter writer;
public void setWriter(FileWriter writer) { public void setFileStorage(FileStorage<?> value) {
this.writer = writer;
}
public void setFile(File value) {
file = value; file = value;
} }
@Override @Override
protected Task createTask() { protected Task<Void> createTask() {
return new Task() { return new Task<>() {
@Override @Override
protected Void call() throws Exception { protected Void call() {
try { try {
writer.write(file); file.save();
} catch (Throwable ex) { } catch (Throwable ex) {
ex.printStackTrace(); ex.printStackTrace();
} }

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -24,47 +24,38 @@
*/ */
package ninja.javafx.smartcsv.fx.list; package ninja.javafx.smartcsv.fx.validation;
import javafx.scene.control.ListCell; import javafx.scene.control.ListCell;
import javafx.scene.text.Text; import javafx.scene.control.ListView;
import ninja.javafx.smartcsv.validation.ValidationError; import javafx.util.Callback;
import ninja.javafx.smartcsv.validation.configuration.StringFormat;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import static ninja.javafx.smartcsv.fx.util.I18nValidationUtil.getI18nValidatioMessage;
/** /**
* Cell to show the error text * cell factory for string formats
*/ */
public class ValidationErrorListCell extends ListCell<ValidationError> { public class StringFormatEditorCellFactory implements Callback<ListView<StringFormat>, ListCell<StringFormat>> {
private ResourceBundle resourceBundle; private ResourceBundle resourceBundle;
public ValidationErrorListCell(ResourceBundle resourceBundle) { public StringFormatEditorCellFactory(ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle; this.resourceBundle = resourceBundle;
} }
@Override @Override
public void updateItem(ValidationError validationError, boolean empty) { public ListCell<StringFormat> call(ListView<StringFormat> param) {
super.updateItem(validationError, empty); return new ListCell<StringFormat>(){
if (empty) { @Override
clearContent(); protected void updateItem(StringFormat item, boolean empty) {
} else { super.updateItem(item, empty);
addContent(validationError); if (item == null || empty) {
}
}
private void clearContent() {
setText(null);
setGraphic(null); setGraphic(null);
} } else {
setText(resourceBundle.getString("format.type."+item));
private void addContent(ValidationError validationError) { }
setText(null); }
Text text = new Text(getI18nValidatioMessage(resourceBundle, validationError)); } ;
text.setWrappingWidth(180);
setGraphic(text);
} }
} }

View File

@@ -0,0 +1,54 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.fx.validation;
import javafx.util.StringConverter;
import ninja.javafx.smartcsv.validation.configuration.StringFormat;
import java.util.ResourceBundle;
/**
* converter for string formats
*/
public class StringFormatStringConverter extends StringConverter<StringFormat> {
private ResourceBundle resourceBundle;
public StringFormatStringConverter(ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
}
@Override
public String toString(StringFormat item) {
return resourceBundle.getString("format.type."+item);
}
@Override
public StringFormat fromString(String string) {
return StringFormat.fromExternalValue(string);
}
}

View File

@@ -0,0 +1,507 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.fx.validation;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.SpinnerValueFactory.IntegerSpinnerValueFactory;
import ninja.javafx.smartcsv.fx.FXMLController;
import ninja.javafx.smartcsv.validation.configuration.*;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory;
import org.fxmisc.richtext.model.StyleSpans;
import org.fxmisc.richtext.model.StyleSpansBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.lang.Boolean.FALSE;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.joining;
import static javafx.beans.binding.Bindings.when;
import static ninja.javafx.smartcsv.validation.configuration.Type.STRING;
/**
* controller for editing column validations
*
* RichText groovy highlighting code is based on the java example of
* https://github.com/TomasMikula/RichTextFX
*/
@Component
public class ValidationEditorController extends FXMLController {
private StringProperty selectedColumn = new SimpleStringProperty();
private ValidationConfiguration validationConfiguration;
private static final String[] KEYWORDS = new String[] {
"abstract", "assert", "boolean", "break", "byte",
"case", "catch", "char", "class",
"continue", "def", "default", "do", "double", "else",
"enum", "extends", "false", "final", "finally", "float",
"for", "if", "implements", "import", "in",
"instanceof", "int", "interface", "length", "long", "native",
"new", "null", "package", "private", "property", "protected", "public",
"return", "short", "static", "super",
"switch", "synchronized", "this", "threadsafe", "throw", "throws",
"transient", "true", "try", "void", "volatile", "while"
};
private static final String KEYWORD_PATTERN = "\\b(" + String.join("|", (CharSequence[]) KEYWORDS) + ")\\b";
private static final String PAREN_PATTERN = "\\(|\\)";
private static final String BRACE_PATTERN = "\\{|\\}";
private static final String BRACKET_PATTERN = "\\[|\\]";
private static final String SEMICOLON_PATTERN = "\\;";
private static final String STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\"";
private static final String STRING2_PATTERN = "'([^'\\\\]|\\\\.)*'";
private static final String COMMENT_PATTERN = "//[^\n]*" + "|" + "/\\*(.|\\R)*?\\*/";
private static final Pattern PATTERN = Pattern.compile(
"(?<KEYWORD>" + KEYWORD_PATTERN + ")"
+ "|(?<PAREN>" + PAREN_PATTERN + ")"
+ "|(?<BRACE>" + BRACE_PATTERN + ")"
+ "|(?<BRACKET>" + BRACKET_PATTERN + ")"
+ "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")"
+ "|(?<STRING>" + STRING_PATTERN + ")"
+ "|(?<STRING2>" + STRING2_PATTERN + ")"
+ "|(?<COMMENT>" + COMMENT_PATTERN + ")"
);
@FXML
private ComboBox<Type> typeComboBox;
@FXML
private ComboBox<StringFormat> formatComboBox;
@FXML
private TextField formatTextField;
@FXML
private Spinner<Integer> minLengthSpinner;
@FXML
private Spinner<Integer> maxLengthSpinner;
@FXML
private TextField regexpRuleTextField;
@FXML
private TextField valueOfRuleTextField;
@FXML
private CodeArea groovyRuleTextArea;
@FXML
private CheckBox enableNotEmptyRule;
@FXML
private CheckBox enableMinLengthRule;
@FXML
private CheckBox enableMaxLengthRule;
@FXML
private CheckBox enableRegexpRule;
@FXML
private CheckBox enableValueOfRule;
@FXML
private CheckBox enableGroovyRule;
@FXML
private CheckBox enableUniqueRule;
@Value("${fxml.smartcvs.validation.editor.view}")
@Override
public void setFxmlFilePath(String filePath) {
this.fxmlFilePath = filePath;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
initTypeAndFormatInput(resources);
initMinMaxSpinner();
initSpinner(minLengthSpinner, enableMinLengthRule);
initSpinner(maxLengthSpinner, enableMaxLengthRule);
initTextInputControl(regexpRuleTextField, enableRegexpRule);
initTextInputControl(valueOfRuleTextField, enableValueOfRule);
initCodeAreaControl(groovyRuleTextArea, enableGroovyRule);
}
public String getSelectedColumn() {
return selectedColumn.get();
}
public StringProperty selectedColumnProperty() {
return selectedColumn;
}
public void setSelectedColumn(String selectedColumn) {
this.selectedColumn.set(selectedColumn);
}
public void setValidationConfiguration(ValidationConfiguration validationConfiguration) {
this.validationConfiguration = validationConfiguration;
}
private void initTypeAndFormatInput(ResourceBundle resources) {
typeComboBox.getItems().addAll(STRING,
Type.INTEGER,
Type.NUMBER,
Type.DATE,
Type.DATETIME,
Type.TIME);
formatComboBox.setCellFactory(new StringFormatEditorCellFactory(resources));
formatComboBox.setConverter(new StringFormatStringConverter(resources));
formatComboBox.getItems().addAll(
StringFormat.DEFAULT,
StringFormat.EMAIL,
StringFormat.URI,
StringFormat.BINARY,
StringFormat.UUID
);
typeComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> changeFormat());
}
private void updateFormatTextField() {
switch (typeComboBox.getValue()) {
case DATE:
case DATETIME:
case TIME:
formatTextField.setVisible(true);
formatTextField.setText(getCurrentFieldConfig().getFormat());
break;
default:
formatTextField.setVisible(false);
formatTextField.setText(null);
break;
}
}
private void updateFormatComboBox() {
switch (typeComboBox.getValue()) {
case STRING:
formatComboBox.setVisible(true);
formatComboBox.getSelectionModel().select(StringFormat.fromExternalValue(getCurrentFieldConfig().getFormat()));
break;
default:
formatComboBox.setVisible(false);
break;
}
}
private void initMinMaxSpinner() {
IntegerSpinnerValueFactory minValueFactory = new IntegerSpinnerValueFactory(0, Integer.MAX_VALUE, 0);
minLengthSpinner.setValueFactory(minValueFactory);
IntegerSpinnerValueFactory maxValueFactory = new IntegerSpinnerValueFactory(0, Integer.MAX_VALUE, 0);
maxLengthSpinner.setValueFactory(maxValueFactory);
minValueFactory.maxProperty().bind(
when(enableMaxLengthRule.selectedProperty()).
then(maxLengthSpinner.valueProperty()).
otherwise(Integer.MAX_VALUE));
maxValueFactory.minProperty().bind(
when(enableMinLengthRule.selectedProperty()).
then(minLengthSpinner.valueProperty()).
otherwise(0));
}
private void changeFormat() {
switch (typeComboBox.getValue()) {
case STRING:
updateFormatComboBox();
break;
case DATE:
case DATETIME:
case TIME:
updateFormatTextField();
break;
case INTEGER:
case NUMBER:
default:
// format: no options
formatComboBox.setVisible(false);
formatTextField.setVisible(false);
break;
}
}
public void updateConfiguration() {
Field config = getCurrentFieldConfig();
config.setType(typeComboBox.getValue());
switch (typeComboBox.getValue()) {
case STRING:
config.setFormat(formatComboBox.getValue().getExternalValue());
break;
case DATE:
case DATETIME:
case TIME:
if (formatTextField.getText().trim().isEmpty()) {
config.setFormat(null);
} else {
// TODO: validate input
config.setFormat(formatTextField.getText());
}
break;
case INTEGER:
case NUMBER:
default:
// format: no options
config.setFormat(null);
break;
}
if (enableGroovyRule.isSelected()) {
config.setGroovy(groovyRuleTextArea.getText());
} else {
config.setGroovy(null);
}
Constraints constraints = config.getConstraints();
if (constraints == null) {
constraints = new Constraints();
config.setConstraints(constraints);
}
if (enableNotEmptyRule.isSelected()) {
constraints.setRequired(enableNotEmptyRule.isSelected());
} else {
constraints.setRequired(null);
}
if (enableUniqueRule.isSelected()) {
constraints.setUnique(enableUniqueRule.isSelected());
} else {
constraints.setUnique(null);
}
if (enableMinLengthRule.isSelected()) {
constraints.setMinLength(minLengthSpinner.getValue());
} else {
constraints.setMinLength(null);
}
if (enableMaxLengthRule.isSelected()) {
constraints.setMaxLength(maxLengthSpinner.getValue());
} else {
constraints.setMaxLength(null);
}
if (enableRegexpRule.isSelected()) {
constraints.setPattern(regexpRuleTextField.getText());
} else {
constraints.setPattern(null);
}
if (enableValueOfRule.isSelected()) {
constraints.setEnumeration(asList(valueOfRuleTextField.getText().split(", ")));
} else {
constraints.setEnumeration(null);
}
}
private Field getCurrentFieldConfig() {
return validationConfiguration.getFieldConfiguration(getSelectedColumn());
}
private void addDependencyListener(CheckBox rule, CheckBox... dependentRules) {
rule.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
for (CheckBox dependentRule: dependentRules) {
dependentRule.selectedProperty().setValue(false);
}
}
});
}
public void updateForm() {
Field config = getCurrentFieldConfig();
if (config.getType() != null) {
typeComboBox.setValue(config.getType());
} else {
typeComboBox.setValue(STRING);
}
updateFormatComboBox();
updateFormatTextField();
updateCodeAreaControl(
groovyRuleTextArea,
config.getGroovy(),
enableGroovyRule
);
Constraints constraints = config.getConstraints();
updateCheckBox(constraints != null ? constraints.getRequired() : FALSE, enableNotEmptyRule);
updateCheckBox(constraints != null ? constraints.getUnique() : FALSE, enableUniqueRule);
updateSpinner(
minLengthSpinner,
constraints != null ? constraints.getMinLength() : null,
enableMinLengthRule
);
updateSpinner(
maxLengthSpinner,
constraints != null ? constraints.getMaxLength() : null,
enableMaxLengthRule
);
updateTextInputControl(
regexpRuleTextField,
constraints != null ? constraints.getPattern() : null,
enableRegexpRule
);
updateTextInputControl(
valueOfRuleTextField,
constraints != null ? constraints.getEnumeration() : null,
enableValueOfRule
);
}
private void updateCheckBox(Boolean value, CheckBox ruleEnabled) {
if (value == null) {
ruleEnabled.setSelected(false);
} else {
ruleEnabled.setSelected(value);
}
}
private void updateSpinner(Spinner rule, Integer value, CheckBox ruleEnabled) {
if (value == null) {
ruleEnabled.setSelected(false);
} else {
ruleEnabled.setSelected(true);
rule.getValueFactory().setValue(value);
}
}
private void updateTextInputControl(TextInputControl rule, String value, CheckBox ruleEnabled) {
if (value == null) {
ruleEnabled.setSelected(false);
} else {
ruleEnabled.setSelected(true);
rule.setText(value);
}
}
private void updateTextInputControl(TextInputControl rule, List<String> values, CheckBox ruleEnabled) {
if (values == null || values.isEmpty()) {
ruleEnabled.setSelected(false);
} else {
ruleEnabled.setSelected(true);
rule.setText(values.stream().collect(joining(", ")));
}
}
private void updateCodeAreaControl(CodeArea rule, String value, CheckBox ruleEnabled) {
if (value == null) {
ruleEnabled.setSelected(false);
} else {
ruleEnabled.setSelected(true);
rule.replaceText(0, rule.getLength(), value);
}
}
private void initSpinner(Spinner rule, CheckBox ruleEnabled) {
rule.disableProperty().bind(ruleEnabled.selectedProperty().not());
ruleEnabled.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
rule.getValueFactory().setValue(0);
}
});
}
private void initTextInputControl(TextInputControl rule, CheckBox ruleEnabled) {
rule.disableProperty().bind(ruleEnabled.selectedProperty().not());
ruleEnabled.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
rule.setText("");
}
});
}
private void initCodeAreaControl(CodeArea rule, CheckBox ruleEnabled) {
rule.disableProperty().bind(ruleEnabled.selectedProperty().not());
ruleEnabled.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
rule.clear();
}
});
rule.setParagraphGraphicFactory(LineNumberFactory.get(rule));
rule.richChanges()
.filter(ch -> !ch.getInserted().equals(ch.getRemoved())) // XXX
.subscribe(change -> {
rule.setStyleSpans(0, computeHighlighting(rule.getText()));
});
}
private static StyleSpans<Collection<String>> computeHighlighting(String text) {
Matcher matcher = PATTERN.matcher(text);
int lastKwEnd = 0;
StyleSpansBuilder<Collection<String>> spansBuilder
= new StyleSpansBuilder<>();
while(matcher.find()) {
String styleClass =
matcher.group("KEYWORD") != null ? "keyword" :
matcher.group("PAREN") != null ? "paren" :
matcher.group("BRACE") != null ? "brace" :
matcher.group("BRACKET") != null ? "bracket" :
matcher.group("SEMICOLON") != null ? "semicolon" :
matcher.group("STRING") != null ? "string" :
matcher.group("STRING2") != null ? "string" :
matcher.group("COMMENT") != null ? "comment" :
null; /* never happens */ assert styleClass != null;
spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd);
spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start());
lastKwEnd = matcher.end();
}
spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd);
return spansBuilder.create();
}
}

View File

@@ -0,0 +1,44 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.preferences;
import java.nio.charset.Charset;
/**
* Helper class for getting charset's name
*/
public class CharsetHelper {
public static String getCharsetName(String name) {
try {
return Charset.forName(name).name();
} catch (Exception e) {
return Charset.defaultCharset().name();
}
}
}

View File

@@ -0,0 +1,62 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.preferences;
import com.google.gson.GsonBuilder;
import ninja.javafx.smartcsv.FileReader;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
/**
* file reader for the file encoding
*/
public class EncodingFileReader implements FileReader<String> {
private String fileEncoding;
public EncodingFileReader() {
fileEncoding = Charset.defaultCharset().name();
}
@Override
public String getContent() {
return fileEncoding;
}
@Override
public void read(File filename) throws IOException {
Map settings = new GsonBuilder().create().fromJson(new java.io.FileReader(filename), HashMap.class);
if (settings != null) {
fileEncoding = CharsetHelper.getCharsetName(settings.get("fileEncoding").toString());
}
}
}

View File

@@ -0,0 +1,59 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.preferences;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import ninja.javafx.smartcsv.FileWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
/**
* Save the file encoding to configuration file
*/
public class EncodingFileWriter implements FileWriter<String> {
private String fileEncoding;
@Override
public void setContent(String fileEncoding) {
this.fileEncoding = fileEncoding;
}
@Override
public void write(File filename) throws IOException {
Map<String, String> encodings = new HashMap<>();
encodings.put("fileEncoding", CharsetHelper.getCharsetName(fileEncoding));
Gson gson = new GsonBuilder().setPrettyPrinting().create();
Files.write(filename.toPath(), gson.toJson(encodings).getBytes());
}
}

View File

@@ -0,0 +1,38 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.preferences;
public record Preferences(
Character quoteChar,
char delimiterChar,
String endOfLineSymbols,
boolean ignoreEmptyLines) {
public static Preferences defaultPreferences() {
return new Preferences('\"',',', "\n", true);
}
}

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -26,54 +26,41 @@
package ninja.javafx.smartcsv.preferences; package ninja.javafx.smartcsv.preferences;
import com.typesafe.config.Config; import com.google.gson.GsonBuilder;
import com.typesafe.config.ConfigFactory;
import ninja.javafx.smartcsv.FileReader; import ninja.javafx.smartcsv.FileReader;
import org.springframework.stereotype.Service;
import org.supercsv.prefs.CsvPreference;
import org.supercsv.quote.AlwaysQuoteMode;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static ninja.javafx.smartcsv.preferences.QuoteModeHelper.getQuoteMode; import static ninja.javafx.smartcsv.preferences.Preferences.defaultPreferences;
/** /**
* file reader for the preferences * file reader for the preferences
*/ */
@Service public class PreferencesFileReader implements FileReader<Preferences> {
public class PreferencesFileReader implements FileReader {
private Config config; private Preferences csvPreference;
private CsvPreference csvPreference;
public PreferencesFileReader() { public PreferencesFileReader() {
csvPreference = new CsvPreference. csvPreference = defaultPreferences();
Builder(CsvPreference.EXCEL_NORTH_EUROPE_PREFERENCE).
useQuoteMode(new AlwaysQuoteMode()).build();
} }
@Override @Override
public void read(File filename) throws IOException { public void read(File filename) throws IOException {
config = ConfigFactory.parseFile(filename); Map config = new GsonBuilder().create().fromJson(new java.io.FileReader(filename), HashMap.class);
if (config != null) { if (config != null) {
char quoteChar = config.getString("quoteChar").charAt(0); Character quoteChar = config.get("quoteChar") == null ? null : config.get("quoteChar").toString().charAt(0);
char delimiterChar = config.getString("delimiterChar").charAt(0); char delimiterChar = config.get("delimiterChar").toString().charAt(0);
String endOfLineSymbols = config.getString("endOfLineSymbols"); String endOfLineSymbols = config.get("endOfLineSymbols").toString();
boolean surroundingSpacesNeedQuotes = config.getBoolean("surroundingSpacesNeedQuotes"); boolean ignoreEmptyLines = (Boolean) config.get("ignoreEmptyLines");
boolean ignoreEmptyLines = config.getBoolean("ignoreEmptyLines"); csvPreference = new Preferences(quoteChar, delimiterChar, endOfLineSymbols, ignoreEmptyLines);
String quoteMode = config.getString("quoteMode");
csvPreference = new CsvPreference.Builder(quoteChar, delimiterChar, endOfLineSymbols)
.useQuoteMode(getQuoteMode(quoteMode))
.surroundingSpacesNeedQuotes(surroundingSpacesNeedQuotes)
.ignoreEmptyLines(ignoreEmptyLines)
.build();
} }
} }
public CsvPreference getCSVpreference() { public Preferences getContent() {
return csvPreference; return csvPreference;
} }

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -26,12 +26,9 @@
package ninja.javafx.smartcsv.preferences; package ninja.javafx.smartcsv.preferences;
import com.typesafe.config.Config; import com.google.gson.Gson;
import com.typesafe.config.ConfigFactory; import com.google.gson.GsonBuilder;
import com.typesafe.config.ConfigRenderOptions;
import ninja.javafx.smartcsv.FileWriter; import ninja.javafx.smartcsv.FileWriter;
import org.springframework.stereotype.Service;
import org.supercsv.prefs.CsvPreference;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -42,26 +39,23 @@ import java.util.Map;
/** /**
* Save preferences to configuration file * Save preferences to configuration file
*/ */
@Service public class PreferencesFileWriter implements FileWriter<Preferences> {
public class PreferencesFileWriter implements FileWriter {
private CsvPreference csvPreference; private Preferences csvPreference;
public void setCsvPreference(CsvPreference csvPreference) { public void setContent(Preferences csvPreference) {
this.csvPreference = csvPreference; this.csvPreference = csvPreference;
} }
public void write(File file) throws IOException { public void write(File file) throws IOException {
Map<String, Object> preferences = new HashMap<>(); Map<String, Object> preferences = new HashMap<>();
preferences.put("quoteChar", Character.toString(csvPreference.getQuoteChar())); preferences.put("quoteChar", csvPreference.quoteChar() == null ? null: csvPreference.quoteChar().toString());
preferences.put("delimiterChar", Character.toString((char)csvPreference.getDelimiterChar())); preferences.put("delimiterChar", Character.toString((char)csvPreference.delimiterChar()));
preferences.put("endOfLineSymbols", csvPreference.getEndOfLineSymbols()); preferences.put("endOfLineSymbols", csvPreference.endOfLineSymbols());
preferences.put("surroundingSpacesNeedQuotes", csvPreference.isSurroundingSpacesNeedQuotes()); preferences.put("ignoreEmptyLines", csvPreference.ignoreEmptyLines());
preferences.put("ignoreEmptyLines", csvPreference.isIgnoreEmptyLines());
preferences.put("quoteMode", QuoteModeHelper.getQuoteModeName(csvPreference.getQuoteMode()));
Config config = ConfigFactory.parseMap(preferences); Gson gson = new GsonBuilder().setPrettyPrinting().create();
Files.write(file.toPath(), config.root().render(ConfigRenderOptions.concise()).getBytes()); Files.write(file.toPath(), gson.toJson(preferences).getBytes());
} }
} }

View File

@@ -0,0 +1,110 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation;
import javafx.beans.property.ObjectProperty;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import ninja.javafx.smartcsv.fx.table.model.CSVRow;
import ninja.javafx.smartcsv.fx.table.model.CSVValue;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Service for running the validation async of the ui thread
*/
public class RevalidationService extends Service<List<ValidationError>> {
private static final Logger logger = LogManager.getLogger(RevalidationService.class);
private Validator validator;
private List<CSVRow> rows;
private String[] header;
public void setValidator(Validator validator) {
this.validator = validator;
}
public void setRows(List<CSVRow> rows) {
this.rows = rows;
}
public void setHeader(String[] header) {
this.header = header;
}
@Override
protected Task<List<ValidationError>> createTask() {
return new Task<List<ValidationError>>() {
@Override
protected List<ValidationError> call() throws Exception {
List<ValidationError> errors = new ArrayList<>();
try {
if (header != null) {
ValidationError headerError = validator.isHeaderValid(header);
if (headerError != null) {
logger.info("revalidate: header error found");
errors.add(headerError);
}
}
int maxRows = rows.size();
for (int lineNumber = 0; lineNumber < maxRows; lineNumber++) {
CSVRow row = rows.get(lineNumber);
Map<String, ObjectProperty<CSVValue>> table = row.getColumns();
Set<String> columns = table.keySet();
for (String column : columns) {
CSVValue value = table.get(column).getValue();
if (validator != null) {
ValidationError validationError = validator.isValid(lineNumber, column, value.getValue());
if (validationError != null) {
logger.info("revalidate: {} errors found in line {}", validationError.getMessages().size(), lineNumber);
errors.add(validationError);
value.setValidationError(validationError);
} else {
value.setValidationError(null);
}
} else {
value.setValidationError(null);
}
}
}
} catch (Throwable t) {
logger.error("validation error", t);
}
return errors;
}
};
}
}

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -38,6 +38,7 @@ public class ValidationError {
private List<ValidationMessage> messages = new ArrayList<>(); private List<ValidationMessage> messages = new ArrayList<>();
private Integer lineNumber; private Integer lineNumber;
private String column = "";
private ValidationError(Integer lineNumber) { private ValidationError(Integer lineNumber) {
this.lineNumber = lineNumber; this.lineNumber = lineNumber;
@@ -51,10 +52,19 @@ public class ValidationError {
return new ValidationError(-1); return new ValidationError(-1);
} }
public ValidationError column(String column) {
this.column = column;
return this;
}
public Integer getLineNumber() { public Integer getLineNumber() {
return lineNumber; return lineNumber;
} }
public String getColumn() {
return column;
}
public List<ValidationMessage> getMessages() { public List<ValidationMessage> getMessages() {
return messages; return messages;
} }

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -26,28 +26,53 @@
package ninja.javafx.smartcsv.validation; package ninja.javafx.smartcsv.validation;
import com.typesafe.config.Config; import com.google.gson.GsonBuilder;
import com.typesafe.config.ConfigFactory;
import ninja.javafx.smartcsv.FileReader; import ninja.javafx.smartcsv.FileReader;
import org.springframework.stereotype.Service; import ninja.javafx.smartcsv.validation.configuration.Field;
import ninja.javafx.smartcsv.validation.configuration.ValidationConfiguration;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import static ninja.javafx.smartcsv.validation.configuration.Type.*;
/** /**
* This class loads the constraints as json config * This class loads the constraints as json config
*/ */
@Service public class ValidationFileReader implements FileReader<ValidationConfiguration> {
public class ValidationFileReader implements FileReader {
private Config config; private ValidationConfiguration config;
@Override @Override
public void read(File file) throws IOException { public void read(File file) throws IOException {
config = ConfigFactory.parseFile(file); config = new GsonBuilder().create().fromJson(new java.io.FileReader(file), ValidationConfiguration.class);
setDefaults();
} }
public Validator getValidator() { private void setDefaults() {
return new Validator(config); for (Field field : config.getFields()) {
if (field.getType() == null) {
field.setType(STRING);
}
if (field.getType() == DATE) {
if (field.getFormat() == null) {
field.setFormat("yyyy-MM-dd");
}
}
if (field.getType() == DATETIME) {
if (field.getFormat() == null) {
field.setFormat("yyyy-MM-ddThh:mm:ssZ");
}
}
if (field.getType() == TIME) {
if (field.getFormat() == null) {
field.setFormat("hh:mm:ss");
}
}
}
}
public ValidationConfiguration getContent() {
return config;
} }
} }

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -24,33 +24,31 @@
*/ */
package ninja.javafx.smartcsv.preferences; package ninja.javafx.smartcsv.validation;
import org.supercsv.quote.AlwaysQuoteMode; import com.google.gson.Gson;
import org.supercsv.quote.ColumnQuoteMode; import com.google.gson.GsonBuilder;
import org.supercsv.quote.NormalQuoteMode; import ninja.javafx.smartcsv.FileWriter;
import org.supercsv.quote.QuoteMode; import ninja.javafx.smartcsv.validation.configuration.ValidationConfiguration;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
/** /**
* Helper class for mapping quote quoteModeName * file writer for the validation configuration
*/ */
public class QuoteModeHelper { public class ValidationFileWriter implements FileWriter<ValidationConfiguration> {
public static QuoteMode getQuoteMode(String quoteModeName) { private ValidationConfiguration validationConfiguration;
switch (quoteModeName) {
case "always": return new AlwaysQuoteMode(); public void setContent(ValidationConfiguration validationConfiguration) {
case "column": return new ColumnQuoteMode(); this.validationConfiguration = validationConfiguration;
default: return new NormalQuoteMode();
}
} }
public static String getQuoteModeName(QuoteMode quoteMode) { @Override
if (quoteMode instanceof AlwaysQuoteMode) { public void write(File file) throws IOException {
return "always"; Gson gson = new GsonBuilder().setPrettyPrinting().create();
} else if (quoteMode instanceof ColumnQuoteMode) { Files.write(file.toPath(), gson.toJson(validationConfiguration).getBytes());
return "column";
} else {
return "normal";
}
} }
} }

View File

@@ -0,0 +1,52 @@
package ninja.javafx.smartcsv.validation;
import java.text.SimpleDateFormat;
/**
* @author abi
*/
public class ValidationFormatHelper {
public static String dateFormat(String format, String defaultFormat) {
if (format != null && !format.trim().isEmpty()) {
format = format.trim();
if (format.startsWith("fmt:")) {
format = format.substring(4);
format = format.replace("%Y", "yyyy");
format = format.replace("%y", "yy");
format = format.replace("%m", "MM");
format = format.replace("%d", "dd");
format = format.replace("%a", "E");
format = format.replace("%A", "EEEE");
format = format.replace("%w", "F");
format = format.replace("%b", "MMM");
format = format.replace("%B", "MMMMM");
format = format.replace("%H", "HH");
format = format.replace("%I", "hh");
format = format.replace("%p", "a");
format = format.replace("%M", "mm");
format = format.replace("%S", "ss");
format = format.replace("%z", "Z");
format = format.replace("%Z", "z");
format = format.replace("%j", "DDD");
format = format.replace("%U", "ww");
return format;
} else {
try {
new SimpleDateFormat(format);
return format;
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
}
return defaultFormat;
}
public static Integer doubleToInteger(Double value) {
if (value == null) return null;
return (int)Math.round(value);
}
}

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -26,18 +26,19 @@
package ninja.javafx.smartcsv.validation; package ninja.javafx.smartcsv.validation;
import com.typesafe.config.Config; import ninja.javafx.smartcsv.fx.table.model.ColumnValueProvider;
import groovy.lang.Binding; import ninja.javafx.smartcsv.validation.checker.*;
import groovy.lang.GroovyShell; import ninja.javafx.smartcsv.validation.configuration.Constraints;
import groovy.lang.Script; import ninja.javafx.smartcsv.validation.configuration.Field;
import org.codehaus.groovy.control.CompilationFailedException; import ninja.javafx.smartcsv.validation.configuration.ValidationConfiguration;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static java.util.stream.Collectors.joining; import static ninja.javafx.smartcsv.validation.ValidationFormatHelper.dateFormat;
import static org.apache.commons.validator.GenericValidator.*; import static ninja.javafx.smartcsv.validation.configuration.StringFormat.*;
import static ninja.javafx.smartcsv.validation.configuration.Type.*;
/** /**
* This class checks all the validations defined in the * This class checks all the validations defined in the
@@ -49,10 +50,9 @@ public class Validator {
// member variables // member variables
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private Config validationConfig; private ValidationConfiguration validationConfig;
private GroovyShell shell = new GroovyShell(); private ColumnValueProvider columnValueProvider;
private Map<String, Script> scriptCache = new HashMap<>(); private Map<String, Map<Validation.Type, Validation>> columnValidationMap = new HashMap<>();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// constructors // constructors
@@ -60,221 +60,227 @@ public class Validator {
/** /**
* JSON configuration for this validator * JSON configuration for this validator
*
* @param validationConfig * @param validationConfig
*/ */
public Validator(Config validationConfig) { public Validator(ValidationConfiguration validationConfig, ColumnValueProvider columnValueProvider) {
this.validationConfig = validationConfig; this.validationConfig = validationConfig;
this.columnValueProvider = columnValueProvider;
initColumnValidations();
} }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// oublic methods // public methods
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public boolean needsColumnValidation(String column) {
Map<Validation.Type, Validation> validationMap = columnValidationMap.get(column);
if (validationMap != null) {
return validationMap.containsKey(Validation.Type.UNIQUE);
}
return false;
}
/** /**
* checks if the value is valid for the column configuration * checks if the value is valid for the column configuration
*
* @param column the column name * @param column the column name
* @param value the value to check * @param value the value to check
* @return ValidationError with information if valid and if not which getMessage happened * @return ValidationError with information if valid and if not which getMessage happened
*/ */
public ValidationError isValid(String column, String value, Integer lineNumber) { public ValidationError isValid(Integer row, String column, String value) {
ValidationError result = null; ValidationError result = null;
if (validationConfig != null) { if (hasConfig()) {
Config columnSectionConfig = getColumnSectionConfig(); ValidationError error = ValidationError.withLineNumber(row).column(column);
if (columnSectionConfig != null) { Map<Validation.Type, Validation> validationMap = columnValidationMap.get(column);
Config columnConfig = getColumnConfig(columnSectionConfig, column); if (validationMap != null) {
if (columnConfig != null) { for (Validation validation: validationMap.values()) {
if (validation.canBeChecked(value)) {
ValidationError error = ValidationError.withLineNumber(lineNumber); validation.check(row, value, error);
checkBlankOrNull(columnConfig, value, error); }
if (value != null && !value.isEmpty()) { }
checkRegularExpression(columnConfig, value, error);
checkAlphaNumeric(columnConfig, value, error);
checkDate(columnConfig, value, error);
checkMaxLength(columnConfig, value, error);
checkMinLength(columnConfig, value, error);
checkInteger(columnConfig, value, error);
checkGroovy(column, columnConfig, value, error);
checkValueOf(columnConfig, value, error);
checkDouble(columnConfig, value, error);
} }
if (!error.isEmpty()) { if (!error.isEmpty()) {
result = error; result = error;
} }
} }
}
}
return result; return result;
} }
public boolean hasConfig() {
return validationConfig != null;
}
public void reinitializeColumn(String column) {
clear(column);
initializeColumnWithRules(column);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// private methods // private methods
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void checkGroovy(String column, Config columnConfig, String value, ValidationError error) { private void add(String column, Validation validation) {
String groovyScript = getString(columnConfig, "groovy"); Map<Validation.Type, Validation> validationMap = columnValidationMap.get(column);
if (groovyScript != null) { if (validationMap == null) {
validationMap = new HashMap<>();
Script script = scriptCache.get(column); columnValidationMap.put(column, validationMap);
if (script == null) { }
script = shell.parse(groovyScript); validationMap.put(validation.getType(), validation);
scriptCache.put(column, script);
} }
Binding binding = new Binding(); private void remove(String column, Validation.Type type) {
binding.setVariable("value", value); Map<Validation.Type, Validation> validationMap = columnValidationMap.get(column);
script.setBinding(binding); if (validationMap == null) {
validationMap = new HashMap<>();
Object groovyResult = null; columnValidationMap.put(column, validationMap);
try {
groovyResult = script.run();
} catch (CompilationFailedException e) {
error.add("validation.message.groovy.exception", groovyScript, e.getMessage());
e.printStackTrace();
} }
if (groovyResult == null) { validationMap.remove(type);
error.add("validation.message.groovy.return.null", groovyScript);
} }
if (!isScriptResultTrue(groovyResult)) { private void clear(String column) {
error.add(groovyResult.toString()); Map<Validation.Type, Validation> validationMap = columnValidationMap.get(column);
if (validationMap != null) {
validationMap.clear();
}
}
private void initColumnValidations() {
if (hasConfig()) {
for (Field column : validationConfig.getFields()) {
initializeColumnWithRules(column);
}
}
}
private void initializeColumnWithRules(String columnName) {
if (hasConfig()) {
for (Field column : validationConfig.getFields()) {
if (column.getName().equals(columnName)) {
initializeColumnWithRules(column);
break;
}
}
}
}
private void initializeColumnWithRules(Field column) {
if (column.getType() != null) {
if (column.getType() == NUMBER) {
add(column.getName(), new DoubleValidation());
}
if (column.getType() == INTEGER) {
add(column.getName(), new IntegerValidation());
}
if (column.getType() == DATE) {
String format = dateFormat(column.getFormat(), "yyyy-MM-dd");
add(column.getName(), new DateValidation(format));
}
if (column.getType() == DATETIME) {
String format = dateFormat(column.getFormat(), "yyyy-MM-ddThh:mm:ssZ");
add(column.getName(), new DateValidation(format));
}
if (column.getType() == TIME) {
String format = dateFormat(column.getFormat(), "hh:mm:ss");
add(column.getName(), new DateValidation(format));
}
if (column.getType() == STRING && column.getFormat() == null) {
remove(column.getName(), Validation.Type.STRING);
} else {
if (column.getType() == STRING && column.getFormat().equalsIgnoreCase(EMAIL.getExternalValue())) {
add(column.getName(), new EmailValidation());
}
if (column.getType() == STRING && column.getFormat().equalsIgnoreCase(URI.getExternalValue())) {
add(column.getName(), new UriValidation());
}
if (column.getType() == STRING && column.getFormat().equalsIgnoreCase(UUID.getExternalValue())) {
add(column.getName(), new UuidValidation());
}
if (column.getType() == STRING && column.getFormat().equalsIgnoreCase(BINARY.getExternalValue())) {
add(column.getName(), new BinaryValidation());
} }
} }
} }
private boolean isScriptResultTrue(Object groovyResult) { String groovy = column.getGroovy();
return groovyResult.equals(true) || groovyResult.toString().trim().toLowerCase().equals("true"); if (groovy != null && !groovy.trim().isEmpty()) {
add(column.getName(), new GroovyValidation(groovy));
} }
private void checkValueOf(Config columnConfig, String value, ValidationError error) { Constraints constraints = column.getConstraints();
List<String> stringList = getStringList(columnConfig, "value of"); if (constraints != null) {
if (stringList != null) { Boolean notEmptyRule = constraints.getRequired();
if (!stringList.contains(value)) { if (notEmptyRule != null && notEmptyRule) {
String commaSeparated = stringList.stream().collect(joining(", ")); add(column.getName(), new NotEmptyValidation());
error.add("validation.message.value.of", value, commaSeparated);
}
}
} }
private void checkBlankOrNull(Config columnConfig, String value, ValidationError error) { Boolean uniqueRule = constraints.getUnique();
if (getBoolean(columnConfig, "not empty")) { if (uniqueRule != null && uniqueRule) {
if (isBlankOrNull(value)) { add(column.getName(), new UniqueValidation(columnValueProvider, column.getName()));
error.add("validation.message.not.empty");
}
}
} }
private void checkInteger(Config columnConfig, String value, ValidationError error) { Integer minLength = constraints.getMinLength();
if (getBoolean(columnConfig, "integer")) {
if (!isInt(value)) {
error.add("validation.message.integer");
}
}
}
private void checkDouble(Config columnConfig, String value, ValidationError error) {
if (getBoolean(columnConfig, "double")) {
if (!isDouble(value)) {
error.add("validation.message.double");
}
}
}
private void checkMinLength(Config columnConfig, String value, ValidationError error) {
Integer minLength = getInteger(columnConfig, "minlength");
if (minLength != null) { if (minLength != null) {
if (!minLength(value, minLength)) { add(column.getName(), new MinLengthValidation(minLength));
error.add("validation.message.min.length", minLength.toString());
}
}
} }
private void checkMaxLength(Config columnConfig, String value, ValidationError error) { Integer maxLength = constraints.getMaxLength();
Integer maxLength = getInteger(columnConfig, "maxlength");
if (maxLength != null) { if (maxLength != null) {
if (!maxLength(value, maxLength)) { add(column.getName(), new MaxLengthValidation(maxLength));
error.add("validation.message.max.length", maxLength.toString());
}
}
} }
private void checkDate(Config columnConfig, String value, ValidationError error) { String regexp = constraints.getPattern();
String dateformat = getString(columnConfig, "date");
if (dateformat != null && !dateformat.trim().isEmpty()) {
if (!isDate(value, dateformat, true)) {
error.add("validation.message.date.format", dateformat);
}
}
}
private void checkAlphaNumeric(Config columnConfig, String value, ValidationError error) {
if (getBoolean(columnConfig, "alphanumeric")) {
if (!matchRegexp(value, "[0-9a-zA-Z]*")) {
error.add("validation.message.alphanumeric");
}
}
}
private void checkRegularExpression(Config columnConfig, String value, ValidationError error) {
String regexp = getString(columnConfig, "regexp");
if (regexp != null && !regexp.trim().isEmpty()) { if (regexp != null && !regexp.trim().isEmpty()) {
if (!matchRegexp(value, regexp)) { add(column.getName(), new RegExpValidation(regexp));
error.add("validation.message.regexp", regexp); }
List<String> valueOfRule = constraints.getEnumeration();
if (valueOfRule != null && !valueOfRule.isEmpty()) {
add(column.getName(), new ValueOfValidation(valueOfRule));
} }
} }
} }
private Config getColumnSectionConfig() {
return validationConfig.hasPath("columns") ? validationConfig.getConfig("columns") : null;
}
private Config getColumnConfig(Config columnSectionConfig, String column) {
return columnSectionConfig.hasPath(column) ? columnSectionConfig.getConfig(column) : null;
}
private String getString(Config columnConfig, String path) {
return columnConfig.hasPath(path) ? columnConfig.getString(path) : null;
}
private Integer getInteger(Config columnConfig, String path) {
return columnConfig.hasPath(path) ? columnConfig.getInt(path) : null;
}
private boolean getBoolean(Config columnConfig, String path) {
return columnConfig.hasPath(path) && columnConfig.getBoolean(path);
}
private List<String> getStringList(Config columnConfig, String path) {
return columnConfig.hasPath(path) ? columnConfig.getStringList(path) : null;
}
public ValidationError isHeaderValid(String[] headerNames) { public ValidationError isHeaderValid(String[] headerNames) {
ValidationError result = null; ValidationError result = null;
if (validationConfig != null) { if (validationConfig != null) {
if (validationConfig.hasPath("headers")) { String[] headerNamesConfig = validationConfig.headerNames();
Config headerSectionConfig = validationConfig.getConfig("headers"); if (headerNamesConfig != null) {
List<String> headerConfig = getStringList(headerSectionConfig, "list"); if (headerNames.length != headerNamesConfig.length) {
if (headerConfig != null) {
if (headerNames.length != headerConfig.size()) {
result = ValidationError.withoutLineNumber().add("validation.message.header.length", result = ValidationError.withoutLineNumber().add("validation.message.header.length",
Integer.toString(headerNames.length), Integer.toString(headerNames.length),
Integer.toString(headerConfig.size())); Integer.toString(headerNamesConfig.length));
return result; return result;
} }
ValidationError error = ValidationError.withoutLineNumber(); ValidationError error = ValidationError.withoutLineNumber();
for(int i=0; i<headerConfig.size(); i++) { for (int i = 0; i < headerNamesConfig.length; i++) {
String header = headerConfig.get(i); if (!headerNamesConfig[i].equals(headerNames[i])) {
if (!header.equals(headerNames[i])) {
error.add("validation.message.header.match", error.add("validation.message.header.match",
Integer.toString(i), Integer.toString(i),
header, headerNamesConfig[i],
headerNames[i]); headerNames[i]);
} }
} }
@@ -283,7 +289,8 @@ public class Validator {
} }
} }
} }
}
return result; return result;
} }
} }

View File

@@ -0,0 +1,50 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.validation.ValidationError;
import java.util.Base64;
/**
* checks if the value is a base64 encoded string representing binary data
*/
public class BinaryValidation extends EmptyValueIsValid {
@Override
public void check(int row, String value, ValidationError error) {
try {
Base64.getDecoder().decode(value);
} catch (IllegalArgumentException e) {
error.add("validation.message.binary");
}
}
@Override
public Type getType() {
return Type.STRING;
}
}

View File

@@ -0,0 +1,55 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.validation.ValidationError;
import static org.apache.commons.validator.GenericValidator.isDate;
/**
* Checks if the date has the right format
*/
public class DateValidation extends EmptyValueIsValid {
private String dateformat;
public DateValidation(String dateformat) {
assert dateformat != null && !dateformat.trim().isEmpty() : "empty date format for date validation";
this.dateformat = dateformat;
}
@Override
public void check(int row, String value, ValidationError error) {
if (!isDate(value, dateformat, true)) {
error.add("validation.message.date.format", dateformat);
}
}
@Override
public Type getType() {
return Type.DATE;
}
}

View File

@@ -0,0 +1,48 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.validation.ValidationError;
import static org.apache.commons.validator.GenericValidator.isDouble;
/**
* Checks if the value is a double
*/
public class DoubleValidation extends EmptyValueIsValid {
@Override
public void check(int row, String value, ValidationError error) {
if (!isDouble(value)) {
error.add("validation.message.double");
}
}
@Override
public Type getType() {
return Type.DOUBLE;
}
}

View File

@@ -0,0 +1,47 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.validation.ValidationError;
import org.apache.commons.validator.routines.EmailValidator;
/**
* checks if the value is a valid email address
*/
public class EmailValidation extends EmptyValueIsValid {
@Override
public void check(int row, String value, ValidationError error) {
if (!EmailValidator.getInstance().isValid(value)) {
error.add("validation.message.email");
}
}
@Override
public Type getType() {
return Type.STRING;
}
}

View File

@@ -0,0 +1,12 @@
package ninja.javafx.smartcsv.validation.checker;
/**
* validations based on this are not validated when the value is null or empty
*/
public abstract class EmptyValueIsValid implements Validation {
@Override
public boolean canBeChecked(String value) {
return value != null && !value.isEmpty();
}
}

View File

@@ -0,0 +1,79 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import ninja.javafx.smartcsv.validation.ValidationError;
import org.codehaus.groovy.control.CompilationFailedException;
/**
* Executes the given groovy as check
*/
public class GroovyValidation extends EmptyValueIsValid {
private String groovyScript;
private Script script;
public GroovyValidation(String groovyScript) {
this.groovyScript = groovyScript;
GroovyShell shell = new GroovyShell();
script = shell.parse(groovyScript);
}
@Override
public void check(int row, String value, ValidationError error) {
Binding binding = new Binding();
binding.setVariable("value", value);
script.setBinding(binding);
Object groovyResult = null;
try {
groovyResult = script.run();
} catch (CompilationFailedException e) {
error.add("validation.message.groovy.exception", groovyScript, e.getMessage());
e.printStackTrace();
}
if (groovyResult == null) {
error.add("validation.message.groovy.return.null", groovyScript);
}
if (!isScriptResultTrue(groovyResult)) {
error.add(groovyResult.toString());
}
}
@Override
public Type getType() {
return Type.GROOVY;
}
private boolean isScriptResultTrue(Object groovyResult) {
return groovyResult.equals(true) || groovyResult.toString().trim().toLowerCase().equals("true");
}
}

View File

@@ -0,0 +1,48 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.validation.ValidationError;
import static org.apache.commons.validator.GenericValidator.isInt;
/**
* Checks if the value is an integer
*/
public class IntegerValidation extends EmptyValueIsValid {
@Override
public void check(int row, String value, ValidationError error) {
if (!isInt(value)) {
error.add("validation.message.integer");
}
}
@Override
public Type getType() {
return Type.INTEGER;
}
}

View File

@@ -0,0 +1,54 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.validation.ValidationError;
import static org.apache.commons.validator.GenericValidator.maxLength;
/**
* Checks if the value is shorter or exactly as long as the given max length
*/
public class MaxLengthValidation extends EmptyValueIsValid {
private int maxLength;
public MaxLengthValidation(int maxLength) {
this.maxLength = maxLength;
}
@Override
public void check(int row, String value, ValidationError error) {
if (!maxLength(value, maxLength)) {
error.add("validation.message.max.length", Integer.toString(maxLength));
}
}
@Override
public Type getType() {
return Type.MAX_LENGTH;
}
}

View File

@@ -0,0 +1,54 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.validation.ValidationError;
import static org.apache.commons.validator.GenericValidator.minLength;
/**
* Checks if the value is at minimum long as the given min length
*/
public class MinLengthValidation extends EmptyValueIsValid {
private int minLength;
public MinLengthValidation(int minLength) {
this.minLength = minLength;
}
@Override
public void check(int row, String value, ValidationError error) {
if (!minLength(value, minLength)) {
error.add("validation.message.min.length", Integer.toString(minLength));
}
}
@Override
public Type getType() {
return Type.MIN_LENGTH;
}
}

View File

@@ -0,0 +1,53 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.validation.ValidationError;
import static org.apache.commons.validator.GenericValidator.isBlankOrNull;
/**
* Checks if the value is not empty
*/
public class NotEmptyValidation implements Validation {
@Override
public void check(int row, String value, ValidationError error) {
if (isBlankOrNull(value)) {
error.add("validation.message.not.empty");
}
}
@Override
public Type getType() {
return Type.NOT_EMPTY;
}
@Override
public boolean canBeChecked(String value) {
return true;
}
}

View File

@@ -0,0 +1,54 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.validation.ValidationError;
import static org.apache.commons.validator.GenericValidator.matchRegexp;
/**
* Checks the value against the given reg exp
*/
public class RegExpValidation extends EmptyValueIsValid {
private String regexp;
public RegExpValidation(String regexp) {
this.regexp = regexp;
}
@Override
public void check(int row, String value, ValidationError error) {
if (!matchRegexp(value, regexp)) {
error.add("validation.message.regexp", regexp);
}
}
@Override
public Type getType() {
return Type.REGEXP;
}
}

View File

@@ -0,0 +1,79 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.fx.table.model.ColumnValueProvider;
import ninja.javafx.smartcsv.validation.ValidationError;
import java.util.ArrayList;
import java.util.List;
import static java.util.Collections.sort;
import static java.util.stream.Collectors.joining;
/**
* Checks if the value is unique in the column
*/
public class UniqueValidation extends EmptyValueIsValid {
private ColumnValueProvider columnValueProvider;
private String column;
public UniqueValidation(ColumnValueProvider columnValueProvider, String column) {
this.columnValueProvider = columnValueProvider;
this.column = column;
}
@Override
public void check(int row, String value, ValidationError error) {
List<Integer> lineNumbers = new ArrayList<>();
int numberOfRows = columnValueProvider.getNumberOfRows();
for (int currentRowOfIteration = 0; currentRowOfIteration < numberOfRows; currentRowOfIteration++) {
String storedValue = columnValueProvider.getValue(currentRowOfIteration, column);
if (value.equals(storedValue) && currentRowOfIteration != row) {
lineNumbers.add(currentRowOfIteration + 1); // show not 0 based line numbers to user
}
}
if (!lineNumbers.isEmpty()) {
if (lineNumbers.size() > 1) {
sort(lineNumbers);
error.add("validation.message.uniqueness.multiple", value, lineNumbers.stream().map(Object::toString).collect(joining(", ")));
} else {
error.add("validation.message.uniqueness.single", value, lineNumbers.get(0).toString());
}
}
}
@Override
public Type getType() {
return Type.UNIQUE;
}
}

View File

@@ -0,0 +1,51 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.validation.ValidationError;
import java.net.URI;
import java.net.URISyntaxException;
/**
* checks if the value is a valid uri address
*/
public class UriValidation extends EmptyValueIsValid {
@Override
public void check(int row, String value, ValidationError error) {
try {
new URI(value);
} catch (URISyntaxException e) {
error.add("validation.message.uri");
}
}
@Override
public Type getType() {
return Type.STRING;
}
}

View File

@@ -0,0 +1,51 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.validation.ValidationError;
import java.util.UUID;
/**
* checks if the value is a valid uuid
*/
public class UuidValidation extends EmptyValueIsValid {
@SuppressWarnings("ResultOfMethodCallIgnored")
@Override
public void check(int row, String value, ValidationError error) {
try {
UUID.fromString(value);
} catch (IllegalArgumentException e) {
error.add("validation.message.uuid");
}
}
@Override
public Type getType() {
return Type.STRING;
}
}

View File

@@ -0,0 +1,39 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.validation.ValidationError;
/**
* Interface for all validations
*/
public interface Validation {
enum Type { NOT_EMPTY, UNIQUE, DOUBLE, INTEGER, MIN_LENGTH, MAX_LENGTH, DATE, REGEXP, VALUE_OF, STRING, GROOVY }
void check(int row, String value, ValidationError error);
Type getType();
boolean canBeChecked(String value);
}

View File

@@ -0,0 +1,57 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.checker;
import ninja.javafx.smartcsv.validation.ValidationError;
import java.util.List;
import static java.util.stream.Collectors.joining;
/**
* Checks if the value is part of a list of values
*/
public class ValueOfValidation extends EmptyValueIsValid {
private List<String> values;
public ValueOfValidation(List<String> values) {
this.values = values;
}
@Override
public void check(int row, String value, ValidationError error) {
if (!values.contains(value)) {
String commaSeparated = values.stream().collect(joining(", "));
error.add("validation.message.value.of", value, commaSeparated);
}
}
@Override
public Type getType() {
return Type.VALUE_OF;
}
}

View File

@@ -0,0 +1,96 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.configuration;
import com.google.gson.annotations.SerializedName;
import java.util.List;
/**
* contraints defined in JSON Table Schema
* @see <a href="http://specs.frictionlessdata.io/json-table-schema/">JSON Table Schema</a>
*/
public class Constraints {
private Boolean required;
private Boolean unique;
private Integer minLength;
private Integer maxLength;
private String pattern;
@SerializedName("enum")
private List<String> enumeration;
public Boolean getRequired() {
return required;
}
public void setRequired(Boolean required) {
this.required = required;
}
public Boolean getUnique() {
return unique;
}
public void setUnique(Boolean unique) {
this.unique = unique;
}
public Integer getMinLength() {
return minLength;
}
public void setMinLength(Integer minLength) {
this.minLength = minLength;
}
public Integer getMaxLength() {
return maxLength;
}
public void setMaxLength(Integer maxLength) {
this.maxLength = maxLength;
}
public String getPattern() {
return pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
public List<String> getEnumeration() {
return enumeration;
}
public void setEnumeration(List<String> enumeration) {
this.enumeration = enumeration;
}
}

View File

@@ -0,0 +1,109 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.configuration;
/**
* this class represents a field in the configuration
* @see <a href="http://specs.frictionlessdata.io/json-table-schema/">JSON Table Schema</a>
*/
public class Field {
private String name;
private String title;
private Type type;
private String description;
private String format;
private Object missingValue;
private Constraints constraints;
private String groovy;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public Object getMissingValue() {
return missingValue;
}
public void setMissingValue(Object missingValue) {
this.missingValue = missingValue;
}
public Constraints getConstraints() {
return constraints;
}
public void setConstraints(Constraints constraints) {
this.constraints = constraints;
}
public String getGroovy() {
return groovy;
}
public void setGroovy(String groovy) {
this.groovy = groovy;
}
}

View File

@@ -0,0 +1,60 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.configuration;
/**
* Enumeration for format values for type string in JSON Table Schema
* @see <a href="http://specs.frictionlessdata.io/json-table-schema/">JSON Table Schema</a>
*/
public enum StringFormat {
DEFAULT(null),
EMAIL("email"),
URI("uri"),
BINARY("binary"),
UUID("uuid");
private String externalValue;
StringFormat(String externalValue) {
this.externalValue = externalValue;
}
public String getExternalValue() {
return externalValue;
}
public static StringFormat fromExternalValue(String externalValue) {
if (externalValue != null) {
for (StringFormat value : StringFormat.values()) {
if (externalValue.equals(value.getExternalValue())) {
return value;
}
}
}
return DEFAULT;
}
}

View File

@@ -0,0 +1,48 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.configuration;
import com.google.gson.annotations.SerializedName;
/**
* Types of JSON Table Schema
* @see <a href="http://specs.frictionlessdata.io/json-table-schema/">JSON Table Schema</a>
*/
public enum Type {
@SerializedName("string")STRING,
@SerializedName("integer")INTEGER,
@SerializedName("number")NUMBER,
@SerializedName("date")DATE,
@SerializedName("datetime")DATETIME,
@SerializedName("time")TIME
// TODO: currently not supported
// @SerializedName("object") OBJECT,
// @SerializedName("array") ARRAY,
// @SerializedName("duration") DURATION,
// @SerializedName("geopoint") GEOPOINT,
// @SerializedName("geojson") GEOJSON
}

View File

@@ -0,0 +1,81 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.validation.configuration;
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
import java.util.List;
/**
* Configuration based on JSON Table Schema
* @see <a href="http://specs.frictionlessdata.io/json-table-schema/">JSON Table Schema</a>
*/
public class ValidationConfiguration {
@SerializedName("fields")
private Field[] fields;
public Field[] getFields() {
return fields;
}
public void setFields(Field[] fields) {
this.fields = fields;
}
public Field getFieldConfiguration(String column) {
for (Field field : fields) {
if (field.getName().equals(column)) {
return field;
}
}
return null;
}
public String[] headerNames() {
if (fields != null) {
List<String> headerNames = new ArrayList<>();
for (Field field : fields) {
headerNames.add(field.getName());
}
return headerNames.toArray(new String[headerNames.size()]);
}
return null;
}
public void setHeaderNames(String[] header) {
fields = new Field[header.length];
int i = 0;
for (String headerName: header) {
fields[i] = new Field();
fields[i].setName(headerName);
i++;
}
}
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error">
<Properties>
<Property name="filename">smartcsv.log</Property>
</Properties>
<ThresholdFilter level="trace"/>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<File name="File" fileName="${filename}" bufferedIO="true">
<PatternLayout>
<pattern>%d %p %C{1.} [%t] %m%n</pattern>
</PatternLayout>
</File>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="File"/>
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

View File

@@ -1,14 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?> <?import javafx.geometry.Insets?>
<?import java.lang.*?> <?import javafx.scene.control.Hyperlink?>
<?import javafx.geometry.*?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.*?> <?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="600.0" spacing="4.0" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="600.0" spacing="4.0" xmlns="http://javafx.com/javafx/8.0.66" xmlns:fx="http://javafx.com/fxml/1">
<children> <children>
<Label text="Thanks for contributions:">
<font>
<Font size="18.0" />
</font>
<VBox.margin>
<Insets top="8.0" />
</VBox.margin>
</Label>
<Label text="AOE Takashi (https://github.com/aoetk)" />
<Label text="Hasan Kara (https://github.com/haisi)" />
<Label text="apixandru (https://github.com/apixandru)" />
<Label text="3rd party software"> <Label text="3rd party software">
<VBox.margin> <VBox.margin>
<Insets top="8.0" /> <Insets top="8.0" />
@@ -19,20 +34,23 @@
</Label> </Label>
<ScrollPane fitToHeight="true" fitToWidth="true"> <ScrollPane fitToHeight="true" fitToWidth="true">
<content> <content>
<GridPane hgap="4.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" vgap="4.0"> <GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity">
<columnConstraints> <columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" /> <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" /> <ColumnConstraints hgrow="ALWAYS" minWidth="10.0" />
</columnConstraints> </columnConstraints>
<rowConstraints> <rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="20.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="20.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="20.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="20.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="20.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="20.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="20.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="20.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="20.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="20.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="20.0" vgrow="SOMETIMES" />
</rowConstraints> </rowConstraints>
<children> <children>
<Label text="junit" /> <Label text="junit" />
@@ -43,14 +61,20 @@
<Hyperlink onAction="#openLinkInBrowser" text="http://www.groovy-lang.org/" GridPane.columnIndex="1" GridPane.rowIndex="2" /> <Hyperlink onAction="#openLinkInBrowser" text="http://www.groovy-lang.org/" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Label text="spring framework" GridPane.rowIndex="3" /> <Label text="spring framework" GridPane.rowIndex="3" />
<Hyperlink onAction="#openLinkInBrowser" text="https://spring.io/" GridPane.columnIndex="1" GridPane.rowIndex="3" /> <Hyperlink onAction="#openLinkInBrowser" text="https://spring.io/" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<Label text="supercsv" GridPane.rowIndex="4" /> <Label text="FastCSV" GridPane.rowIndex="4" />
<Hyperlink onAction="#openLinkInBrowser" text="http://super-csv.github.io/super-csv/" GridPane.columnIndex="1" GridPane.rowIndex="4" /> <Hyperlink onAction="#openLinkInBrowser" text="https://github.com/osiegmar/FastCSV" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<Label text="config" GridPane.rowIndex="5" /> <Label text="Gson" GridPane.rowIndex="5" />
<Hyperlink onAction="#openLinkInBrowser" text="https://github.com/typesafehub/config" GridPane.columnIndex="1" GridPane.rowIndex="5" /> <Hyperlink onAction="#openLinkInBrowser" text="https://github.com/google/gson/" GridPane.columnIndex="1" GridPane.rowIndex="5" />
<Label text="commons-validator" GridPane.rowIndex="6" /> <Label text="commons-validator" GridPane.rowIndex="6" />
<Hyperlink onAction="#openLinkInBrowser" text="https://commons.apache.org/proper/commons-validator/" GridPane.columnIndex="1" GridPane.rowIndex="6" /> <Hyperlink onAction="#openLinkInBrowser" text="https://commons.apache.org/proper/commons-validator/" GridPane.columnIndex="1" GridPane.rowIndex="6" />
<Label text="fontawesomefx" GridPane.rowIndex="7" /> <Label text="fontawesomefx" GridPane.rowIndex="7" />
<Hyperlink onAction="#openLinkInBrowser" text="https://bitbucket.org/Jerady/fontawesomefx" GridPane.columnIndex="1" GridPane.rowIndex="7" /> <Hyperlink onAction="#openLinkInBrowser" text="https://bitbucket.org/Jerady/fontawesomefx" GridPane.columnIndex="1" GridPane.rowIndex="7" />
<Label text="ControlsFX" GridPane.rowIndex="8" />
<Hyperlink onAction="#openLinkInBrowser" text="http://fxexperience.com/controlsfx/" GridPane.columnIndex="1" GridPane.rowIndex="8" />
<Label text="Apache Log4j 2" GridPane.rowIndex="9" />
<Hyperlink onAction="#openLinkInBrowser" text="http://logging.apache.org/log4j/2.x/" GridPane.columnIndex="1" GridPane.rowIndex="9" />
<Label text="RichTextFX" GridPane.rowIndex="10" />
<Hyperlink onAction="#openLinkInBrowser" text="https://github.com/TomasMikula/RichTextFX" GridPane.columnIndex="1" GridPane.rowIndex="10" />
</children> </children>
<padding> <padding>
<Insets bottom="4.0" left="8.0" right="8.0" top="4.0" /> <Insets bottom="4.0" left="8.0" right="8.0" top="4.0" />
@@ -66,7 +90,7 @@
<Font size="18.0" /> <Font size="18.0" />
</font> </font>
</Label> </Label>
<TextArea editable="false" prefHeight="200.0" prefWidth="200.0" text="Copyright (c) 2015 Andreas Billmann &lt;andreas.billmann@javafx.ninja&gt;&#10;&#10;Permission is hereby granted, free of charge, to any person obtaining a copy&#10;of this software and associated documentation files (the &quot;Software&quot;), to deal&#10;in the Software without restriction, including without limitation the rights&#10;to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&#10;copies of the Software, and to permit persons to whom the Software is&#10;furnished to do so, subject to the following conditions:&#10;&#10;The above copyright notice and this permission notice shall be included in&#10;all copies or substantial portions of the Software.&#10;&#10;&#10;&#10;THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&#10;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&#10;FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&#10;AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&#10;LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&#10;OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN&#10;THE SOFTWARE." wrapText="true" /> <TextArea editable="false" prefHeight="200.0" prefWidth="200.0" text="Copyright (c) 2015-2021 Andreas Billmann &lt;andreas.billmann@javafx.ninja&gt;&#10;&#10;Permission is hereby granted, free of charge, to any person obtaining a copy&#10;of this software and associated documentation files (the &quot;Software&quot;), to deal&#10;in the Software without restriction, including without limitation the rights&#10;to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&#10;copies of the Software, and to permit persons to whom the Software is&#10;furnished to do so, subject to the following conditions:&#10;&#10;The above copyright notice and this permission notice shall be included in&#10;all copies or substantial portions of the Software.&#10;&#10;&#10;&#10;THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&#10;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&#10;FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&#10;AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&#10;LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&#10;OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN&#10;THE SOFTWARE." wrapText="true" />
</children> </children>
<opaqueInsets> <opaqueInsets>
<Insets /> <Insets />

View File

@@ -1,10 +1,11 @@
application.name = SmartCSV.fx application.name = SmartCSV.fx
application.version = 0.3 application.version = 1.0.0
# fxml views # fxml views
fxml.smartcvs.view = /ninja/javafx/smartcsv/fx/smartcsv.fxml fxml.smartcvs.view = /ninja/javafx/smartcsv/fx/smartcsv.fxml
fxml.smartcvs.about.view = /ninja/javafx/smartcsv/fx/about/about.fxml fxml.smartcvs.about.view = /ninja/javafx/smartcsv/fx/about/about.fxml
fxml.smartcvs.preferences.view = /ninja/javafx/smartcsv/fx/preferences/preferences.fxml fxml.smartcvs.preferences.view = /ninja/javafx/smartcsv/fx/preferences/preferences.fxml
fxml.smartcvs.validation.editor.view = /ninja/javafx/smartcsv/fx/validation/validationEditor.fxml
# resources # resources
resource.main = ninja.javafx.smartcsv.fx.smartcsv resource.main = ninja.javafx.smartcsv.fx.smartcsv

View File

@@ -1,10 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<GridPane hgap="8.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" vgap="8.0" xmlns="http://javafx.com/javafx/8.0.66" xmlns:fx="http://javafx.com/fxml/1"> <GridPane hgap="8.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" vgap="8.0" xmlns="http://javafx.com/javafx/8.0.66" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints> <columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" /> <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
@@ -15,19 +13,16 @@
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER" />
</rowConstraints> </rowConstraints>
<children> <children>
<Label text="%preferences.quoteChar" /> <Label text="%preferences.quoteChar" />
<TextField fx:id="quoteChar" GridPane.columnIndex="1" /> <TextField fx:id="quoteChar" GridPane.columnIndex="1" />
<Label text="%preferences.delimiterChar" GridPane.rowIndex="1" /> <Label text="%preferences.delimiterChar" GridPane.rowIndex="1" />
<TextField fx:id="delimiterChar" GridPane.columnIndex="1" GridPane.rowIndex="1" /> <TextField fx:id="delimiterChar" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label text="%preferences.surroundingSpacesNeedQuotes" GridPane.rowIndex="2" /> <Label text="%preferences.ignoreEmptyLines" GridPane.rowIndex="2" />
<CheckBox fx:id="surroundingSpacesNeedQuotes" mnemonicParsing="false" text="" GridPane.columnIndex="1" GridPane.rowIndex="2" /> <CheckBox fx:id="ignoreEmptyLines" mnemonicParsing="false" text="" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Label text="%preferences.ignoreEmptyLines" GridPane.rowIndex="3" /> <Label text="%preferences.fileEncoding" GridPane.rowIndex="3" />
<CheckBox fx:id="ignoreEmptyLines" mnemonicParsing="false" text="" GridPane.columnIndex="1" GridPane.rowIndex="3" /> <ComboBox fx:id="fileEncoding" editable="true" prefWidth="150.0" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<Label text="%preferences.quoteMode" GridPane.rowIndex="4" />
<ComboBox fx:id="quoteMode" prefWidth="150.0" GridPane.columnIndex="1" GridPane.rowIndex="4" />
</children> </children>
<padding> <padding>
<Insets bottom="8.0" left="8.0" right="8.0" top="8.0" /> <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" />

View File

@@ -1,50 +1,229 @@
.list-view { .table-view .list-view {
-fx-background-color: -fx-background; -fx-background-color: -fx-background;
-fx-padding: 1; -fx-padding: 1;
} }
.list-cell { .table-view .list-cell {
-fx-padding: 1.0em 1.0em 1.0em 1.0em; -fx-padding: 1.0em 1.0em 1.0em 1.0em;
} }
.list-cell:odd { .table-view .list-cell:odd {
-fx-background: -fx-control-inner-background-alt; -fx-background: -fx-control-inner-background-alt;
} }
.list-cell:even { .table-view .list-cell:even {
-fx-background: -fx-control-inner-background; -fx-background: -fx-control-inner-background;
} }
.open-icon { .open-icon {
-glyph-name: "FILE_TEXT_ALT"; -glyph-name: "FILE_IMPORT";
-glyph-size: 14px; -glyph-size: 16px;
}
.file-document-icon {
-glyph-name: "FILE_DOCUMENT";
-glyph-size: 16px;
} }
.config-icon { .config-icon {
-glyph-name: "CHECK"; -glyph-name: "CLIPBOARD";
-glyph-size: 14px; -glyph-size: 16px;
}
.create-config-icon {
-glyph-name: "STAR";
-glyph-size: 10px;
}
.load-config-icon {
-glyph-name: "ARROW_UP_BOLD";
-glyph-size: 10px;
}
.save-config-icon {
-glyph-name: "ARROW_DOWN_BOLD";
-glyph-size: 10px;
} }
.save-icon { .save-icon {
-glyph-name: "FLOPPY_ALT"; -glyph-name: "FILE_EXPORT";
-glyph-size: 14px; -glyph-size: 16px;
} }
.exit-icon { .exit-icon {
-glyph-name: "SIGN_OUT"; -glyph-name: "EXIT_TO_APP";
-glyph-size: 14px; -glyph-size: 16px;
} }
.info-icon { .info-icon {
-glyph-name: "INFO"; -glyph-name: "INFORMATION_OUTLINE";
-glyph-size: 14px; -glyph-size: 16px;
} }
.error-title-icon { .config-check-icon {
-glyph-name: "EXCLAMATION_TRIANGLE"; -glyph-name: "CLIPBOARD_CHECK";
-glyph-size: 14px; -glyph-size: 16px;
} }
.preferences-icon { .preferences-icon {
-glyph-name: "COG"; -glyph-name: "SETTINGS";
-glyph-size: 14px; -glyph-size: 16px;
}
.delete-icon {
-glyph-name: "TABLE_ROW_REMOVE";
-glyph-size: 16px;
}
.add-icon {
-glyph-name: "TABLE_ROW_PLUS_AFTER";
-glyph-size: 16px;
}
.goto-icon {
-glyph-name: "SUBDIRECTORY_ARROW_RIGHT";
-glyph-size: 16px;
}
.export-icon {
-glyph-name: "BUG";
-glyph-size: 16px;
}
/* toolbar customization based on http://fxexperience.com/2012/02/customized-segmented-toolbar-buttons/ */
#background {
-light-black: rgb(74, 75, 78);
-dark-highlight: rgb(87, 89, 92);
-dark-black: rgb(39, 40, 40);
-darkest-black: rgb(5, 5, 5);
-mid-gray: rgb(216, 222, 227);
-fx-background-color: -mid-gray;
}
.tool-bar {
-fx-base: -dark-black;
-fx-font-size: 12px;
-fx-background-color:
linear-gradient(to bottom, derive(-fx-base,-30%), derive(-fx-base,-60%)),
linear-gradient(to bottom, -light-black 2%, -dark-black 98%);
-fx-background-insets: 0, 0 0 1 0;
-fx-padding: .6em 0.416667em .6em 0.416667em;
-fx-effect: dropshadow(two-pass-box,black,5,.2,0,0);
}
.tool-bar .open-icon {
-glyph-size: 16px;
-fx-fill: white;
}
.tool-bar .config-icon {
-glyph-size: 16px;
-fx-fill: white;
}
.menu-item .load-config-icon,
.menu-item .save-config-icon,
.menu-item .create-config-icon
{
-fx-fill: white;
}
.tool-bar .save-icon {
-glyph-size: 16px;
-fx-fill: white;
}
.tool-bar .preferences-icon {
-glyph-size: 16px;
-fx-fill: white;
}
.tool-bar .exit-icon {
-glyph-size: 16px;
-fx-fill: white;
}
.tool-bar .delete-icon {
-glyph-size: 16px;
-fx-fill: white;
}
.tool-bar .add-icon {
-glyph-size: 16px;
-fx-fill: white;
}
.tool-bar .export-icon {
-glyph-size: 16px;
-fx-fill: white;
}
.segmented-button-bar .button {
-fx-background-color:
-darkest-black,
-dark-highlight,
linear-gradient(to bottom, -light-black 2%, -dark-black 98%);
-fx-background-insets: 0, 1 1 1 0, 2 1 1 1;
-fx-background-radius: 0;
-fx-padding: 0.4em 1.833333em 0.4em 1.833333em;
}
.segmented-button-bar .button.first {
-fx-background-insets: 0, 1, 2 1 1 1;
-fx-background-radius: 3 0 0 3, 2 0 0 2, 2 0 0 2;
}
.segmented-button-bar .button.last {
-fx-background-insets: 0, 1 1 1 0, 2 1 1 1;
-fx-background-radius: 0 3 3 0, 0 2 2 0, 0 2 2 0;
}
.segmented-button-bar .button:pressed {
-fx-background-color:
-darkest-black,
rgb(55, 57, 58),
linear-gradient(to top, -light-black 2%, -dark-black 98%);
}
.tool-bar .spacer {
-fx-padding: 0 5.417em 0 0;
}
.tooltip {
-fx-background-radius: 2 2 2 2;
-fx-background-color: linear-gradient(#eeeeee, #aaaaaa);
-fx-text-fill: black;
-fx-font-size: 12px;
-fx-padding: 10 10 10 10;
}
.keyword {
-fx-fill: purple;
-fx-font-weight: bold;
}
.semicolon {
-fx-font-weight: bold;
}
.paren {
-fx-fill: firebrick;
-fx-font-weight: bold;
}
.bracket {
-fx-fill: darkgreen;
-fx-font-weight: bold;
}
.brace {
-fx-fill: teal;
-fx-font-weight: bold;
}
.string {
-fx-fill: blue;
}
.comment {
-fx-fill: cadetblue;
}
.paragraph-box:has-caret {
-fx-background-color: #f2f9fc;
} }

View File

@@ -1,107 +1,269 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import de.jensd.fx.glyphs.fontawesome.*?> <?import de.jensd.fx.glyphs.*?>
<?import de.jensd.fx.glyphs.materialdesignicons.*?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import java.net.URL?> <BorderPane fx:id="applicationPane" maxHeight="-Infinity" maxWidth="1000.0" minHeight="700.0" minWidth="-Infinity" prefHeight="700.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8.0.101" xmlns:fx="http://javafx.com/fxml/1">
<BorderPane fx:id="applicationPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" prefHeight="700.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8.0.66"
xmlns:fx="http://javafx.com/fxml/1">
<top> <top>
<VBox prefWidth="100.0" BorderPane.alignment="CENTER"> <VBox id="background" prefWidth="100.0" BorderPane.alignment="CENTER">
<children> <children>
<MenuBar> <MenuBar useSystemMenuBar="true">
<menus> <menus>
<Menu mnemonicParsing="false" text="%menu.file"> <Menu mnemonicParsing="false" text="%menu.file">
<graphic>
<FontAwesomeIconView styleClass="save-icon"/>
</graphic>
<items> <items>
<MenuItem mnemonicParsing="false" onAction="#openCsv" text="%menu.open.csv"> <MenuItem mnemonicParsing="false" onAction="#openCsv" text="%menu.open.csv">
<graphic> <graphic>
<FontAwesomeIconView styleClass="open-icon"/> <MaterialDesignIconView styleClass="open-icon" />
</graphic> </graphic>
</MenuItem> </MenuItem>
<MenuItem mnemonicParsing="false" onAction="#openConfig" text="%menu.open.config"> <MenuItem fx:id="saveMenuItem" disable="true" mnemonicParsing="false" onAction="#saveCsv" text="%menu.save">
<graphic> <graphic>
<FontAwesomeIconView styleClass="config-icon"/> <MaterialDesignIconView styleClass="save-icon" />
</graphic> </graphic>
</MenuItem> </MenuItem>
<MenuItem fx:id="saveAsMenuItem" disable="true" mnemonicParsing="false" onAction="#saveAsCsv" text="%menu.save.as">
<graphic>
<MaterialDesignIconView styleClass="save-icon" />
</graphic>
</MenuItem>
<SeparatorMenuItem mnemonicParsing="false" /> <SeparatorMenuItem mnemonicParsing="false" />
<MenuItem fx:id="saveMenuItem" mnemonicParsing="false" onAction="#saveCsv" text="%menu.save" disable="true"> <MenuItem fx:id="createConfigMenuItem" disable="true" mnemonicParsing="false" onAction="#createConfig" text="%menu.create.config">
<graphic> <graphic>
<FontAwesomeIconView styleClass="save-icon"/> <GlyphsStack>
<children>
<MaterialDesignIconView styleClass="config-icon" />
<MaterialDesignIconView style="-fx-opacity: 0.7;" styleClass="create-config-icon" />
</children>
</GlyphsStack>
</graphic> </graphic>
</MenuItem> </MenuItem>
<MenuItem fx:id="saveAsMenuItem" mnemonicParsing="false" onAction="#saveAsCsv" text="%menu.save.as" disable="true"> <MenuItem fx:id="loadConfigMenuItem" disable="true" mnemonicParsing="false" onAction="#openConfig" text="%menu.open.config">
<graphic> <graphic>
<FontAwesomeIconView styleClass="save-icon"/> <GlyphsStack>
<children>
<MaterialDesignIconView styleClass="config-icon" />
<MaterialDesignIconView style="-fx-opacity: 0.7;" styleClass="load-config-icon" />
</children>
</GlyphsStack>
</graphic> </graphic>
</MenuItem> </MenuItem>
<MenuItem fx:id="saveConfigMenuItem" disable="true" mnemonicParsing="false" onAction="#saveConfig" text="%menu.save.config">
<graphic>
<GlyphsStack>
<children>
<MaterialDesignIconView styleClass="config-icon" />
<MaterialDesignIconView style="-fx-opacity: 0.7;" styleClass="save-config-icon" />
</children>
</GlyphsStack>
</graphic>
</MenuItem>
<MenuItem fx:id="saveAsConfigMenuItem" disable="true" mnemonicParsing="false" onAction="#saveAsConfig" text="%menu.save.as.config">
<graphic>
<GlyphsStack>
<children>
<MaterialDesignIconView styleClass="config-icon" />
<MaterialDesignIconView style="-fx-opacity: 0.7;" styleClass="save-config-icon" />
</children>
</GlyphsStack>
</graphic>
</MenuItem>
<SeparatorMenuItem mnemonicParsing="false" /> <SeparatorMenuItem mnemonicParsing="false" />
<MenuItem mnemonicParsing="false" onAction="#preferences" text="%menu.preferences"> <MenuItem mnemonicParsing="false" onAction="#preferences" text="%menu.preferences">
<graphic> <graphic>
<FontAwesomeIconView styleClass="preferences-icon"/> <MaterialDesignIconView styleClass="preferences-icon" />
</graphic>
</MenuItem>
<MenuItem fx:id="exportMenuItem" disable="true" mnemonicParsing="false" onAction="#export" text="%menu.export">
<graphic>
<MaterialDesignIconView styleClass="export-icon" />
</graphic> </graphic>
</MenuItem> </MenuItem>
<SeparatorMenuItem mnemonicParsing="false" /> <SeparatorMenuItem mnemonicParsing="false" />
<MenuItem mnemonicParsing="false" onAction="#close" text="%menu.close"> <MenuItem mnemonicParsing="false" onAction="#close" text="%menu.close">
<graphic> <graphic>
<FontAwesomeIconView styleClass="exit-icon"/> <MaterialDesignIconView styleClass="exit-icon" />
</graphic>
</MenuItem>
</items>
</Menu>
<Menu mnemonicParsing="false" text="%menu.edit">
<items>
<MenuItem fx:id="deleteRowMenuItem" disable="true" mnemonicParsing="false" onAction="#deleteRow" text="%menu.delete.row">
<graphic>
<MaterialDesignIconView styleClass="delete-icon" />
</graphic>
</MenuItem>
<MenuItem fx:id="addRowMenuItem" disable="true" mnemonicParsing="false" onAction="#addRow" text="%menu.add.row">
<graphic>
<MaterialDesignIconView styleClass="add-icon" />
</graphic>
</MenuItem>
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem fx:id="gotoLineMenuItem" disable="true" mnemonicParsing="false" onAction="#gotoLine" text="%menu.goto.line">
<graphic>
<MaterialDesignIconView styleClass="goto-icon" />
</graphic> </graphic>
</MenuItem> </MenuItem>
</items> </items>
</Menu> </Menu>
<Menu mnemonicParsing="false" text="%menu.help"> <Menu mnemonicParsing="false" text="%menu.help">
<graphic>
<FontAwesomeIconView styleClass="info-icon"/>
</graphic>
<items> <items>
<MenuItem mnemonicParsing="false" onAction="#about" text="%menu.about"> <MenuItem mnemonicParsing="false" onAction="#about" text="%menu.about">
<graphic> <graphic>
<FontAwesomeIconView styleClass="info-icon"/> <MaterialDesignIconView styleClass="info-icon" />
</graphic> </graphic>
</MenuItem> </MenuItem>
</items> </items>
</Menu> </Menu>
</menus> </menus>
</MenuBar> </MenuBar>
<ToolBar prefHeight="40.0" prefWidth="200.0">
<HBox styleClass="segmented-button-bar">
<Button mnemonicParsing="false" onAction="#openCsv" styleClass="first">
<tooltip>
<Tooltip text="%menu.open.csv" />
</tooltip>
<graphic>
<MaterialDesignIconView styleClass="open-icon" />
</graphic>
</Button>
<Button fx:id="saveButton" disable="true" mnemonicParsing="false" onAction="#saveCsv">
<tooltip>
<Tooltip text="%menu.save" />
</tooltip>
<graphic>
<MaterialDesignIconView styleClass="save-icon" />
</graphic>
</Button>
<Button fx:id="saveAsButton" disable="true" mnemonicParsing="false" onAction="#saveAsCsv" styleClass="last" text="...">
<tooltip>
<Tooltip text="%menu.save.as" />
</tooltip>
<graphic>
<MaterialDesignIconView styleClass="save-icon" />
</graphic>
</Button>
</HBox>
<Region styleClass="spacer" />
<HBox styleClass="segmented-button-bar">
<Button fx:id="createConfigButton" disable="true" mnemonicParsing="false" onAction="#createConfig">
<tooltip>
<Tooltip text="%menu.create.config" />
</tooltip>
<graphic>
<GlyphsStack>
<children>
<MaterialDesignIconView styleClass="config-icon" />
<MaterialDesignIconView style="-fx-opacity: 0.7;" styleClass="create-config-icon" />
</children>
</GlyphsStack>
</graphic>
</Button>
<Button fx:id="loadConfigButton" disable="true" mnemonicParsing="false" onAction="#openConfig">
<tooltip>
<Tooltip text="%menu.open.config" />
</tooltip>
<graphic>
<GlyphsStack>
<children>
<MaterialDesignIconView styleClass="config-icon" />
<MaterialDesignIconView style="-fx-opacity: 0.7;" styleClass="load-config-icon" />
</children>
</GlyphsStack>
</graphic>
</Button>
<Button fx:id="saveConfigButton" disable="true" mnemonicParsing="false" onAction="#saveConfig">
<tooltip>
<Tooltip text="%menu.save.config" />
</tooltip>
<graphic>
<GlyphsStack>
<children>
<MaterialDesignIconView styleClass="config-icon" />
<MaterialDesignIconView style="-fx-opacity: 0.7;" styleClass="save-config-icon" />
</children>
</GlyphsStack>
</graphic>
</Button>
<Button fx:id="saveAsConfigButton" disable="true" mnemonicParsing="false" onAction="#saveAsConfig" styleClass="last" text="...">
<tooltip>
<Tooltip text="%menu.save.as.config" />
</tooltip>
<graphic>
<GlyphsStack>
<children>
<MaterialDesignIconView styleClass="config-icon" />
<MaterialDesignIconView style="-fx-opacity: 0.7;" styleClass="save-config-icon" />
</children>
</GlyphsStack>
</graphic>
</Button>
</HBox>
<Region styleClass="spacer" />
<HBox styleClass="segmented-button-bar">
<Button fx:id="deleteRowButton" disable="true" mnemonicParsing="false" onAction="#deleteRow" styleClass="first">
<tooltip>
<Tooltip text="%menu.delete.row" />
</tooltip>
<graphic>
<MaterialDesignIconView styleClass="delete-icon" />
</graphic>
</Button>
<Button fx:id="addRowButton" disable="true" mnemonicParsing="false" onAction="#addRow" styleClass="last">
<tooltip>
<Tooltip text="%menu.add.row" />
</tooltip>
<graphic>
<MaterialDesignIconView styleClass="add-icon" />
</graphic>
</Button>
</HBox>
<Region styleClass="spacer" />
<HBox styleClass="segmented-button-bar">
<Button mnemonicParsing="false" onAction="#preferences" styleClass="first">
<tooltip>
<Tooltip text="%menu.preferences" />
</tooltip>
<graphic>
<MaterialDesignIconView styleClass="preferences-icon" />
</graphic>
</Button>
<Button fx:id="exportButton" disable="true" mnemonicParsing="false" onAction="#export">
<tooltip>
<Tooltip text="%menu.export" />
</tooltip>
<graphic>
<MaterialDesignIconView styleClass="export-icon" />
</graphic>
</Button>
<Button mnemonicParsing="false" onAction="#close" styleClass="last">
<tooltip>
<Tooltip text="%menu.close" />
</tooltip>
<graphic>
<MaterialDesignIconView styleClass="exit-icon" />
</graphic>
</Button>
</HBox>
</ToolBar>
</children> </children>
</VBox> </VBox>
</top> </top>
<center> <center>
<SplitPane dividerPositions="0.8" prefHeight="160.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<items>
<AnchorPane fx:id="tableWrapper"> <AnchorPane fx:id="tableWrapper">
<TableView fx:id="tableView" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" <TableView fx:id="tableView" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" BorderPane.alignment="CENTER">
AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
BorderPane.alignment="CENTER">
<columns> <columns>
</columns> </columns>
</TableView> </TableView>
</AnchorPane> </AnchorPane>
<VBox>
<children>
<Label text="%title.validation.errors">
<padding>
<Insets bottom="8.0" left="8.0" right="8.0" top="8.0"/>
</padding>
<graphic>
<FontAwesomeIconView styleClass="error-title-icon"/>
</graphic>
</Label>
<ListView fx:id="errorList" minWidth="10.0" prefWidth="200.0" VBox.vgrow="ALWAYS"/>
</children>
</VBox>
</items>
</SplitPane>
</center> </center>
<left> <left>
</left> </left>
<stylesheets>
<URL value="@/ninja/javafx/smartcsv/fx/smartcsv.css"/>
</stylesheets>
<bottom> <bottom>
<GridPane hgap="8.0" BorderPane.alignment="CENTER"> <GridPane hgap="8.0" BorderPane.alignment="CENTER">
<columnConstraints> <columnConstraints>
@@ -109,26 +271,31 @@
<ColumnConstraints hgrow="NEVER" minWidth="10.0" /> <ColumnConstraints hgrow="NEVER" minWidth="10.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" /> <ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="NEVER" minWidth="10.0" /> <ColumnConstraints hgrow="NEVER" minWidth="10.0" />
<ColumnConstraints hgrow="NEVER" minWidth="10.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" />
<ColumnConstraints hgrow="NEVER" minWidth="10.0" />
<ColumnConstraints fillWidth="false" halignment="RIGHT" hgrow="NEVER" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" /> <ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="NEVER" minWidth="10.0"/> <ColumnConstraints fillWidth="false" halignment="RIGHT" hgrow="NEVER" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="NEVER" minWidth="10.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" /> <ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
</columnConstraints> </columnConstraints>
<rowConstraints> <rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints> </rowConstraints>
<children> <children>
<FontAwesomeIconView styleClass="open-icon" GridPane.hgrow="NEVER"/> <MaterialDesignIconView styleClass="file-document-icon" GridPane.hgrow="NEVER" />
<Label text="%stateline.csv" GridPane.columnIndex="1" GridPane.hgrow="NEVER" /> <Label text="%stateline.csv" GridPane.columnIndex="1" GridPane.hgrow="NEVER" />
<Label fx:id="csvName" GridPane.columnIndex="2" GridPane.hgrow="ALWAYS" /> <Label fx:id="csvName" GridPane.columnIndex="2" GridPane.hgrow="ALWAYS" />
<Label text="%stateline.state" GridPane.columnIndex="3" GridPane.hgrow="NEVER"/> <MaterialDesignIconView styleClass="config-check-icon" GridPane.columnIndex="3" GridPane.hgrow="NEVER" />
<Label fx:id="stateName" GridPane.columnIndex="4" GridPane.hgrow="ALWAYS"/> <Label text="%stateline.configuration" GridPane.columnIndex="4" GridPane.hgrow="NEVER" />
<FontAwesomeIconView styleClass="open-icon" GridPane.columnIndex="5" GridPane.hgrow="NEVER"/> <Label fx:id="configurationName" GridPane.columnIndex="5" GridPane.hgrow="ALWAYS" />
<Label text="%stateline.configuration" GridPane.columnIndex="6" GridPane.hgrow="NEVER"/> <Label text="%lineNumber" GridPane.columnIndex="7" />
<Label fx:id="configurationName" GridPane.columnIndex="7" GridPane.hgrow="ALWAYS"/> <Label fx:id="currentLineNumber" text="" GridPane.columnIndex="8" />
<Label text="%totalNumberOfLines" GridPane.columnIndex="9" />
<Label fx:id="totalNumberOfLines" text="0" GridPane.columnIndex="10" />
</children> </children>
<BorderPane.margin> <BorderPane.margin>
<Insets top="4.0" left="8.0" bottom="4.0" right="8.0"/> <Insets bottom="4.0" left="8.0" right="8.0" top="4.0" />
</BorderPane.margin> </BorderPane.margin>
</GridPane> </GridPane>
</bottom> </bottom>

View File

@@ -1,13 +1,21 @@
menu.open.csv = Open CSV File menu.open.csv = Open CSV File
menu.open.config = Open Validation Config menu.open.config = Open Validation Config
menu.create.config = Create Validation Config
menu.save = Save menu.save = Save
menu.save.as = Save As ... menu.save.as = Save As ...
menu.save.config = Save Validation Config
menu.save.as.config = Save Validation Config as ...
menu.close = Close menu.close = Close
menu.about = About menu.about = About
menu.file = File menu.file = File
menu.edit = Edit menu.edit = Edit
menu.help = Help menu.help = Help
menu.preferences = Preferences menu.preferences = Preferences
menu.delete.row = Delete row
menu.add.row = Add row
menu.goto.line = Goto line
menu.export = Export error log
title.validation.errors = Validation Errors: title.validation.errors = Validation Errors:
stateline.csv = CSV: stateline.csv = CSV:
@@ -24,16 +32,22 @@ dialog.exit.text = There are changes made to the csv file. If you close now, the
dialog.preferences.title = Preferences dialog.preferences.title = Preferences
dialog.preferences.header.text = Preferences dialog.preferences.header.text = Preferences
dialog.goto.line.title = Go to line
dialog.goto.line.header.text = Go to given line (max. {0}) and select line!
dialog.goto.line.label = Line:
preferences.quoteChar = Quote character: preferences.quoteChar = Quote character:
preferences.delimiterChar = Delimiter character: preferences.delimiterChar = Delimiter character:
preferences.ignoreEmptyLines = Ignore empty lines: preferences.ignoreEmptyLines = Ignore empty lines:
preferences.surroundingSpacesNeedQuotes = Surrounding spaces need quotes: preferences.surroundingSpacesNeedQuotes = Surrounding spaces need quotes:
preferences.quoteMode = Quote mode: preferences.quoteMode = Quote mode:
preferences.fileEncoding = File encoding:
# validaton messages # validaton messages
validation.message.not.empty = should not be empty validation.message.not.empty = should not be empty
validation.message.integer = should be an integer validation.message.integer = should be an integer
validation.message.double = should be a double validation.message.double = should be a double
validation.message.email = should be an email address
validation.message.alphanumeric = should be alphanumeric validation.message.alphanumeric = should be alphanumeric
validation.message.groovy.exception = groovy script '{0}' throws exception: {1} validation.message.groovy.exception = groovy script '{0}' throws exception: {1}
validation.message.groovy.return.null = groovy script '{0}' returns null validation.message.groovy.return.null = groovy script '{0}' returns null
@@ -41,7 +55,47 @@ validation.message.min.length = has not min length of {0}
validation.message.max.length = has not max length of {0} validation.message.max.length = has not max length of {0}
validation.message.date.format = is not a date of format {0} validation.message.date.format = is not a date of format {0}
validation.message.regexp = does not match {0} validation.message.regexp = does not match {0}
validation.message.uniqueness.multiple = value {0} is not unique (found in rows {1})
validation.message.uniqueness.single = value {0} is not unique (found in row {1})
validation.message.uri = should be an uri
validation.message.uuid = should be an uuid
validation.message.binary = should be a base64 encoded string representing binary data
validation.message.header.length = number of headers is not correct! there are {0} but there should be {1} validation.message.header.length = number of headers is not correct! there are {0} but there should be {1}
validation.message.header.match = header number {0} does not match "{1}" should be "{3}" validation.message.header.match = header number {0} does not match "{1}" should be "{2}"
validation.message.value.of = Value {0} is not part of this list {1} validation.message.value.of = Value {0} is not part of this list {1}
validation.rule.label.not_empty = not empty
validation.rule.label.integer = integer
validation.rule.label.double = double
validation.rule.label.minlength = minimum length:
validation.rule.label.maxlength = maximum length:
validation.rule.label.dateformat = date format:
validation.rule.label.alphanumeric = alphanumeric
validation.rule.label.regexp = regular expression:
validation.rule.label.value_of = value in list:
validation.rule.label.groovy = groovy:
validation.rules.active = active
validation.rules.name = rule
validation.rules.value = value
validation.rule.label.unique = Unique in column
validation.rule.format = Format:
validation.rule.type = Type:
dialog.validation.rules.title = Validation rules
dialog.validation.rules.header = Validation rules of column "{0}"
context.menu.edit.column.rules = Edit rules
lineNumber = Selected line:
totalNumberOfLines = Lines:
log.header.message = {0} has {1} errors
log.message = row {0} column {1} : {2}
column = column
format.type.DEFAULT = default
format.type.EMAIL = email
format.type.URI = uri
format.type.BINARY = binary
format.type.UUID = uuid

View File

@@ -8,14 +8,23 @@
menu.open.csv = CSV Datei \u00f6ffnen menu.open.csv = CSV Datei \u00f6ffnen
menu.open.config = Pr\u00fcfkonfiguration \u00f6ffnen menu.open.config = Pr\u00fcfkonfiguration \u00f6ffnen
menu.create.config = Pr\u00fcfkonfiguration erzeugen
menu.save = Speichern menu.save = Speichern
menu.save.as = Speichern als ... menu.save.as = Speichern als ...
menu.save.config = Pr\u00fcfkonfiguration speichern
menu.save.as.config = Pr\u00fcfkonfiguration speichern als ...
menu.close = Beenden menu.close = Beenden
menu.about = \u00dcber ... menu.about = \u00dcber ...
menu.file = Datei menu.file = Datei
menu.edit = Bearbeiten menu.edit = Bearbeiten
menu.help = Hilfe menu.help = Hilfe
menu.preferences = Einstellungen menu.preferences = Einstellungen
menu.delete.row = Zeile l\u00f6schen
menu.add.row = Zeile hinzuf\u00fcgen
menu.goto.line = Springe zur Zeile
menu.export = Export Fehlerdatei
title.validation.errors = Fehler in der Datei: title.validation.errors = Fehler in der Datei:
stateline.csv = CSV: stateline.csv = CSV:
@@ -28,6 +37,10 @@ state.unchanged = Unver\u00e4ndert
dialog.preferences.title = Eigenschaften dialog.preferences.title = Eigenschaften
dialog.preferences.header.text = Eigenschaften dialog.preferences.header.text = Eigenschaften
dialog.goto.line.title = Springe zur Zeile
dialog.goto.line.header.text = Zur angegebenen Zeile (max. {0}) springen und ausw\u00e4hlen!
dialog.goto.line.label = Zeile:
dialog.exit.title = Anwendung beenden dialog.exit.title = Anwendung beenden
dialog.exit.header.text = M\u00f6chten Sie wirklich die Anwendung beenden? dialog.exit.header.text = M\u00f6chten Sie wirklich die Anwendung beenden?
dialog.exit.text = Es gibt noch ungespeicherte \u00c4nderungen, die verloren gehen, wenn Sie die Anwendung jetzt beenden. dialog.exit.text = Es gibt noch ungespeicherte \u00c4nderungen, die verloren gehen, wenn Sie die Anwendung jetzt beenden.
@@ -37,11 +50,13 @@ preferences.delimiterChar = Trennzeichen:
preferences.ignoreEmptyLines = Leere Zeilen ignorieren: preferences.ignoreEmptyLines = Leere Zeilen ignorieren:
preferences.surroundingSpacesNeedQuotes = Leerzeichen einfassen: preferences.surroundingSpacesNeedQuotes = Leerzeichen einfassen:
preferences.quoteMode = Einfassungsmodus: preferences.quoteMode = Einfassungsmodus:
preferences.fileEncoding = Dateicodierung:
# validaton messages # validaton messages
validation.message.not.empty = Darf nicht leer sein. validation.message.not.empty = Darf nicht leer sein.
validation.message.integer = Muss eine Zahl sein. validation.message.integer = Muss eine Zahl sein.
validation.message.double = Muss eine Gleitkommazahl sein validation.message.double = Muss eine Gleitkommazahl sein
validation.message.email = Muss eine Email-Adresse sein
validation.message.alphanumeric = Darf nur Zahlen und Buchstaben enthalten. validation.message.alphanumeric = Darf nur Zahlen und Buchstaben enthalten.
validation.message.groovy.exception = groovy script '{0}' wirft folgenden Fehler: {1} validation.message.groovy.exception = groovy script '{0}' wirft folgenden Fehler: {1}
validation.message.groovy.return.null = groovy script '{0}' meldet "null" validation.message.groovy.return.null = groovy script '{0}' meldet "null"
@@ -49,7 +64,48 @@ validation.message.min.length = Hat nicht die minimale L\u00e4nge von {0}
validation.message.max.length = Hat nicht die maximale L\u00e4nge von {0} validation.message.max.length = Hat nicht die maximale L\u00e4nge von {0}
validation.message.date.format = Das Datumsformat entspricht nicht {0} validation.message.date.format = Das Datumsformat entspricht nicht {0}
validation.message.regexp = entspricht nicht dem regul\u00e4ren Ausdruck {0} validation.message.regexp = entspricht nicht dem regul\u00e4ren Ausdruck {0}
validation.message.uniqueness.multiple = Wert {0} ist nicht einmalig (gefunden in den Zeilen {1})
validation.message.uniqueness.single = Wert {0} ist nicht einmalig (gefunden in Zeile {1})
validation.message.uri = Muss eine URI sein
validation.message.uuid = Muss eine UUID sein
validation.message.binary = Muss ein base64 encoded Text sein, der bin\u00e4re Daten enth\u00e4lt
validation.message.header.length = Anzahl der \u00dcberschriften ist nicht korrekt! Es sind {0} aber es sollten {1} sein validation.message.header.length = Anzahl der \u00dcberschriften ist nicht korrekt! Es sind {0} aber es sollten {1} sein
validation.message.header.match = \u00dcberschrift in Spalte {0} stimmt nicht. "{1}" sollte "{3}" sein validation.message.header.match = \u00dcberschrift in Spalte {0} stimmt nicht. "{1}" sollte "{3}" sein
validation.message.value.of = Der Wert {0} ist nicht in der Liste {1} enthalten validation.message.value.of = Der Wert {0} ist nicht in der Liste {1} enthalten
validation.rule.label.not_empty = Nicht leer
validation.rule.label.integer = Zahl
validation.rule.label.double = Gleitkommazahl
validation.rule.label.minlength = Minimale L\u00e4nge:
validation.rule.label.maxlength = Maximale L\u00e4nge:
validation.rule.label.dateformat = Datumsformat:
validation.rule.label.alphanumeric = Nur Zahlen und Buchstaben:
validation.rule.label.regexp = Regul\u00e4rer Ausdruck:
validation.rule.label.value_of = In der Liste:
validation.rule.label.groovy = groovy:
validation.rules.active = Aktiv
validation.rules.name = Regel
validation.rules.value = Wert
validation.rule.label.unique = Einmalig in Spalte
validation.rule.format = Format:
validation.rule.type = Typ:
dialog.validation.rules.title = Pr\u00fcfregeln
dialog.validation.rules.header = Pr\u00fcfregeln f\u00fcr die Spalte "{0}"
context.menu.edit.column.rules = Pr\u00fcfregeln bearbeiten
lineNumber = Ausgew\u00e4hlte Zeile:
totalNumberOfLines = Zeilen:
log.header.message = {0} hat {1} Fehler
log.message = Zeile {0} Spalte {1} : {2}
column = Spalte
format.type.DEFAULT = Standard
format.type.EMAIL = Email
format.type.URI = URI
format.type.BINARY = Bin\u00e4r
format.type.UUID = UUID

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.net.URL?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.Spinner?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import org.fxmisc.richtext.CodeArea?>
<GridPane hgap="20.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="800.0" vgap="6.0" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints halignment="LEFT" hgrow="NEVER" />
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS" />
<ColumnConstraints halignment="LEFT" hgrow="NEVER" />
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" valignment="CENTER" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" valignment="CENTER" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" valignment="CENTER" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" valignment="TOP" vgrow="ALWAYS" />
<RowConstraints />
</rowConstraints>
<children>
<Label text="%validation.rule.type" GridPane.columnSpan="2" GridPane.halignment="LEFT" GridPane.hgrow="ALWAYS" />
<Label text="%validation.rule.format" GridPane.columnSpan="2" GridPane.halignment="LEFT" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" />
<ComboBox fx:id="typeComboBox" maxWidth="1.7976931348623157E308" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.halignment="LEFT" GridPane.hgrow="ALWAYS" />
<ComboBox fx:id="formatComboBox" prefWidth="150.0" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="1" visible="false" />
<TextField fx:id="formatTextField" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="1" visible="false" />
<CheckBox fx:id="enableNotEmptyRule" mnemonicParsing="false" text="%validation.rule.label.not_empty" GridPane.rowIndex="3" />
<CheckBox fx:id="enableUniqueRule" mnemonicParsing="false" text="%validation.rule.label.unique" GridPane.columnIndex="2" GridPane.rowIndex="3" />
<CheckBox fx:id="enableMinLengthRule" mnemonicParsing="false" text="%validation.rule.label.minlength" GridPane.rowIndex="4" />
<Spinner fx:id="minLengthSpinner" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<CheckBox fx:id="enableMaxLengthRule" mnemonicParsing="false" text="%validation.rule.label.maxlength" GridPane.columnIndex="2" GridPane.rowIndex="4" />
<Spinner fx:id="maxLengthSpinner" GridPane.columnIndex="3" GridPane.rowIndex="4" />
<CheckBox fx:id="enableRegexpRule" mnemonicParsing="false" text="%validation.rule.label.regexp" GridPane.rowIndex="5" />
<TextField fx:id="regexpRuleTextField" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="5" />
<CheckBox fx:id="enableValueOfRule" mnemonicParsing="false" text="%validation.rule.label.value_of" GridPane.rowIndex="6" />
<TextField fx:id="valueOfRuleTextField" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="6" />
<CheckBox fx:id="enableGroovyRule" mnemonicParsing="false" text="%validation.rule.label.groovy" GridPane.rowIndex="7">
<GridPane.margin>
<Insets top="8.0" />
</GridPane.margin>
</CheckBox>
<CodeArea fx:id="groovyRuleTextArea" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="7">
<GridPane.margin>
<Insets top="8.0" />
</GridPane.margin>
</CodeArea>
<Separator prefWidth="200.0" GridPane.columnSpan="4" GridPane.rowIndex="2" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
<stylesheets>
<URL value="@/ninja/javafx/smartcsv/fx/smartcsv.css" />
</stylesheets>
</GridPane>

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,109 @@
package ninja.javafx.smartcsv.files;
import ninja.javafx.smartcsv.FileReader;
import ninja.javafx.smartcsv.FileWriter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.File;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
public class FileStorageTest {
private FileReader<String> reader;
private FileWriter<String> writer;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// subject under test
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private FileStorage<String> sut;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// init
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@BeforeEach
public void initialize() {
reader = mock(FileReader.class);
writer = mock(FileWriter.class);
sut = new FileStorage<>(reader, writer);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// tests
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Test
public void setFile_ShouldStoreFile() {
// pre execution assertion
assertThat("get file returns null", sut.getFile(), nullValue());
assertThat("property is null", sut.fileProperty().isNull().get(), equalTo(true));
// execution
final File file = new File("file");
sut.setFile(file);
// assertion
assertThat("get file returns the file", sut.getFile(), sameInstance(file));
assertThat("property is not null", sut.fileProperty().isNull().get(), equalTo(false));
}
@Test
public void load_ShouldReadContentFromReader() throws Exception {
// setup
final File file = new File("file");
sut.setFile(file);
when(reader.getContent()).thenReturn("CONTENT");
// execution
sut.load();
// assertion
verify(reader).read(file);
assertThat("content is set", sut.getContent(), equalTo("CONTENT"));
}
@Test
public void load_ShouldResetFileChangedProperty() throws Exception {
// setup
sut.setFileChanged(true);
when(reader.getContent()).thenReturn("CONTENT");
// execution
sut.load();
// assertion
assertThat("file changed is reset", sut.isFileChanged(), equalTo(false));
}
@Test
public void save_ShouldWriteContentToWriter() throws Exception {
// setup
final String content = "CONTENT";
sut.setContent(content);
final File file = new File("file");
sut.setFile(file);
// execution
sut.save();
// assertion
verify(writer).setContent(content);
verify(writer).write(file);
}
@Test
public void save_ShouldResetFileChangedProperty() throws Exception {
// setup
sut.setFileChanged(true);
// execution
sut.save();
// assertion
assertThat("file changed is reset", sut.isFileChanged(), equalTo(false));
}
}

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -26,12 +26,11 @@
package ninja.javafx.smartcsv.fx.table.model; package ninja.javafx.smartcsv.fx.table.model;
import ninja.javafx.smartcsv.validation.Validator; import org.junit.jupiter.api.Test;
import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/** /**
* unit test for the csv model * unit test for the csv model
@@ -41,14 +40,13 @@ public class CSVModelTest {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// constants // constants
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static final String TESTHEADER = "TESTHEADER"; private static final String TESTHEADER = "TESTHEADER";
static final String TESTVALUE = "TESTVALUE"; private static final String TESTVALUE = "TESTVALUE";
static final String FILEPATH = "filepath";
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// subject under test // subject under test
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CSVModel sut = new CSVModel(); private CSVModel sut = new CSVModel();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// tests // tests
@@ -77,20 +75,6 @@ public class CSVModelTest {
assertThat(sut.getRows().indexOf(newRow), is(newRow.getRowNumber())); assertThat(sut.getRows().indexOf(newRow), is(newRow.getRowNumber()));
} }
@Test
public void set_validator_calls_when_model_has_data() {
// setup
Validator validator = mock(Validator.class);
setup_model_with_one_row_one_column_and_value();
// execution
sut.setValidator(validator);
// assertion
verify(validator).isValid(TESTHEADER, TESTVALUE, 0);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// private methods // private methods
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -1,13 +1,11 @@
package ninja.javafx.smartcsv.fx.table.model; package ninja.javafx.smartcsv.fx.table.model;
import ninja.javafx.smartcsv.validation.Validator; import org.junit.jupiter.api.Test;
import org.hamcrest.Matchers;
import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
/** /**
* unit test for row class * unit test for row class
@@ -17,13 +15,13 @@ public class CSVRowTest {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// constants // constants
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static final String COLUMN = "COLUMN"; private static final String COLUMN = "COLUMN";
static final String VALUE = "VALUE"; private static final String VALUE = "VALUE";
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// subject under test // subject under test
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CSVRow sut = new CSVRow(); private CSVRow sut = new CSVRow();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -1,60 +0,0 @@
package ninja.javafx.smartcsv.fx.table.model;
import ninja.javafx.smartcsv.validation.Validator;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* unit test for the value class
*/
public class CSVValueTest {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// constants
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static final String COLUMN = "COLUMN";
static final String VALUE = "VALUE";
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// mocks
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Validator validator = mock(Validator.class);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// subject under test
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CSVValue sut;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// init
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Before
public void initialize() {
sut = new CSVValue();
sut.setValidator(validator);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// tests
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Test
public void set_value_calls_validation() {
//setup
sut.setColumn(COLUMN);
// execution
sut.setValue(VALUE);
// assertion
verify(validator).isValid(eq(COLUMN), eq(VALUE), anyInt());
}
}

View File

@@ -0,0 +1,57 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ninja.javafx.smartcsv.preferences;
import org.junit.jupiter.api.Test;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.*;
public class CharsetHelperTest {
@Test
public void getCharsetName_known_charset() {
String result = CharsetHelper.getCharsetName("UTF-16");
assertEquals(StandardCharsets.UTF_16.name(), result);
assertNotEquals(StandardCharsets.UTF_16.name(), Charset.defaultCharset());
}
@Test
public void getCharsetName_unknown_charset() {
String result = CharsetHelper.getCharsetName("foobar");
assertEquals(Charset.defaultCharset().name(), result);
}
@Test
public void getCharsetName_null_charset() {
String result = CharsetHelper.getCharsetName(null);
assertEquals(Charset.defaultCharset().name(), result);
}
}

View File

@@ -1,62 +0,0 @@
package ninja.javafx.smartcsv.validation;
import com.typesafe.config.Config;
import java.util.List;
import static java.util.Arrays.asList;
import static org.mockito.Mockito.*;
public class ConfigMock {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// constants
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private static final String HEADER_SECTION_KEY = "headers";
private static final String COLUMN_SECTION_KEY = "columns";
private static final String LIST_KEY = "list";
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// mocks
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static Config headerSectionConfig(String[] headerNames) {
Config headerSectionConfig = mock(Config.class);
Config listConfig = mock(Config.class);
when(headerSectionConfig.hasPath(HEADER_SECTION_KEY)).thenReturn(true);
when(headerSectionConfig.getConfig(HEADER_SECTION_KEY)).thenReturn(listConfig);
when(listConfig.hasPath(LIST_KEY)).thenReturn(true);
when(listConfig.getStringList(LIST_KEY)).thenReturn(asList(headerNames));
return headerSectionConfig;
}
public static Config columnSectionConfig(String column, String validation, Object value) {
Config columnSectionConfig = mock(Config.class);
Config columnConfig = mock(Config.class);
Config validatorConfig = mock(Config.class);
when(columnSectionConfig.hasPath(COLUMN_SECTION_KEY)).thenReturn(true);
when(columnSectionConfig.getConfig(COLUMN_SECTION_KEY)).thenReturn(columnConfig);
when(columnConfig.hasPath(column)).thenReturn(true);
when(columnConfig.getConfig(column)).thenReturn(validatorConfig);
when(validatorConfig.hasPath(validation)).thenReturn(true);
if (value instanceof Boolean) {
when(validatorConfig.getBoolean(validation)).thenReturn((Boolean) value);
} else if (value instanceof String) {
when(validatorConfig.getString(validation)).thenReturn((String) value);
} else if (value instanceof Integer) {
when(validatorConfig.getInt(validation)).thenReturn((Integer) value);
} else if (value instanceof List) {
when(validatorConfig.getStringList(validation)).thenReturn((List<String>) value);
}
return columnSectionConfig;
}
}

View File

@@ -2,7 +2,7 @@
The MIT License (MIT) The MIT License (MIT)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Copyright (c) 2015 javafx.ninja <info@javafx.ninja> Copyright (c) 2015-2021 javafx.ninja <info@javafx.ninja>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -26,70 +26,42 @@
package ninja.javafx.smartcsv.validation; package ninja.javafx.smartcsv.validation;
import com.typesafe.config.Config; import com.google.gson.Gson;
import org.junit.Before; import com.google.gson.GsonBuilder;
import org.junit.Test; import ninja.javafx.smartcsv.validation.configuration.ValidationConfiguration;
import org.junit.runner.RunWith; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.runners.Parameterized; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static ninja.javafx.smartcsv.validation.ConfigMock.headerSectionConfig; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.Assert.assertTrue;
/** /**
* unit test for header validator * unit test for header validator
*/ */
@RunWith(Parameterized.class)
public class HeaderValidationTest { public class HeaderValidationTest {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameters
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private Config config;
private Boolean expectedResult;
private List<ValidationMessage> expectedErrors;
private String[] headerNames;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// subject under test
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private Validator sut;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameterized constructor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public HeaderValidationTest(String[] configHeaderNames,
String[] headerNames,
Boolean expectedResult,
List<ValidationMessage> expectedErrors) {
this.config = headerSectionConfig(configHeaderNames);
this.headerNames = headerNames;
this.expectedResult = expectedResult;
this.expectedErrors = expectedErrors;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// init
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Before
public void initialize() {
sut = new Validator(config);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// tests // tests
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Test @ParameterizedTest
public void validation() { @MethodSource("validationConfigurations")
public void validation(String configHeaderNames,
String[] headerNames,
Boolean expectedResult,
List<ValidationMessage> expectedErrors) {
// setup
Gson gson = new GsonBuilder().create();
ValidationConfiguration config = gson.fromJson(configHeaderNames, ValidationConfiguration.class);
Validator sut = new Validator(config, new TestColumnValueProvider());
// execution // execution
ValidationError result = sut.isHeaderValid(headerNames); ValidationError result = sut.isHeaderValid(headerNames);
@@ -103,14 +75,33 @@ public class HeaderValidationTest {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameters for tests // parameters for tests
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Parameterized.Parameters public static Stream<Arguments> validationConfigurations() {
public static Collection validationConfigurations() { return Stream.of(
return asList(new Object[][] { Arguments.of( json(), new String[] {}, true, null ),
{ new String[] {}, new String[] {}, true, null }, Arguments.of( json("a"), new String[] {"a"}, true, null ),
{ new String[] {"a"}, new String[] {"a"}, true, null }, Arguments.of( json("a"), new String[] {"b"}, false, singletonList(new ValidationMessage("validation.message.header.match", "0", "a", "b"))),
{ new String[] {"a"}, new String[] {"b"}, false, singletonList(new ValidationMessage("validation.message.header.match", "0", "a", "b"))}, Arguments.of( json("a"), new String[] {"a","b"}, false, singletonList(new ValidationMessage("validation.message.header.length", "2", "1"))),
{ new String[] {"a"}, new String[] {"a","b"}, false, singletonList(new ValidationMessage("validation.message.header.length", "2", "1"))}, Arguments.of( json("a", "b"), new String[] {"b", "a"}, false, asList(new ValidationMessage("validation.message.header.match", "0", "a", "b"), new ValidationMessage("validation.message.header.match", "1", "b", "a")) )
{ new String[] {"a", "b"}, new String[] {"b", "a"}, false, asList(new ValidationMessage("validation.message.header.match", "0", "a", "b"), new ValidationMessage("validation.message.header.match", "1", "b", "a")) } );
});
} }
@SuppressWarnings("StringConcatenationInLoop")
public static String json(String... headerNames) {
String json = "{ \"fields\": [";
for (String headerName: headerNames) {
json += "{\"name\" : \""+headerName+"\" },";
}
if (headerNames != null && headerNames.length > 0) {
json = json.substring(0, json.length() - 1);
}
json += "]}";
return json;
}
} }

View File

@@ -0,0 +1,18 @@
package ninja.javafx.smartcsv.validation;
import ninja.javafx.smartcsv.fx.table.model.ColumnValueProvider;
/**
* @author abi
*/
public class TestColumnValueProvider implements ColumnValueProvider {
@Override
public String getValue(int row, String column) {
return null;
}
@Override
public int getNumberOfRows() {
return 0;
}
}

View File

@@ -1,75 +1,42 @@
package ninja.javafx.smartcsv.validation; package ninja.javafx.smartcsv.validation;
import com.typesafe.config.Config; import com.google.gson.Gson;
import org.junit.Before; import com.google.gson.GsonBuilder;
import org.junit.Test; import ninja.javafx.smartcsv.validation.configuration.ValidationConfiguration;
import org.junit.runner.RunWith; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.runners.Parameterized; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.Collection; import java.util.List;
import java.util.stream.Stream;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static ninja.javafx.smartcsv.validation.ConfigMock.columnSectionConfig; import static java.util.stream.Collectors.joining;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
/** /**
* unit test for validator * unit test for validator
*/ */
@RunWith(Parameterized.class)
public class ValidatorTest { public class ValidatorTest {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameters
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private Config config;
private String column;
private String value;
private Boolean expectedResult;
private ValidationMessage expectedError;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// subject under test
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private Validator sut;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameterized constructor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public ValidatorTest(String configcolumn,
String configValidation,
Object configValue,
String column,
String value,
Boolean expectedResult,
ValidationMessage expectedError) {
this.config = columnSectionConfig(configcolumn, configValidation, configValue);
this.column = column;
this.value = value;
this.expectedResult = expectedResult;
this.expectedError = expectedError;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// init
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Before
public void initialize() {
sut = new Validator(config);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// tests // tests
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Test @ParameterizedTest
public void validation() { @MethodSource("validationConfigurations")
public void validation(String config,
String value,
Boolean expectedResult,
ValidationMessage expectedError) {
// setup
Gson gson = new GsonBuilder().create();
ValidationConfiguration validationConfiguration = gson.fromJson(config, ValidationConfiguration.class);
Validator sut = new Validator(validationConfiguration, new TestColumnValueProvider());
// execution // execution
ValidationError result = sut.isValid(column, value, 0); ValidationError result = sut.isValid(0, "column", value);
// assertion // assertion
assertThat(result == null, is(expectedResult)); assertThat(result == null, is(expectedResult));
@@ -81,33 +48,80 @@ public class ValidatorTest {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameters for tests // parameters for tests
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Parameterized.Parameters public static Stream<Arguments> validationConfigurations() {
public static Collection validationConfigurations() { return Stream.of(
return asList(new Object[][] { Arguments.of( constraintsJSON("string", "required", true), "value", true, null ),
{ "column", "not empty", true, "column", "value", true, null }, Arguments.of( constraintsJSON("string", "required", true), "", false, new ValidationMessage("validation.message.not.empty") ),
{ "column", "not empty", true, "column", "", false, new ValidationMessage("validation.message.not.empty") }, Arguments.of( constraintsJSON("string", "required", true), null, false, new ValidationMessage("validation.message.not.empty") ),
{ "column", "not empty", true, "column", null, false, new ValidationMessage("validation.message.not.empty") }, Arguments.of( constraintsJSON("integer", null, true), "999", true, null ),
{ "column", "integer", true, "column", "999", true, null }, Arguments.of( constraintsJSON("integer", null, true), "a", false, new ValidationMessage("validation.message.integer") ),
{ "column", "integer", true, "column", "a", false, new ValidationMessage("validation.message.integer") }, Arguments.of( constraintsJSON("number", null, true), "999", true, null ),
{ "column", "double", true, "column", "999", true, null }, Arguments.of( constraintsJSON("number", null, true), "999.000", true, null ),
{ "column", "double", true, "column", "999.000", true, null }, Arguments.of( constraintsJSON("number", null, true), "a", false, new ValidationMessage("validation.message.double") ),
{ "column", "double", true, "column", "a", false, new ValidationMessage("validation.message.double") }, Arguments.of( constraintsJSON("string", "minLength", 2), "12", true, null ),
{ "column", "minlength", 2, "column", "12", true, null }, Arguments.of( constraintsJSON("string", "minLength", 2), "1", false, new ValidationMessage("validation.message.min.length", "2") ),
{ "column", "minlength", 2, "column", "1", false, new ValidationMessage("validation.message.min.length", "2") }, Arguments.of( constraintsJSON("string", "maxLength", 2), "12", true, null ),
{ "column", "maxlength", 2, "column", "12", true, null }, Arguments.of( constraintsJSON("string", "maxLength", 2), "123", false, new ValidationMessage("validation.message.max.length", "2") ),
{ "column", "maxlength", 2, "column", "123", false, new ValidationMessage("validation.message.max.length", "2") }, Arguments.of( constraintsJSON("string", "pattern", "[a-z]*"), "abc", true, null ),
{ "column", "date", "yyyyMMdd", "column", "20151127", true, null }, Arguments.of( constraintsJSON("string", "pattern", "[a-z]*"), "abcA", false, new ValidationMessage("validation.message.regexp", "[a-z]*") ),
{ "column", "date", "yyyyMMdd", "column", "27.11.2015", false, new ValidationMessage("validation.message.date.format", "yyyyMMdd") }, Arguments.of( constraintsJSON("string", "enum", asList("a","b","c","d","e")), "c", true, null ),
{ "column", "alphanumeric", true, "column", "abcABC123", true, null }, Arguments.of( constraintsJSON("string", "enum", asList("a","b","c","d","e")), "f", false, new ValidationMessage("validation.message.value.of", "f", "a, b, c, d, e") ),
{ "column", "alphanumeric", true, "column", "-abcABC123", false, new ValidationMessage("validation.message.alphanumeric") },
{ "column", "regexp", "[a-z]*", "column", "abc", true, null }, Arguments.of( formatJSON("string", null), "some string", true, null ),
{ "column", "regexp", "[a-z]*", "column", "abcA", false, new ValidationMessage("validation.message.regexp", "[a-z]*") }, Arguments.of( formatJSON("string", "email"), "test@javafx.ninja", true, null ),
{ "column", "groovy", "value.contains('a')? 'true' : 'no a inside'", "column", "abcdef", true, null }, Arguments.of( formatJSON("string", "email"), "wrong email", false, new ValidationMessage("validation.message.email") ),
{ "column", "groovy", "value.contains('a')? 'true' : 'no a inside'", "column", "bcdefg", false, new ValidationMessage("no a inside") }, Arguments.of( formatJSON("string", "uri"), "http://www.javafx.ninja", true, null ),
{ "column", "value of", asList("a","b","c","d","e"), "column", "c", true, null }, Arguments.of( formatJSON("string", "uri"), "!$%&/()", false, new ValidationMessage("validation.message.uri") ),
{ "column", "value of", asList("a","b","c","d","e"), "column", "f", false, new ValidationMessage("validation.message.value.of", "f", "a, b, c, d, e") }, Arguments.of( formatJSON("string", "binary"), "dGVzdA==", true, null ),
}); Arguments.of( formatJSON("string", "binary"), "no binary", false, new ValidationMessage("validation.message.binary") ),
Arguments.of( formatJSON("string", "uuid"), "6ba7b810-9dad-11d1-80b4-00c04fd430c8", true, null ),
Arguments.of( formatJSON("string", "uuid"), "no uuid", false, new ValidationMessage("validation.message.uuid") ),
Arguments.of( formatJSON("date", null), "2015-11-27", true, null ),
Arguments.of( formatJSON("date", null), "27.11.2015", false, new ValidationMessage("validation.message.date.format", "yyyy-MM-dd") ),
Arguments.of( formatJSON("date", "yyyyMMdd"), "20151127", true, null ),
Arguments.of( formatJSON("date", "yyyyMMdd"), "27.11.2015", false, new ValidationMessage("validation.message.date.format", "yyyyMMdd") ),
Arguments.of( customAttributeJSON( "groovy", "value.contains('a')? 'true' : 'no a inside'"), "abcdef", true, null ),
Arguments.of( customAttributeJSON( "groovy", "value.contains('a')? 'true' : 'no a inside'"), "bcdefg", false, new ValidationMessage("no a inside") )
);
} }
public static String constraintsJSON(String type, String constraint, Object value) {
String json = "{\"fields\": [ { \"name\": \"column\", \"type\" : \"" + type +"\"";
if (constraint != null) {
json += ", \"constraints\": { \"" + constraint + "\":";
if (value instanceof String) {
json += "\"" + value + "\"";
} else if (value instanceof List) {
List<String> list = (List<String>) value;
json += "[" + list.stream().collect(joining(", ")) + "]";
} else {
json += value;
}
json += "}";
}
json += "}]}";
return json;
}
public static String formatJSON(String type, String format) {
String json = "{\"fields\": [ { \"name\": \"column\", \"type\" : \"" + type +"\"";
if (format != null) {
json += ", \"format\": \"" + format + "\"";
}
json += "}]}";
return json;
}
public static String customAttributeJSON(String attribute, String value) {
String json = "{\"fields\": [ { \"name\": \"column\", \"type\" : \"string\"";
if (attribute != null) {
json += ", \""+attribute+"\": \"" + value + "\"";
}
json += "}]}";
return json;
}
} }