From 57eb307a67e53ad975baec495dd15c25ff404c3f Mon Sep 17 00:00:00 2001 From: David SPORN Date: Tue, 19 Feb 2019 22:05:33 +0100 Subject: [PATCH] new sub-project 'sporniket-core-strings', resolves #23, address partially #21, #22 --- pom.xml | 1 + sporniket-core-strings/pom.xml | 67 ++++ .../java/com/sporniket/strings/QuickDiff.java | 352 ++++++++++++++++++ .../sporniket/strings/StringPredicates.java | 27 ++ .../com/sporniket/strings/package-info.java | 29 ++ .../FormattedInputSimpleParserFactory.java | 230 ++++++++++++ .../strings/parsers/package-info.java | 32 ++ .../strings/pipeline/StringFilter.java | 62 +++ .../strings/pipeline/StringPipeline.java | 41 ++ .../pipeline/StringPipelineBuilder.java | 33 ++ .../pipeline/StringTransformation.java | 60 +++ .../strings/pipeline/package-info.java | 7 + sporniket-core-strings/src/site/site.xml | 9 + .../test/sporniket/strings/QuickDiffTest.java | 97 +++++ .../strings/StringPredicatesTest.java | 65 ++++ .../java/test/sporniket/strings/TestBase.java | 40 ++ ...FormattedInputSimpleParserFactoryTest.java | 88 +++++ .../strings/pipeline/StringFilterTest.java | 74 ++++ .../strings/pipeline/StringPipelineTest.java | 54 +++ .../pipeline/StringTransformationTest.java | 134 +++++++ .../strings/QuickDiffTest_data/specs.json | 195 ++++++++++ 21 files changed, 1697 insertions(+) create mode 100644 sporniket-core-strings/pom.xml create mode 100644 sporniket-core-strings/src/main/java/com/sporniket/strings/QuickDiff.java create mode 100644 sporniket-core-strings/src/main/java/com/sporniket/strings/StringPredicates.java create mode 100644 sporniket-core-strings/src/main/java/com/sporniket/strings/package-info.java create mode 100644 sporniket-core-strings/src/main/java/com/sporniket/strings/parsers/FormattedInputSimpleParserFactory.java create mode 100644 sporniket-core-strings/src/main/java/com/sporniket/strings/parsers/package-info.java create mode 100644 sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringFilter.java create mode 100644 sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringPipeline.java create mode 100644 sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringPipelineBuilder.java create mode 100644 sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringTransformation.java create mode 100644 sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/package-info.java create mode 100644 sporniket-core-strings/src/site/site.xml create mode 100644 sporniket-core-strings/src/test/java/test/sporniket/strings/QuickDiffTest.java create mode 100644 sporniket-core-strings/src/test/java/test/sporniket/strings/StringPredicatesTest.java create mode 100644 sporniket-core-strings/src/test/java/test/sporniket/strings/TestBase.java create mode 100644 sporniket-core-strings/src/test/java/test/sporniket/strings/parsers/FormattedInputSimpleParserFactoryTest.java create mode 100644 sporniket-core-strings/src/test/java/test/sporniket/strings/pipeline/StringFilterTest.java create mode 100644 sporniket-core-strings/src/test/java/test/sporniket/strings/pipeline/StringPipelineTest.java create mode 100644 sporniket-core-strings/src/test/java/test/sporniket/strings/pipeline/StringTransformationTest.java create mode 100644 sporniket-core-strings/src/test/resources/test/sporniket/strings/QuickDiffTest_data/specs.json diff --git a/pom.xml b/pom.xml index cf21c95..618c086 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,7 @@ ./sporniket-core-lang + ./sporniket-core-strings ./sporniket-core-ml ./sporniket-core-io ./sporniket-core-ui diff --git a/sporniket-core-strings/pom.xml b/sporniket-core-strings/pom.xml new file mode 100644 index 0000000..06015b2 --- /dev/null +++ b/sporniket-core-strings/pom.xml @@ -0,0 +1,67 @@ + + 4.0.0 + + com.sporniket.core + sporniket-core + 16.09.01-SNAPSHOT + ../pom.xml + + + sporniket-core-strings + jar + + sporniket-core-strings + String manipulation library + ${url.base}/blob/master/${project.artifactId} + + + + + + + + + org.junit.jupiter + junit-jupiter + 5.4.0 + test + + + + org.assertj + assertj-core + 3.11.1 + test + + + + com.fasterxml.jackson.core + jackson-databind + 2.9.4 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${version.maven-compiler-plugin} + + ${version.jdk} + ${version.jdk} + + + + + + + localMvnSite-${project.artifactId} + Sporniket Java Core Library - ${project.name} + ${url.base.site.deploy}/${project.parent.artifactId}/${project.version}/module/${project.artifactId} + + + diff --git a/sporniket-core-strings/src/main/java/com/sporniket/strings/QuickDiff.java b/sporniket-core-strings/src/main/java/com/sporniket/strings/QuickDiff.java new file mode 100644 index 0000000..ae67eb1 --- /dev/null +++ b/sporniket-core-strings/src/main/java/com/sporniket/strings/QuickDiff.java @@ -0,0 +1,352 @@ +/** + * + */ +package com.sporniket.strings; + +import java.text.MessageFormat; +import java.util.LinkedList; +import java.util.List; + +/** + * Rough implementation of a diff tool, for basic needs (the report does not follows a standard diff format). + * + * The typical use will be through {@link #reportDiff(String[], String[], boolean, boolean)}. + * + *

+ * © Copyright 2002-2016 David Sporn + *

+ *
+ * + *

+ * This file is part of The Sporniket Core Library – lang. + * + *

+ * The Sporniket Core Library – lang is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + *

+ * The Sporniket Core Library – lang is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + *

+ * You should have received a copy of the GNU Lesser General Public License along with The Sporniket Core Library – + * lang. If not, see http://www.gnu.org/licenses/. 2 + * + *


+ * + * @author David SPORN + * @version 16.08.02 + * @since 12.09.01 + */ +public class QuickDiff +{ + private static final String REPORT_LINE__EMPTY = ""; + + /** + * Template for pointing different text in the text on left. + */ + private static final String TEMPLATE__TEXT_ON_LEFT = "<<{0,number,00000}=={1}"; + + /** + * Template for pointing different text in the text on right. + */ + private static final String TEMPLATE__TEXT_ON_RIGHT = "--{0,number,00000}>>{1}"; + + /** + * Flag to set to true if empty lines are meaningless. + */ + private boolean myIgnoreEmptyLines = false; + + /** + * Flag to set to true if lines must be compared after a {@link String#trim()}. + */ + private boolean myIgnoreTrailingWhiteSpaces = false; + + private MessageFormat myTemplateOnLeft = new MessageFormat(TEMPLATE__TEXT_ON_LEFT); + + private MessageFormat myTemplateOnRight = new MessageFormat(TEMPLATE__TEXT_ON_RIGHT); + + /** + * @return the ignoreEmptyLines + */ + public boolean isIgnoreEmptyLines() + { + return myIgnoreEmptyLines; + } + + /** + * @param ignoreEmptyLines + * the ignoreEmptyLines to set + */ + public void setIgnoreEmptyLines(boolean ignoreEmptyLines) + { + myIgnoreEmptyLines = ignoreEmptyLines; + } + + /** + * @return the ignoreTrailingWhiteSpaces + */ + public boolean isIgnoreTrailingWhiteSpaces() + { + return myIgnoreTrailingWhiteSpaces; + } + + /** + * @param ignoreTrailingWhiteSpaces + * the ignoreTrailingWhiteSpaces to set + */ + public void setIgnoreTrailingWhiteSpaces(boolean ignoreTrailingWhiteSpaces) + { + myIgnoreTrailingWhiteSpaces = ignoreTrailingWhiteSpaces; + } + + /** + * Compute a diff report. + * + * @param textOnLeft + * The text on left as an array of lines. + * @param textOnRight + * The text on right as an array of lines. + * @return The diff report as an array of lines. + */ + // FIXME refactor this mammoth using submethods... + public String[] reportDiff(String[] textOnLeft, String[] textOnRight) + { + List _report = new LinkedList(); + int _currentLeft = 0; + int _currentRight = 0; + + while (_currentLeft < textOnLeft.length || _currentRight < textOnRight.length) + { + if (_currentLeft < textOnLeft.length && _currentRight < textOnRight.length) + { + // normal case + String _left = getTextLine(textOnLeft, _currentLeft); + String _right = getTextLine(textOnRight, _currentRight); + if (isIgnoreEmptyLines()) + { + boolean _hasSkippedLine = false; + if (0 == _left.length()) + { + ++_currentLeft; + _hasSkippedLine = true; + } + if (0 == _right.length()) + { + ++_currentRight; + _hasSkippedLine = true; + } + if (_hasSkippedLine) continue; + } + if (_left.equals(_right)) + { + ++_currentLeft; + ++_currentRight; + continue; + } + else + { + // find the range of the difference + int _foundLeft = findNextMatchingLine(textOnLeft, _currentLeft, _right); + + int _foundRight = findNextMatchingLine(textOnRight, _currentRight, _left); + + if (-1 == _foundLeft && -1 == _foundRight) + { + // maybe only this line is different + outputReportLine(_report, myTemplateOnLeft, textOnLeft, _currentLeft); + _currentLeft++; + + outputReportLine(_report, myTemplateOnRight, textOnRight, _currentRight); + _currentRight++; + } + else if (-1 == _foundLeft) + { + // added lines on right + outputReportLine(_report, myTemplateOnLeft, textOnLeft, _currentLeft); + _currentLeft++; + + ++_foundRight; + outputRangeOfTextInReport(_report, myTemplateOnRight, textOnRight, _currentRight, _foundRight); + _currentRight = _foundRight; + } + else if (-1 == _foundRight) + { + // added lines on left + ++_foundLeft; + outputRangeOfTextInReport(_report, myTemplateOnLeft, textOnLeft, _currentLeft, _foundLeft); + _currentLeft = _foundLeft; + + outputReportLine(_report, myTemplateOnRight, textOnRight, _currentRight); + _currentRight++; + } + else + { + // 2 possibilities, find the minimum range size + ++_foundLeft; + int _leftSize = _foundLeft - _currentLeft; + + ++_foundRight; + int _rightSize = _foundRight - _currentRight; + + if (_leftSize < _rightSize) + { + // choose left edits + outputRangeOfTextInReport(_report, myTemplateOnLeft, textOnLeft, _currentLeft, _foundLeft); + _currentLeft = _foundLeft + 1; + + outputReportLine(_report, myTemplateOnRight, textOnRight, _currentRight); + _currentRight++; + } + else + { + // choose right edits + outputReportLine(_report, myTemplateOnLeft, textOnLeft, _currentLeft); + _currentLeft++; + + outputRangeOfTextInReport(_report, myTemplateOnRight, textOnRight, _currentRight, _foundRight); + _currentRight = _foundRight + 1; + } + } + _report.add(REPORT_LINE__EMPTY); + } + } + else if (_currentLeft < textOnLeft.length) + { + // supplemental lines on left + String _line = getTextLine(textOnLeft, _currentLeft); + if (!(isIgnoreEmptyLines() && 0 == _line.length())) + { + outputReportLine(_report, myTemplateOnLeft, textOnLeft, _currentLeft); + } + ++_currentLeft; + } + else if (_currentRight < textOnRight.length) + { + // supplemental lines on right + String _line = getTextLine(textOnRight, _currentRight); + if (!(isIgnoreEmptyLines() && 0 == _line.length())) + { + outputReportLine(_report, myTemplateOnRight, textOnRight, _currentRight); + } + _currentRight++; + } + } + + return _report.toArray(new String[_report.size()]); + } + + /** + * Add a report line the designated line. + * + * @param report + * The report buffer. + * @param template + * The template to use (to distinguish between text on left and text on right). + * @param textLines + * The source text as an array of lines. + * @param currentLine + * The line to output. + */ + private void outputReportLine(List report, MessageFormat template, String[] textLines, int currentLine) + { + Object[] _args = + { + currentLine, textLines[currentLine] + }; + report.add(template.format(_args)); + } + + /** + * Add a report line for each designated line. + * + * @param textLines + * The source text as an array of lines. + * @param report + * The report buffer. + * @param template + * The template to use (to distinguish between text on left and text on right). + * @param startLine + * First line to output (inclusive) + * @param endLine + * Last line to output (exclusive) + */ + private void outputRangeOfTextInReport(List report, MessageFormat template, String[] textLines, int startLine, + int endLine) + { + for (int _temp = startLine; _temp < endLine; _temp++) + { + outputReportLine(report, template, textLines, _temp); + } + } + + /** + * Find the matching line, if any. + * + * @param textLines + * The source text as an array of lines. + * @param startingLine + * The line number from which to begin looking for the textToMatch. + * @param textToMatch + * The text to look for. + * @return the line number that is the textToMatch. + */ + private int findNextMatchingLine(String[] textLines, int startingLine, String textToMatch) + { + int _foundLeft = -1; + { + int _tempLine = startingLine + 1; + while (_tempLine < textLines.length) + { + String _tempText = getTextLine(textLines, _tempLine); + if (_tempText.equals(textToMatch)) + { + _foundLeft = _tempLine; + break; + } + ++_tempLine; + } + } + return _foundLeft; + } + + /** + * Return the specified line from the text. + * + * @param textLines + * The source text as an array of lines. + * @param line + * The line to return. + * @return the line as is, or trimed, according to {@link #isIgnoreTrailingWhiteSpaces()}. + */ + private String getTextLine(String[] textLines, int line) + { + return (isIgnoreTrailingWhiteSpaces()) ? textLines[line].trim() : textLines[line]; + } + + /** + * Macro to compute a diff report. + * + * @param textOnLeft + * The text on left as an array of lines. + * @param textOnRight + * The text on right as an array of lines. + * @param ignoreEmptyLines + * Set to true if you want to skip empty lines. + * @param ignoreTrailingWhiteSpaces + * Set to true if lines are to be compared without leading and trailing whitespaces (will use + * {@link String#trim()}). + * @return The diff report as an array of lines. + */ + public static String[] reportDiff(String[] textOnLeft, String[] textOnRight, boolean ignoreEmptyLines, + boolean ignoreTrailingWhiteSpaces) + { + QuickDiff _instance = new QuickDiff(); + _instance.setIgnoreEmptyLines(ignoreEmptyLines); + _instance.setIgnoreTrailingWhiteSpaces(ignoreTrailingWhiteSpaces); + + return _instance.reportDiff(textOnLeft, textOnRight); + } +} diff --git a/sporniket-core-strings/src/main/java/com/sporniket/strings/StringPredicates.java b/sporniket-core-strings/src/main/java/com/sporniket/strings/StringPredicates.java new file mode 100644 index 0000000..1bd9110 --- /dev/null +++ b/sporniket-core-strings/src/main/java/com/sporniket/strings/StringPredicates.java @@ -0,0 +1,27 @@ +/** + * + */ +package com.sporniket.strings; + +import java.util.function.Predicate; + +/** + * Various predicates about strings + * + * @author dsporn + * + */ +public class StringPredicates +{ + // ordered before public predicates for references. + private static final Predicate AA_IS_EMPTY = s -> null == s || 0 == s.length(); + + public static final Predicate IS_BLANK = AA_IS_EMPTY.or(s -> 0 == s.trim().length()); + + public static final Predicate IS_EMPTY = AA_IS_EMPTY; + + public static final Predicate IS_NOT_BLANK = IS_BLANK.negate(); + + public static final Predicate IS_NOT_EMPTY = IS_EMPTY.negate(); + +} diff --git a/sporniket-core-strings/src/main/java/com/sporniket/strings/package-info.java b/sporniket-core-strings/src/main/java/com/sporniket/strings/package-info.java new file mode 100644 index 0000000..ba4d274 --- /dev/null +++ b/sporniket-core-strings/src/main/java/com/sporniket/strings/package-info.java @@ -0,0 +1,29 @@ +/** + * Utility package for processing Strings. + * + *

© Copyright 2002-2016 David Sporn

+ *
+ * + *

This file is part of The Sporniket Core Library – lang. + * + *

The Sporniket Core Library – lang is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + *

The Sporniket Core Library – lang is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + *

You should have received a copy of the GNU Lesser General Public License + * along with The Sporniket Core Library – lang. If not, see http://www.gnu.org/licenses/. 2 + * + *


+ * + * @author David SPORN + * @version 16.08.02 + * @since 12.06.01 + */ +package com.sporniket.strings; + diff --git a/sporniket-core-strings/src/main/java/com/sporniket/strings/parsers/FormattedInputSimpleParserFactory.java b/sporniket-core-strings/src/main/java/com/sporniket/strings/parsers/FormattedInputSimpleParserFactory.java new file mode 100644 index 0000000..820de7b --- /dev/null +++ b/sporniket-core-strings/src/main/java/com/sporniket/strings/parsers/FormattedInputSimpleParserFactory.java @@ -0,0 +1,230 @@ +/** + * + */ +package com.sporniket.strings.parsers; + +import static com.sporniket.strings.StringPredicates.IS_NOT_EMPTY; +import static com.sporniket.strings.StringPredicates.IS_EMPTY; + +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +/** + * Regular expression factory based on very simple description of the input format. + * + *

+ * typical input format will look like : bla blah : # , # , $; meaning there is some literal display + * bla blah : followed by 2 numerical values (# placeholders) and an alphanumerical value ($ + * placeholder) and a literal display ;. + * + *

+ * The Regular expression will match each one char placeholder found in the input format. Obviously, there MUST be some separation + * between placeholders to work as intended. + * + *

+ * Spaces and tabulations are converted as optionnal blank spaces [ \t]* ; Non breaking spaces (\u00a0) + * are converted as mandatory blank spaces [ \t]+. + * + *

+ * Thus, if one want to use blank spaces to separate values, one MUST use non breaking spaces in the input format, e.g. + * blah\u00a0#\u00a0#. + * + *

+ * The input format is trimed before processing, thus one MUST trim the input to parse. + * + * + *

+ * By default, this factory use the following placeholders : + *

+ *
# + *
integers matching [0-9]+ + *
$ + *
alphanumeric words matching [0-9A-Za-z-_.]+ + *
+ * + *

+ * The list of placeholders and their matching expression can be provided as an array of String[], each array + * containing at least 2 non-empty Strings. The first item is the placeholder, only the first char is taken into account, the second + * item is the regular expression to match, it will be enclosed inside () in the final pattern. + * + *

+ * © Copyright 2002-2016 David Sporn + *

+ *
+ * + *

+ * This file is part of The Sporniket Core Library – lang. + * + *

+ * The Sporniket Core Library – lang is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + *

+ * The Sporniket Core Library – lang is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + *

+ * You should have received a copy of the GNU Lesser General Public License along with The Sporniket Core Library – + * lang. If not, see http://www.gnu.org/licenses/. 2 + * + *


+ * + * @author David SPORN + * @version 16.08.02 + * @since 16.08.00 + */ +public class FormattedInputSimpleParserFactory +{ + private static final String[][] DEFAULT_PLACEHOLDERS_DEFINITION = + { + { + "#", "[0-9]+" + }, + { + "$", "[0-9A-Za-z-_.]+" + } + }; + + private static final FormattedInputSimpleParserFactory INSTANCE = new FormattedInputSimpleParserFactory(); + + /** + * Shortcut for new FormattedInputSimpleParserFactory().getParser(inputFormat). + * + * @param inputFormat + * the input format. the input format to match. + * @return a regular expression matching the specified input format. + */ + public static Pattern getSimpleParser(String inputFormat) + { + return INSTANCE.getParser(inputFormat); + } + + private static final String MATCHER_BLANK__MANDATORY = "[ \t]+"; + + private static final String MATCHER_BLANK__OPTIONAL = "[ \t]*"; + + /** + * Characters that need to be escaped, if they are not a placeholder. + */ + private static final String SPECIAL_CHARS = "\\(){}[]<>^$.*+?|-&"; + + private static final String SPECIAL_CHARS__BLANK = " \t"; + + private static final String SPECIAL_CHARS__NBSP = "\u00a0"; + + private final Map myPlaceHolderSpecs = new TreeMap(); + + public FormattedInputSimpleParserFactory() + { + this(DEFAULT_PLACEHOLDERS_DEFINITION); + } + + /** + * Create a factory using specified placeholders. + * + * @param placeHolderSpecs + * invalid specification are silently ignored. + */ + public FormattedInputSimpleParserFactory(String[][] placeHolderSpecs) + { + buildPlaceHolderSpecs(placeHolderSpecs); + } + + /** + * Get a parser suitable to matche multiple values. + * + * @param inputFormat + * the input format to match. + * @return a regular expression matching the specified input format. + */ + public Pattern getParser(String inputFormat) + { + return Pattern.compile(computePattern(inputFormat.trim())); + } + + private void buildPlaceHolderSpecs(String[][] specs) + { + for (String[] _spec : specs) + { + // silently ignore bad specs + if (_spec.length < 2) + { + continue; + } + if (IS_EMPTY.test(_spec[0])) + { + continue; + } + if (IS_EMPTY.test(_spec[1])) + { + continue; + } + getPlaceHolderSpecs().put(_spec[0].charAt(0), _spec[1]); + + } + } + + private String computePattern(String inputFormat) + { + StringBuilder _result = new StringBuilder(); + boolean _isInBlankSpaces = false; + for (char _char : inputFormat.toCharArray()) + { + if (isMandatoryBlank(_char) && !_isInBlankSpaces) + { + _result.append(MATCHER_BLANK__MANDATORY); + _isInBlankSpaces = true; + } + else if (isBlank(_char) && !_isInBlankSpaces) + { + _result.append(MATCHER_BLANK__OPTIONAL); + _isInBlankSpaces = true; + } + else if (isPlaceHolder(_char)) + { + _result.append("(").append(getPlaceHolderSpecs().get(_char)).append(")"); + _isInBlankSpaces = false; + } + else if (isSpecialChar(_char)) + { + _result.append("\\").append(_char); + _isInBlankSpaces = false; + } + else + { + _result.append(_char); + _isInBlankSpaces = false; + } + } + return _result.toString(); + } + + private Map getPlaceHolderSpecs() + { + return myPlaceHolderSpecs; + } + + private boolean isBlank(char character) + { + return SPECIAL_CHARS__BLANK.indexOf(character) > -1; + } + + private boolean isMandatoryBlank(char character) + { + return SPECIAL_CHARS__NBSP.indexOf(character) > -1; + } + + private boolean isPlaceHolder(char character) + { + return getPlaceHolderSpecs().containsKey(character); + } + + private boolean isSpecialChar(char character) + { + return SPECIAL_CHARS.indexOf(character) > -1; + } +} diff --git a/sporniket-core-strings/src/main/java/com/sporniket/strings/parsers/package-info.java b/sporniket-core-strings/src/main/java/com/sporniket/strings/parsers/package-info.java new file mode 100644 index 0000000..cb0a343 --- /dev/null +++ b/sporniket-core-strings/src/main/java/com/sporniket/strings/parsers/package-info.java @@ -0,0 +1,32 @@ +/** + * Utility package for regular expressions. + * + *

+ * © Copyright 2002-2016 David Sporn + *

+ *
+ * + *

+ * This file is part of The Sporniket Core Library – lang. + * + *

+ * The Sporniket Core Library – lang is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + *

+ * The Sporniket Core Library – lang is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + *

+ * You should have received a copy of the GNU Lesser General Public License along with The Sporniket Core Library – + * lang. If not, see http://www.gnu.org/licenses/. 2 + * + *


+ * + * @author David SPORN + * @version 16.08.02 + * @since 16.08.00 + */ +package com.sporniket.strings.parsers; \ No newline at end of file diff --git a/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringFilter.java b/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringFilter.java new file mode 100644 index 0000000..4476d91 --- /dev/null +++ b/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringFilter.java @@ -0,0 +1,62 @@ +/** + * + */ +package com.sporniket.strings.pipeline; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * String transformation that only keep specified chars. + * + * @author dsporn + * + */ +public class StringFilter implements StringTransformation +{ + public static final StringFilter KEEP_ALPHA = new StringFilter("A-Za-z"); + + public static final StringFilter KEEP_ALPHA_LOWER_CASE = new StringFilter("a-z"); + + public static final StringFilter KEEP_ALPHA_UPPER_CASE = new StringFilter("A-Z"); + + public static final StringFilter KEEP_ALPHANUM = new StringFilter("0-9A-Za-z"); + + public static final StringFilter KEEP_ALPHANUM_LOWER_CASE = new StringFilter("0-9a-z"); + + public static final StringFilter KEEP_ALPHANUM_UPPER_CASE = new StringFilter("0-9A-Z"); + + public static final StringFilter KEEP_DIGIT = new StringFilter("0-9"); + + public static final StringFilter KEEP_FILENAME_VALID_CHARS = new StringFilter("-_.0-9A-Za-z"); + + public static final StringFilter KEEP_FILEPATH_UNIX_VALID_CHARS = new StringFilter("-_./0-9A-Za-z"); + + public static final StringFilter KEEP_FILEPATH_WINDOWS_VALID_CHARS = new StringFilter("-_.:\\\\0-9A-Za-z"); + + final Pattern myFilter; + + public StringFilter(String charactersToKeep) + { + super(); + myFilter = Pattern.compile(String.format("[%s]+", charactersToKeep)); + } + + /* + * (non-Javadoc) + * + * @see com.sporniket.strings.pipeline.StringTransformation#transform(java.lang.String) + */ + @Override + public String transform(String input) + { + StringBuilder result = new StringBuilder(); + int scanFrom = 0; + for (Matcher matcher = myFilter.matcher(input); matcher.find(scanFrom); scanFrom = matcher.end()) + { + result.append(input.substring(matcher.start(), matcher.end())); + } + return result.toString(); + } + +} diff --git a/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringPipeline.java b/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringPipeline.java new file mode 100644 index 0000000..14a8104 --- /dev/null +++ b/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringPipeline.java @@ -0,0 +1,41 @@ +/** + * + */ +package com.sporniket.strings.pipeline; + +import java.util.ArrayList; +import java.util.List; + +/** + * Model of compounded string transformation. + * @author dsporn + * + */ +public class StringPipeline implements StringTransformation +{ + + final List myTransformers; + + public StringPipeline(List transformers) + { + if (null == transformers || transformers.isEmpty()) + { + throw new IllegalStateException("A pipeline MUST contains at least one transformer."); + } + myTransformers = new ArrayList<>(transformers); + } + + /* (non-Javadoc) + * @see com.sporniket.strings.pipeline.StringTransformation#transform(java.lang.String) + */ + @Override + public String transform(String input) + { + String result = input; + for (StringTransformation transformer : myTransformers) + { + result = transformer.transform(result); + } + return result; + } +} diff --git a/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringPipelineBuilder.java b/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringPipelineBuilder.java new file mode 100644 index 0000000..38b078e --- /dev/null +++ b/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringPipelineBuilder.java @@ -0,0 +1,33 @@ +/** + * + */ +package com.sporniket.strings.pipeline; + +import java.util.LinkedList; +import java.util.List; + +/** + * Fluent builder of {@link StringPipeline}. + * + * @author dsporn + * + */ +public class StringPipelineBuilder +{ + private List myTransformers = new LinkedList<>(); + + public StringPipelineBuilder pipeThrough(StringTransformation transformer) + { + if (null == transformer) + { + throw new IllegalArgumentException("transformer MUST be defined"); + } + myTransformers.add(transformer); + return this; + } + + public StringPipeline done() + { + return new StringPipeline(myTransformers); + } +} diff --git a/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringTransformation.java b/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringTransformation.java new file mode 100644 index 0000000..2b0101a --- /dev/null +++ b/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/StringTransformation.java @@ -0,0 +1,60 @@ +/** + * + */ +package com.sporniket.strings.pipeline; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import javax.xml.bind.DatatypeConverter; + +/** + * Model of an elementary String transformation, and builtin usual transformations . + * + * @author dsporn + * + */ +public interface StringTransformation +{ + // FIXME to sha-1, sha-256, url encode/decode + + public static final StringTransformation NULL_TO_EMPTY = t -> null == t ? "" : t; + + public static final StringTransformation TO_HASH_MD5 = t -> { + try + { + final MessageDigest _hasher = MessageDigest.getInstance("MD5"); + byte[] _sourceBytes = t.getBytes(StandardCharsets.UTF_8); + _hasher.update(_sourceBytes); + byte[] _hashBytes = _hasher.digest(); + return DatatypeConverter.printHexBinary(_hashBytes); + } + catch (NoSuchAlgorithmException _exception) + { + throw new RuntimeException(_exception); + } + }; + + public static final StringTransformation TO_LOWERCASE = t -> t.toLowerCase(); + + public static final StringTransformation TO_UPPERCASE = t -> t.toUpperCase(); + + public static final StringTransformation TRIM = t -> t.trim(); + + public static final StringTransformation TRIM_START = t -> { + String trimmed = t.trim(); + return 0 == trimmed.length() // + ? trimmed + : t.substring(t.indexOf(trimmed.charAt(0))); + }; + + public static final StringTransformation TRIM_END = t -> { + String trimmed = t.trim(); + return 0 == trimmed.length() // + ? trimmed + : t.substring(0, trimmed.length() + t.indexOf(trimmed.charAt(0))); + }; + + String transform(String input); +} diff --git a/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/package-info.java b/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/package-info.java new file mode 100644 index 0000000..6ff8d45 --- /dev/null +++ b/sporniket-core-strings/src/main/java/com/sporniket/strings/pipeline/package-info.java @@ -0,0 +1,7 @@ +/** + * Model of compounded string transformation through a pipeline of elementary transformations. + * + * @author dsporn + * + */ +package com.sporniket.strings.pipeline; \ No newline at end of file diff --git a/sporniket-core-strings/src/site/site.xml b/sporniket-core-strings/src/site/site.xml new file mode 100644 index 0000000..e1e7a2f --- /dev/null +++ b/sporniket-core-strings/src/site/site.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/sporniket-core-strings/src/test/java/test/sporniket/strings/QuickDiffTest.java b/sporniket-core-strings/src/test/java/test/sporniket/strings/QuickDiffTest.java new file mode 100644 index 0000000..3bb964b --- /dev/null +++ b/sporniket-core-strings/src/test/java/test/sporniket/strings/QuickDiffTest.java @@ -0,0 +1,97 @@ +/** + * + */ +package test.sporniket.strings; + +import static com.sporniket.strings.QuickDiff.reportDiff; +import static java.lang.String.format; +import static java.lang.String.join; +import static java.util.Arrays.asList; +import static java.util.Arrays.deepEquals; +import static org.assertj.core.api.BDDAssertions.then; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Test suite for {@link QuickDiff}. + *

+ * © Copyright 2002-2016 David Sporn + *

+ *
+ * + *

+ * This file is part of The Sporniket Core Library – lang. + * + *

+ * The Sporniket Core Library – lang is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + *

+ * The Sporniket Core Library – lang is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + *

+ * You should have received a copy of the GNU Lesser General Public License along with The Sporniket Core Library – + * lang. If not, see http://www.gnu.org/licenses/. 2 + * + *


+ * + * @author David SPORN + * @version 16.08.02 + * @since 12.09.01 + */ +public class QuickDiffTest extends TestBase +{ + public static class TestSpecStruct + { + public String label; + + public boolean ignoreEmptyLines; + + public boolean ignoreTrailingWhiteSpaces; + + public String[] left; + + public String[] right; + + public String[] reportLtr; + + public String[] reportRtl; + } + + @TestFactory + public Stream shouldPassSpecifiedScenario() + throws URISyntaxException, JsonParseException, JsonMappingException, IOException + { + String resourceName = format("%s_data/specs.json", getClass().getName().replace('.', File.separatorChar)); + return asList(loadJsonData(resourceName, TestSpecStruct[].class))// + .stream()// + .flatMap(s -> asList(// + dynamicTest(format("%s -- ltr", s.label), + () -> then(join("\n", reportDiff(s.left, s.right, s.ignoreEmptyLines, s.ignoreTrailingWhiteSpaces))) + .isEqualTo(join("\n", s.reportLtr)))// + , + dynamicTest(format("%s -- rtl", s.label), + () -> then(join("\n", reportDiff(s.right, s.left, s.ignoreEmptyLines, s.ignoreTrailingWhiteSpaces))) + .isEqualTo(join("\n", s.reportRtl)))// + ).stream()); + } +} diff --git a/sporniket-core-strings/src/test/java/test/sporniket/strings/StringPredicatesTest.java b/sporniket-core-strings/src/test/java/test/sporniket/strings/StringPredicatesTest.java new file mode 100644 index 0000000..3f96e69 --- /dev/null +++ b/sporniket-core-strings/src/test/java/test/sporniket/strings/StringPredicatesTest.java @@ -0,0 +1,65 @@ +package test.sporniket.strings; + +import static com.sporniket.strings.StringPredicates.IS_BLANK; +import static com.sporniket.strings.StringPredicates.IS_EMPTY; +import static com.sporniket.strings.StringPredicates.IS_NOT_BLANK; +import static com.sporniket.strings.StringPredicates.IS_NOT_EMPTY; +import static java.util.Arrays.asList; +import static org.assertj.core.api.BDDAssertions.then; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +public class StringPredicatesTest +{ + @TestFactory + public Stream testIsBlank() + { + return asList(// + dynamicTest("null is blank", () -> then(IS_BLANK.test(null)).isTrue())// + , dynamicTest("'' is blank", () -> then(IS_BLANK.test("")).isTrue())// + , dynamicTest("' ' is blank", () -> then(IS_BLANK.test(" ")).isTrue())// + , dynamicTest("'aa' is not blank", () -> then(IS_BLANK.test("aaa")).isFalse())// + , dynamicTest("' aa ' is not blank", () -> then(IS_BLANK.test(" aaa ")).isFalse())// + ).stream(); + } + + @TestFactory + public Stream testIsEmpty() + { + return asList(// + dynamicTest("null is empty", () -> then(IS_EMPTY.test(null)).isTrue())// + , dynamicTest("'' is empty", () -> then(IS_EMPTY.test("")).isTrue())// + , dynamicTest("' ' is not empty", () -> then(IS_EMPTY.test(" ")).isFalse())// + , dynamicTest("'aa' is not empty", () -> then(IS_EMPTY.test("aaa")).isFalse())// + , dynamicTest("' aa ' is not empty", () -> then(IS_EMPTY.test(" aaa ")).isFalse())// + ).stream(); + } + + @TestFactory + public Stream testIsNotBlank() + { + return asList(// + dynamicTest("null is blank", () -> then(IS_NOT_BLANK.test(null)).isFalse())// + , dynamicTest("'' is blank", () -> then(IS_NOT_BLANK.test("")).isFalse())// + , dynamicTest("' ' is blank", () -> then(IS_NOT_BLANK.test(" ")).isFalse())// + , dynamicTest("'aa' is not blank", () -> then(IS_NOT_BLANK.test("aaa")).isTrue())// + , dynamicTest("' aa ' is not blank", () -> then(IS_NOT_BLANK.test(" aaa ")).isTrue())// + ).stream(); + } + + @TestFactory + public Stream testIsNotEmpty() + { + return asList(// + dynamicTest("null is empty", () -> then(IS_NOT_EMPTY.test(null)).isFalse())// + , dynamicTest("'' is empty", () -> then(IS_NOT_EMPTY.test("")).isFalse())// + , dynamicTest("' ' is not empty", () -> then(IS_NOT_EMPTY.test(" ")).isTrue())// + , dynamicTest("'aa' is not empty", () -> then(IS_NOT_EMPTY.test("aaa")).isTrue())// + , dynamicTest("' aa ' is not empty", () -> then(IS_NOT_EMPTY.test(" aaa ")).isTrue())// + ).stream(); + } +} diff --git a/sporniket-core-strings/src/test/java/test/sporniket/strings/TestBase.java b/sporniket-core-strings/src/test/java/test/sporniket/strings/TestBase.java new file mode 100644 index 0000000..0dad1ff --- /dev/null +++ b/sporniket-core-strings/src/test/java/test/sporniket/strings/TestBase.java @@ -0,0 +1,40 @@ +/** + * + */ +package test.sporniket.strings; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import test.sporniket.strings.QuickDiffTest.TestSpecStruct; + +/** + * Base class for test, when one needs some utilities. + * + * @author dsporn + * + */ +public class TestBase +{ + private ObjectMapper mapper = new ObjectMapper(); + + /** + * Reads a JSON resource file. + * + * @param relativePath + * the path relative to the current class + * @param type + * the type to obtain + * @return the extracted object + * @throws JsonParseException + * @throws JsonMappingException + * @throws IOException + */ + protected T loadJsonData(String relativePath, Class type) throws JsonParseException, JsonMappingException, IOException + { + return mapper.readValue(getClass().getClassLoader().getResourceAsStream(relativePath), type); + } +} diff --git a/sporniket-core-strings/src/test/java/test/sporniket/strings/parsers/FormattedInputSimpleParserFactoryTest.java b/sporniket-core-strings/src/test/java/test/sporniket/strings/parsers/FormattedInputSimpleParserFactoryTest.java new file mode 100644 index 0000000..0c6b03b --- /dev/null +++ b/sporniket-core-strings/src/test/java/test/sporniket/strings/parsers/FormattedInputSimpleParserFactoryTest.java @@ -0,0 +1,88 @@ +/** + * + */ +package test.sporniket.strings.parsers; + +import static org.assertj.core.api.BDDAssertions.then; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.Test; + +import com.sporniket.strings.parsers.FormattedInputSimpleParserFactory; + +/** + * Test suite for the {@link FormattedInputSimpleParserFactory}. + * + *

+ * © Copyright 2002-2016 David Sporn + *

+ *
+ * + *

+ * This file is part of The Sporniket Core Library – lang. + * + *

+ * The Sporniket Core Library – lang is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + *

+ * The Sporniket Core Library – lang is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + *

+ * You should have received a copy of the GNU Lesser General Public License along with The Sporniket Core Library – + * lang. If not, see http://www.gnu.org/licenses/. 2 + * + *


+ * + * @author David SPORN + * @version 16.08.02 + * @since 16.08.00 + */ +public class FormattedInputSimpleParserFactoryTest +{ + @Test + public void testTypicalInputFormat() + { + Pattern _parserToTest = FormattedInputSimpleParserFactory.getSimpleParser("foo:#,#,$"); + String _testInput = "foo:1,2,trois"; + Matcher _parsedResult = _parserToTest.matcher(_testInput); + then(_parsedResult.matches()).as("syntax error in input :'%s'", _testInput).isTrue() ; + then(_parsedResult.group(1)).isEqualTo("1") ; + then(_parsedResult.group(2)).isEqualTo("2") ; + then(_parsedResult.group(3)).isEqualTo("trois") ; + } + + @Test + public void testTypicalInputFormat_specialChars() + { + Pattern _parserToTest = FormattedInputSimpleParserFactory.getSimpleParser("foo:{#},[#],($)"); + String _testInput = "foo:{1},[2],(trois)"; + Matcher _parsedResult = _parserToTest.matcher(_testInput); + then(_parsedResult.matches()).as("syntax error in input :'%s'", _testInput).isTrue() ; + then(_parsedResult.group(1)).isEqualTo("1") ; + then(_parsedResult.group(2)).isEqualTo("2") ; + then(_parsedResult.group(3)).isEqualTo("trois") ; + } + + @Test + public void testTypicalInputFormat__blanks() + { + Pattern _parserToTest = FormattedInputSimpleParserFactory.getSimpleParser("foo\u00a0# , # , $"); + String _testInput = "foo 1 ,2 ,trois"; + String _testInputInvalid = "foo1 ,2 ,trois"; + then(_parserToTest.matcher(_testInputInvalid).matches()).as("MUST reject input with missing mandatory blank spaces").isFalse(); + + Matcher _parsedResult = _parserToTest.matcher(_testInput); + then(_parsedResult.matches()).as("syntax error in input :'%s'", _testInput).isTrue() ; + then(_parsedResult.group(1)).isEqualTo("1") ; + then(_parsedResult.group(2)).isEqualTo("2") ; + then(_parsedResult.group(3)).isEqualTo("trois") ; + } +} diff --git a/sporniket-core-strings/src/test/java/test/sporniket/strings/pipeline/StringFilterTest.java b/sporniket-core-strings/src/test/java/test/sporniket/strings/pipeline/StringFilterTest.java new file mode 100644 index 0000000..4b7ab78 --- /dev/null +++ b/sporniket-core-strings/src/test/java/test/sporniket/strings/pipeline/StringFilterTest.java @@ -0,0 +1,74 @@ +package test.sporniket.strings.pipeline; + +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.Test; + +import com.sporniket.strings.pipeline.StringFilter; + +public class StringFilterTest +{ + public final String SAMPLE_TEXT = "SELECT * from DUAL where TOTO='0023' and bar=-3;"; + + public final String SAMPLE_TEXT_FILES = "/root/foo_bar--01.toto;c:\\win\\bar_foo--02.zip"; + + @Test + public void testKeepAlpha() + { + BDDAssertions.then(StringFilter.KEEP_ALPHA.transform(SAMPLE_TEXT)).isEqualTo("SELECTfromDUALwhereTOTOandbar"); + } + + @Test + public void testKeepAlphaLowerCase() + { + BDDAssertions.then(StringFilter.KEEP_ALPHA_LOWER_CASE.transform(SAMPLE_TEXT)).isEqualTo("fromwhereandbar"); + } + + @Test + public void testKeepAlphanum() + { + BDDAssertions.then(StringFilter.KEEP_ALPHANUM.transform(SAMPLE_TEXT)).isEqualTo("SELECTfromDUALwhereTOTO0023andbar3"); + } + + @Test + public void testKeepAlphanumLowerCase() + { + BDDAssertions.then(StringFilter.KEEP_ALPHANUM_LOWER_CASE.transform(SAMPLE_TEXT)).isEqualTo("fromwhere0023andbar3"); + } + + @Test + public void testKeepAlphanumUpperCase() + { + BDDAssertions.then(StringFilter.KEEP_ALPHANUM_UPPER_CASE.transform(SAMPLE_TEXT)).isEqualTo("SELECTDUALTOTO00233"); + } + + @Test + public void testKeepAlphaUpperCase() + { + BDDAssertions.then(StringFilter.KEEP_ALPHA_UPPER_CASE.transform(SAMPLE_TEXT)).isEqualTo("SELECTDUALTOTO"); + } + + @Test + public void testKeepDigit() + { + BDDAssertions.then(StringFilter.KEEP_DIGIT.transform(SAMPLE_TEXT)).isEqualTo("00233"); + } + + @Test + public void testKeepFilenameValidChars() + { + BDDAssertions.then(StringFilter.KEEP_FILENAME_VALID_CHARS.transform(SAMPLE_TEXT_FILES)).isEqualTo("rootfoo_bar--01.totocwinbar_foo--02.zip"); + } + + @Test + public void testKeepFilepathUnixValidChars() + { + BDDAssertions.then(StringFilter.KEEP_FILEPATH_UNIX_VALID_CHARS.transform(SAMPLE_TEXT_FILES)).isEqualTo("/root/foo_bar--01.totocwinbar_foo--02.zip"); + } + + @Test + public void testKeepFilepathWindowsValidChars() + { + BDDAssertions.then(StringFilter.KEEP_FILEPATH_WINDOWS_VALID_CHARS.transform(SAMPLE_TEXT_FILES)).isEqualTo("rootfoo_bar--01.totoc:\\win\\bar_foo--02.zip"); + } + +} diff --git a/sporniket-core-strings/src/test/java/test/sporniket/strings/pipeline/StringPipelineTest.java b/sporniket-core-strings/src/test/java/test/sporniket/strings/pipeline/StringPipelineTest.java new file mode 100644 index 0000000..cf5e3a7 --- /dev/null +++ b/sporniket-core-strings/src/test/java/test/sporniket/strings/pipeline/StringPipelineTest.java @@ -0,0 +1,54 @@ +package test.sporniket.strings.pipeline; + +import static com.sporniket.strings.pipeline.StringTransformation.NULL_TO_EMPTY; +import static com.sporniket.strings.pipeline.StringTransformation.TO_LOWERCASE; +import static com.sporniket.strings.pipeline.StringTransformation.TRIM; +import static java.util.Arrays.asList; +import static org.assertj.core.api.BDDAssertions.then; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; + +import com.sporniket.strings.pipeline.StringPipeline; +import com.sporniket.strings.pipeline.StringPipelineBuilder; + +public class StringPipelineTest +{ + @Test + public void cannotCreatePipelineWithEmptyTransformerList() + { + assertThrows(IllegalStateException.class, () -> new StringPipelineBuilder().done()); + } + + @Test + public void cannotCreatePipelineWithoutTransformerList() + { + assertThrows(IllegalStateException.class, () -> new StringPipeline(null)); + } + + @Test + public void cannotPipeThroughNull() + { + assertThrows(IllegalStateException.class, () -> new StringPipelineBuilder().pipeThrough(null)); + } + + @TestFactory + public Stream shouldTransformStringAsDesigned() + { + StringPipeline pipeline = new StringPipelineBuilder()// + .pipeThrough(NULL_TO_EMPTY)// + .pipeThrough(TO_LOWERCASE)// + .pipeThrough(TRIM).done(); + return asList(// + dynamicTest("null give empty string", () -> then(pipeline.transform(null)).isEqualTo(""))// + , dynamicTest("test on ' WhatEVER R you DOING ? '", + () -> then(pipeline.transform(" WhatEVER R you DOING ? ")).isEqualTo("whatever r you doing ?"))// + ).stream(); + } +} diff --git a/sporniket-core-strings/src/test/java/test/sporniket/strings/pipeline/StringTransformationTest.java b/sporniket-core-strings/src/test/java/test/sporniket/strings/pipeline/StringTransformationTest.java new file mode 100644 index 0000000..8ee7e46 --- /dev/null +++ b/sporniket-core-strings/src/test/java/test/sporniket/strings/pipeline/StringTransformationTest.java @@ -0,0 +1,134 @@ +/** + * + */ +package test.sporniket.strings.pipeline; + +import static com.sporniket.strings.pipeline.StringTransformation.NULL_TO_EMPTY; +import static com.sporniket.strings.pipeline.StringTransformation.TO_HASH_MD5; +import static com.sporniket.strings.pipeline.StringTransformation.TO_LOWERCASE; +import static com.sporniket.strings.pipeline.StringTransformation.TO_UPPERCASE; +import static com.sporniket.strings.pipeline.StringTransformation.TRIM; +import static com.sporniket.strings.pipeline.StringTransformation.TRIM_END; +import static com.sporniket.strings.pipeline.StringTransformation.TRIM_START; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static org.assertj.core.api.BDDAssertions.then; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +import com.sporniket.strings.pipeline.StringTransformation; + +/** + * Test built-in String transformations + * + * @author dsporn + * + */ +public class StringTransformationTest +{ + List WHITE_SPACE_COMBINATIONS = Arrays.asList(" ", "\t", " \t", "\t "); + + @TestFactory + public Stream testNullToEmpty() + { + return asList(// + dynamicTest("Should return an empty string when input is null", + () -> then(NULL_TO_EMPTY.transform(null)).isNotNull().isEqualTo(""))// + , dynamicTest("Should return the input string when it is not null", + () -> then(NULL_TO_EMPTY.transform("a")).isEqualTo("a"))// + ).stream(); + } + + @TestFactory + public Stream testToHashMd5() + { + return asList(// + dynamicTest("Should compute the MD5 hash of the input string", + () -> then(TO_HASH_MD5.transform("abcd")).isEqualTo("E2FC714C4727EE9395F324CD2E7F331F"))// + ).stream(); + } + + @TestFactory + public Stream testToLowercase() + { + return asList(// + dynamicTest("Should convert to upper case all the chars of the input string", + () -> then(TO_LOWERCASE.transform("WhAtEvEr")).isEqualTo("whatever"))// + ).stream(); + } + + @TestFactory + public Stream testToUppercase() + { + return asList(// + dynamicTest("Should convert to upper case all the chars of the input string", + () -> then(TO_UPPERCASE.transform("WhatEver")).isEqualTo("WHATEVER"))// + ).stream(); + } + + @TestFactory + public Stream testTrim() + { + final String _expected = "a"; + return WHITE_SPACE_COMBINATIONS.parallelStream()// + .map(t -> t + _expected)// + .flatMap(t -> WHITE_SPACE_COMBINATIONS.parallelStream().map(suffix -> t + suffix))// + .map(text -> dynamicTest(format("Should remove whitespaces at both ends of '%s'", text), + () -> then(TRIM.transform(text)).isEqualTo(_expected)))// + ; + } + + @TestFactory + public Stream testTrim_OnBlankStrings() + { + final String _expected = ""; + return WHITE_SPACE_COMBINATIONS.parallelStream()// + .map(text -> dynamicTest(format("Should remove whitespaces at both ends of '%s'", text), + () -> then(TRIM.transform(text)).isEqualTo(_expected)))// + ; + } + + @TestFactory + public Stream testTrimEnd() + { + final String _expected = "a"; + return WHITE_SPACE_COMBINATIONS.parallelStream()// + .map(t -> _expected + t)// + .map(text -> dynamicTest(format("Should remove whitespaces at the end of '%s'", text), + () -> then(TRIM_END.transform(text)).isEqualTo(_expected))); + } + + @TestFactory + public Stream testTrimEnd_OnBlankStrings() + { + final String _expected = ""; + return WHITE_SPACE_COMBINATIONS.parallelStream()// + .map(text -> dynamicTest(format("Should remove whitespaces at the end of '%s'", text), + () -> then(TRIM_END.transform(text)).isEqualTo(_expected))); + } + + @TestFactory + public Stream testTrimStart() + { + final String _expected = "a"; + return WHITE_SPACE_COMBINATIONS.parallelStream()// + .map(t -> t + _expected)// + .map(text -> dynamicTest(format("Should remove whitespaces at the start of '%s'", text), + () -> then(TRIM_START.transform(text)).isEqualTo(_expected))); + } + + @TestFactory + public Stream testTrimStart_OnBlankStrings() + { + final String _expected = ""; + return WHITE_SPACE_COMBINATIONS.parallelStream()// + .map(text -> dynamicTest(format("Should remove whitespaces at the start of '%s'", text), + () -> then(TRIM_START.transform(text)).isEqualTo(_expected))); + } +} diff --git a/sporniket-core-strings/src/test/resources/test/sporniket/strings/QuickDiffTest_data/specs.json b/sporniket-core-strings/src/test/resources/test/sporniket/strings/QuickDiffTest_data/specs.json new file mode 100644 index 0000000..c42301d --- /dev/null +++ b/sporniket-core-strings/src/test/resources/test/sporniket/strings/QuickDiffTest_data/specs.json @@ -0,0 +1,195 @@ +[ + { + "label": "Same content", + "ignoreEmptyLines": false, + "ignoreTrailingWhiteSpaces": false, + "left": [ + "line 1", + "line 2" + ], + "right": [ + "line 1", + "line 2" + ], + "reportLtr": [], + "reportRtl": [] + }, + { + "label": "Ignored empty lines give no differences", + "ignoreEmptyLines": true, + "ignoreTrailingWhiteSpaces": false, + "left": [ + "line 1", + "", + "line 2" + ], + "right": [ + "line 1", + "line 2" + ], + "reportLtr": [], + "reportRtl": [] + }, + { + "label": "One line changed", + "ignoreEmptyLines": false, + "ignoreTrailingWhiteSpaces": false, + "left": [ + "line 1", + "line 2a", + "line 3" + ], + "right": [ + "line 1", + "line 2b", + "line 3" + ], + "reportLtr": [ + "<<00001==line 2a", + "--00001>>line 2b", + "" + ], + "reportRtl": [ + "<<00001==line 2b", + "--00001>>line 2a", + "" + ] + }, + { + "label": "Multiple changes", + "ignoreEmptyLines": false, + "ignoreTrailingWhiteSpaces": false, + "left": [ + "line 1", + "line 2aa", + "line 2ab", + "line 2ac", + "line 2ba" + ], + "right": [ + "line 1", + "line 2ba", + "line 2bb", + "line 2aa" + ], + "reportLtr": [ + "<<00001==line 2aa", + "--00001>>line 2ba", + "--00002>>line 2bb", + "--00003>>line 2aa", + "", + "<<00002==line 2ab", + "<<00003==line 2ac", + "<<00004==line 2ba" + ], + "reportRtl": [ + "<<00001==line 2ba", + "<<00002==line 2bb", + "<<00003==line 2aa", + "--00001>>line 2aa", + "", + "--00002>>line 2ab", + "--00003>>line 2ac", + "--00004>>line 2ba" + ] + }, + { + "label": "Empty line added with empty lines not ignored", + "ignoreEmptyLines": false, + "ignoreTrailingWhiteSpaces": false, + "left": [ + "line 1", + "", + "line 2" + ], + "right": [ + "line 1", + "line 2" + ], + "reportLtr": [ + "<<00001==", + "<<00002==line 2", + "--00001>>line 2", + "" + ], + "reportRtl": [ + "<<00001==line 2", + "--00001>>", + "--00002>>line 2", + "" + ] + }, + { + "label": "One line added inside", + "ignoreEmptyLines": false, + "ignoreTrailingWhiteSpaces": false, + "left": [ + "line 1", + "line 2", + "line 3" + ], + "right": [ + "line 1", + "line 3" + ], + "reportLtr": [ + "<<00001==line 2", + "<<00002==line 3", + "--00001>>line 3", + "" + ], + "reportRtl": [ + "<<00001==line 3", + "--00001>>line 2", + "--00002>>line 3", + "" + ] + }, + { + "label": "One line appended", + "ignoreEmptyLines": false, + "ignoreTrailingWhiteSpaces": false, + "left": [ + "line 1", + "line 2", + "line 3" + ], + "right": [ + "line 1", + "line 2" + ], + "reportLtr": [ + "<<00002==line 3" + ], + "reportRtl": [ + "--00002>>line 3" + ] + }, + { + "label": "No differences when ignoring different trailing whitespaces", + "ignoreEmptyLines": false, + "ignoreTrailingWhiteSpaces": true, + "left": [ + "line 1 ", + " line 2", + "line 3\t", + "\tline 4", + "line 5 ", + " line 6", + "line 7\t", + "\tline 8" + ], + "right": [ + "line 1", + " line 2", + " line 3", + "line 4\t", + "\tline 5", + "\tline 6 ", + " line 7\t", + "\tline 8\t" + ], + "reportLtr": [], + "reportRtl": [] + } +] \ No newline at end of file