5 Commits
0.6 ... 0.7

28 changed files with 279 additions and 41 deletions

View File

@@ -14,7 +14,7 @@ even in a "normal" CSV editor. So I decided to write this simple JavaFX applicat
[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)
binary distribution of the [latest release (0.6)](https://github.com/frosch95/SmartCSV.fx/releases/download/0.6/SmartCSV.fx-0.6-SNAPSHOT.zip)
##Talks
[Introduction](http://javafx.ninja/talks/introduction/)

View File

@@ -1,5 +1,5 @@
group 'ninja.javafx'
version '0.6-SNAPSHOT'
version '0.7-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'groovy'

View File

@@ -0,0 +1,71 @@
package ninja.javafx.smartcsv.export;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import ninja.javafx.smartcsv.fx.table.model.CSVModel;
import ninja.javafx.smartcsv.validation.ValidationError;
import java.io.File;
import java.io.StringWriter;
import java.nio.file.Files;
import java.util.ResourceBundle;
import static java.text.MessageFormat.format;
import static ninja.javafx.smartcsv.fx.util.I18nValidationUtil.getI18nValidatioMessage;
/**
* this class exports the error messages into a log file
*/
@org.springframework.stereotype.Service
public class ErrorExport extends Service {
private CSVModel model;
private File file;
private ResourceBundle resourceBundle;
private String csvFilename;
public void setCsvFilename(String csvFilename) {
this.csvFilename = csvFilename;
}
public void setResourceBundle(ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
}
public void setModel(CSVModel model) {
this.model = model;
}
public void setFile(File file) {
this.file = file;
}
@Override
protected Task createTask() {
return new Task() {
@Override
protected Void call() throws Exception {
try {
StringWriter log = new StringWriter();
log.append(
format(resourceBundle.getString("log.header.message"),
csvFilename,
Integer.toString(model.getValidationError().size()))).append("\n\n");
for (ValidationError error:model.getValidationError()) {
log.append(
format(resourceBundle.getString("log.message"),
error.getLineNumber().toString(),
error.getColumn(),
getI18nValidatioMessage(resourceBundle, error))).append("\n");
}
Files.write(file.toPath(), log.toString().getBytes());
} catch (Throwable ex) {
ex.printStackTrace();
}
return null;
}
};
}
}

View File

@@ -26,6 +26,7 @@
package ninja.javafx.smartcsv.fx;
import javafx.beans.binding.Bindings;
import javafx.collections.ListChangeListener;
import javafx.collections.WeakListChangeListener;
import javafx.concurrent.WorkerStateEvent;
@@ -39,6 +40,7 @@ import javafx.scene.layout.BorderPane;
import javafx.stage.FileChooser;
import ninja.javafx.smartcsv.csv.CSVFileReader;
import ninja.javafx.smartcsv.csv.CSVFileWriter;
import ninja.javafx.smartcsv.export.ErrorExport;
import ninja.javafx.smartcsv.files.FileStorage;
import ninja.javafx.smartcsv.fx.about.AboutController;
import ninja.javafx.smartcsv.fx.list.ErrorSideBar;
@@ -95,6 +97,9 @@ public class SmartCSVController extends FXMLController {
public static final String CSV_FILTER_EXTENSION = "*.csv";
public static final String JSON_FILTER_TEXT = "JSON files (*.json)";
public static final String JSON_FILTER_EXTENSION = "*.json";
public static final String EXPORT_LOG_FILTER_TEXT = "Error log files (*.log)";
public static final String EXPORT_LOG_FILTER_EXTENSION = "*.log";
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// injections
@@ -115,6 +120,9 @@ public class SmartCSVController extends FXMLController {
@Autowired
private SaveFileService saveFileService;
@Autowired
private ErrorExport errorExport;
@FXML
private BorderPane applicationPane;
@@ -157,6 +165,9 @@ public class SmartCSVController extends FXMLController {
@FXML
private MenuItem gotoLineMenuItem;
@FXML
private MenuItem exportMenuItem;
@FXML
private Button saveButton;
@@ -181,6 +192,9 @@ public class SmartCSVController extends FXMLController {
@FXML
private Button addRowButton;
@FXML
private Button exportButton;
@FXML
private Label currentLineNumber;
@@ -370,6 +384,26 @@ public class SmartCSVController extends FXMLController {
}
}
@FXML
public void export(ActionEvent actionEvent) {
final FileChooser fileChooser = new FileChooser();
//Set extension filter
final FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter(EXPORT_LOG_FILTER_TEXT, EXPORT_LOG_FILTER_EXTENSION);
fileChooser.getExtensionFilters().add(extFilter);
fileChooser.setTitle("Save");
//Show open file dialog
File file = fileChooser.showSaveDialog(applicationPane.getScene().getWindow());
if (file != null) {
errorExport.setCsvFilename(currentCsvFile.getFile().getName());
errorExport.setModel(currentCsvFile.getContent());
errorExport.setFile(file);
errorExport.setResourceBundle(resourceBundle);
errorExport.restart();
}
}
public boolean canExit() {
boolean canExit = true;
if (currentCsvFile.getContent() != null && currentCsvFile.isFileChanged()) {
@@ -552,6 +586,8 @@ public class SmartCSVController extends FXMLController {
* Creates new table view and add the new content
*/
private void resetContent() {
resetExportButtons();
if (currentCsvFile.getContent() != null) {
currentCsvFile.getContent().getValidationError().addListener(weakErrorListListener);
currentCsvFile.getContent().setValidationConfiguration(currentConfigFile.getContent());
@@ -575,9 +611,22 @@ public class SmartCSVController extends FXMLController {
setRightAnchor(tableView, 0.0);
tableWrapper.getChildren().setAll(tableView);
errorSideBar.setModel(currentCsvFile.getContent());
binExportButtons();
}
}
private void binExportButtons() {
exportButton.disableProperty().bind(Bindings.isEmpty(currentCsvFile.getContent().getValidationError()));
exportMenuItem.disableProperty().bind(Bindings.isEmpty(currentCsvFile.getContent().getValidationError()));
}
private void resetExportButtons() {
exportButton.disableProperty().unbind();
exportMenuItem.disableProperty().unbind();
exportButton.disableProperty().setValue(true);
exportMenuItem.disableProperty().setValue(true);
}
/**
* Adds a column with the given name to the tableview
* @param header name of the column header

View File

@@ -48,7 +48,7 @@ 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;
import static ninja.javafx.smartcsv.fx.util.I18nValidationUtil.getI18nValidatioMessageWithColumn;
/**
* clickable side bar with error markers
@@ -158,7 +158,7 @@ public class ErrorSideBar extends Region {
errorMarker.setStyle("-fx-background-color: " + ERROR_COLOR);
errorMarker.setOnMouseClicked(event -> selectedValidationError.setValue(error));
errorMarker.setOnMouseEntered(event -> {
popOver.setContentNode(popupContent(getI18nValidatioMessage(resourceBundle, error)));
popOver.setContentNode(popupContent(getI18nValidatioMessageWithColumn(resourceBundle, error)));
popOver.show(errorMarker, -16);
});
return errorMarker;

View File

@@ -39,7 +39,7 @@ 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 final class CSVModel {
public final class CSVModel implements ColumnValueProvider {
private static final Logger logger = LogManager.getLogger(CSVModel.class);
@@ -55,7 +55,7 @@ public final class CSVModel {
* @param validationConfiguration the validator configuration for this data
*/
public void setValidationConfiguration(ValidationConfiguration validationConfiguration) {
this.validator = new Validator(validationConfiguration);
this.validator = new Validator(validationConfiguration, this);
revalidate();
}
@@ -145,11 +145,19 @@ public final class CSVModel {
public ValidationConfiguration createValidationConfiguration() {
ValidationConfiguration newValidationConfiguration = new ValidationConfiguration();
newValidationConfiguration.setHeaderNames(this.header);
this.validator = new Validator(newValidationConfiguration);
this.validator = new Validator(newValidationConfiguration, this);
this.revalidate();
return newValidationConfiguration;
}
@Override
public String getValue(int row, String column) {
return rows.get(row).getColumns().get(column).getValue().getValue();
}
@Override
public int getNumberOfRows() {
return rows.size();
}
}

View File

@@ -0,0 +1,11 @@
package ninja.javafx.smartcsv.fx.table.model;
/**
* interface for easier access to values in a column
*/
public interface ColumnValueProvider {
String getValue(int row, String column);
int getNumberOfRows();
}

View File

@@ -54,11 +54,20 @@ public class I18nValidationUtil {
return "";
}
public static String getI18nValidatioMessageWithColumn(ResourceBundle resourceBundle, ValidationError error) {
return getI18nValidatioMessage(resourceBundle, error, resourceBundle.getString("column") + " " + error.getColumn() + " : ");
}
public static String getI18nValidatioMessage(ResourceBundle resourceBundle, ValidationError error) {
return getI18nValidatioMessage(resourceBundle, error, "");
}
private static String getI18nValidatioMessage(ResourceBundle resourceBundle, ValidationError error, String prefix) {
List<ValidationMessage> validationMessages = error.getMessages();
StringWriter message = new StringWriter();
for (ValidationMessage validationMessage: validationMessages) {
message.append(prefix);
if (resourceBundle.containsKey(validationMessage.getKey())) {
String resourceText = resourceBundle.getString(validationMessage.getKey());
if (validationMessage.getParameters().length > 0) {

View File

@@ -30,7 +30,7 @@ import static org.apache.commons.validator.GenericValidator.matchRegexp;
/**
* Checks if the value is alpha numeric
*/
public class AlphaNumericValidation implements Validation {
public class AlphaNumericValidation extends EmptyValueIsValid {
@Override
public void check(int row, String value, ValidationError error) {
if (!matchRegexp(value, "[0-9a-zA-Z]*")) {
@@ -42,4 +42,6 @@ public class AlphaNumericValidation implements Validation {
public Type getType() {
return Type.ALPHANUMERIC;
}
}

View File

@@ -30,7 +30,7 @@ import static org.apache.commons.validator.GenericValidator.isDate;
/**
* Checks if the date has the right format
*/
public class DateValidation implements Validation {
public class DateValidation extends EmptyValueIsValid {
private String dateformat;

View File

@@ -30,7 +30,7 @@ import static org.apache.commons.validator.GenericValidator.isDouble;
/**
* Checks if the value is a double
*/
public class DoubleValidation implements Validation {
public class DoubleValidation extends EmptyValueIsValid {
@Override
public void check(int row, String value, ValidationError error) {

View File

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

View File

@@ -33,7 +33,7 @@ import org.codehaus.groovy.control.CompilationFailedException;
/**
* Executes the given groovy as check
*/
public class GroovyValidation implements Validation {
public class GroovyValidation extends EmptyValueIsValid {
private String groovyScript;
private GroovyShell shell = new GroovyShell();

View File

@@ -30,7 +30,7 @@ import static org.apache.commons.validator.GenericValidator.isInt;
/**
* Checks if the value is an integer
*/
public class IntegerValidation implements Validation {
public class IntegerValidation extends EmptyValueIsValid {
@Override
public void check(int row, String value, ValidationError error) {

View File

@@ -30,7 +30,7 @@ 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 {
public class MaxLengthValidation extends EmptyValueIsValid {
private int maxLength;

View File

@@ -30,7 +30,7 @@ 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 {
public class MinLengthValidation extends EmptyValueIsValid {
private int minLength;

View File

@@ -43,4 +43,9 @@ public class NotEmptyValidation implements Validation {
public Type getType() {
return Type.NOT_EMPTY;
}
@Override
public boolean canBeChecked(String value) {
return true;
}
}

View File

@@ -30,7 +30,7 @@ import static org.apache.commons.validator.GenericValidator.matchRegexp;
/**
* Checks the value against the given reg exp
*/
public class RegExpValidation implements Validation {
public class RegExpValidation extends EmptyValueIsValid {
private String regexp;

View File

@@ -25,26 +25,50 @@
*/
package ninja.javafx.smartcsv.validation;
import java.util.HashMap;
import ninja.javafx.smartcsv.fx.table.model.ColumnValueProvider;
import java.util.ArrayList;
import java.util.List;
import static java.util.Collections.sort;
import static java.util.stream.Collectors.joining;
/**
* Checks if the value is unique in the column
*/
public class UniqueValidation implements Validation {
public class UniqueValidation extends EmptyValueIsValid {
private HashMap<String, Integer> columnValueMap = new HashMap<>();
private ColumnValueProvider columnValueProvider;
private String column;
public UniqueValidation(ColumnValueProvider columnValueProvider, String column) {
this.columnValueProvider = columnValueProvider;
this.column = column;
}
@Override
public void check(int row, String value, ValidationError error) {
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());
List<Integer> lineNumbers = new ArrayList<>();
int numberOfRows = columnValueProvider.getNumberOfRows();
for (int currentRowOfIteration = 0; currentRowOfIteration < numberOfRows; currentRowOfIteration++) {
String storedValue = columnValueProvider.getValue(currentRowOfIteration, column);
if (value.equals(storedValue) && currentRowOfIteration != row) {
lineNumbers.add(currentRowOfIteration + 1); // show not 0 based line numbers to user
}
} else {
columnValueMap.put(value, row);
}
if (!lineNumbers.isEmpty()) {
if (lineNumbers.size() > 1) {
sort(lineNumbers);
error.add("validation.message.uniqueness.multiple", value, lineNumbers.stream().map(Object::toString).collect(joining(", ")));
} else {
error.add("validation.message.uniqueness.single", value, lineNumbers.get(0).toString());
}
}
}
@Override

View File

@@ -33,4 +33,5 @@ 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();
boolean canBeChecked(String value);
}

View File

@@ -38,6 +38,7 @@ public class ValidationError {
private List<ValidationMessage> messages = new ArrayList<>();
private Integer lineNumber;
private String column = "";
private ValidationError(Integer lineNumber) {
this.lineNumber = lineNumber;
@@ -51,10 +52,19 @@ public class ValidationError {
return new ValidationError(-1);
}
public ValidationError column(String column) {
this.column = column;
return this;
}
public Integer getLineNumber() {
return lineNumber;
}
public String getColumn() {
return column;
}
public List<ValidationMessage> getMessages() {
return messages;
}

View File

@@ -26,6 +26,8 @@
package ninja.javafx.smartcsv.validation;
import ninja.javafx.smartcsv.fx.table.model.ColumnValueProvider;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -41,6 +43,7 @@ public class Validator {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private ValidationConfiguration validationConfig;
private ColumnValueProvider columnValueProvider;
private Map<String, Map<Validation.Type, Validation>> columnValidationMap = new HashMap<>();
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -52,8 +55,9 @@ public class Validator {
*
* @param validationConfig
*/
public Validator(ValidationConfiguration validationConfig) {
public Validator(ValidationConfiguration validationConfig, ColumnValueProvider columnValueProvider) {
this.validationConfig = validationConfig;
this.columnValueProvider = columnValueProvider;
initColumnValidations();
}
@@ -82,17 +86,12 @@ public class Validator {
public ValidationError isValid(Integer row, String column, String value) {
ValidationError result = null;
if (hasConfig()) {
ValidationError error = ValidationError.withLineNumber(row);
ValidationError error = ValidationError.withLineNumber(row).column(column);
Map<Validation.Type, Validation> validationMap = columnValidationMap.get(column);
if (validationMap != null) {
for (Validation validation: validationMap.values()) {
if (validation.getType() == Validation.Type.NOT_EMPTY) {
if (validation.canBeChecked(value)) {
validation.check(row, value, error);
} else {
if (value != null && !value.isEmpty()) {
validation.check(row, value, error);
}
}
}
}
@@ -165,7 +164,7 @@ public class Validator {
Boolean uniqueRule = validationConfig.getUniqueRuleFor(column);
if (uniqueRule != null && uniqueRule) {
add(column, new UniqueValidation());
add(column, new UniqueValidation(columnValueProvider, column));
}
String dateRule = validationConfig.getDateRuleFor(column);

View File

@@ -32,7 +32,7 @@ import static java.util.stream.Collectors.joining;
/**
* Checks if the value is part of a list of values
*/
public class ValueOfValidation implements Validation {
public class ValueOfValidation extends EmptyValueIsValid {
private List<String> values;

View File

@@ -1,5 +1,5 @@
application.name = SmartCSV.fx
application.version = 0.6
application.version = 0.7
# fxml views
fxml.smartcvs.view = /ninja/javafx/smartcsv/fx/smartcsv.fxml

View File

@@ -82,9 +82,14 @@
.goto-icon {
-glyph-name: "SUBDIRECTORY_ARROW_RIGHT";
-glyph-size: 16px;
}
.export-icon {
-glyph-name: "BUG";
-glyph-size: 16px;
}
/* toolbar customization based on http://fxexperience.com/2012/02/customized-segmented-toolbar-buttons/ */
#background {
@@ -149,6 +154,11 @@
-fx-fill: white;
}
.tool-bar .export-icon {
-glyph-size: 16px;
-fx-fill: white;
}
.segmented-button-bar .button {
-fx-background-color:
-darkest-black,

View File

@@ -78,6 +78,11 @@
<MaterialDesignIconView styleClass="preferences-icon" />
</graphic>
</MenuItem>
<MenuItem fx:id="exportMenuItem" disable="true" mnemonicParsing="false" onAction="#export" text="%menu.export">
<graphic>
<MaterialDesignIconView styleClass="export-icon" />
</graphic>
</MenuItem>
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem mnemonicParsing="false" onAction="#close" text="%menu.close">
<graphic>
@@ -229,6 +234,14 @@
<MaterialDesignIconView styleClass="preferences-icon" />
</graphic>
</Button>
<Button fx:id="exportButton" disable="true" mnemonicParsing="false" onAction="#export">
<tooltip>
<Tooltip text="%menu.export" />
</tooltip>
<graphic>
<MaterialDesignIconView styleClass="export-icon" />
</graphic>
</Button>
<Button mnemonicParsing="false" onAction="#close" styleClass="last">
<tooltip>
<Tooltip text="%menu.close" />

View File

@@ -14,6 +14,7 @@ menu.preferences = Preferences
menu.delete.row = Delete row
menu.add.row = Add row
menu.goto.line = Goto line
menu.export = Export error log
title.validation.errors = Validation Errors:
@@ -52,7 +53,8 @@ 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.uniqueness.multiple = value {0} is not unique (found in rows {1})
validation.message.uniqueness.single = value {0} is not unique (found in row {1})
validation.message.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 "{2}"
@@ -77,4 +79,9 @@ 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:
lineNumber = Selected line:
log.header.message = {0} has {1} errors
log.message = row {0} column {1} : {2}
column = column

View File

@@ -23,6 +23,7 @@ menu.preferences = Einstellungen
menu.delete.row = Zeile l\u00f6schen
menu.add.row = Zeile hinzuf\u00fcgen
menu.goto.line = Springe zur Zeile
menu.export = Export Fehlerdatei
title.validation.errors = Fehler in der Datei:
@@ -61,7 +62,8 @@ 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.uniqueness.multiple = Wert {0} ist nicht einmalig (gefunden in den Zeilen {1})
validation.message.uniqueness.single = Wert {0} ist nicht einmalig (gefunden in Zeile {1})
validation.message.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
@@ -87,4 +89,9 @@ dialog.validation.rules.header = Pr\u00fcfregeln f\u00fcr die Spalte "{0}"
context.menu.edit.column.rules = Pr\u00fcfregeln bearbeiten
lineNumber = Ausgew\u00e4hlte Zeile:
lineNumber = Ausgew\u00e4hlte Zeile:
log.header.message = {0} hat {1} Fehler
log.message = Zeile {0} Spalte {1} : {2}
column = Spalte