41 Commits

Author SHA1 Message Date
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
43 changed files with 2332 additions and 597 deletions

View File

@@ -7,7 +7,17 @@ At work I have the need to fix wrong CSV files from customers. It is hard to fin
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 (0.5)](https://drive.google.com/file/d/0BwY9gBUvn5qmejllOTRwbEJYdDA/view?usp=sharing)
##Talks
[Introduction](http://javafx.ninja/talks/introduction/)
##License ##License
###The MIT License (MIT) ###The MIT License (MIT)

View File

@@ -1,5 +1,5 @@
group 'ninja.javafx' group 'ninja.javafx'
version '0.3-SNAPSHOT' version '0.5-SNAPSHOT'
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'groovy' apply plugin: 'groovy'
@@ -18,10 +18,21 @@ dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '1.3' testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '1.3'
testCompile group: 'org.mockito', name: 'mockito-all', version: '1.10.19' testCompile group: 'org.mockito', name: 'mockito-all', version: '1.10.19'
compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.5' compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.7'
compile group: 'org.springframework', name:'spring-context', version: '4.2.4.RELEASE' compile group: 'org.springframework', name:'spring-context', version: '4.3.1.RELEASE'
compile group: 'net.sf.supercsv', name: 'super-csv', version: '2.4.0' compile group: 'net.sf.supercsv', name: 'super-csv', version: '2.4.0'
compile group: 'com.typesafe', name: 'config', version: '1.3.0' compile group: 'commons-validator', name: 'commons-validator', version: '1.5.1'
compile group: 'commons-validator', name: 'commons-validator', version: '1.5.0' compile group: 'de.jensd', name: 'fontawesomefx-commons', version: '8.12'
compile group: 'de.jensd', name: 'fontawesomefx', version: '8.8' compile group: 'de.jensd', name: 'fontawesomefx-fontawesome', version: '4.6.3'
compile group: 'de.jensd', name: 'fontawesomefx-materialdesignfont', version: '1.6.50'
compile group: 'de.jensd', name: 'fontawesomefx-materialicons', version: '2.2.0'
compile group: 'org.controlsfx', name: 'controlsfx', version: '8.40.11'
compile group: 'com.google.code.gson', name: 'gson', version: '2.7'
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.6.2'
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.6.2'
compile group: 'org.fxmisc.richtext', name: 'richtextfx', version: '0.6.10'
}
task wrapper(type: Wrapper) {
gradleVersion = '2.14.1' //version required
} }

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#Thu Oct 01 19:02:27 CEST 2015 #Sat Jul 23 09:35:51 CEST 2016
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
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 distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip

10
gradlew vendored
View File

@@ -42,11 +42,6 @@ case "`uname`" in
;; ;;
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 # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" PRG="$0"
@@ -61,9 +56,9 @@ while [ -h "$PRG" ] ; do
fi fi
done done
SAVED="`pwd`" SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&- cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`" APP_HOME="`pwd -P`"
cd "$SAVED" >&- cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -114,6 +109,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`

2
gradlew.bat vendored
View File

@@ -46,7 +46,7 @@ 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 if "%@eval[2+2]" == "4" goto 4NT_args

View File

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

View File

@@ -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

@@ -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

@@ -40,8 +40,7 @@ import java.util.Map;
/** /**
* 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;
@@ -72,7 +71,7 @@ public class CSVFileReader extends CSVConfigurable implements FileReader {
} }
} }
public CSVModel getData() { public CSVModel getContent() {
return model; return model;
} }

View File

@@ -42,12 +42,11 @@ import static java.util.stream.Collectors.toMap;
/** /**
* 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;
} }

View File

@@ -0,0 +1,78 @@
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 there 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<E>();
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

@@ -26,8 +26,13 @@
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.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
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,12 +41,13 @@ 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 javafx.util.converter.NumberStringConverter;
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.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;
@@ -50,10 +56,13 @@ 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.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.fx.validation.ValidationEditorController;
import ninja.javafx.smartcsv.preferences.PreferencesFileReader; import ninja.javafx.smartcsv.preferences.PreferencesFileReader;
import ninja.javafx.smartcsv.preferences.PreferencesFileWriter; import ninja.javafx.smartcsv.preferences.PreferencesFileWriter;
import ninja.javafx.smartcsv.validation.ValidationConfiguration;
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 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;
@@ -62,12 +71,16 @@ 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.text.MessageFormat;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import static java.lang.Integer.parseInt;
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.*;
/** /**
@@ -85,37 +98,29 @@ public class SmartCSVController extends FXMLController {
".SmartCSV.fx" + ".SmartCSV.fx" +
File.separator + "" + File.separator + "" +
"preferences.json"); "preferences.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";
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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;
@FXML @FXML
private BorderPane applicationPane; private BorderPane applicationPane;
@@ -129,9 +134,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 +143,53 @@ 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 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 Label currentLineNumber;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// members // members
@@ -148,13 +197,16 @@ 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 File currentConfigFile;
private FileStorage<CSVModel> currentCsvFile = new FileStorage<>(new CSVFileReader(), new CSVFileWriter());
private FileStorage<ValidationConfiguration> currentConfigFile = new FileStorage<>(new ValidationFileReader(), new ValidationFileWriter());
private FileStorage<CsvPreference> csvPreferenceFile = new FileStorage<>(new PreferencesFileReader(), new PreferencesFileWriter());
private ListChangeListener<ValidationError> errorListListener = c -> tableView.refresh();
private WeakListChangeListener<ValidationError> weakErrorListListener = new WeakListChangeListener<>(errorListListener);
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// init // init
@@ -163,16 +215,35 @@ 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);
loadCsvPreferencesFromFile();
} }
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 +263,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
@@ -261,13 +337,49 @@ public class SmartCSVController extends FXMLController {
setCsvPreference(csvPreference); setCsvPreference(csvPreference);
saveCsvPreferences(csvPreference); saveCsvPreferences(csvPreference);
} else { } else {
preferencesController.setCsvPreference(preferencesLoader.getCSVpreference()); preferencesController.setCsvPreference(csvPreferenceFile.getContent());
}
}
@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()) {
row.addValue(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);
}
} }
} }
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 +394,111 @@ public class SmartCSVController extends FXMLController {
return canExit; return canExit;
} }
public void showValidationEditor(String column) {
validationEditorController.setSelectedColumn(column);
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();
});
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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()));
} else { } else {
csvName.setText(""); setCsvPreference(CsvPreference.EXCEL_NORTH_EUROPE_PREFERENCE);
} }
} }
private void setConfigFileName() {
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) { 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(CsvPreference csvPreference) {
csvLoader.setCsvPreference(csvPreference);
csvFileWriter.setCsvPreference(csvPreference);
preferencesController.setCsvPreference(csvPreference); preferencesController.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,77 +506,74 @@ 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); storageFile.setFile(file);
return file; useLoadFileService(storageFile, t -> resetContent());
} else {
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); loadFileService.setFileStorage(fileStorage);
loadFileService.setFileReader(fileReader);
loadFileService.restart(); loadFileService.restart();
loadFileService.setOnSucceeded(event -> runLater(() -> { loadFileService.setOnSucceeded(onSucceededHandler);
resetContent();
fileChanged.setValue(false);
}));
} }
private void useSaveFileService(FileWriter writer, File file) { private void useSaveFileService(FileStorage fileStorage) {
saveFileService.setFile(file); saveFileService.setFileStorage(fileStorage);
saveFileService.setWriter(writer);
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(); if (currentCsvFile.getContent() != null) {
if (model != null) { currentCsvFile.getContent().getValidationError().addListener(weakErrorListListener);
model.setValidator(validationLoader.getValidator()); currentCsvFile.getContent().setValidationConfiguration(currentConfigFile.getContent());
validationEditorController.setValidationConfiguration(currentConfigFile.getContent());
tableView = new TableView<>(); tableView = new TableView<>();
bindLineNumber();
for (String column : model.getHeader()) { bindMenuItemsToTableSelection(deleteRowMenuItem);
bindButtonsToTableSelection(deleteRowButton);
for (String column : currentCsvFile.getContent().getHeader()) {
addColumn(column, tableView); addColumn(column, tableView);
} }
tableView.getItems().setAll(model.getRows());
tableView.getItems().setAll(currentCsvFile.getContent().getRows());
tableView.setEditable(true); tableView.setEditable(true);
setBottomAnchor(tableView, 0.0); setBottomAnchor(tableView, 0.0);
@@ -435,8 +581,7 @@ public class SmartCSVController extends FXMLController {
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());
} }
} }
@@ -450,14 +595,19 @@ public class SmartCSVController extends FXMLController {
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(); currentCsvFile.getContent().revalidate();
}); });
} }
}); });
@@ -465,8 +615,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 +634,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

@@ -0,0 +1,176 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015 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.fx.util.ColorConstants;
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.getI18nValidatioMessage;
/**
* 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;
for (ValidationError error : errorList) {
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(getI18nValidatioMessage(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,91 @@
package ninja.javafx.smartcsv.fx.list;
import com.sun.javafx.scene.control.skin.resources.ControlResources;
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;
/**
* Created by abi on 05.08.2016.
*/
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

@@ -26,12 +26,8 @@
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;

View File

@@ -33,10 +33,12 @@ import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
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.ColorConstants;
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 +79,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())));
} }

View File

@@ -28,8 +28,16 @@ package ninja.javafx.smartcsv.fx.table.model;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import ninja.javafx.smartcsv.validation.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;
import java.util.ArrayList;
import java.util.List;
/** /**
* The CSVModel is the client representation for the csv filepath. * The CSVModel is the client representation for the csv filepath.
@@ -37,17 +45,20 @@ import ninja.javafx.smartcsv.validation.Validator;
*/ */
public class CSVModel { public class CSVModel {
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);
revalidate(); revalidate();
} }
@@ -99,8 +110,62 @@ public class CSVModel {
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;
validator.clearScriptCache();
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();
}
private boolean hasValidator() {
return validator != null && validator.hasConfig();
}
public ValidationConfiguration createValidationConfiguration() {
ValidationConfiguration newValidationConfiguration = new ValidationConfiguration();
newValidationConfiguration.setHeaderNames(this.header);
this.validator = new Validator(newValidationConfiguration);
this.revalidate();
return newValidationConfiguration;
}
private static class RevalidationService extends Service<List<ValidationError>> {
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);
}
} }
for (int lineNumber = 0; lineNumber < rows.size(); lineNumber++) { for (int lineNumber = 0; lineNumber < rows.size(); lineNumber++) {
@@ -112,7 +177,8 @@ public class CSVModel {
if (validator != null) { if (validator != null) {
ValidationError validationError = validator.isValid(column, value.getValue(), lineNumber); ValidationError validationError = validator.isValid(column, value.getValue(), lineNumber);
if (validationError != null) { if (validationError != null) {
addValidationError(validationError); logger.info("revalidate: {} errors found in line {}", validationError.getMessages().size(), lineNumber);
errors.add(validationError);
value.setValidationError(validationError); value.setValidationError(validationError);
} else { } else {
value.setValidationError(null); value.setValidationError(null);
@@ -122,11 +188,12 @@ public class CSVModel {
} }
} }
} }
} catch (Throwable t) {
logger.error("validation error", t);
} }
return errors;
private void addValidationError(ValidationError validationError) { }
if (validationError != null) { };
this.validationError.add(validationError);
} }
} }

View File

@@ -81,9 +81,9 @@ public class CSVRow {
public void addValue(String column, String value) { public void addValue(String column, String value) {
CSVValue v = new CSVValue(); CSVValue v = new CSVValue();
v.setValidator(validator); v.setValidator(validator);
v.setValue(value);
v.setColumn(column); v.setColumn(column);
v.setRowNumber(rowNumber); v.setRowNumber(rowNumber);
v.setValue(value);
columns.put(column, new SimpleObjectProperty<>(v)); columns.put(column, new SimpleObjectProperty<>(v));
} }

View File

@@ -0,0 +1,35 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015 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 = "#ff6622";
}

View File

@@ -40,6 +40,20 @@ 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();
for (ValidationError validationError: errors) {
message.append(getI18nValidatioMessage(resourceBundle, validationError)).append("\n");
}
if (message.toString().length() != 0) {
return cutOffLastLineBreak(message.toString());
}
return "";
}
public static String getI18nValidatioMessage(ResourceBundle resourceBundle, ValidationError error) { public static String getI18nValidatioMessage(ResourceBundle resourceBundle, ValidationError error) {
List<ValidationMessage> validationMessages = error.getMessages(); List<ValidationMessage> validationMessages = error.getMessages();

View File

@@ -29,6 +29,7 @@ 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.FileReader;
import ninja.javafx.smartcsv.files.FileStorage;
import java.io.File; import java.io.File;
@@ -38,14 +39,10 @@ import java.io.File;
@org.springframework.stereotype.Service @org.springframework.stereotype.Service
public class LoadFileService extends Service { public class LoadFileService extends Service {
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
@@ -54,7 +51,7 @@ public class LoadFileService extends Service {
@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

@@ -30,6 +30,7 @@ import javafx.concurrent.Service;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import ninja.javafx.smartcsv.FileWriter; import ninja.javafx.smartcsv.FileWriter;
import ninja.javafx.smartcsv.csv.CSVFileWriter; import ninja.javafx.smartcsv.csv.CSVFileWriter;
import ninja.javafx.smartcsv.files.FileStorage;
import java.io.File; import java.io.File;
@@ -41,14 +42,9 @@ import static javafx.application.Platform.runLater;
@org.springframework.stereotype.Service @org.springframework.stereotype.Service
public class SaveFileService extends Service { public class SaveFileService extends Service {
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;
} }
@@ -58,7 +54,7 @@ public class SaveFileService extends Service {
@Override @Override
protected Void call() throws Exception { protected Void call() throws Exception {
try { try {
writer.write(file); file.save();
} catch (Throwable ex) { } catch (Throwable ex) {
ex.printStackTrace(); ex.printStackTrace();
} }

View File

@@ -0,0 +1,440 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015 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 ninja.javafx.smartcsv.fx.FXMLController;
import ninja.javafx.smartcsv.validation.ValidationConfiguration;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory;
import org.fxmisc.richtext.StyleSpans;
import org.fxmisc.richtext.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.util.Arrays.asList;
import static java.util.stream.Collectors.joining;
/**
* 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("|", 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 CheckBox notEmptyRuleCheckBox;
@FXML
private CheckBox integerRuleCheckBox;
@FXML
private CheckBox doublerRuleCheckBox;
@FXML
private CheckBox alphanumericRuleCheckBox;
@FXML
private Spinner<Integer> minLengthSpinner;
@FXML
private Spinner<Integer> maxLengthSpinner;
@FXML
private TextField dateformatRuleTextField;
@FXML
private TextField regexpRuleTextField;
@FXML
private TextField valueOfRuleTextField;
@FXML
private CodeArea groovyRuleTextArea;
@FXML
private CheckBox enableNotEmptyRule;
@FXML
private CheckBox enableIntegerRule;
@FXML
private CheckBox enableDoubleRule;
@FXML
private CheckBox enableAlphanumericRule;
@FXML
private CheckBox enableMinLengthRule;
@FXML
private CheckBox enableMaxLengthRule;
@FXML
private CheckBox enableDateRule;
@FXML
private CheckBox enableRegexpRule;
@FXML
private CheckBox enableValueOfRule;
@FXML
private CheckBox enableGroovyRule;
@Value("${fxml.smartcvs.validation.editor.view}")
@Override
public void setFxmlFilePath(String filePath) {
this.fxmlFilePath = filePath;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
minLengthSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, Integer.MAX_VALUE, 0));
maxLengthSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, Integer.MAX_VALUE, 0));
initCheckBox(notEmptyRuleCheckBox, enableNotEmptyRule);
initCheckBox(integerRuleCheckBox, enableIntegerRule);
initCheckBox(doublerRuleCheckBox, enableDoubleRule);
initCheckBox(alphanumericRuleCheckBox, enableAlphanumericRule);
initSpinner(minLengthSpinner, enableMinLengthRule);
initSpinner(maxLengthSpinner, enableMaxLengthRule);
initTextInputControl(dateformatRuleTextField, enableDateRule);
initTextInputControl(regexpRuleTextField, enableRegexpRule);
initTextInputControl(valueOfRuleTextField, enableValueOfRule);
initCodeAreaControl(groovyRuleTextArea, enableGroovyRule);
selectedColumn.addListener(observable -> {
updateForm();
});
}
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;
}
public void updateConfiguration() {
if (enableIntegerRule.isSelected()) {
validationConfiguration.setIntegerRuleFor(selectedColumn.getValue(), integerRuleCheckBox.isSelected());
} else {
validationConfiguration.setIntegerRuleFor(selectedColumn.getValue(), null);
}
if (enableNotEmptyRule.isSelected()) {
validationConfiguration.setNotEmptyRuleFor(selectedColumn.getValue(), notEmptyRuleCheckBox.isSelected());
} else {
validationConfiguration.setNotEmptyRuleFor(selectedColumn.getValue(), null);
}
if (enableDoubleRule.isSelected()) {
validationConfiguration.setDoubleRuleFor(selectedColumn.getValue(), doublerRuleCheckBox.isSelected());
} else {
validationConfiguration.setDoubleRuleFor(selectedColumn.getValue(), null);
}
if (enableAlphanumericRule.isSelected()) {
validationConfiguration.setAlphanumericRuleFor(selectedColumn.getValue(), alphanumericRuleCheckBox.isSelected());
} else {
validationConfiguration.setAlphanumericRuleFor(selectedColumn.getValue(), null);
}
if (enableDateRule.isSelected()) {
validationConfiguration.setDateRuleFor(selectedColumn.getValue(), dateformatRuleTextField.getText());
} else {
validationConfiguration.setDateRuleFor(selectedColumn.getValue(), null);
}
if (enableGroovyRule.isSelected()) {
validationConfiguration.setGroovyRuleFor(selectedColumn.getValue(), groovyRuleTextArea.getText());
} else {
validationConfiguration.setGroovyRuleFor(selectedColumn.getValue(), null);
}
if (enableMinLengthRule.isSelected()) {
validationConfiguration.setMinLengthRuleFor(selectedColumn.getValue(), minLengthSpinner.getValue());
} else {
validationConfiguration.setMinLengthRuleFor(selectedColumn.getValue(), null);
}
if (enableMaxLengthRule.isSelected()) {
validationConfiguration.setMaxLengthRuleFor(selectedColumn.getValue(), maxLengthSpinner.getValue());
} else {
validationConfiguration.setMaxLengthRuleFor(selectedColumn.getValue(), null);
}
if (enableRegexpRule.isSelected()) {
validationConfiguration.setRegexpRuleFor(selectedColumn.getValue(), regexpRuleTextField.getText());
} else {
validationConfiguration.setRegexpRuleFor(selectedColumn.getValue(), null);
}
if (enableValueOfRule.isSelected()) {
validationConfiguration.setValueOfRuleFor(selectedColumn.getValue(), asList(valueOfRuleTextField.getText().split(", ")));
} else {
validationConfiguration.setValueOfRuleFor(selectedColumn.getValue(), null);
}
}
private void updateForm() {
updateCheckBox(
notEmptyRuleCheckBox,
validationConfiguration.getNotEmptyRuleFor(getSelectedColumn()),
enableNotEmptyRule
);
updateCheckBox(
integerRuleCheckBox,
validationConfiguration.getIntegerRuleFor(getSelectedColumn()),
enableIntegerRule
);
updateCheckBox(
doublerRuleCheckBox,
validationConfiguration.getDoubleRuleFor(getSelectedColumn()),
enableDoubleRule
);
updateCheckBox(
alphanumericRuleCheckBox,
validationConfiguration.getAlphanumericRuleFor(getSelectedColumn()),
enableAlphanumericRule
);
updateSpinner(
minLengthSpinner,
validationConfiguration.getMinLengthRuleFor(getSelectedColumn()),
enableMinLengthRule
);
updateSpinner(
maxLengthSpinner,
validationConfiguration.getMaxLengthRuleFor(getSelectedColumn()),
enableMaxLengthRule
);
updateTextInputControl(
dateformatRuleTextField,
validationConfiguration.getDateRuleFor(getSelectedColumn()),
enableDateRule
);
updateTextInputControl(
regexpRuleTextField,
validationConfiguration.getRegexpRuleFor(getSelectedColumn()),
enableRegexpRule
);
updateTextInputControl(
valueOfRuleTextField,
validationConfiguration.getValueOfRuleFor(getSelectedColumn()),
enableValueOfRule
);
updateCodeAreaControl(
groovyRuleTextArea,
validationConfiguration.getGroovyRuleFor(getSelectedColumn()),
enableGroovyRule
);
}
private void updateCheckBox(CheckBox rule, Boolean value, CheckBox ruleEnabled) {
if (value == null) {
ruleEnabled.setSelected(false);
} else {
rule.setSelected(value);
ruleEnabled.setSelected(true);
}
}
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, 0, value);
}
}
private void initCheckBox(CheckBox rule, CheckBox ruleEnabled) {
rule.disableProperty().bind(ruleEnabled.selectedProperty().not());
ruleEnabled.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
rule.setSelected(false);
}
});
}
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

@@ -26,8 +26,7 @@
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.springframework.stereotype.Service;
import org.supercsv.prefs.CsvPreference; import org.supercsv.prefs.CsvPreference;
@@ -35,16 +34,17 @@ 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.QuoteModeHelper.getQuoteMode;
/** /**
* file reader for the preferences * file reader for the preferences
*/ */
@Service public class PreferencesFileReader implements FileReader<CsvPreference> {
public class PreferencesFileReader implements FileReader {
private Config config; private Map config;
private CsvPreference csvPreference; private CsvPreference csvPreference;
public PreferencesFileReader() { public PreferencesFileReader() {
@@ -55,15 +55,15 @@ public class PreferencesFileReader implements FileReader {
@Override @Override
public void read(File filename) throws IOException { public void read(File filename) throws IOException {
config = ConfigFactory.parseFile(filename); 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); char quoteChar = 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 surroundingSpacesNeedQuotes = (Boolean)config.get("surroundingSpacesNeedQuotes");
boolean ignoreEmptyLines = config.getBoolean("ignoreEmptyLines"); boolean ignoreEmptyLines = (Boolean)config.get("ignoreEmptyLines");
String quoteMode = config.getString("quoteMode"); String quoteMode = config.get("quoteMode").toString();
csvPreference = new CsvPreference.Builder(quoteChar, delimiterChar, endOfLineSymbols) csvPreference = new CsvPreference.Builder(quoteChar, delimiterChar, endOfLineSymbols)
.useQuoteMode(getQuoteMode(quoteMode)) .useQuoteMode(getQuoteMode(quoteMode))
@@ -73,7 +73,7 @@ public class PreferencesFileReader implements FileReader {
} }
} }
public CsvPreference getCSVpreference() { public CsvPreference getContent() {
return csvPreference; return csvPreference;
} }

View File

@@ -26,9 +26,8 @@
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.springframework.stereotype.Service;
import org.supercsv.prefs.CsvPreference; import org.supercsv.prefs.CsvPreference;
@@ -42,12 +41,11 @@ import java.util.Map;
/** /**
* Save preferences to configuration file * Save preferences to configuration file
*/ */
@Service public class PreferencesFileWriter implements FileWriter<CsvPreference> {
public class PreferencesFileWriter implements FileWriter {
private CsvPreference csvPreference; private CsvPreference csvPreference;
public void setCsvPreference(CsvPreference csvPreference) { public void setContent(CsvPreference csvPreference) {
this.csvPreference = csvPreference; this.csvPreference = csvPreference;
} }
@@ -60,8 +58,8 @@ public class PreferencesFileWriter implements FileWriter {
preferences.put("ignoreEmptyLines", csvPreference.isIgnoreEmptyLines()); preferences.put("ignoreEmptyLines", csvPreference.isIgnoreEmptyLines());
preferences.put("quoteMode", QuoteModeHelper.getQuoteModeName(csvPreference.getQuoteMode())); 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,46 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015 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 com.google.gson.annotations.SerializedName;
/**
* header configuration for the validation
*/
public class HeaderConfiguration {
@SerializedName("list")
private String[] names ;
public String[] getNames() {
return names;
}
public void setNames(String[] names) {
this.names = names;
}
}

View File

@@ -0,0 +1,175 @@
/*
The MIT License (MIT)
-----------------------------------------------------------------------------
Copyright (c) 2015 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 com.google.gson.annotations.SerializedName;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* validation configuration
*/
public class ValidationConfiguration {
@SerializedName("headers")
private HeaderConfiguration headerConfiguration = new HeaderConfiguration();
@SerializedName("columns")
private Map<String, Map<String, Object>> columnConfigurations = new HashMap<>();
public String[] headerNames() {
if (noHeader()) return null;
return headerConfiguration.getNames();
}
public void setHeaderNames(String[] headerNames) {
headerConfiguration.setNames(headerNames);
}
public Boolean getUniquenessRuleFor(String column) {
return (Boolean)getValue(column, "unique");
}
public Boolean getIntegerRuleFor(String column) {
return (Boolean)getValue(column, "integer");
}
public Boolean getDoubleRuleFor(String column) {
return (Boolean)getValue(column, "double");
}
public Boolean getNotEmptyRuleFor(String column) {
return (Boolean)getValue(column, "not empty");
}
public Integer getMinLengthRuleFor(String column) {
return doubleToInteger((Double)getValue(column, "minlength"));
}
public Integer getMaxLengthRuleFor(String column) {
if (noRulesFor(column)) return null;
return doubleToInteger((Double)getValue(column, "maxlength"));
}
public String getDateRuleFor(String column) {
if (noRulesFor(column)) return null;
return (String)getValue(column, "date");
}
public Boolean getAlphanumericRuleFor(String column) {
if (noRulesFor(column)) return null;
return (Boolean)getValue(column, "alphanumeric");
}
public String getRegexpRuleFor(String column) {
if (noRulesFor(column)) return null;
return (String)getValue(column, "regexp");
}
public List<String> getValueOfRuleFor(String column) {
if (noRulesFor(column)) return null;
return (List<String>)getValue(column, "value of");
}
public String getGroovyRuleFor(String column) {
if (noRulesFor(column)) return null;
return (String)getValue(column, "groovy");
}
public void setIntegerRuleFor(String column, Boolean value) {
setValue(column, value, "integer");
}
public void setDoubleRuleFor(String column, Boolean value) {
setValue(column, value, "double");
}
public void setNotEmptyRuleFor(String column, Boolean value) {
setValue(column, value, "not empty");
}
public void setMinLengthRuleFor(String column, Integer value) {
setValue(column, value == null ? null : value.doubleValue(), "minlength");
}
public void setMaxLengthRuleFor(String column, Integer value) {
setValue(column, value == null ? null : value.doubleValue(), "maxlength");
}
public void setDateRuleFor(String column, String value) {
setValue(column, value, "date");
}
public void setAlphanumericRuleFor(String column, Boolean value) {
setValue(column, value, "alphanumeric");
}
public void setRegexpRuleFor(String column, String value) {
setValue(column, value, "regexp");
}
public void setValueOfRuleFor(String column, List<String> value) {
setValue(column, value, "value of");
}
public void setGroovyRuleFor(String column, String value) {
setValue(column, value, "groovy");
}
private void setValue(String column, Object value, String key) {
if (!columnConfigurations.containsKey(column)) {
columnConfigurations.put(column, new HashMap<>());
}
if (value == null && columnConfigurations.get(column).containsKey(key)) {
columnConfigurations.get(column).remove(key);
} else {
columnConfigurations.get(column).put(key, value);
}
}
private Object getValue(String column, String key) {
if (noRulesFor(column)) return null;
return columnConfigurations.get(column).get(key);
}
private boolean noHeader() {
return headerConfiguration == null;
}
private boolean noRulesFor(String column) {
return columnConfigurations == null || columnConfigurations.get(column) == null;
}
private Integer doubleToInteger(Double value) {
if (value == null) return null;
return (int)Math.round(value);
}
}

View File

@@ -26,8 +26,7 @@
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 org.springframework.stereotype.Service;
@@ -37,17 +36,16 @@ import java.io.IOException;
/** /**
* 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);
} }
public Validator getValidator() { public ValidationConfiguration getContent() {
return new Validator(config); return config;
} }
} }

View File

@@ -24,47 +24,31 @@
*/ */
package ninja.javafx.smartcsv.fx.list; package ninja.javafx.smartcsv.validation;
import javafx.scene.control.ListCell; import com.google.gson.Gson;
import javafx.scene.text.Text; import com.google.gson.GsonBuilder;
import ninja.javafx.smartcsv.validation.ValidationError; import ninja.javafx.smartcsv.FileWriter;
import org.springframework.stereotype.Service;
import java.util.ResourceBundle; import java.io.File;
import java.io.IOException;
import static ninja.javafx.smartcsv.fx.util.I18nValidationUtil.getI18nValidatioMessage; import java.nio.file.Files;
/** /**
* Cell to show the error text * file writer for the validation configuration
*/ */
public class ValidationErrorListCell extends ListCell<ValidationError> { public class ValidationFileWriter implements FileWriter<ValidationConfiguration> {
private ResourceBundle resourceBundle; private ValidationConfiguration validationConfiguration;
public ValidationErrorListCell(ResourceBundle resourceBundle) { public void setContent(ValidationConfiguration validationConfiguration) {
this.resourceBundle = resourceBundle; this.validationConfiguration = validationConfiguration;
} }
@Override @Override
public void updateItem(ValidationError validationError, boolean empty) { public void write(File file) throws IOException {
super.updateItem(validationError, empty); Gson gson = new GsonBuilder().setPrettyPrinting().create();
if (empty) { Files.write(file.toPath(), gson.toJson(validationConfiguration).getBytes());
clearContent();
} else {
addContent(validationError);
}
}
private void clearContent() {
setText(null);
setGraphic(null);
}
private void addContent(ValidationError validationError) {
setText(null);
Text text = new Text(getI18nValidatioMessage(resourceBundle, validationError));
text.setWrappingWidth(180);
setGraphic(text);
} }
} }

View File

@@ -26,13 +26,13 @@
package ninja.javafx.smartcsv.validation; package ninja.javafx.smartcsv.validation;
import com.typesafe.config.Config;
import groovy.lang.Binding; import groovy.lang.Binding;
import groovy.lang.GroovyShell; import groovy.lang.GroovyShell;
import groovy.lang.Script; import groovy.lang.Script;
import org.codehaus.groovy.control.CompilationFailedException; import org.codehaus.groovy.control.CompilationFailedException;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -49,10 +49,10 @@ public class Validator {
// member variables // member variables
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private Config validationConfig; private ValidationConfiguration validationConfig;
private GroovyShell shell = new GroovyShell(); private GroovyShell shell = new GroovyShell();
private Map<String, Script> scriptCache = new HashMap<>(); private Map<String, Script> scriptCache = new HashMap<>();
private Map<String, HashMap<String, Integer>> uniquenessLookupTable = new HashMap<>();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// constructors // constructors
@@ -62,13 +62,13 @@ 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) {
this.validationConfig = validationConfig; this.validationConfig = validationConfig;
} }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// oublic methods // public methods
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/** /**
@@ -79,42 +79,63 @@ public class Validator {
*/ */
public ValidationError isValid(String column, String value, Integer lineNumber) { public ValidationError isValid(String column, String value, Integer lineNumber) {
ValidationError result = null; ValidationError result = null;
if (validationConfig != null) { if (hasConfig()) {
Config columnSectionConfig = getColumnSectionConfig();
if (columnSectionConfig != null) {
Config columnConfig = getColumnConfig(columnSectionConfig, column);
if (columnConfig != null) {
ValidationError error = ValidationError.withLineNumber(lineNumber); ValidationError error = ValidationError.withLineNumber(lineNumber);
checkBlankOrNull(columnConfig, value, error); checkBlankOrNull(column, value, error);
if (value != null && !value.isEmpty()) { if (value != null && !value.isEmpty()) {
checkRegularExpression(columnConfig, value, error); checkRegularExpression(column, value, error);
checkAlphaNumeric(columnConfig, value, error); checkAlphaNumeric(column, value, error);
checkDate(columnConfig, value, error); checkDate(column, value, error);
checkMaxLength(columnConfig, value, error); checkMaxLength(column, value, error);
checkMinLength(columnConfig, value, error); checkMinLength(column, value, error);
checkInteger(columnConfig, value, error); checkInteger(column, value, error);
checkGroovy(column, columnConfig, value, error); checkGroovy(column, value, error);
checkValueOf(columnConfig, value, error); checkValueOf(column, value, error);
checkDouble(columnConfig, value, error); checkDouble(column, value, error);
checkUniqueness(column, value, lineNumber, error);
} }
if (!error.isEmpty()) { if (!error.isEmpty()) {
result = error; result = error;
} }
} }
}
}
return result; return result;
} }
public boolean hasConfig() {
return validationConfig != null;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// private methods // private methods
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void checkGroovy(String column, Config columnConfig, String value, ValidationError error) { private void checkUniqueness(String column, String value, Integer lineNumber, ValidationError error) {
String groovyScript = getString(columnConfig, "groovy"); if (validationConfig.getUniquenessRuleFor(column) != null && validationConfig.getUniquenessRuleFor(column)) {
HashMap<String, Integer> columnValueMap = uniquenessLookupTable.get(column);
columnValueMap = getColumnValueMap(column, columnValueMap);
Integer valueInLineNumber = columnValueMap.get(value);
if (valueInLineNumber != null) {
if (!valueInLineNumber.equals(lineNumber)) {
error.add("validation.message.uniqueness", value, valueInLineNumber.toString());
}
} else {
columnValueMap.put(value, lineNumber);
}
}
}
private HashMap<String, Integer> getColumnValueMap(String column, HashMap<String, Integer> valueLineNumber) {
if (valueLineNumber == null) {
valueLineNumber = new HashMap<>();
uniquenessLookupTable.put(column, valueLineNumber);
}
return valueLineNumber;
}
private void checkGroovy(String column, String value, ValidationError error) {
String groovyScript = validationConfig.getGroovyRuleFor(column);
if (groovyScript != null) { if (groovyScript != null) {
Script script = scriptCache.get(column); Script script = scriptCache.get(column);
@@ -149,43 +170,42 @@ public class Validator {
return groovyResult.equals(true) || groovyResult.toString().trim().toLowerCase().equals("true"); return groovyResult.equals(true) || groovyResult.toString().trim().toLowerCase().equals("true");
} }
private void checkValueOf(Config columnConfig, String value, ValidationError error) { private void checkValueOf(String column, String value, ValidationError error) {
List<String> stringList = getStringList(columnConfig, "value of"); List<String> values = validationConfig.getValueOfRuleFor(column);
if (stringList != null) { if (values != null) {
if (!stringList.contains(value)) { if (!values.contains(value)) {
String commaSeparated = stringList.stream().collect(joining(", ")); String commaSeparated = values.stream().collect(joining(", "));
error.add("validation.message.value.of", value, commaSeparated); error.add("validation.message.value.of", value, commaSeparated);
} }
} }
} }
private void checkBlankOrNull(Config columnConfig, String value, ValidationError error) { private void checkBlankOrNull(String column, String value, ValidationError error) {
if (getBoolean(columnConfig, "not empty")) { if (validationConfig.getNotEmptyRuleFor(column) != null && validationConfig.getNotEmptyRuleFor(column)) {
if (isBlankOrNull(value)) { if (isBlankOrNull(value)) {
error.add("validation.message.not.empty"); error.add("validation.message.not.empty");
} }
} }
} }
private void checkInteger(Config columnConfig, String value, ValidationError error) { private void checkInteger(String column, String value, ValidationError error) {
if (getBoolean(columnConfig, "integer")) { if (validationConfig.getIntegerRuleFor(column) != null && validationConfig.getIntegerRuleFor(column)) {
if (!isInt(value)) { if (!isInt(value)) {
error.add("validation.message.integer"); error.add("validation.message.integer");
} }
} }
} }
private void checkDouble(Config columnConfig, String value, ValidationError error) { private void checkDouble(String column, String value, ValidationError error) {
if (getBoolean(columnConfig, "double")) { if (validationConfig.getDoubleRuleFor(column) != null && validationConfig.getDoubleRuleFor(column)) {
if (!isDouble(value)) { if (!isDouble(value)) {
error.add("validation.message.double"); error.add("validation.message.double");
} }
} }
} }
private void checkMinLength(String column, String value, ValidationError error) {
private void checkMinLength(Config columnConfig, String value, ValidationError error) { Integer minLength = validationConfig.getMinLengthRuleFor(column);
Integer minLength = getInteger(columnConfig, "minlength");
if (minLength != null) { if (minLength != null) {
if (!minLength(value, minLength)) { if (!minLength(value, minLength)) {
error.add("validation.message.min.length", minLength.toString()); error.add("validation.message.min.length", minLength.toString());
@@ -193,8 +213,8 @@ public class Validator {
} }
} }
private void checkMaxLength(Config columnConfig, String value, ValidationError error) { private void checkMaxLength(String column, String value, ValidationError error) {
Integer maxLength = getInteger(columnConfig, "maxlength"); Integer maxLength = validationConfig.getMaxLengthRuleFor(column);
if (maxLength != null) { if (maxLength != null) {
if (!maxLength(value, maxLength)) { if (!maxLength(value, maxLength)) {
error.add("validation.message.max.length", maxLength.toString()); error.add("validation.message.max.length", maxLength.toString());
@@ -202,8 +222,8 @@ public class Validator {
} }
} }
private void checkDate(Config columnConfig, String value, ValidationError error) { private void checkDate(String column, String value, ValidationError error) {
String dateformat = getString(columnConfig, "date"); String dateformat = validationConfig.getDateRuleFor(column);
if (dateformat != null && !dateformat.trim().isEmpty()) { if (dateformat != null && !dateformat.trim().isEmpty()) {
if (!isDate(value, dateformat, true)) { if (!isDate(value, dateformat, true)) {
error.add("validation.message.date.format", dateformat); error.add("validation.message.date.format", dateformat);
@@ -211,16 +231,16 @@ public class Validator {
} }
} }
private void checkAlphaNumeric(Config columnConfig, String value, ValidationError error) { private void checkAlphaNumeric(String column, String value, ValidationError error) {
if (getBoolean(columnConfig, "alphanumeric")) { if (validationConfig.getAlphanumericRuleFor(column) != null && validationConfig.getAlphanumericRuleFor(column)) {
if (!matchRegexp(value, "[0-9a-zA-Z]*")) { if (!matchRegexp(value, "[0-9a-zA-Z]*")) {
error.add("validation.message.alphanumeric"); error.add("validation.message.alphanumeric");
} }
} }
} }
private void checkRegularExpression(Config columnConfig, String value, ValidationError error) { private void checkRegularExpression(String column, String value, ValidationError error) {
String regexp = getString(columnConfig, "regexp"); String regexp = validationConfig.getRegexpRuleFor(column);
if (regexp != null && !regexp.trim().isEmpty()) { if (regexp != null && !regexp.trim().isEmpty()) {
if (!matchRegexp(value, regexp)) { if (!matchRegexp(value, regexp)) {
error.add("validation.message.regexp", regexp); error.add("validation.message.regexp", regexp);
@@ -228,53 +248,25 @@ public class Validator {
} }
} }
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 +275,10 @@ public class Validator {
} }
} }
} }
}
return result; return result;
} }
public void clearScriptCache() {
scriptCache.clear();
}
} }

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,37 @@
<?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.60" 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="SmartCSV.fx">
<font>
<Font size="18.0" />
</font>
<VBox.margin>
<Insets top="8.0" />
</VBox.margin>
</Label>
<Label text="A simple JavaFX application to load, save and edit a CSV file and provide a JSON configuration for columns " wrapText="true" />
<Label text="to check the values in the columns." />
<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="3rd party software"> <Label text="3rd party software">
<VBox.margin> <VBox.margin>
<Insets top="8.0" /> <Insets top="8.0" />
@@ -19,7 +42,7 @@
</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" />
@@ -33,6 +56,9 @@
<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" 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" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints> </rowConstraints>
<children> <children>
<Label text="junit" /> <Label text="junit" />
@@ -45,12 +71,18 @@
<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="supercsv" GridPane.rowIndex="4" />
<Hyperlink onAction="#openLinkInBrowser" text="http://super-csv.github.io/super-csv/" GridPane.columnIndex="1" GridPane.rowIndex="4" /> <Hyperlink onAction="#openLinkInBrowser" text="http://super-csv.github.io/super-csv/" 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" />

View File

@@ -1,10 +1,11 @@
application.name = SmartCSV.fx application.name = SmartCSV.fx
application.version = 0.3 application.version = 0.5
# 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

@@ -15,36 +15,205 @@
} }
.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;
}
/* 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;
}
.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,101 +1,270 @@
<?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.GlyphsStack?>
<?import javafx.geometry.Insets?> <?import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIconView?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import java.net.URL?> <?import java.net.URL?>
<BorderPane fx:id="applicationPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" <?import javafx.geometry.Insets?>
minWidth="-Infinity" prefHeight="700.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8.0.66" <?import javafx.scene.control.Button?>
xmlns:fx="http://javafx.com/fxml/1"> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.SeparatorMenuItem?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<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">
<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> </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 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>
@@ -109,26 +278,27 @@
<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="ALWAYS" minWidth="10.0" prefWidth="100.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 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> </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" />
</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,20 @@
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
title.validation.errors = Validation Errors: title.validation.errors = Validation Errors:
stateline.csv = CSV: stateline.csv = CSV:
@@ -24,6 +31,10 @@ 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:
@@ -41,7 +52,28 @@ 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 = value {0} is not unique (found in line {1})
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
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:

View File

@@ -8,14 +8,22 @@
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
title.validation.errors = Fehler in der Datei: title.validation.errors = Fehler in der Datei:
stateline.csv = CSV: stateline.csv = CSV:
@@ -28,6 +36,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.
@@ -49,7 +61,29 @@ 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 = Wert {0} ist nicht einmalig (gefunden in Zeile {1})
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
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:

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Spinner?>
<?import javafx.scene.control.TextArea?>
<?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?>
<?import java.net.URL?>
<GridPane hgap="10.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="600.0" vgap="10.0" xmlns="http://javafx.com/javafx/8.0.101" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints halignment="CENTER" hgrow="NEVER" maxWidth="50.0" prefWidth="50.0" />
<ColumnConstraints hgrow="NEVER" />
<ColumnConstraints 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" 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" 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" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="%validation.rule.label.not_empty" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label text="%validation.rule.label.integer" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Label text="%validation.rule.label.double" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<Label text="%validation.rule.label.minlength" GridPane.columnIndex="1" GridPane.rowIndex="5" />
<Label text="%validation.rule.label.maxlength" GridPane.columnIndex="1" GridPane.rowIndex="6" />
<Label text="%validation.rule.label.dateformat" GridPane.columnIndex="1" GridPane.rowIndex="7" />
<Label text="%validation.rule.label.alphanumeric" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<Label text="%validation.rule.label.regexp" GridPane.columnIndex="1" GridPane.rowIndex="8" />
<Label text="%validation.rule.label.value_of" GridPane.columnIndex="1" GridPane.rowIndex="9" />
<Label text="%validation.rule.label.groovy" GridPane.columnIndex="1" GridPane.rowIndex="10" />
<CheckBox fx:id="notEmptyRuleCheckBox" mnemonicParsing="false" GridPane.columnIndex="2" GridPane.rowIndex="1" />
<CheckBox fx:id="integerRuleCheckBox" mnemonicParsing="false" GridPane.columnIndex="2" GridPane.rowIndex="2" />
<CheckBox fx:id="doublerRuleCheckBox" mnemonicParsing="false" GridPane.columnIndex="2" GridPane.rowIndex="3" />
<CheckBox fx:id="alphanumericRuleCheckBox" mnemonicParsing="false" GridPane.columnIndex="2" GridPane.rowIndex="4" />
<Spinner fx:id="minLengthSpinner" GridPane.columnIndex="2" GridPane.rowIndex="5" />
<Spinner fx:id="maxLengthSpinner" GridPane.columnIndex="2" GridPane.rowIndex="6" />
<TextField fx:id="dateformatRuleTextField" GridPane.columnIndex="2" GridPane.rowIndex="7" />
<TextField fx:id="regexpRuleTextField" GridPane.columnIndex="2" GridPane.rowIndex="8" />
<TextField fx:id="valueOfRuleTextField" GridPane.columnIndex="2" GridPane.rowIndex="9" />
<CodeArea fx:id="groovyRuleTextArea" prefHeight="300.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="10" GridPane.rowSpan="2" />
<CheckBox fx:id="enableNotEmptyRule" mnemonicParsing="false" GridPane.rowIndex="1" />
<CheckBox fx:id="enableIntegerRule" mnemonicParsing="false" GridPane.rowIndex="2" />
<CheckBox fx:id="enableDoubleRule" mnemonicParsing="false" GridPane.rowIndex="3" />
<CheckBox fx:id="enableAlphanumericRule" mnemonicParsing="false" GridPane.rowIndex="4" />
<CheckBox fx:id="enableMinLengthRule" mnemonicParsing="false" GridPane.rowIndex="5" />
<CheckBox fx:id="enableMaxLengthRule" mnemonicParsing="false" GridPane.rowIndex="6" />
<CheckBox fx:id="enableDateRule" mnemonicParsing="false" GridPane.rowIndex="7" />
<CheckBox fx:id="enableRegexpRule" mnemonicParsing="false" GridPane.rowIndex="8" />
<CheckBox fx:id="enableValueOfRule" mnemonicParsing="false" GridPane.rowIndex="9" />
<CheckBox fx:id="enableGroovyRule" mnemonicParsing="false" GridPane.rowIndex="10" />
<Label style="-fx-font-weight: bold;" text="%validation.rules.active" />
<Label style="-fx-font-weight: bold;" text="%validation.rules.name" GridPane.columnIndex="1" />
<Label style="-fx-font-weight: bold;" text="%validation.rules.value" GridPane.columnIndex="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>

View File

@@ -26,6 +26,7 @@
package ninja.javafx.smartcsv.fx.table.model; package ninja.javafx.smartcsv.fx.table.model;
import ninja.javafx.smartcsv.validation.ValidationConfiguration;
import ninja.javafx.smartcsv.validation.Validator; import ninja.javafx.smartcsv.validation.Validator;
import org.junit.Test; import org.junit.Test;
@@ -77,20 +78,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,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

@@ -26,20 +26,19 @@
package ninja.javafx.smartcsv.validation; package ninja.javafx.smartcsv.validation;
import com.typesafe.config.Config; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized; import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
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 java.util.stream.Collectors.joining;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -52,7 +51,7 @@ public class HeaderValidationTest {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameters // parameters
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private Config config; private ValidationConfiguration config;
private Boolean expectedResult; private Boolean expectedResult;
private List<ValidationMessage> expectedErrors; private List<ValidationMessage> expectedErrors;
private String[] headerNames; private String[] headerNames;
@@ -66,11 +65,12 @@ public class HeaderValidationTest {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameterized constructor // parameterized constructor
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public HeaderValidationTest(String[] configHeaderNames, public HeaderValidationTest(String configHeaderNames,
String[] headerNames, String[] headerNames,
Boolean expectedResult, Boolean expectedResult,
List<ValidationMessage> expectedErrors) { List<ValidationMessage> expectedErrors) {
this.config = headerSectionConfig(configHeaderNames); Gson gson = new GsonBuilder().create();
this.config = gson.fromJson(configHeaderNames, ValidationConfiguration.class);
this.headerNames = headerNames; this.headerNames = headerNames;
this.expectedResult = expectedResult; this.expectedResult = expectedResult;
this.expectedErrors = expectedErrors; this.expectedErrors = expectedErrors;
@@ -106,11 +106,16 @@ public class HeaderValidationTest {
@Parameterized.Parameters @Parameterized.Parameters
public static Collection validationConfigurations() { public static Collection validationConfigurations() {
return asList(new Object[][] { return asList(new Object[][] {
{ new String[] {}, new String[] {}, true, null }, { json(), new String[] {}, true, null },
{ new String[] {"a"}, new String[] {"a"}, true, null }, { json("a"), new String[] {"a"}, true, null },
{ new String[] {"a"}, new String[] {"b"}, false, singletonList(new ValidationMessage("validation.message.header.match", "0", "a", "b"))}, { json("a"), new String[] {"b"}, false, singletonList(new ValidationMessage("validation.message.header.match", "0", "a", "b"))},
{ new String[] {"a"}, new String[] {"a","b"}, false, singletonList(new ValidationMessage("validation.message.header.length", "2", "1"))}, { json("a"), new String[] {"a","b"}, false, singletonList(new ValidationMessage("validation.message.header.length", "2", "1"))},
{ 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")) } { 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")) }
}); });
} }
public static String json(String... headerNames) {
return "{\"headers\":{\"list\":[" + asList(headerNames).stream().collect(joining(", ")) + "]}}";
}
} }

View File

@@ -1,15 +1,17 @@
package ninja.javafx.smartcsv.validation; package ninja.javafx.smartcsv.validation;
import com.typesafe.config.Config; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized; import org.junit.runners.Parameterized;
import java.util.Collection; import java.util.Collection;
import java.util.List;
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.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; import static org.junit.Assert.assertThat;
@@ -23,7 +25,7 @@ public class ValidatorTest {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameters // parameters
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private Config config; private ValidationConfiguration config;
private String column; private String column;
private String value; private String value;
private Boolean expectedResult; private Boolean expectedResult;
@@ -39,14 +41,14 @@ public class ValidatorTest {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameterized constructor // parameterized constructor
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public ValidatorTest(String configcolumn, public ValidatorTest(String config,
String configValidation,
Object configValue,
String column, String column,
String value, String value,
Boolean expectedResult, Boolean expectedResult,
ValidationMessage expectedError) { ValidationMessage expectedError) {
this.config = columnSectionConfig(configcolumn, configValidation, configValue); System.out.println(config);
Gson gson = new GsonBuilder().create();
this.config = gson.fromJson(config, ValidationConfiguration.class);
this.column = column; this.column = column;
this.value = value; this.value = value;
this.expectedResult = expectedResult; this.expectedResult = expectedResult;
@@ -84,30 +86,42 @@ public class ValidatorTest {
@Parameterized.Parameters @Parameterized.Parameters
public static Collection validationConfigurations() { public static Collection validationConfigurations() {
return asList(new Object[][] { return asList(new Object[][] {
{ "column", "not empty", true, "column", "value", true, null }, { json("column", "not empty", true), "column", "value", true, null },
{ "column", "not empty", true, "column", "", false, new ValidationMessage("validation.message.not.empty") }, { json("column", "not empty", true), "column", "", false, new ValidationMessage("validation.message.not.empty") },
{ "column", "not empty", true, "column", null, false, new ValidationMessage("validation.message.not.empty") }, { json("column", "not empty", true), "column", null, false, new ValidationMessage("validation.message.not.empty") },
{ "column", "integer", true, "column", "999", true, null }, { json("column", "integer", true), "column", "999", true, null },
{ "column", "integer", true, "column", "a", false, new ValidationMessage("validation.message.integer") }, { json("column", "integer", true), "column", "a", false, new ValidationMessage("validation.message.integer") },
{ "column", "double", true, "column", "999", true, null }, { json("column", "double", true), "column", "999", true, null },
{ "column", "double", true, "column", "999.000", true, null }, { json("column", "double", true), "column", "999.000", true, null },
{ "column", "double", true, "column", "a", false, new ValidationMessage("validation.message.double") }, { json("column", "double", true), "column", "a", false, new ValidationMessage("validation.message.double") },
{ "column", "minlength", 2, "column", "12", true, null }, { json("column", "minlength", 2), "column", "12", true, null },
{ "column", "minlength", 2, "column", "1", false, new ValidationMessage("validation.message.min.length", "2") }, { json("column", "minlength", 2), "column", "1", false, new ValidationMessage("validation.message.min.length", "2") },
{ "column", "maxlength", 2, "column", "12", true, null }, { json("column", "maxlength", 2), "column", "12", true, null },
{ "column", "maxlength", 2, "column", "123", false, new ValidationMessage("validation.message.max.length", "2") }, { json("column", "maxlength", 2), "column", "123", false, new ValidationMessage("validation.message.max.length", "2") },
{ "column", "date", "yyyyMMdd", "column", "20151127", true, null }, { json("column", "date", "yyyyMMdd"), "column", "20151127", true, null },
{ "column", "date", "yyyyMMdd", "column", "27.11.2015", false, new ValidationMessage("validation.message.date.format", "yyyyMMdd") }, { json("column", "date", "yyyyMMdd"), "column", "27.11.2015", false, new ValidationMessage("validation.message.date.format", "yyyyMMdd") },
{ "column", "alphanumeric", true, "column", "abcABC123", true, null }, { json("column", "alphanumeric", true), "column", "abcABC123", true, null },
{ "column", "alphanumeric", true, "column", "-abcABC123", false, new ValidationMessage("validation.message.alphanumeric") }, { json("column", "alphanumeric", true), "column", "-abcABC123", false, new ValidationMessage("validation.message.alphanumeric") },
{ "column", "regexp", "[a-z]*", "column", "abc", true, null }, { json("column", "regexp", "[a-z]*"), "column", "abc", true, null },
{ "column", "regexp", "[a-z]*", "column", "abcA", false, new ValidationMessage("validation.message.regexp", "[a-z]*") }, { json("column", "regexp", "[a-z]*"), "column", "abcA", false, new ValidationMessage("validation.message.regexp", "[a-z]*") },
{ "column", "groovy", "value.contains('a')? 'true' : 'no a inside'", "column", "abcdef", true, null }, { json("column", "groovy", "value.contains('a')? 'true' : 'no a inside'"), "column", "abcdef", true, null },
{ "column", "groovy", "value.contains('a')? 'true' : 'no a inside'", "column", "bcdefg", false, new ValidationMessage("no a inside") }, { json("column", "groovy", "value.contains('a')? 'true' : 'no a inside'"), "column", "bcdefg", false, new ValidationMessage("no a inside") },
{ "column", "value of", asList("a","b","c","d","e"), "column", "c", true, null }, { json("column", "value of", asList("a","b","c","d","e")), "column", "c", true, null },
{ "column", "value of", asList("a","b","c","d","e"), "column", "f", false, new ValidationMessage("validation.message.value.of", "f", "a, b, c, d, e") }, { json("column", "value of", asList("a","b","c","d","e")), "column", "f", false, new ValidationMessage("validation.message.value.of", "f", "a, b, c, d, e") },
}); });
} }
public static String json(String column, String rule, Object value) {
String json = "{\"columns\":{\"" + column + "\":{\"" + rule + "\":";
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 += "}}}";
return json;
}
} }