50 Commits

Author SHA1 Message Date
dcdea4236f version 0.6 2016-08-08 17:14:51 +02:00
11358c5bec Merge remote-tracking branch 'remotes/origin/validation_algorithm_revisited' 2016-08-08 17:12:25 +02:00
9c21357059 the error color should be more red 2016-08-08 17:11:11 +02:00
909617d9b7 Merge remote-tracking branch 'remotes/origin/validation_algorithm_revisited' 2016-08-07 23:41:26 +02:00
ab952aa98f optimized imports based on intellij IDEA 2016-08-07 23:13:47 +02:00
a7bd8d5a3e revisted the model to speed up the validation and to have a better control how much revalitaions have to be done when something changes 2016-08-07 23:13:02 +02:00
bc59f08bbb first version of new validation algorithm, that is a lot faster 2016-08-07 16:57:55 +02:00
4c4d25f3b5 little performance optimization (less method calls) 2016-08-06 21:41:14 +02:00
6af20575e8 uniqueness validation is now also supported by the validation rules editor 2016-08-05 23:45:23 +02:00
b6731f7641 - added unique validation
- show line number
- goto line action
2016-08-05 23:09:47 +02:00
d2f81d7d3e added introduction talk 2016-08-03 10:58:01 +02:00
59f38b9abb version 0.4 2016-07-30 17:31:40 +02:00
82c9c336f8 clear script cache before revalidation, otherwise a script change in the edit dialog does not work 2016-07-30 17:12:34 +02:00
f173089b14 added exception handling 2016-07-24 11:21:45 +02:00
4801203378 support of RichTextFX to have syntax highlighting in the groovy edit field of validation editor 2016-07-23 16:56:48 +02:00
68a68a51a9 updated look of validation rule editor 2016-07-23 10:01:27 +02:00
e5b19d4ad9 upgrade gradle to 2.14.1 2016-07-23 09:36:40 +02:00
7f8977f399 updated video link 2016-07-23 00:47:59 +02:00
0214744184 NPE when new validation is created 2016-07-23 00:40:41 +02:00
9fe9246c55 version 0.4 2016-07-23 00:04:48 +02:00
9570730478 Merge remote-tracking branch 'remotes/origin/create_validation_config_based_on_csv' 2016-07-23 00:04:01 +02:00
63b05c2307 version 0.4 2016-07-23 00:03:37 +02:00
328c1b0b88 use of better icons, create a new validation file for csv 2016-07-22 23:51:08 +02:00
3aa9303b3e handle validation in own thread 2016-07-22 21:41:39 +02:00
c2c29433b7 added log4j2 to the 3rd party section 2016-07-22 21:17:58 +02:00
83638d918f found rounding bug in ErrorSideBar when the number of rows is greater than height of application 2016-07-22 21:11:59 +02:00
64d5000b62 reorganized storage of models 2016-07-22 20:45:26 +02:00
6e7c3226ab removed unused import 2016-07-20 17:09:32 +02:00
8e7731f554 updated the about dialog and add thanks to AOE Takashi 2016-07-09 22:19:23 +02:00
7dc15760d5 update of the dependencies 2016-07-09 21:55:40 +02:00
d48c6ddeb8 Merge pull request #3 from aoetk/macmenubar
Supported the Mac OS menubar.
2016-07-09 21:39:18 +02:00
AOE Takashi
dce228123a Supported the Mac OS menubar. 2016-07-09 22:46:06 +09:00
Andreas Billmann
3b220c3fd5 set the some default preferences if file not found 2016-02-16 17:18:37 +01:00
Andreas Billmann
028af43510 set "\n" as default end of line 2016-02-16 17:09:28 +01:00
Andreas Billmann
a4a9e9914b fixed growing and prefered size of the config file name 2016-02-12 19:33:25 +01:00
Andreas Billmann
ecdb4837d4 deleted unused import 2016-02-12 18:28:05 +01:00
Andreas Billmann
e29bbf4008 added missing properties 2016-02-12 16:03:23 +01:00
Andreas Billmann
41a8fb41b1 added a status color block and changed the error color 2016-02-12 13:39:41 +01:00
Andreas Billmann
ab6d99815e Merge branch 'editable_column_rules' 2016-02-05 09:44:51 +01:00
Andreas Billmann
744d86fea4 null check to prevent NPE 2016-02-05 09:43:24 +01:00
Andreas Billmann
fc26dcc9aa validation rules are now editable through context menu 2016-02-05 08:01:13 +01:00
Andreas Billmann
10c2592510 switched from config to Gson for better json support as the validation config will be editable in the application directly 2016-02-02 03:20:14 +01:00
Andreas Billmann
d9405eb536 formal revision 2016-01-27 23:17:31 +01:00
Andreas Billmann
4c9f468e08 use regions instead of drawing into canvas. thx to Dirk Lemmermann 2016-01-27 23:11:39 +01:00
Andreas Billmann
c996ce660d added controlsfx project to about dialog 2016-01-27 10:05:47 +01:00
Andreas Billmann
ef1ad63b3f improved side bar popup 2016-01-27 09:59:12 +01:00
Andreas Billmann
26c06a0908 removed the error list and added a clickable sidaebar wih error markers to have more editing space 2016-01-27 04:39:53 +01:00
Andreas Billmann
b4cc3f9922 bugfix: NPE when adding new row as the setValue has to be called at the end, after all other properties are set 2016-01-14 10:43:11 +01:00
Andreas Billmann
16b848adc9 add and remove rows 2016-01-13 23:07:29 +01:00
Andreas Billmann
4e94cf2091 added toolbar 2016-01-13 11:50:54 +01:00
61 changed files with 3032 additions and 808 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.
##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
###The MIT License (MIT)

View File

@@ -1,5 +1,5 @@
group 'ninja.javafx'
version '0.3-SNAPSHOT'
version '0.6-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'groovy'
@@ -18,10 +18,21 @@ dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '1.3'
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.springframework', name:'spring-context', version: '4.2.4.RELEASE'
compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.7'
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: 'com.typesafe', name: 'config', version: '1.3.0'
compile group: 'commons-validator', name: 'commons-validator', version: '1.5.0'
compile group: 'de.jensd', name: 'fontawesomefx', version: '8.8'
compile group: 'commons-validator', name: 'commons-validator', version: '1.5.1'
compile group: 'de.jensd', name: 'fontawesomefx-commons', version: '8.12'
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
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
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
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -61,9 +56,9 @@ while [ -h "$PRG" ] ; do
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -114,6 +109,7 @@ fi
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
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
: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 "%@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
*/
public interface FileReader {
public interface FileReader<E> {
E getContent();
void read(File filename) throws IOException;
}

View File

@@ -32,6 +32,7 @@ import java.io.IOException;
/**
* write some file
*/
public interface FileWriter {
public interface FileWriter<E> {
void setContent(E content);
void write(File filename) throws IOException;
}

View File

@@ -29,7 +29,6 @@ package ninja.javafx.smartcsv.csv;
import ninja.javafx.smartcsv.FileReader;
import ninja.javafx.smartcsv.fx.table.model.CSVModel;
import ninja.javafx.smartcsv.fx.table.model.CSVRow;
import org.springframework.stereotype.Service;
import org.supercsv.io.CsvMapReader;
import org.supercsv.io.ICsvMapReader;
@@ -40,8 +39,7 @@ import java.util.Map;
/**
* reads the csv file and stores the values in csv model
*/
@Service
public class CSVFileReader extends CSVConfigurable implements FileReader {
public class CSVFileReader extends CSVConfigurable implements FileReader<CSVModel> {
private CSVModel model;
@@ -61,7 +59,7 @@ public class CSVFileReader extends CSVConfigurable implements FileReader {
while ((customerMap = mapReader.read(header)) != null) {
CSVRow row = model.addRow();
for (String column : header) {
row.addValue(column, customerMap.get(column));
model.addValue(row, column, customerMap.get(column));
}
}
@@ -72,7 +70,7 @@ public class CSVFileReader extends CSVConfigurable implements FileReader {
}
}
public CSVModel getData() {
public CSVModel getContent() {
return model;
}

View File

@@ -28,7 +28,6 @@ package ninja.javafx.smartcsv.csv;
import ninja.javafx.smartcsv.fx.table.model.CSVModel;
import ninja.javafx.smartcsv.fx.table.model.CSVRow;
import org.springframework.stereotype.Service;
import org.supercsv.io.CsvMapWriter;
import org.supercsv.io.ICsvMapWriter;
@@ -42,12 +41,11 @@ import static java.util.stream.Collectors.toMap;
/**
* filewriter for the csv
*/
@Service
public class CSVFileWriter extends CSVConfigurable implements ninja.javafx.smartcsv.FileWriter {
public class CSVFileWriter extends CSVConfigurable implements ninja.javafx.smartcsv.FileWriter<CSVModel> {
private CSVModel model;
public void setModel(CSVModel model) {
public void setContent(CSVModel 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

@@ -28,11 +28,9 @@ package ninja.javafx.smartcsv.fx;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import ninja.javafx.smartcsv.fx.about.AboutController;
import org.springframework.context.annotation.*;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

View File

@@ -26,8 +26,9 @@
package ninja.javafx.smartcsv.fx;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.WeakListChangeListener;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
@@ -36,12 +37,12 @@ import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.FileChooser;
import ninja.javafx.smartcsv.FileReader;
import ninja.javafx.smartcsv.FileWriter;
import ninja.javafx.smartcsv.csv.CSVFileReader;
import ninja.javafx.smartcsv.csv.CSVFileWriter;
import ninja.javafx.smartcsv.files.FileStorage;
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.table.ObservableMapValueFactory;
import ninja.javafx.smartcsv.fx.table.ValidationCellFactory;
@@ -50,10 +51,13 @@ import ninja.javafx.smartcsv.fx.table.model.CSVRow;
import ninja.javafx.smartcsv.fx.table.model.CSVValue;
import ninja.javafx.smartcsv.fx.util.LoadFileService;
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.PreferencesFileWriter;
import ninja.javafx.smartcsv.validation.ValidationConfiguration;
import ninja.javafx.smartcsv.validation.ValidationError;
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.Value;
import org.springframework.stereotype.Component;
@@ -66,8 +70,10 @@ import java.util.Optional;
import java.util.ResourceBundle;
import static java.lang.Math.max;
import static java.text.MessageFormat.format;
import static javafx.application.Platform.exit;
import static javafx.application.Platform.runLater;
import static javafx.beans.binding.Bindings.*;
import static javafx.scene.layout.AnchorPane.*;
/**
@@ -85,37 +91,29 @@ public class SmartCSVController extends FXMLController {
".SmartCSV.fx" +
File.separator + "" +
"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
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Autowired
private PreferencesFileReader preferencesLoader;
@Autowired
private PreferencesFileWriter preferencesWriter;
@Autowired
private CSVFileReader csvLoader;
@Autowired
private ValidationFileReader validationLoader;
@Autowired
private CSVFileWriter csvFileWriter;
@Autowired
private AboutController aboutController;
@Autowired
private PreferencesController preferencesController;
@Autowired
private ValidationEditorController validationEditorController;
@Autowired
private LoadFileService loadFileService;
@Autowired
private SaveFileService saveFileService;;
private SaveFileService saveFileService;
@FXML
private BorderPane applicationPane;
@@ -129,9 +127,6 @@ public class SmartCSVController extends FXMLController {
@FXML
private Label stateName;
@FXML
private ListView errorList;
@FXML
private AnchorPane tableWrapper;
@@ -141,6 +136,53 @@ public class SmartCSVController extends FXMLController {
@FXML
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
@@ -148,13 +190,16 @@ public class SmartCSVController extends FXMLController {
private ValidationCellFactory cellFactory;
private CSVModel model;
private TableView<CSVRow> tableView;
private BooleanProperty fileChanged = new SimpleBooleanProperty(true);
private ErrorSideBar errorSideBar;
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
@@ -163,16 +208,35 @@ public class SmartCSVController extends FXMLController {
@Override
public void initialize(URL location, ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
saveFileService.setWriter(csvFileWriter);
cellFactory = new ValidationCellFactory(resourceBundle);
errorList.setCellFactory(param -> new ValidationErrorListCell(resourceBundle));
errorList.getSelectionModel().selectedItemProperty().addListener(observable -> scrollToError());
fileChanged.addListener(observable -> setStateName());
setStateName();
loadCsvPreferences();
setupTableCellFactory();
setupErrorSideBar(resourceBundle);
bindMenuItemsToContentExistence(currentCsvFile, saveMenuItem, saveAsMenuItem, addRowMenuItem, gotoLineMenuItem, createConfigMenuItem, loadConfigMenuItem);
bindButtonsToContentExistence(currentCsvFile, saveButton, saveAsButton, addRowButton, createConfigButton, loadConfigButton);
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 +256,44 @@ public class SmartCSVController extends FXMLController {
@FXML
public void openCsv(ActionEvent actionEvent) {
currentCsvFile = loadFile(csvLoader,
"CSV files (*.csv)",
"*.csv",
"Open CSV",
currentCsvFile);
enableSaveMenuItems();
setCsvFileName();
loadFile(CSV_FILTER_TEXT, CSV_FILTER_EXTENSION, "Open CSV", currentCsvFile);
}
@FXML
public void openConfig(ActionEvent actionEvent) {
currentConfigFile = loadFile(validationLoader,
"JSON files (*.json)",
"*.json",
"Open Validation Configuration",
currentConfigFile);
setConfigFileName();
loadFile(JSON_FILTER_TEXT, JSON_FILTER_EXTENSION, "Open Validation Configuration", currentConfigFile);
}
@FXML
public void createConfig(ActionEvent actionEvent) {
currentConfigFile.setContent(currentCsvFile.getContent().createValidationConfiguration());
currentConfigFile.setFile(null);
currentConfigFile.setFileChanged(true);
resetContent();
}
@FXML
public void saveCsv(ActionEvent actionEvent) {
csvFileWriter.setModel(model);
useSaveFileService(csvFileWriter, currentCsvFile);
useSaveFileService(currentCsvFile);
}
@FXML
public void saveAsCsv(ActionEvent actionEvent) {
csvFileWriter.setModel(model);
currentCsvFile = saveFile(csvFileWriter,
"CSV files (*.csv)",
"*.csv",
currentCsvFile);
setCsvFileName();
saveFile(CSV_FILTER_TEXT, CSV_FILTER_EXTENSION, currentCsvFile);
}
@FXML
public void saveConfig(ActionEvent actionEvent) {
if (currentConfigFile.getFile() == null) {
saveAsConfig(actionEvent);
} else {
useSaveFileService(currentConfigFile);
}
}
@FXML
public void saveAsConfig(ActionEvent actionEvent) {
saveFile(JSON_FILTER_TEXT, JSON_FILTER_EXTENSION, currentConfigFile);
}
@FXML
@@ -261,13 +330,49 @@ public class SmartCSVController extends FXMLController {
setCsvPreference(csvPreference);
saveCsvPreferences(csvPreference);
} 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()) {
currentCsvFile.getContent().addValue(row, column, "");
}
currentCsvFile.setFileChanged(true);
resetContent();
selectNewRow();
}
@FXML
public void gotoLine(ActionEvent actionEvent) {
int maxLineNumber = currentCsvFile.getContent().getRows().size();
GotoLineDialog dialog = new GotoLineDialog(maxLineNumber);
dialog.setTitle(resourceBundle.getString("dialog.goto.line.title"));
dialog.setHeaderText(format(resourceBundle.getString("dialog.goto.line.header.text"), maxLineNumber));
dialog.setContentText(resourceBundle.getString("dialog.goto.line.label"));
Optional<Integer> result = dialog.showAndWait();
if (result.isPresent()){
Integer lineNumber = result.get();
if (lineNumber != null) {
tableView.scrollTo(max(0, lineNumber - 2));
tableView.getSelectionModel().select(lineNumber - 1);
}
}
}
public boolean canExit() {
boolean canExit = true;
if (model != null && fileChanged.get()) {
if (currentCsvFile.getContent() != null && currentCsvFile.isFileChanged()) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle(resourceBundle.getString("dialog.exit.title"));
alert.setHeaderText(resourceBundle.getString("dialog.exit.header.text"));
@@ -282,74 +387,111 @@ public class SmartCSVController extends FXMLController {
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(column);
});
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// private methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void enableSaveMenuItems() {
if (currentCsvFile != null) {
saveMenuItem.setDisable(false);
saveAsMenuItem.setDisable(false);
private void selectNewRow() {
int lastRow = tableView.getItems().size()-1;
tableView.scrollTo(lastRow);
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() {
if (currentCsvFile != null) {
csvName.setText(currentCsvFile.getName());
private void bindButtonsToContentExistence(FileStorage file, Button... items) {
for (Button item: items) {
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 {
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) {
try {
createPreferenceFile();
preferencesWriter.setCsvPreference(csvPreference);
useSaveFileService(preferencesWriter, PREFERENCES_FILE);
csvPreferenceFile.setContent(csvPreference);
useSaveFileService(csvPreferenceFile);
} catch (IOException e) {
e.printStackTrace();
}
}
private void createPreferenceFile() throws IOException {
if (!PREFERENCES_FILE.exists()) {
if (!csvPreferenceFile.getFile().exists()) {
createPreferencesFileFolder();
PREFERENCES_FILE.createNewFile();
csvPreferenceFile.getFile().createNewFile();
}
}
private void createPreferencesFileFolder() {
if (!PREFERENCES_FILE.getParentFile().exists()) {
PREFERENCES_FILE.getParentFile().mkdir();
if (!csvPreferenceFile.getFile().getParentFile().exists()) {
csvPreferenceFile.getFile().getParentFile().mkdir();
}
}
private void setCsvPreference(CsvPreference csvPreference) {
csvLoader.setCsvPreference(csvPreference);
csvFileWriter.setCsvPreference(csvPreference);
preferencesController.setCsvPreference(csvPreference);
}
private File loadFile(FileReader fileReader,
String filterText,
private void loadFile(String filterText,
String filter,
String title,
File initChildFile) {
FileStorage storageFile) {
final FileChooser fileChooser = new FileChooser();
//Set extension filter
@@ -357,77 +499,74 @@ public class SmartCSVController extends FXMLController {
fileChooser.getExtensionFilters().add(extFilter);
fileChooser.setTitle(title);
if (initChildFile != null) {
fileChooser.setInitialDirectory(initChildFile.getParentFile());
if (storageFile.getFile() != null) {
fileChooser.setInitialDirectory(storageFile.getFile().getParentFile());
}
//Show open file dialog
File file = fileChooser.showOpenDialog(applicationPane.getScene().getWindow());
if (file != null) {
useLoadFileService(fileReader, file);
return file;
} else {
return initChildFile;
storageFile.setFile(file);
useLoadFileService(storageFile, t -> resetContent());
}
}
private File saveFile(FileWriter writer, String filterText, String filter, File initFile) {
File file = initFile;
if (model != null) {
private File saveFile(String filterText, String filter, FileStorage fileStorage) {
File file = fileStorage.getFile();
if (fileStorage.getContent() != null) {
final FileChooser fileChooser = new FileChooser();
//Set extension filter
final FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter(filterText, filter);
fileChooser.getExtensionFilters().add(extFilter);
if (initFile != null) {
fileChooser.setInitialDirectory(initFile.getParentFile());
fileChooser.setInitialFileName(initFile.getName());
if (fileStorage.getFile() != null) {
fileChooser.setInitialDirectory(fileStorage.getFile().getParentFile());
fileChooser.setInitialFileName(fileStorage.getFile().getName());
}
fileChooser.setTitle("Save File");
//Show open file dialog
file = fileChooser.showSaveDialog(applicationPane.getScene().getWindow());
if (file != null) {
useSaveFileService(writer, file);
fileStorage.setFile(file);
useSaveFileService(fileStorage);
}
}
return file;
}
private void useLoadFileService(FileReader fileReader, File file) {
loadFileService.setFile(file);
loadFileService.setFileReader(fileReader);
private void useLoadFileService(FileStorage fileStorage, EventHandler<WorkerStateEvent> onSucceededHandler) {
loadFileService.setFileStorage(fileStorage);
loadFileService.restart();
loadFileService.setOnSucceeded(event -> runLater(() -> {
resetContent();
fileChanged.setValue(false);
}));
loadFileService.setOnSucceeded(onSucceededHandler);
}
private void useSaveFileService(FileWriter writer, File file) {
saveFileService.setFile(file);
saveFileService.setWriter(writer);
private void useSaveFileService(FileStorage fileStorage) {
saveFileService.setFileStorage(fileStorage);
saveFileService.restart();
saveFileService.setOnSucceeded(event -> runLater(() -> {
resetContent();
fileChanged.setValue(false);
}));
saveFileService.setOnSucceeded(t -> resetContent());
}
/**
* Creates new table view and add the new content
*/
private void resetContent() {
model = csvLoader.getData();
if (model != null) {
model.setValidator(validationLoader.getValidator());
if (currentCsvFile.getContent() != null) {
currentCsvFile.getContent().getValidationError().addListener(weakErrorListListener);
currentCsvFile.getContent().setValidationConfiguration(currentConfigFile.getContent());
validationEditorController.setValidationConfiguration(currentConfigFile.getContent());
tableView = new TableView<>();
bindLineNumber();
for (String column : model.getHeader()) {
bindMenuItemsToTableSelection(deleteRowMenuItem);
bindButtonsToTableSelection(deleteRowButton);
for (String column : currentCsvFile.getContent().getHeader()) {
addColumn(column, tableView);
}
tableView.getItems().setAll(model.getRows());
tableView.getItems().setAll(currentCsvFile.getContent().getRows());
tableView.setEditable(true);
setBottomAnchor(tableView, 0.0);
@@ -435,8 +574,7 @@ public class SmartCSVController extends FXMLController {
setLeftAnchor(tableView, 0.0);
setRightAnchor(tableView, 0.0);
tableWrapper.getChildren().setAll(tableView);
errorList.setItems(model.getValidationError());
errorSideBar.setModel(currentCsvFile.getContent());
}
}
@@ -445,19 +583,23 @@ public class SmartCSVController extends FXMLController {
* @param header name of the column header
* @param tableView the tableview
*/
private void addColumn(String header, TableView tableView) {
private void addColumn(final String header, TableView tableView) {
TableColumn column = new TableColumn(header);
column.setCellValueFactory(new ObservableMapValueFactory(header));
column.setCellFactory(cellFactory);
column.setEditable(true);
column.setSortable(false);
ContextMenu contextMenu = contextMenuForColumn(header);
column.setContextMenu(contextMenu);
column.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<CSVRow, CSVValue>>() {
@Override
public void handle(TableColumn.CellEditEvent<CSVRow, CSVValue> event) {
event.getTableView().getItems().get(event.getTablePosition().getRow()).
getColumns().get(header).setValue(event.getNewValue());
runLater(() -> {
fileChanged.setValue(true);
model.revalidate();
currentCsvFile.setFileChanged(true);
});
}
});
@@ -465,8 +607,16 @@ public class SmartCSVController extends FXMLController {
tableView.getColumns().add(column);
}
private void scrollToError() {
ValidationError entry = (ValidationError)errorList.getSelectionModel().getSelectedItem();
private ContextMenu contextMenuForColumn(String header) {
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.getLineNumber() != null) {
tableView.scrollTo(max(0, entry.getLineNumber() - 1));
@@ -476,16 +626,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,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.fx.list;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.WeakListChangeListener;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import ninja.javafx.smartcsv.fx.table.model.CSVModel;
import ninja.javafx.smartcsv.validation.ValidationError;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.controlsfx.control.PopOver;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import static javafx.geometry.Pos.CENTER;
import static ninja.javafx.smartcsv.fx.util.ColorConstants.ERROR_COLOR;
import static ninja.javafx.smartcsv.fx.util.ColorConstants.OK_COLOR;
import static ninja.javafx.smartcsv.fx.util.I18nValidationUtil.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,90 @@
package ninja.javafx.smartcsv.fx.list;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import java.util.function.UnaryOperator;
/**
* 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;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;

View File

@@ -37,6 +37,7 @@ import ninja.javafx.smartcsv.fx.table.model.CSVValue;
import java.util.ResourceBundle;
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;
/**
@@ -77,7 +78,7 @@ public class EditableValidationCell extends TableCell<CSVRow, CSVValue> {
setStyle("");
setTooltip(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())));
}

View File

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

View File

@@ -30,24 +30,14 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import ninja.javafx.smartcsv.validation.Validator;
/**
* This class represents a single row in the csv file.
*/
public class CSVRow {
private Validator validator;
private ObservableMap<String, ObjectProperty<CSVValue>> columns = FXCollections.observableHashMap();
private int rowNumber;
/**
* single row
* @param validator the reference to the validator
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
/**
* sets the row number
* @param rowNumber
@@ -78,13 +68,11 @@ public class CSVRow {
* @param column column name
* @param value the value to store
*/
public void addValue(String column, String value) {
CSVValue addValue(String column, String value) {
CSVValue v = new CSVValue();
v.setValidator(validator);
v.setValue(value);
v.setColumn(column);
v.setRowNumber(rowNumber);
columns.put(column, new SimpleObjectProperty<>(v));
return v;
}
}

View File

@@ -29,7 +29,6 @@ package ninja.javafx.smartcsv.fx.table.model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import ninja.javafx.smartcsv.validation.ValidationError;
import ninja.javafx.smartcsv.validation.Validator;
/**
* The csv value represents the value of a single cell.
@@ -37,36 +36,9 @@ import ninja.javafx.smartcsv.validation.Validator;
* and if the value is valid based on the validator.
*/
public class CSVValue {
private Validator validator;
private int rowNumber;
private String column;
private StringProperty value = new SimpleStringProperty();
private ValidationError valid;
/**
* single value of a cell
* @param validator the reference to the validator
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
/**
* the row number this value is stored in
* @param row row number
*/
public void setRowNumber(int row) {
this.rowNumber = row;
}
/**
* the column this value is stored in
* @param column header name of the column
*/
public void setColumn(String column) {
this.column = column;
}
/**
* returns the real value
* @return the real value
@@ -88,9 +60,6 @@ public class CSVValue {
* @param value the real value
*/
public void setValue(String value) {
if (validator != null) {
valid = validator.isValid(column, value, rowNumber);
}
this.value.set(value);
}
@@ -106,7 +75,7 @@ public class CSVValue {
* sets the state if a value is valid or not
* @param valid the validation state
*/
protected void setValidationError(ValidationError valid) {
public void setValidationError(ValidationError valid) {
this.valid = valid;
}
}

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 = "#ff3333";
}

View File

@@ -40,6 +40,20 @@ import static java.text.MessageFormat.format;
*/
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) {
List<ValidationMessage> validationMessages = error.getMessages();
@@ -48,7 +62,7 @@ public class I18nValidationUtil {
if (resourceBundle.containsKey(validationMessage.getKey())) {
String resourceText = resourceBundle.getString(validationMessage.getKey());
if (validationMessage.getParameters().length > 0) {
message.append(format(resourceText, validationMessage.getParameters())).append("\n");
message.append(format(resourceText, (Object[]) validationMessage.getParameters())).append("\n");
} else {
message.append(resourceText).append("\n");
}

View File

@@ -28,9 +28,7 @@ package ninja.javafx.smartcsv.fx.util;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import ninja.javafx.smartcsv.FileReader;
import java.io.File;
import ninja.javafx.smartcsv.files.FileStorage;
/**
* Service class for async load of a csv file
@@ -38,14 +36,10 @@ import java.io.File;
@org.springframework.stereotype.Service
public class LoadFileService extends Service {
private File file;
private FileReader fileReader;
private FileStorage file;
public void setFile(File value) {
file = value;
}
public void setFileReader(FileReader fileReader) {
this.fileReader = fileReader;
public void setFileStorage(FileStorage file) {
this.file = file;
}
@Override
@@ -54,7 +48,7 @@ public class LoadFileService extends Service {
@Override
protected Void call() throws Exception {
if (file != null) {
fileReader.read(file);
file.load();
}
return null;
}

View File

@@ -28,12 +28,7 @@ package ninja.javafx.smartcsv.fx.util;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import ninja.javafx.smartcsv.FileWriter;
import ninja.javafx.smartcsv.csv.CSVFileWriter;
import java.io.File;
import static javafx.application.Platform.runLater;
import ninja.javafx.smartcsv.files.FileStorage;
/**
* Service class for async load of a csv file
@@ -41,14 +36,9 @@ import static javafx.application.Platform.runLater;
@org.springframework.stereotype.Service
public class SaveFileService extends Service {
private File file;
private FileWriter writer;
private FileStorage file;
public void setWriter(FileWriter writer) {
this.writer = writer;
}
public void setFile(File value) {
public void setFileStorage(FileStorage value) {
file = value;
}
@@ -58,7 +48,7 @@ public class SaveFileService extends Service {
@Override
protected Void call() throws Exception {
try {
writer.write(file);
file.save();
} catch (Throwable ex) {
ex.printStackTrace();
}

View File

@@ -0,0 +1,461 @@
/*
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 CheckBox uniqueRuleCheckBox;
@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;
@FXML
private CheckBox enableUniqueRule;
@Value("${fxml.smartcvs.validation.editor.view}")
@Override
public void setFxmlFilePath(String filePath) {
this.fxmlFilePath = filePath;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
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);
initCheckBox(uniqueRuleCheckBox, enableUniqueRule);
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 (enableUniqueRule.isSelected()) {
validationConfiguration.setUniqueRuleFor(selectedColumn.getValue(), uniqueRuleCheckBox.isSelected());
} else {
validationConfiguration.setUniqueRuleFor(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
);
updateCheckBox(
uniqueRuleCheckBox,
validationConfiguration.getUniqueRuleFor(getSelectedColumn()),
enableUniqueRule
);
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,25 +26,24 @@
package ninja.javafx.smartcsv.preferences;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.google.gson.GsonBuilder;
import ninja.javafx.smartcsv.FileReader;
import org.springframework.stereotype.Service;
import org.supercsv.prefs.CsvPreference;
import org.supercsv.quote.AlwaysQuoteMode;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static ninja.javafx.smartcsv.preferences.QuoteModeHelper.getQuoteMode;
/**
* file reader for the preferences
*/
@Service
public class PreferencesFileReader implements FileReader {
public class PreferencesFileReader implements FileReader<CsvPreference> {
private Config config;
private Map config;
private CsvPreference csvPreference;
public PreferencesFileReader() {
@@ -55,15 +54,15 @@ public class PreferencesFileReader implements FileReader {
@Override
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) {
char quoteChar = config.getString("quoteChar").charAt(0);
char delimiterChar = config.getString("delimiterChar").charAt(0);
String endOfLineSymbols = config.getString("endOfLineSymbols");
boolean surroundingSpacesNeedQuotes = config.getBoolean("surroundingSpacesNeedQuotes");
boolean ignoreEmptyLines = config.getBoolean("ignoreEmptyLines");
String quoteMode = config.getString("quoteMode");
char quoteChar = config.get("quoteChar").toString().charAt(0);
char delimiterChar = config.get("delimiterChar").toString().charAt(0);
String endOfLineSymbols = config.get("endOfLineSymbols").toString();
boolean surroundingSpacesNeedQuotes = (Boolean)config.get("surroundingSpacesNeedQuotes");
boolean ignoreEmptyLines = (Boolean)config.get("ignoreEmptyLines");
String quoteMode = config.get("quoteMode").toString();
csvPreference = new CsvPreference.Builder(quoteChar, delimiterChar, endOfLineSymbols)
.useQuoteMode(getQuoteMode(quoteMode))
@@ -73,7 +72,7 @@ public class PreferencesFileReader implements FileReader {
}
}
public CsvPreference getCSVpreference() {
public CsvPreference getContent() {
return csvPreference;
}

View File

@@ -26,11 +26,9 @@
package ninja.javafx.smartcsv.preferences;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigRenderOptions;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import ninja.javafx.smartcsv.FileWriter;
import org.springframework.stereotype.Service;
import org.supercsv.prefs.CsvPreference;
import java.io.File;
@@ -42,12 +40,11 @@ import java.util.Map;
/**
* Save preferences to configuration file
*/
@Service
public class PreferencesFileWriter implements FileWriter {
public class PreferencesFileWriter implements FileWriter<CsvPreference> {
private CsvPreference csvPreference;
public void setCsvPreference(CsvPreference csvPreference) {
public void setContent(CsvPreference csvPreference) {
this.csvPreference = csvPreference;
}
@@ -60,8 +57,8 @@ public class PreferencesFileWriter implements FileWriter {
preferences.put("ignoreEmptyLines", csvPreference.isIgnoreEmptyLines());
preferences.put("quoteMode", QuoteModeHelper.getQuoteModeName(csvPreference.getQuoteMode()));
Config config = ConfigFactory.parseMap(preferences);
Files.write(file.toPath(), config.root().render(ConfigRenderOptions.concise()).getBytes());
Gson gson = new GsonBuilder().setPrettyPrinting().create();
Files.write(file.toPath(), gson.toJson(preferences).getBytes());
}
}

View File

@@ -0,0 +1,45 @@
/*
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 static org.apache.commons.validator.GenericValidator.matchRegexp;
/**
* Checks if the value is alpha numeric
*/
public class AlphaNumericValidation implements Validation {
@Override
public void check(int row, String value, ValidationError error) {
if (!matchRegexp(value, "[0-9a-zA-Z]*")) {
error.add("validation.message.alphanumeric");
}
}
@Override
public Type getType() {
return Type.ALPHANUMERIC;
}
}

View File

@@ -0,0 +1,53 @@
/*
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 static org.apache.commons.validator.GenericValidator.isDate;
/**
* Checks if the date has the right format
*/
public class DateValidation implements Validation {
private String dateformat;
public DateValidation(String dateformat) {
assert dateformat != null && !dateformat.trim().isEmpty() : "empty date format for date validation";
this.dateformat = dateformat;
}
@Override
public void check(int row, String value, ValidationError error) {
if (!isDate(value, dateformat, true)) {
error.add("validation.message.date.format", dateformat);
}
}
@Override
public Type getType() {
return Type.DATE;
}
}

View File

@@ -0,0 +1,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 static org.apache.commons.validator.GenericValidator.isDouble;
/**
* Checks if the value is a double
*/
public class DoubleValidation implements Validation {
@Override
public void check(int row, String value, ValidationError error) {
if (!isDouble(value)) {
error.add("validation.message.double");
}
}
@Override
public Type getType() {
return Type.DOUBLE;
}
}

View File

@@ -0,0 +1,78 @@
/*
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 groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import org.codehaus.groovy.control.CompilationFailedException;
/**
* Executes the given groovy as check
*/
public class GroovyValidation implements Validation {
private String groovyScript;
private GroovyShell shell = new GroovyShell();
private Script script;
public GroovyValidation(String groovyScript) {
this.groovyScript = groovyScript;
script = shell.parse(groovyScript);
}
@Override
public void check(int row, String value, ValidationError error) {
Binding binding = new Binding();
binding.setVariable("value", value);
script.setBinding(binding);
Object groovyResult = null;
try {
groovyResult = script.run();
} catch (CompilationFailedException e) {
error.add("validation.message.groovy.exception", groovyScript, e.getMessage());
e.printStackTrace();
}
if (groovyResult == null) {
error.add("validation.message.groovy.return.null", groovyScript);
}
if (!isScriptResultTrue(groovyResult)) {
error.add(groovyResult.toString());
}
}
@Override
public Type getType() {
return Type.GROOVY;
}
private boolean isScriptResultTrue(Object groovyResult) {
return groovyResult.equals(true) || groovyResult.toString().trim().toLowerCase().equals("true");
}
}

View File

@@ -0,0 +1,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,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 static org.apache.commons.validator.GenericValidator.isInt;
/**
* Checks if the value is an integer
*/
public class IntegerValidation implements Validation {
@Override
public void check(int row, String value, ValidationError error) {
if (!isInt(value)) {
error.add("validation.message.integer");
}
}
@Override
public Type getType() {
return Type.INTEGER;
}
}

View File

@@ -0,0 +1,52 @@
/*
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 static org.apache.commons.validator.GenericValidator.maxLength;
/**
* Checks if the value is shorter or exactly as long as the given max length
*/
public class MaxLengthValidation implements Validation {
private int maxLength;
public MaxLengthValidation(int maxLength) {
this.maxLength = maxLength;
}
@Override
public void check(int row, String value, ValidationError error) {
if (!maxLength(value, maxLength)) {
error.add("validation.message.max.length", Integer.toString(maxLength));
}
}
@Override
public Type getType() {
return Type.MAX_LENGTH;
}
}

View File

@@ -0,0 +1,52 @@
/*
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 static org.apache.commons.validator.GenericValidator.minLength;
/**
* Checks if the value is at minimum long as the given min length
*/
public class MinLengthValidation implements Validation {
private int minLength;
public MinLengthValidation(int minLength) {
this.minLength = minLength;
}
@Override
public void check(int row, String value, ValidationError error) {
if (!minLength(value, minLength)) {
error.add("validation.message.min.length", Integer.toString(minLength));
}
}
@Override
public Type getType() {
return Type.MIN_LENGTH;
}
}

View File

@@ -0,0 +1,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 static org.apache.commons.validator.GenericValidator.isBlankOrNull;
/**
* Checks if the value is not empty
*/
public class NotEmptyValidation implements Validation {
@Override
public void check(int row, String value, ValidationError error) {
if (isBlankOrNull(value)) {
error.add("validation.message.not.empty");
}
}
@Override
public Type getType() {
return Type.NOT_EMPTY;
}
}

View File

@@ -0,0 +1,52 @@
/*
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 static org.apache.commons.validator.GenericValidator.matchRegexp;
/**
* Checks the value against the given reg exp
*/
public class RegExpValidation implements Validation {
private String regexp;
public RegExpValidation(String regexp) {
this.regexp = regexp;
}
@Override
public void check(int row, String value, ValidationError error) {
if (!matchRegexp(value, regexp)) {
error.add("validation.message.regexp", regexp);
}
}
@Override
public Type getType() {
return Type.REGEXP;
}
}

View File

@@ -0,0 +1,110 @@
/*
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 javafx.beans.property.ObjectProperty;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import ninja.javafx.smartcsv.fx.table.model.CSVRow;
import ninja.javafx.smartcsv.fx.table.model.CSVValue;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Service for running the validation async of the ui thread
*/
public class RevalidationService extends Service<List<ValidationError>> {
private static final Logger logger = LogManager.getLogger(RevalidationService.class);
private Validator validator;
private List<CSVRow> rows;
private String[] header;
public void setValidator(Validator validator) {
this.validator = validator;
}
public void setRows(List<CSVRow> rows) {
this.rows = rows;
}
public void setHeader(String[] header) {
this.header = header;
}
@Override
protected Task<List<ValidationError>> createTask() {
return new Task<List<ValidationError>>() {
@Override
protected List<ValidationError> call() throws Exception {
List<ValidationError> errors = new ArrayList<>();
try {
if (header != null) {
ValidationError headerError = validator.isHeaderValid(header);
if (headerError != null) {
logger.info("revalidate: header error found");
errors.add(headerError);
}
}
int maxRows = rows.size();
for (int lineNumber = 0; lineNumber < maxRows; lineNumber++) {
CSVRow row = rows.get(lineNumber);
Map<String, ObjectProperty<CSVValue>> table = row.getColumns();
Set<String> columns = table.keySet();
for (String column : columns) {
CSVValue value = table.get(column).getValue();
if (validator != null) {
ValidationError validationError = validator.isValid(lineNumber, column, value.getValue());
if (validationError != null) {
logger.info("revalidate: {} errors found in line {}", validationError.getMessages().size(), lineNumber);
errors.add(validationError);
value.setValidationError(validationError);
} else {
value.setValidationError(null);
}
} else {
value.setValidationError(null);
}
}
}
} catch (Throwable t) {
logger.error("validation error", t);
}
return errors;
}
};
}
}

View File

@@ -0,0 +1,54 @@
/*
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 java.util.HashMap;
/**
* Checks if the value is unique in the column
*/
public class UniqueValidation implements Validation {
private HashMap<String, Integer> columnValueMap = new HashMap<>();
@Override
public void check(int row, String value, ValidationError error) {
Integer valueInLineNumber = columnValueMap.get(value);
if (valueInLineNumber != null) {
if (!valueInLineNumber.equals(row)) {
valueInLineNumber += 1; // show not 0 based line numbers to user
error.add("validation.message.uniqueness", value, valueInLineNumber.toString());
}
} else {
columnValueMap.put(value, row);
}
}
@Override
public Type getType() {
return Type.UNIQUE;
}
}

View File

@@ -0,0 +1,36 @@
/*
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;
/**
* Interface for all validations
*/
public interface Validation {
enum Type { NOT_EMPTY, UNIQUE, DOUBLE, INTEGER, MIN_LENGTH, MAX_LENGTH, DATE, ALPHANUMERIC, REGEXP, VALUE_OF, GROOVY }
void check(int row, String value, ValidationError error);
Type getType();
}

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.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 getUniqueRuleFor(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) {
Double value = (Double)getValue(column, "maxlength");
return value != null ? doubleToInteger(value) : null;
}
public String getDateRuleFor(String column) {
return (String)getValue(column, "date");
}
public Boolean getAlphanumericRuleFor(String column) {
return (Boolean)getValue(column, "alphanumeric");
}
public String getRegexpRuleFor(String column) {
return (String)getValue(column, "regexp");
}
public List<String> getValueOfRuleFor(String column) {
return (List<String>)getValue(column, "value of");
}
public String getGroovyRuleFor(String column) {
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 setUniqueRuleFor(String column, Boolean value) {
setValue(column, value, "unique");
}
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 (columnConfigurations != null) {
Map rulesForColumn = columnConfigurations.get(column);
if (rulesForColumn != null) {
return columnConfigurations.get(column).get(key);
}
}
return null;
}
private boolean noHeader() {
return headerConfiguration == null;
}
private Integer doubleToInteger(Double value) {
if (value == null) return null;
return (int)Math.round(value);
}
}

View File

@@ -26,10 +26,8 @@
package ninja.javafx.smartcsv.validation;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.google.gson.GsonBuilder;
import ninja.javafx.smartcsv.FileReader;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
@@ -37,17 +35,16 @@ import java.io.IOException;
/**
* This class loads the constraints as json config
*/
@Service
public class ValidationFileReader implements FileReader {
public class ValidationFileReader implements FileReader<ValidationConfiguration> {
private Config config;
private ValidationConfiguration config;
@Override
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() {
return new Validator(config);
public ValidationConfiguration getContent() {
return config;
}
}

View File

@@ -24,47 +24,30 @@
*/
package ninja.javafx.smartcsv.fx.list;
package ninja.javafx.smartcsv.validation;
import javafx.scene.control.ListCell;
import javafx.scene.text.Text;
import ninja.javafx.smartcsv.validation.ValidationError;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import ninja.javafx.smartcsv.FileWriter;
import java.util.ResourceBundle;
import static ninja.javafx.smartcsv.fx.util.I18nValidationUtil.getI18nValidatioMessage;
import java.io.File;
import java.io.IOException;
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) {
this.resourceBundle = resourceBundle;
public void setContent(ValidationConfiguration validationConfiguration) {
this.validationConfiguration = validationConfiguration;
}
@Override
public void updateItem(ValidationError validationError, boolean empty) {
super.updateItem(validationError, empty);
if (empty) {
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);
public void write(File file) throws IOException {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
Files.write(file.toPath(), gson.toJson(validationConfiguration).getBytes());
}
}

View File

@@ -26,19 +26,10 @@
package ninja.javafx.smartcsv.validation;
import com.typesafe.config.Config;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import org.codehaus.groovy.control.CompilationFailedException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.stream.Collectors.joining;
import static org.apache.commons.validator.GenericValidator.*;
/**
* This class checks all the validations defined in the
* Config against a given value
@@ -49,10 +40,8 @@ public class Validator {
// member variables
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private Config validationConfig;
private GroovyShell shell = new GroovyShell();
private Map<String, Script> scriptCache = new HashMap<>();
private ValidationConfiguration validationConfig;
private Map<String, Map<Validation.Type, Validation>> columnValidationMap = new HashMap<>();
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// constructors
@@ -60,228 +49,181 @@ public class Validator {
/**
* JSON configuration for this validator
*
* @param validationConfig
*/
public Validator(Config validationConfig) {
this.validationConfig = validationConfig;
public Validator(ValidationConfiguration validationConfig) {
this.validationConfig = validationConfig;
initColumnValidations();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// oublic methods
// public methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public boolean needsColumnValidation(String column) {
Map<Validation.Type, Validation> validationMap = columnValidationMap.get(column);
if (validationMap != null) {
return validationMap.containsKey(Validation.Type.UNIQUE);
}
return false;
}
/**
* checks if the value is valid for the column configuration
*
* @param column the column name
* @param value the value to check
* @param value the value to check
* @return ValidationError with information if valid and if not which getMessage happened
*/
public ValidationError isValid(String column, String value, Integer lineNumber) {
public ValidationError isValid(Integer row, String column, String value) {
ValidationError result = null;
if (validationConfig != null) {
Config columnSectionConfig = getColumnSectionConfig();
if (columnSectionConfig != null) {
Config columnConfig = getColumnConfig(columnSectionConfig, column);
if (columnConfig != null) {
if (hasConfig()) {
ValidationError error = ValidationError.withLineNumber(row);
Map<Validation.Type, Validation> validationMap = columnValidationMap.get(column);
if (validationMap != null) {
for (Validation validation: validationMap.values()) {
ValidationError error = ValidationError.withLineNumber(lineNumber);
checkBlankOrNull(columnConfig, value, error);
if (value != null && !value.isEmpty()) {
checkRegularExpression(columnConfig, value, error);
checkAlphaNumeric(columnConfig, value, error);
checkDate(columnConfig, value, error);
checkMaxLength(columnConfig, value, error);
checkMinLength(columnConfig, value, error);
checkInteger(columnConfig, value, error);
checkGroovy(column, columnConfig, value, error);
checkValueOf(columnConfig, value, error);
checkDouble(columnConfig, value, error);
}
if (!error.isEmpty()) {
result = error;
if (validation.getType() == Validation.Type.NOT_EMPTY) {
validation.check(row, value, error);
} else {
if (value != null && !value.isEmpty()) {
validation.check(row, value, error);
}
}
}
}
if (!error.isEmpty()) {
result = error;
}
}
return result;
}
public boolean hasConfig() {
return validationConfig != null;
}
public void reinitializeColumn(String column) {
clear(column);
initializeColumnWithRules(column);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// private methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void checkGroovy(String column, Config columnConfig, String value, ValidationError error) {
String groovyScript = getString(columnConfig, "groovy");
if (groovyScript != null) {
Script script = scriptCache.get(column);
if (script == null) {
script = shell.parse(groovyScript);
scriptCache.put(column, script);
}
Binding binding = new Binding();
binding.setVariable("value", value);
script.setBinding(binding);
Object groovyResult = null;
try {
groovyResult = script.run();
} catch (CompilationFailedException e) {
error.add("validation.message.groovy.exception", groovyScript, e.getMessage());
e.printStackTrace();
}
if (groovyResult == null) {
error.add("validation.message.groovy.return.null", groovyScript);
}
if (!isScriptResultTrue(groovyResult)) {
error.add(groovyResult.toString());
}
private void add(String column, Validation validation) {
Map<Validation.Type, Validation> validationMap = columnValidationMap.get(column);
if (validationMap == null) {
validationMap = new HashMap<>();
columnValidationMap.put(column, validationMap);
}
validationMap.put(validation.getType(), validation);
}
private void clear(String column) {
Map<Validation.Type, Validation> validationMap = columnValidationMap.get(column);
if (validationMap != null) {
validationMap.clear();
}
}
private boolean isScriptResultTrue(Object groovyResult) {
return groovyResult.equals(true) || groovyResult.toString().trim().toLowerCase().equals("true");
}
private void checkValueOf(Config columnConfig, String value, ValidationError error) {
List<String> stringList = getStringList(columnConfig, "value of");
if (stringList != null) {
if (!stringList.contains(value)) {
String commaSeparated = stringList.stream().collect(joining(", "));
error.add("validation.message.value.of", value, commaSeparated);
private void initColumnValidations() {
if (hasConfig()) {
String[] columns = validationConfig.headerNames();
for (String column : columns) {
initializeColumnWithRules(column);
}
}
}
private void checkBlankOrNull(Config columnConfig, String value, ValidationError error) {
if (getBoolean(columnConfig, "not empty")) {
if (isBlankOrNull(value)) {
error.add("validation.message.not.empty");
}
private void initializeColumnWithRules(String column) {
Boolean alphaNumeric = validationConfig.getAlphanumericRuleFor(column);
if (alphaNumeric != null && alphaNumeric) {
add(column, new AlphaNumericValidation());
}
}
private void checkInteger(Config columnConfig, String value, ValidationError error) {
if (getBoolean(columnConfig, "integer")) {
if (!isInt(value)) {
error.add("validation.message.integer");
}
Boolean doubleRule = validationConfig.getDoubleRuleFor(column);
if (doubleRule != null && doubleRule) {
add(column, new DoubleValidation());
}
}
private void checkDouble(Config columnConfig, String value, ValidationError error) {
if (getBoolean(columnConfig, "double")) {
if (!isDouble(value)) {
error.add("validation.message.double");
}
Boolean integerRule = validationConfig.getIntegerRuleFor(column);
if (integerRule != null && integerRule) {
add(column, new IntegerValidation());
}
}
Boolean notEmptyRule = validationConfig.getNotEmptyRuleFor(column);
if (notEmptyRule != null && notEmptyRule) {
add(column, new NotEmptyValidation());
}
private void checkMinLength(Config columnConfig, String value, ValidationError error) {
Integer minLength = getInteger(columnConfig, "minlength");
Boolean uniqueRule = validationConfig.getUniqueRuleFor(column);
if (uniqueRule != null && uniqueRule) {
add(column, new UniqueValidation());
}
String dateRule = validationConfig.getDateRuleFor(column);
if (dateRule != null && !dateRule.trim().isEmpty()) {
add(column, new DateValidation(dateRule));
}
Integer minLength = validationConfig.getMinLengthRuleFor(column);
if (minLength != null) {
if (!minLength(value, minLength)) {
error.add("validation.message.min.length", minLength.toString());
}
add(column, new MinLengthValidation(minLength));
}
}
private void checkMaxLength(Config columnConfig, String value, ValidationError error) {
Integer maxLength = getInteger(columnConfig, "maxlength");
Integer maxLength = validationConfig.getMaxLengthRuleFor(column);
if (maxLength != null) {
if (!maxLength(value, maxLength)) {
error.add("validation.message.max.length", maxLength.toString());
}
add(column, new MaxLengthValidation(maxLength));
}
}
private void checkDate(Config columnConfig, String value, ValidationError error) {
String dateformat = getString(columnConfig, "date");
if (dateformat != null && !dateformat.trim().isEmpty()) {
if (!isDate(value, dateformat, true)) {
error.add("validation.message.date.format", dateformat);
}
}
}
private void checkAlphaNumeric(Config columnConfig, String value, ValidationError error) {
if (getBoolean(columnConfig, "alphanumeric")) {
if (!matchRegexp(value, "[0-9a-zA-Z]*")) {
error.add("validation.message.alphanumeric");
}
}
}
private void checkRegularExpression(Config columnConfig, String value, ValidationError error) {
String regexp = getString(columnConfig, "regexp");
String regexp = validationConfig.getRegexpRuleFor(column);
if (regexp != null && !regexp.trim().isEmpty()) {
if (!matchRegexp(value, regexp)) {
error.add("validation.message.regexp", regexp);
}
add(column, new RegExpValidation(regexp));
}
String groovy = validationConfig.getGroovyRuleFor(column);
if (groovy != null && !groovy.trim().isEmpty()) {
add(column, new GroovyValidation(groovy));
}
List<String> valueOfRule = validationConfig.getValueOfRuleFor(column);
if (valueOfRule != null && !valueOfRule.isEmpty()) {
add(column, new ValueOfValidation(valueOfRule));
}
}
private Config getColumnSectionConfig() {
return validationConfig.hasPath("columns") ? validationConfig.getConfig("columns") : null;
}
private Config getColumnConfig(Config columnSectionConfig, String column) {
return columnSectionConfig.hasPath(column) ? columnSectionConfig.getConfig(column) : null;
}
private String getString(Config columnConfig, String path) {
return columnConfig.hasPath(path) ? columnConfig.getString(path) : null;
}
private Integer getInteger(Config columnConfig, String path) {
return columnConfig.hasPath(path) ? columnConfig.getInt(path) : null;
}
private boolean getBoolean(Config columnConfig, String path) {
return columnConfig.hasPath(path) && columnConfig.getBoolean(path);
}
private List<String> getStringList(Config columnConfig, String path) {
return columnConfig.hasPath(path) ? columnConfig.getStringList(path) : null;
}
public ValidationError isHeaderValid(String[] headerNames) {
ValidationError result = null;
if (validationConfig != null) {
if (validationConfig.hasPath("headers")) {
Config headerSectionConfig = validationConfig.getConfig("headers");
List<String> headerConfig = getStringList(headerSectionConfig, "list");
if (headerConfig != null) {
if (headerNames.length != headerConfig.size()) {
result = ValidationError.withoutLineNumber().add("validation.message.header.length",
Integer.toString(headerNames.length),
Integer.toString(headerConfig.size()));
return result;
}
String[] headerNamesConfig = validationConfig.headerNames();
if (headerNamesConfig != null) {
if (headerNames.length != headerNamesConfig.length) {
result = ValidationError.withoutLineNumber().add("validation.message.header.length",
Integer.toString(headerNames.length),
Integer.toString(headerNamesConfig.length));
return result;
}
ValidationError error = ValidationError.withoutLineNumber();
ValidationError error = ValidationError.withoutLineNumber();
for(int i=0; i<headerConfig.size(); i++) {
String header = headerConfig.get(i);
if (!header.equals(headerNames[i])) {
error.add("validation.message.header.match",
Integer.toString(i),
header,
headerNames[i]);
}
}
if (!error.isEmpty()) {
result = error;
for (int i = 0; i < headerNamesConfig.length; i++) {
if (!headerNamesConfig[i].equals(headerNames[i])) {
error.add("validation.message.header.match",
Integer.toString(i),
headerNamesConfig[i],
headerNames[i]);
}
}
if (!error.isEmpty()) {
result = error;
}
}
}
return result;

View File

@@ -0,0 +1,55 @@
/*
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 java.util.List;
import static java.util.stream.Collectors.joining;
/**
* Checks if the value is part of a list of values
*/
public class ValueOfValidation implements Validation {
private List<String> values;
public ValueOfValidation(List<String> values) {
this.values = values;
}
@Override
public void check(int row, String value, ValidationError error) {
if (!values.contains(value)) {
String commaSeparated = values.stream().collect(joining(", "));
error.add("validation.message.value.of", value, commaSeparated);
}
}
@Override
public Type getType() {
return Type.VALUE_OF;
}
}

View File

@@ -0,0 +1,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,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<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">
<?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">
<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">
<VBox.margin>
<Insets top="8.0" />
@@ -19,7 +35,7 @@
</Label>
<ScrollPane fitToHeight="true" fitToWidth="true">
<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 hgrow="SOMETIMES" minWidth="10.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" />
@@ -33,6 +49,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>
<children>
<Label text="junit" />
@@ -45,12 +64,18 @@
<Hyperlink onAction="#openLinkInBrowser" text="https://spring.io/" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<Label text="supercsv" 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" />
<Hyperlink onAction="#openLinkInBrowser" text="https://github.com/typesafehub/config" GridPane.columnIndex="1" GridPane.rowIndex="5" />
<Label text="Gson" 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" />
<Hyperlink onAction="#openLinkInBrowser" text="https://commons.apache.org/proper/commons-validator/" GridPane.columnIndex="1" GridPane.rowIndex="6" />
<Label text="fontawesomefx" 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>
<padding>
<Insets bottom="4.0" left="8.0" right="8.0" top="4.0" />

View File

@@ -1,10 +1,11 @@
application.name = SmartCSV.fx
application.version = 0.3
application.version = 0.6
# fxml views
fxml.smartcvs.view = /ninja/javafx/smartcsv/fx/smartcsv.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.validation.editor.view = /ninja/javafx/smartcsv/fx/validation/validationEditor.fxml
# resources
resource.main = ninja.javafx.smartcsv.fx.smartcsv

View File

@@ -1,10 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<GridPane hgap="8.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" vgap="8.0" xmlns="http://javafx.com/javafx/8.0.66" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />

View File

@@ -15,36 +15,205 @@
}
.open-icon {
-glyph-name: "FILE_TEXT_ALT";
-glyph-size: 14px;
-glyph-name: "FILE_IMPORT";
-glyph-size: 16px;
}
.file-document-icon {
-glyph-name: "FILE_DOCUMENT";
-glyph-size: 16px;
}
.config-icon {
-glyph-name: "CHECK";
-glyph-size: 14px;
-glyph-name: "CLIPBOARD";
-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 {
-glyph-name: "FLOPPY_ALT";
-glyph-size: 14px;
-glyph-name: "FILE_EXPORT";
-glyph-size: 16px;
}
.exit-icon {
-glyph-name: "SIGN_OUT";
-glyph-size: 14px;
-glyph-name: "EXIT_TO_APP";
-glyph-size: 16px;
}
.info-icon {
-glyph-name: "INFO";
-glyph-size: 14px;
-glyph-name: "INFORMATION_OUTLINE";
-glyph-size: 16px;
}
.error-title-icon {
-glyph-name: "EXCLAMATION_TRIANGLE";
-glyph-size: 14px;
.config-check-icon {
-glyph-name: "CLIPBOARD_CHECK";
-glyph-size: 16px;
}
.preferences-icon {
-glyph-name: "COG";
-glyph-size: 14px;
-glyph-name: "SETTINGS";
-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,134 +1,288 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import de.jensd.fx.glyphs.fontawesome.*?>
<?import de.jensd.fx.glyphs.*?>
<?import de.jensd.fx.glyphs.materialdesignicons.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import java.net.URL?>
<BorderPane fx:id="applicationPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" prefHeight="700.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8.0.66"
xmlns:fx="http://javafx.com/fxml/1">
<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>
<VBox prefWidth="100.0" BorderPane.alignment="CENTER">
<VBox id="background" prefWidth="100.0" BorderPane.alignment="CENTER">
<children>
<MenuBar>
<MenuBar useSystemMenuBar="true">
<menus>
<Menu mnemonicParsing="false" text="%menu.file">
<graphic>
<FontAwesomeIconView styleClass="save-icon"/>
</graphic>
<items>
<MenuItem mnemonicParsing="false" onAction="#openCsv" text="%menu.open.csv">
<graphic>
<FontAwesomeIconView styleClass="open-icon"/>
<MaterialDesignIconView styleClass="open-icon" />
</graphic>
</MenuItem>
<MenuItem mnemonicParsing="false" onAction="#openConfig" text="%menu.open.config">
<MenuItem fx:id="saveMenuItem" disable="true" mnemonicParsing="false" onAction="#saveCsv" text="%menu.save">
<graphic>
<FontAwesomeIconView styleClass="config-icon"/>
<MaterialDesignIconView styleClass="save-icon" />
</graphic>
</MenuItem>
<SeparatorMenuItem mnemonicParsing="false"/>
<MenuItem fx:id="saveMenuItem" mnemonicParsing="false" onAction="#saveCsv" text="%menu.save" disable="true">
<MenuItem fx:id="saveAsMenuItem" disable="true" mnemonicParsing="false" onAction="#saveAsCsv" text="%menu.save.as">
<graphic>
<FontAwesomeIconView styleClass="save-icon"/>
<MaterialDesignIconView styleClass="save-icon" />
</graphic>
</MenuItem>
<MenuItem fx:id="saveAsMenuItem" mnemonicParsing="false" onAction="#saveAsCsv" text="%menu.save.as" disable="true">
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem fx:id="createConfigMenuItem" disable="true" mnemonicParsing="false" onAction="#createConfig" text="%menu.create.config">
<graphic>
<FontAwesomeIconView styleClass="save-icon"/>
<GlyphsStack>
<children>
<MaterialDesignIconView styleClass="config-icon" />
<MaterialDesignIconView style="-fx-opacity: 0.7;" styleClass="create-config-icon" />
</children>
</GlyphsStack>
</graphic>
</MenuItem>
<SeparatorMenuItem mnemonicParsing="false"/>
<MenuItem fx:id="loadConfigMenuItem" disable="true" mnemonicParsing="false" onAction="#openConfig" text="%menu.open.config">
<graphic>
<GlyphsStack>
<children>
<MaterialDesignIconView styleClass="config-icon" />
<MaterialDesignIconView style="-fx-opacity: 0.7;" styleClass="load-config-icon" />
</children>
</GlyphsStack>
</graphic>
</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" />
<MenuItem mnemonicParsing="false" onAction="#preferences" text="%menu.preferences">
<graphic>
<FontAwesomeIconView styleClass="preferences-icon"/>
<MaterialDesignIconView styleClass="preferences-icon" />
</graphic>
</MenuItem>
<SeparatorMenuItem mnemonicParsing="false"/>
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem mnemonicParsing="false" onAction="#close" text="%menu.close">
<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>
</MenuItem>
</items>
</Menu>
<Menu mnemonicParsing="false" text="%menu.help">
<graphic>
<FontAwesomeIconView styleClass="info-icon"/>
</graphic>
<items>
<MenuItem mnemonicParsing="false" onAction="#about" text="%menu.about">
<graphic>
<FontAwesomeIconView styleClass="info-icon"/>
<MaterialDesignIconView styleClass="info-icon" />
</graphic>
</MenuItem>
</items>
</Menu>
</menus>
</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>
</VBox>
</top>
<center>
<SplitPane dividerPositions="0.8" prefHeight="160.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<items>
<AnchorPane fx:id="tableWrapper">
<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">
<columns>
</columns>
</TableView>
</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>
<AnchorPane fx:id="tableWrapper">
<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">
<columns>
</columns>
</TableView>
</AnchorPane>
</center>
<left>
</left>
<stylesheets>
<URL value="@/ninja/javafx/smartcsv/fx/smartcsv.css"/>
<URL value="@/ninja/javafx/smartcsv/fx/smartcsv.css" />
</stylesheets>
<bottom>
<GridPane hgap="8.0" BorderPane.alignment="CENTER">
<columnConstraints>
<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="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" 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 fillWidth="false" halignment="RIGHT" hgrow="NEVER" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<FontAwesomeIconView styleClass="open-icon" GridPane.hgrow="NEVER"/>
<Label text="%stateline.csv" GridPane.columnIndex="1" GridPane.hgrow="NEVER"/>
<Label fx:id="csvName" GridPane.columnIndex="2" GridPane.hgrow="ALWAYS"/>
<Label text="%stateline.state" GridPane.columnIndex="3" GridPane.hgrow="NEVER"/>
<Label fx:id="stateName" GridPane.columnIndex="4" GridPane.hgrow="ALWAYS"/>
<FontAwesomeIconView styleClass="open-icon" GridPane.columnIndex="5" GridPane.hgrow="NEVER"/>
<Label text="%stateline.configuration" GridPane.columnIndex="6" GridPane.hgrow="NEVER"/>
<Label fx:id="configurationName" GridPane.columnIndex="7" GridPane.hgrow="ALWAYS"/>
<MaterialDesignIconView styleClass="file-document-icon" GridPane.hgrow="NEVER" />
<Label text="%stateline.csv" GridPane.columnIndex="1" GridPane.hgrow="NEVER" />
<Label fx:id="csvName" GridPane.columnIndex="2" GridPane.hgrow="ALWAYS" />
<MaterialDesignIconView styleClass="config-check-icon" GridPane.columnIndex="3" GridPane.hgrow="NEVER" />
<Label text="%stateline.configuration" GridPane.columnIndex="4" GridPane.hgrow="NEVER" />
<Label fx:id="configurationName" GridPane.columnIndex="5" GridPane.hgrow="ALWAYS" />
<Label text="%lineNumber" GridPane.columnIndex="7" />
<Label fx:id="currentLineNumber" text="" GridPane.columnIndex="8" />
</children>
<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>
</GridPane>
</bottom>

View File

@@ -1,13 +1,20 @@
menu.open.csv = Open CSV File
menu.open.config = Open Validation Config
menu.create.config = Create Validation Config
menu.save = Save
menu.save.as = Save As ...
menu.save.config = Save Validation Config
menu.save.as.config = Save Validation Config as ...
menu.close = Close
menu.about = About
menu.file = File
menu.edit = Edit
menu.help = Help
menu.preferences = Preferences
menu.delete.row = Delete row
menu.add.row = Add row
menu.goto.line = Goto line
title.validation.errors = Validation Errors:
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.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.delimiterChar = Delimiter character:
preferences.ignoreEmptyLines = Ignore empty lines:
@@ -41,7 +52,29 @@ validation.message.min.length = has not min 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.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.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.rule.label.not_empty = not empty:
validation.rule.label.integer = integer:
validation.rule.label.double = double:
validation.rule.label.minlength = minimum length:
validation.rule.label.maxlength = maximum length:
validation.rule.label.dateformat = date format:
validation.rule.label.alphanumeric = alphanumeric:
validation.rule.label.regexp = regular expression:
validation.rule.label.value_of = value in list:
validation.rule.label.groovy = groovy:
validation.rules.active = active
validation.rules.name = rule
validation.rules.value = value
validation.rule.label.unique = Unique in column:
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.config = Pr\u00fcfkonfiguration \u00f6ffnen
menu.create.config = Pr\u00fcfkonfiguration erzeugen
menu.save = Speichern
menu.save.as = Speichern als ...
menu.save.config = Pr\u00fcfkonfiguration speichern
menu.save.as.config = Pr\u00fcfkonfiguration speichern als ...
menu.close = Beenden
menu.about = \u00dcber ...
menu.file = Datei
menu.edit = Bearbeiten
menu.help = Hilfe
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:
stateline.csv = CSV:
@@ -28,6 +36,10 @@ state.unchanged = Unver\u00e4ndert
dialog.preferences.title = 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.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.
@@ -49,7 +61,30 @@ 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.date.format = Das Datumsformat entspricht nicht {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.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.rule.label.not_empty = Nicht leer:
validation.rule.label.integer = Zahl:
validation.rule.label.double = Gleitkommazahl:
validation.rule.label.minlength = Minimale L\u00e4nge:
validation.rule.label.maxlength = Maximale L\u00e4nge:
validation.rule.label.dateformat = Datumsformat:
validation.rule.label.alphanumeric = Nur Zahlen und Buchstaben:
validation.rule.label.regexp = Regul\u00e4rer Ausdruck:
validation.rule.label.value_of = In der Liste:
validation.rule.label.groovy = groovy:
validation.rules.active = Aktiv
validation.rules.name = Regel
validation.rules.value = Wert
validation.rule.label.unique = Einmalig in Spalte:
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,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?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 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="6" />
<Label text="%validation.rule.label.maxlength" GridPane.columnIndex="1" GridPane.rowIndex="7" />
<Label text="%validation.rule.label.dateformat" GridPane.columnIndex="1" GridPane.rowIndex="8" />
<Label text="%validation.rule.label.alphanumeric" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<Label text="%validation.rule.label.regexp" GridPane.columnIndex="1" GridPane.rowIndex="9" />
<Label text="%validation.rule.label.value_of" GridPane.columnIndex="1" GridPane.rowIndex="10" />
<Label text="%validation.rule.label.groovy" GridPane.columnIndex="1" GridPane.rowIndex="11" />
<Label text="%validation.rule.label.unique" GridPane.columnIndex="1" GridPane.rowIndex="5" />
<CheckBox fx:id="enableNotEmptyRule" mnemonicParsing="false" GridPane.rowIndex="1" />
<CheckBox fx:id="notEmptyRuleCheckBox" mnemonicParsing="false" GridPane.columnIndex="2" GridPane.rowIndex="1" />
<CheckBox fx:id="enableIntegerRule" mnemonicParsing="false" GridPane.rowIndex="2" />
<CheckBox fx:id="integerRuleCheckBox" mnemonicParsing="false" GridPane.columnIndex="2" GridPane.rowIndex="2" />
<CheckBox fx:id="enableDoubleRule" mnemonicParsing="false" GridPane.rowIndex="3" />
<CheckBox fx:id="doublerRuleCheckBox" mnemonicParsing="false" GridPane.columnIndex="2" GridPane.rowIndex="3" />
<CheckBox fx:id="enableAlphanumericRule" mnemonicParsing="false" GridPane.rowIndex="4" />
<CheckBox fx:id="alphanumericRuleCheckBox" mnemonicParsing="false" GridPane.columnIndex="2" GridPane.rowIndex="4" />
<CheckBox fx:id="enableUniqueRule" mnemonicParsing="false" GridPane.rowIndex="5" />
<CheckBox fx:id="uniqueRuleCheckBox" mnemonicParsing="false" GridPane.columnIndex="2" GridPane.rowIndex="5" />
<CheckBox fx:id="enableMinLengthRule" mnemonicParsing="false" GridPane.rowIndex="6" />
<Spinner fx:id="minLengthSpinner" GridPane.columnIndex="2" GridPane.rowIndex="6" />
<CheckBox fx:id="enableMaxLengthRule" mnemonicParsing="false" GridPane.rowIndex="7" />
<Spinner fx:id="maxLengthSpinner" GridPane.columnIndex="2" GridPane.rowIndex="7" />
<CheckBox fx:id="enableDateRule" mnemonicParsing="false" GridPane.rowIndex="8" />
<TextField fx:id="dateformatRuleTextField" GridPane.columnIndex="2" GridPane.rowIndex="8" />
<CheckBox fx:id="enableRegexpRule" mnemonicParsing="false" GridPane.rowIndex="9" />
<TextField fx:id="regexpRuleTextField" GridPane.columnIndex="2" GridPane.rowIndex="9" />
<CheckBox fx:id="enableValueOfRule" mnemonicParsing="false" GridPane.rowIndex="10" />
<TextField fx:id="valueOfRuleTextField" GridPane.columnIndex="2" GridPane.rowIndex="10" />
<CheckBox fx:id="enableGroovyRule" mnemonicParsing="false" GridPane.rowIndex="11" />
<CodeArea fx:id="groovyRuleTextArea" prefHeight="300.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="11" GridPane.rowSpan="2" />
<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,12 +26,10 @@
package ninja.javafx.smartcsv.fx.table.model;
import ninja.javafx.smartcsv.validation.Validator;
import org.junit.Test;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.junit.Assert.assertThat;
/**
* unit test for the csv model
@@ -77,20 +75,6 @@ public class CSVModelTest {
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
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -1,13 +1,10 @@
package ninja.javafx.smartcsv.fx.table.model;
import ninja.javafx.smartcsv.validation.Validator;
import org.hamcrest.Matchers;
import org.junit.Test;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.junit.Assert.assertThat;
/**
* unit test for row class

View File

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

View File

@@ -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;
import com.typesafe.config.Config;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static java.util.Arrays.asList;
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.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -52,7 +51,7 @@ public class HeaderValidationTest {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameters
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private Config config;
private ValidationConfiguration config;
private Boolean expectedResult;
private List<ValidationMessage> expectedErrors;
private String[] headerNames;
@@ -66,11 +65,12 @@ public class HeaderValidationTest {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameterized constructor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public HeaderValidationTest(String[] configHeaderNames,
public HeaderValidationTest(String configHeaderNames,
String[] headerNames,
Boolean expectedResult,
List<ValidationMessage> expectedErrors) {
this.config = headerSectionConfig(configHeaderNames);
Gson gson = new GsonBuilder().create();
this.config = gson.fromJson(configHeaderNames, ValidationConfiguration.class);
this.headerNames = headerNames;
this.expectedResult = expectedResult;
this.expectedErrors = expectedErrors;
@@ -106,11 +106,16 @@ public class HeaderValidationTest {
@Parameterized.Parameters
public static Collection validationConfigurations() {
return asList(new Object[][] {
{ new String[] {}, new String[] {}, true, null },
{ new String[] {"a"}, new String[] {"a"}, true, null },
{ new String[] {"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"))},
{ 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(), new String[] {}, true, null },
{ json("a"), new String[] {"a"}, true, null },
{ json("a"), new String[] {"b"}, false, singletonList(new ValidationMessage("validation.message.header.match", "0", "a", "b"))},
{ json("a"), new String[] {"a","b"}, false, singletonList(new ValidationMessage("validation.message.header.length", "2", "1"))},
{ 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;
import com.typesafe.config.Config;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Collection;
import java.util.List;
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.is;
import static org.junit.Assert.assertThat;
@@ -23,7 +25,7 @@ public class ValidatorTest {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameters
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private Config config;
private ValidationConfiguration config;
private String column;
private String value;
private Boolean expectedResult;
@@ -39,14 +41,14 @@ public class ValidatorTest {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parameterized constructor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public ValidatorTest(String configcolumn,
String configValidation,
Object configValue,
public ValidatorTest(String config,
String column,
String value,
Boolean expectedResult,
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.value = value;
this.expectedResult = expectedResult;
@@ -68,8 +70,10 @@ public class ValidatorTest {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Test
public void validation() {
System.out.println(column + " " + value + " " + expectedResult + " " + expectedError);
// execution
ValidationError result = sut.isValid(column, value, 0);
ValidationError result = sut.isValid(0, column, value);
// assertion
assertThat(result == null, is(expectedResult));
@@ -84,30 +88,42 @@ public class ValidatorTest {
@Parameterized.Parameters
public static Collection validationConfigurations() {
return asList(new Object[][] {
{ "column", "not empty", true, "column", "value", true, null },
{ "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") },
{ "column", "integer", true, "column", "999", true, null },
{ "column", "integer", true, "column", "a", false, new ValidationMessage("validation.message.integer") },
{ "column", "double", true, "column", "999", true, null },
{ "column", "double", true, "column", "999.000", true, null },
{ "column", "double", true, "column", "a", false, new ValidationMessage("validation.message.double") },
{ "column", "minlength", 2, "column", "12", true, null },
{ "column", "minlength", 2, "column", "1", false, new ValidationMessage("validation.message.min.length", "2") },
{ "column", "maxlength", 2, "column", "12", true, null },
{ "column", "maxlength", 2, "column", "123", false, new ValidationMessage("validation.message.max.length", "2") },
{ "column", "date", "yyyyMMdd", "column", "20151127", true, null },
{ "column", "date", "yyyyMMdd", "column", "27.11.2015", false, new ValidationMessage("validation.message.date.format", "yyyyMMdd") },
{ "column", "alphanumeric", true, "column", "abcABC123", true, null },
{ "column", "alphanumeric", true, "column", "-abcABC123", false, new ValidationMessage("validation.message.alphanumeric") },
{ "column", "regexp", "[a-z]*", "column", "abc", true, null },
{ "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 },
{ "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 },
{ "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", "not empty", true), "column", "value", true, null },
{ json("column", "not empty", true), "column", "", false, new ValidationMessage("validation.message.not.empty") },
{ json("column", "not empty", true), "column", null, false, new ValidationMessage("validation.message.not.empty") },
{ json("column", "integer", true), "column", "999", true, null },
{ json("column", "integer", true), "column", "a", false, new ValidationMessage("validation.message.integer") },
{ json("column", "double", true), "column", "999", true, null },
{ json("column", "double", true), "column", "999.000", true, null },
{ json("column", "double", true), "column", "a", false, new ValidationMessage("validation.message.double") },
{ json("column", "minlength", 2), "column", "12", true, null },
{ json("column", "minlength", 2), "column", "1", false, new ValidationMessage("validation.message.min.length", "2") },
{ json("column", "maxlength", 2), "column", "12", true, null },
{ json("column", "maxlength", 2), "column", "123", false, new ValidationMessage("validation.message.max.length", "2") },
{ json("column", "date", "yyyyMMdd"), "column", "20151127", true, null },
{ json("column", "date", "yyyyMMdd"), "column", "27.11.2015", false, new ValidationMessage("validation.message.date.format", "yyyyMMdd") },
{ json("column", "alphanumeric", true), "column", "abcABC123", true, null },
{ json("column", "alphanumeric", true), "column", "-abcABC123", false, new ValidationMessage("validation.message.alphanumeric") },
{ json("column", "regexp", "[a-z]*"), "column", "abc", true, null },
{ json("column", "regexp", "[a-z]*"), "column", "abcA", false, new ValidationMessage("validation.message.regexp", "[a-z]*") },
{ json("column", "groovy", "value.contains('a')? 'true' : 'no a inside'"), "column", "abcdef", true, null },
{ json("column", "groovy", "value.contains('a')? 'true' : 'no a inside'"), "column", "bcdefg", false, new ValidationMessage("no a inside") },
{ json("column", "value of", asList("a","b","c","d","e")), "column", "c", true, null },
{ 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 = "{\"headers\": { \"list\": [\""+column+"\"]},\"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;
}
}