Skip to content

Commit

Permalink
Fixed bug from issue #2: Remove escape char in strings with escaped s…
Browse files Browse the repository at this point in the history
…pecial characters
  • Loading branch information
terzerm committed May 4, 2017
1 parent c65ccd4 commit 4363987
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 52 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apply plugin: 'license'

sourceCompatibility = 1.8
group = "org.tools4j"
version = '1.3'
version = '1.4-SNAPSHOT'
archivesBaseName = "tools4j-spockito"

jar {
Expand Down
38 changes: 12 additions & 26 deletions src/main/java/org/tools4j/spockito/Converters.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,12 @@
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.regex.Pattern;

/**
* Contains conversion functions and value converters used by {@link SpockitoValueConverter}.
*/
public final class Converters {

private static final Pattern UNESCAPED_COMMA = Pattern.compile("(?<=[^\\\\]),");
private static final Pattern UNESCAPED_SEMICOLON = Pattern.compile("(?<=[^\\\\]);");
private static final Pattern UNESCAPED_COLON = Pattern.compile("(?<=[^\\\\]):");
private static final Pattern UNESCAPED_EQUAL = Pattern.compile("(?<=[^\\\\])=");

public static final Function<? super String, Object> OBJECT_CONVERTER = Function.identity();
public static final Function<? super String, Long> LONG_CONVERTER = Long::valueOf;
public static final Function<? super String, Integer> INTEGER_CONVERTER = Integer::valueOf;
Expand All @@ -65,7 +59,7 @@ public final class Converters {
public static final Function<? super String, java.sql.Date> SQL_DATE_CONVERTER = java.sql.Date::valueOf;
public static final Function<? super String, Time> SQL_TIME_CONVERTER = Time::valueOf;
public static final Function<? super String, Timestamp> SQL_TIMESTAMP_CONVERTER = Timestamp::valueOf;
public static final Function<? super String, String> STRING_CONVERTER = s -> removeStartAndEndChars(s, '\'', '\'');
public static final Function<? super String, String> STRING_CONVERTER = s -> Strings.removeStartAndEndChars(s, '\'', '\'');
public static final Function<? super String, StringBuilder> STRING_BUILDER_CONVERTER = s -> new StringBuilder(STRING_CONVERTER.apply(s));
public static final Function<? super String, StringBuffer> STRING_BUFFER_CONVERTER = s -> new StringBuffer(STRING_CONVERTER.apply(s));
public static final Function<? super String, Character> CHAR_CONVERTER = s -> {
Expand Down Expand Up @@ -182,10 +176,10 @@ public <T> T convert(final Class<T> type, final Type genericType, final String v
}

private List<Object> toList(final ActualType elementType, final String value) {
final String plainValue = removeStartAndEndChars(value, '[', ']');
String[] parts = UNESCAPED_COMMA.split(plainValue);
final String plainValue = Strings.removeStartAndEndChars(value, '[', ']');
String[] parts = Strings.UNESCAPED_COMMA.split(plainValue);
if (parts.length == 1) {
parts = UNESCAPED_SEMICOLON.split(plainValue);
parts = Strings.UNESCAPED_SEMICOLON.split(plainValue);
}
final List<Object> list = new ArrayList<>(parts.length);
for (int i = 0; i < parts.length; i++) {
Expand Down Expand Up @@ -284,10 +278,10 @@ public <T> T convert(final Class<T> type, final Type genericType, final String v
}

private Map<Object, Object> toMap(final ActualType keyType, final ActualType valueType, final String value) {
final String plainValue = removeStartAndEndChars(value, '{', '}');
String[] parts = UNESCAPED_COMMA.split(plainValue);
final String plainValue = Strings.removeStartAndEndChars(value, '{', '}');
String[] parts = Strings.UNESCAPED_COMMA.split(plainValue);
if (parts.length == 1) {
parts = UNESCAPED_SEMICOLON.split(plainValue);
parts = Strings.UNESCAPED_SEMICOLON.split(plainValue);
}
final Map<Object, Object> map = new LinkedHashMap<>();
for (int i = 0; i < parts.length; i++) {
Expand Down Expand Up @@ -351,10 +345,10 @@ private <T> T newInstance(final Class<T> type, final String value) {
}

private void injectValues(final Object instance, final Map<String, Accessor> accessorByName, final String value) {
final String plainValue = removeStartAndEndChars(value, '{', '}');
String[] parts = UNESCAPED_COMMA.split(plainValue);
final String plainValue = Strings.removeStartAndEndChars(value, '{', '}');
String[] parts = Strings.UNESCAPED_COMMA.split(plainValue);
if (parts.length == 1) {
parts = UNESCAPED_SEMICOLON.split(plainValue);
parts = Strings.UNESCAPED_SEMICOLON.split(plainValue);
}
final Map<String, String> valueByName = new LinkedHashMap<>();
for (int i = 0; i < parts.length; i++) {
Expand Down Expand Up @@ -463,9 +457,9 @@ private static String normalizeFieldName(final String name) {
}

private static final String[] parseKeyValue(final String pair) {
final String[] split = UNESCAPED_EQUAL.split(pair);
final String[] split = Strings.UNESCAPED_EQUAL.split(pair);
if (split.length == 1) {
return UNESCAPED_COLON.split(pair);
return Strings.UNESCAPED_COLON.split(pair);
}
return split;
}
Expand Down Expand Up @@ -527,14 +521,6 @@ public String toString() {
};
}

private static String removeStartAndEndChars(final String s, final char startQuoteChar, final char endQuoteChar) {
final int len = s.length();
if (len >= 2 && s.charAt(0) == startQuoteChar && s.charAt(len - 1) == endQuoteChar) {
return s.substring(1, len - 1);
}
return s;
}

private Converters() {
throw new RuntimeException("No Converters for you!");
}
Expand Down
98 changes: 98 additions & 0 deletions src/main/java/org/tools4j/spockito/Strings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2017 tools4j.org (Marco Terzer)
*
* 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 org.tools4j.spockito;

import java.util.regex.Pattern;

/**
* Contains static utility methods dealing with strings.
*/
final class Strings {

static final Pattern UNESCAPED_COMMA = Pattern.compile("(?<=[^\\\\]),");
static final Pattern UNESCAPED_SEMICOLON = Pattern.compile("(?<=[^\\\\]);");
static final Pattern UNESCAPED_COLON = Pattern.compile("(?<=[^\\\\]):");
static final Pattern UNESCAPED_EQUAL = Pattern.compile("(?<=[^\\\\])=");

static String firstCharToUpperCase(final String value) {
return value.length() > 0 ? Character.toUpperCase(value.charAt(0)) + value.substring(1) : value;
}

static String removeSurroundingPipes(final String s) {
return removeStartAndEndChars(s, '|', '|');
}

static String removeStartAndEndChars(final String s, final char startQuoteChar, final char endQuoteChar) {
final int len = s.length();
if (len >= 2 && s.charAt(0) == startQuoteChar && s.charAt(len - 1) == endQuoteChar) {
return s.substring(1, len - 1);
}
return s;
}

static boolean allCharsMatchingAnyOf(final String s, final char ch1, final char ch2) {
final int len = s.length();
for (int i = 0; i < len; i++) {
if (s.charAt(i) != ch1 && s.charAt(i) != ch2) {
return false;
}
}
return true;
}

static String unescape(final String s) {
return unescape(s, ',', ';', '|', '=', '\\','\'');
}

static String unescape(final String s, final char... escapedChars) {
int index = s.indexOf('\\');
if (index < 0) {
return s;
}
final StringBuilder sb = new StringBuilder(s);
while (index >= 0 && index + 1 < sb.length()) {
final char ch = sb.charAt(index + 1);
if (isCharAnyOf(ch, escapedChars)) {
sb.delete(index, index + 1);
index = sb.indexOf("\\", index + 1);
} else {
index = sb.indexOf("\\", index + 2);
}
}
return s.length() == sb.length() ? s : sb.toString();
}

static boolean isCharAnyOf(final char ch, final char... chars) {
for (final char c : chars) {
if (ch == c) {
return true;
}
}
return false;
}

private Strings() {
throw new RuntimeException("No Strings for you!");
}
}
5 changes: 1 addition & 4 deletions src/main/java/org/tools4j/spockito/Table.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public int getColumnIndexByName(final String columnName) {
int columnIndex = headers.indexOf(columnName);
if (columnIndex < 0) {
if (columnName.length() > 0 && Character.isLowerCase(columnName.charAt(0))) {
columnIndex = headers.indexOf(firstCharToUpperCase(columnName));
columnIndex = headers.indexOf(Strings.firstCharToUpperCase(columnName));
}
}
if (columnIndex < 0) {
Expand Down Expand Up @@ -130,7 +130,4 @@ private static TableRow parseRow(final Table table, final int row, final String
return tableRow;
}

private final String firstCharToUpperCase(final String value) {
return value.length() > 0 ? Character.toUpperCase(value.charAt(0)) + value.substring(1) : value;
}
}
24 changes: 3 additions & 21 deletions src/main/java/org/tools4j/spockito/TableRow.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ public static TableRow empty(final Table table) {
}

public static TableRow parse(final Table table, final String rowString) {
final String noBars = removeSurroundingPipes(rowString);
final String noBars = Strings.removeSurroundingPipes(rowString);
final String[] parts = UNESCAPED_PIPE.split(noBars);
final TableRow tableRow = new TableRow(table);
for (final String part : parts) {
tableRow.values.add(Converters.STRING_CONVERTER.apply(part.trim()));
tableRow.values.add(Converters.STRING_CONVERTER.apply(Strings.unescape(part.trim())));
}
for (int i = parts.length; i < table.getColumnCount(); i++) {
tableRow.values.add(null);
Expand All @@ -72,7 +72,7 @@ public Table getTable() {

public boolean isSeparatorRow() {
return values.stream().anyMatch(s -> s.contains("-") || s.contains("=")) &&
values.stream().allMatch(s -> allCharsMatchingAnyOf(s, '-', '='));
values.stream().allMatch(s -> Strings.allCharsMatchingAnyOf(s, '-', '='));
}

public boolean isValidRefName(final String refName) {
Expand Down Expand Up @@ -159,22 +159,4 @@ public Map<String, String> asMap() {
public String toString() {
return "TableRow" + values;
}

private static String removeSurroundingPipes(final String s) {
final int len = s.length();
if (len >= 2 && s.charAt(0) == '|' && s.charAt(len - 1) == '|') {
return s.substring(1, len - 1);
}
return s;
}

private static boolean allCharsMatchingAnyOf(final String s, final char ch1, final char ch2) {
final int len = s.length();
for (int i = 0; i < len; i++) {
if (s.charAt(i) != ch1 && s.charAt(i) != ch2) {
return false;
}
}
return true;
}
}
1 change: 1 addition & 0 deletions src/test/java/org/tools4j/spockito/FaqTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public void testSpecialStrings(String input) {
"|123%7c321|123|321|"
})
public void testWithPipe(String input, BigInteger locationId, BigInteger eventId) {
Assert.assertTrue(!input.contains("\\"));
Assert.assertTrue(input.contains("|") || input.startsWith("123") && input.endsWith("321"));
}

Expand Down
55 changes: 55 additions & 0 deletions src/test/java/org/tools4j/spockito/UnescapeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2017 tools4j.org (Marco Terzer)
*
* 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 org.tools4j.spockito;

import org.junit.Assert;
import org.junit.Test;

/**
* Unit test for {@link Strings#unescape(String)}
*/
public class UnescapeTest {

@Test
public void shoudUnescape() {
Assert.assertEquals(",", Strings.unescape("\\,"));
Assert.assertEquals(";", Strings.unescape("\\;"));
Assert.assertEquals("|", Strings.unescape("\\|"));
Assert.assertEquals("=", Strings.unescape("\\="));
Assert.assertEquals("\\", Strings.unescape("\\\\"));
Assert.assertEquals("\'", Strings.unescape("\\'"));
Assert.assertEquals("123|456", Strings.unescape("123\\|456"));
}

@Test
public void shoudNotUnescape() {
Assert.assertEquals("\\blabla", Strings.unescape("\\blabla"));
Assert.assertEquals("\\123", Strings.unescape("\\123"));
}

@Test
public void shoudUnescapeMultiple() {
Assert.assertEquals("\\,123|456;\\a\\;\\8bla'\\'xxx", Strings.unescape("\\\\,123\\|456\\;\\a\\\\\\;\\8bla\\'\\\\'xxx"));
}
}

0 comments on commit 4363987

Please sign in to comment.