diff --git a/common/src/main/java/io/netty/util/DomainMappingBuilder.java b/common/src/main/java/io/netty/util/DomainMappingBuilder.java
new file mode 100644
index 0000000000..0cdb9712b7
--- /dev/null
+++ b/common/src/main/java/io/netty/util/DomainMappingBuilder.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2015 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package io.netty.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static io.netty.util.internal.ObjectUtil.checkNotNull;
+
+/**
+ * Builder for immutable {@link DomainNameMapping} instances.
+ *
+ * @param concrete type of value objects
+ */
+public final class DomainMappingBuilder {
+
+ private final V defaultValue;
+ private final Map map;
+
+ /**
+ * Constructor with default initial capacity of the map holding the mappings
+ *
+ * @param defaultValue the default value for {@link DomainNameMapping#map(String)} to return
+ * when nothing matches the input
+ */
+ public DomainMappingBuilder(V defaultValue) {
+ this(4, defaultValue);
+ }
+
+ /**
+ * Constructor with initial capacity of the map holding the mappings
+ *
+ * @param initialCapacity initial capacity for the internal map
+ * @param defaultValue the default value for {@link DomainNameMapping#map(String)} to return
+ * when nothing matches the input
+ */
+ public DomainMappingBuilder(int initialCapacity, V defaultValue) {
+ this.defaultValue = checkNotNull(defaultValue, "defaultValue");
+ this.map = new LinkedHashMap(initialCapacity);
+ }
+
+ /**
+ * Adds a mapping that maps the specified (optionally wildcard) host name to the specified output value.
+ * Null values are forbidden for both hostnames and values.
+ *
+ * DNS wildcard is supported as hostname.
+ * For example, you can use {@code *.netty.io} to match {@code netty.io} and {@code downloads.netty.io}.
+ *
+ *
+ * @param hostname the host name (optionally wildcard)
+ * @param output the output value that will be returned by {@link DomainNameMapping#map(String)}
+ * when the specified host name matches the specified input host name
+ */
+ public DomainMappingBuilder add(String hostname, V output) {
+ map.put(checkNotNull(hostname, "hostname"), checkNotNull(output, "output"));
+ return this;
+ }
+
+ /**
+ * Creates a new instance of immutable {@link DomainNameMapping}
+ * Attempts to add new mappings to the result object will cause {@link UnsupportedOperationException} to be thrown
+ *
+ * @return new {@link DomainNameMapping} instance
+ */
+ public DomainNameMapping build() {
+ return new ImmutableDomainNameMapping(this.defaultValue, this.map);
+ }
+
+ /**
+ * Immutable mapping from domain name pattern to its associated value object.
+ * Mapping is represented by two arrays: keys and values. Key domainNamePatterns[i] is associated with values[i].
+ *
+ * @param concrete type of value objects
+ */
+ private static final class ImmutableDomainNameMapping extends DomainNameMapping {
+ private static final String REPR_HEADER = "ImmutableDomainNameMapping(default: ";
+ private static final String REPR_MAP_OPENING = ", map: {";
+ private static final String REPR_MAP_CLOSING = "})";
+ private static final int REPR_CONST_PART_LENGTH =
+ REPR_HEADER.length() + REPR_MAP_OPENING.length() + REPR_MAP_CLOSING.length();
+
+ private final String[] domainNamePatterns;
+ private final V[] values;
+
+ @SuppressWarnings("unchecked")
+ private ImmutableDomainNameMapping(V defaultValue, Map map) {
+ super(null, defaultValue);
+
+ Set> mappings = map.entrySet();
+ int numberOfMappings = mappings.size();
+ domainNamePatterns = new String[numberOfMappings];
+ values = (V[]) new Object[numberOfMappings];
+
+ int index = 0;
+ for (Map.Entry mapping : mappings) {
+ domainNamePatterns[index] = normalizeHostname(mapping.getKey());
+ values[index] = mapping.getValue();
+ ++index;
+ }
+ }
+
+ @Override
+ @Deprecated
+ public DomainNameMapping add(String hostname, V output) {
+ throw new UnsupportedOperationException(
+ "Immutable DomainNameMapping does not support modification after initial creation");
+ }
+
+ @Override
+ public V map(String hostname) {
+ if (hostname != null) {
+ hostname = normalizeHostname(hostname);
+
+ int length = domainNamePatterns.length;
+ for (int index = 0; index < length; ++index) {
+ if (matches(domainNamePatterns[index], hostname)) {
+ return values[index];
+ }
+ }
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public String toString() {
+ String defaultValueStr = defaultValue.toString();
+
+ int numberOfMappings = domainNamePatterns.length;
+ if (numberOfMappings == 0) {
+ return REPR_HEADER + defaultValueStr + REPR_MAP_OPENING + REPR_MAP_CLOSING;
+ }
+
+ String pattern0 = domainNamePatterns[0];
+ String value0 = values[0].toString();
+ int oneMappingLength = pattern0.length() + value0.length() + 3; // 2 for separator ", " and 1 for '='
+ int estimatedBufferSize = estimateBufferSize(defaultValueStr.length(), numberOfMappings, oneMappingLength);
+
+ StringBuilder sb = new StringBuilder(estimatedBufferSize)
+ .append(REPR_HEADER).append(defaultValueStr).append(REPR_MAP_OPENING);
+
+ appendMapping(sb, pattern0, value0);
+ for (int index = 1; index < numberOfMappings; ++index) {
+ sb.append(", ");
+ appendMapping(sb, index);
+ }
+
+ return sb.append(REPR_MAP_CLOSING).toString();
+ }
+
+ /**
+ * Estimates the length of string representation of the given instance:
+ * est = lengthOfConstantComponents + defaultValueLength + (estimatedMappingLength * numOfMappings) * 1.10
+ *
+ * @param defaultValueLength length of string representation of {@link #defaultValue}
+ * @param numberOfMappings number of mappings the given instance holds,
+ * e.g. {@link #domainNamePatterns#length}
+ * @param estimatedMappingLength estimated size taken by one mapping
+ * @return estimated length of string returned by {@link #toString()}
+ */
+ private static int estimateBufferSize(int defaultValueLength,
+ int numberOfMappings,
+ int estimatedMappingLength) {
+ return REPR_CONST_PART_LENGTH + defaultValueLength
+ + (int) (estimatedMappingLength * numberOfMappings * 1.10);
+ }
+
+ private StringBuilder appendMapping(StringBuilder sb, int mappingIndex) {
+ return appendMapping(sb, domainNamePatterns[mappingIndex], values[mappingIndex].toString());
+ }
+
+ private static StringBuilder appendMapping(StringBuilder sb, String domainNamePattern, String value) {
+ return sb.append(domainNamePattern).append('=').append(value);
+ }
+ }
+}
diff --git a/common/src/main/java/io/netty/util/DomainNameMapping.java b/common/src/main/java/io/netty/util/DomainNameMapping.java
index b3cf039367..ff9aeb9cb0 100644
--- a/common/src/main/java/io/netty/util/DomainNameMapping.java
+++ b/common/src/main/java/io/netty/util/DomainNameMapping.java
@@ -22,7 +22,9 @@ import java.net.IDN;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
-import java.util.regex.Pattern;
+
+import static io.netty.util.internal.ObjectUtil.checkNotNull;
+import static io.netty.util.internal.StringUtil.commonSuffixOfLength;
/**
* Maps a domain name to its associated value object.
@@ -33,18 +35,17 @@ import java.util.regex.Pattern;
*/
public class DomainNameMapping implements Mapping {
- private static final Pattern DNS_WILDCARD_PATTERN = Pattern.compile("^\\*\\..*");
-
+ final V defaultValue;
private final Map map;
- private final V defaultValue;
-
/**
* Creates a default, order-sensitive mapping. If your hostnames are in conflict, the mapping
* will choose the one you add first.
*
* @param defaultValue the default value for {@link #map(String)} to return when nothing matches the input
+ * @deprecated use {@link DomainMappingBuilder} to create and fill the mapping instead
*/
+ @Deprecated
public DomainNameMapping(V defaultValue) {
this(4, defaultValue);
}
@@ -54,14 +55,17 @@ public class DomainNameMapping implements Mapping {
* will choose the one you add first.
*
* @param initialCapacity initial capacity for the internal map
- * @param defaultValue the default value for {@link #map(String)} to return when nothing matches the input
+ * @param defaultValue the default value for {@link #map(String)} to return when nothing matches the input
+ * @deprecated use {@link DomainMappingBuilder} to create and fill the mapping instead
*/
+ @Deprecated
public DomainNameMapping(int initialCapacity, V defaultValue) {
- if (defaultValue == null) {
- throw new NullPointerException("defaultValue");
- }
- map = new LinkedHashMap(initialCapacity);
- this.defaultValue = defaultValue;
+ this(new LinkedHashMap(initialCapacity), defaultValue);
+ }
+
+ DomainNameMapping(Map map, V defaultValue) {
+ this.defaultValue = checkNotNull(defaultValue, "defaultValue");
+ this.map = map;
}
/**
@@ -72,39 +76,31 @@ public class DomainNameMapping implements Mapping {
*
*
* @param hostname the host name (optionally wildcard)
- * @param output the output value that will be returned by {@link #map(String)} when the specified host name
- * matches the specified input host name
+ * @param output the output value that will be returned by {@link #map(String)} when the specified host name
+ * matches the specified input host name
+ * @deprecated use {@link DomainMappingBuilder} to create and fill the mapping instead
*/
+ @Deprecated
public DomainNameMapping add(String hostname, V output) {
- if (hostname == null) {
- throw new NullPointerException("input");
- }
-
- if (output == null) {
- throw new NullPointerException("output");
- }
-
- map.put(normalizeHostname(hostname), output);
+ map.put(normalizeHostname(checkNotNull(hostname, "hostname")), checkNotNull(output, "output"));
return this;
}
/**
* Simple function to match DNS wildcard.
*/
- private static boolean matches(String hostNameTemplate, String hostName) {
- // note that inputs are converted and lowercased already
- if (DNS_WILDCARD_PATTERN.matcher(hostNameTemplate).matches()) {
- return hostNameTemplate.substring(2).equals(hostName) ||
- hostName.endsWith(hostNameTemplate.substring(1));
- } else {
- return hostNameTemplate.equals(hostName);
+ static boolean matches(String template, String hostName) {
+ if (template.startsWith("*.")) {
+ return template.regionMatches(2, hostName, 0, hostName.length())
+ || commonSuffixOfLength(hostName, template, template.length() - 1);
}
+ return template.equals(hostName);
}
/**
* IDNA ASCII conversion and case normalization
*/
- private static String normalizeHostname(String hostname) {
+ static String normalizeHostname(String hostname) {
if (needsNormalization(hostname)) {
hostname = IDN.toASCII(hostname, IDN.ALLOW_UNASSIGNED);
}
@@ -113,7 +109,7 @@ public class DomainNameMapping implements Mapping {
private static boolean needsNormalization(String hostname) {
final int length = hostname.length();
- for (int i = 0; i < length; i ++) {
+ for (int i = 0; i < length; i++) {
int c = hostname.charAt(i);
if (c > 0x7F) {
return true;
@@ -123,17 +119,16 @@ public class DomainNameMapping implements Mapping {
}
@Override
- public V map(String input) {
- if (input != null) {
- input = normalizeHostname(input);
+ public V map(String hostname) {
+ if (hostname != null) {
+ hostname = normalizeHostname(hostname);
for (Map.Entry entry : map.entrySet()) {
- if (matches(entry.getKey(), input)) {
+ if (matches(entry.getKey(), hostname)) {
return entry.getValue();
}
}
}
-
return defaultValue;
}
diff --git a/common/src/main/java/io/netty/util/internal/StringUtil.java b/common/src/main/java/io/netty/util/internal/StringUtil.java
index 6764caeddb..eb1adc6854 100644
--- a/common/src/main/java/io/netty/util/internal/StringUtil.java
+++ b/common/src/main/java/io/netty/util/internal/StringUtil.java
@@ -183,6 +183,18 @@ public final class StringUtil {
return null;
}
+ /**
+ * Checks if two strings have the same suffix of specified length
+ *
+ * @param s string
+ * @param p string
+ * @param len length of the common suffix
+ * @return true if both s and p are not null and both have the same suffix. Otherwise - false
+ */
+ public static boolean commonSuffixOfLength(String s, String p, int len) {
+ return s != null && p != null && len >= 0 && s.regionMatches(s.length() - len, p, p.length() - len, len);
+ }
+
/**
* Converts the specified byte value into a 2-digit hexadecimal integer.
*/
diff --git a/common/src/test/java/io/netty/util/DomainNameMappingTest.java b/common/src/test/java/io/netty/util/DomainNameMappingTest.java
new file mode 100644
index 0000000000..bd369e9301
--- /dev/null
+++ b/common/src/test/java/io/netty/util/DomainNameMappingTest.java
@@ -0,0 +1,184 @@
+/*
+* Copyright 2015 The Netty Project
+*
+* The Netty Project licenses this file to you under the Apache License,
+* version 2.0 (the "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at:
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+* License for the specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.netty.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+@SuppressWarnings("deprecation")
+public class DomainNameMappingTest {
+
+ // Deprecated API
+
+ @Test(expected = NullPointerException.class)
+ public void testNullDefaultValueInDeprecatedApi() {
+ new DomainNameMapping(null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testNullDomainNamePatternsAreForbiddenInDeprecatedApi() {
+ new DomainNameMapping("NotFound").add(null, "Some value");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testNullValuesAreForbiddenInDeprecatedApi() {
+ new DomainNameMapping("NotFound").add("Some key", null);
+ }
+
+ @Test
+ public void testDefaultValueInDeprecatedApi() {
+ DomainNameMapping mapping = new DomainNameMapping("NotFound");
+
+ assertEquals("NotFound", mapping.map("not-existing"));
+
+ mapping.add("*.netty.io", "Netty");
+
+ assertEquals("NotFound", mapping.map("not-existing"));
+ }
+
+ @Test
+ public void testStrictEqualityInDeprecatedApi() {
+ DomainNameMapping mapping = new DomainNameMapping("NotFound")
+ .add("netty.io", "Netty")
+ .add("downloads.netty.io", "Netty-Downloads");
+
+ assertEquals("Netty", mapping.map("netty.io"));
+ assertEquals("Netty-Downloads", mapping.map("downloads.netty.io"));
+
+ assertEquals("NotFound", mapping.map("x.y.z.netty.io"));
+ }
+
+ @Test
+ public void testWildcardMatchesAnyPrefixInDeprecatedApi() {
+ DomainNameMapping mapping = new DomainNameMapping("NotFound")
+ .add("*.netty.io", "Netty");
+
+ assertEquals("Netty", mapping.map("netty.io"));
+ assertEquals("Netty", mapping.map("downloads.netty.io"));
+ assertEquals("Netty", mapping.map("x.y.z.netty.io"));
+
+ assertEquals("NotFound", mapping.map("netty.io.x"));
+ }
+
+ @Test
+ public void testFirstMatchWinsInDeprecatedApi() {
+ assertEquals("Netty",
+ new DomainNameMapping("NotFound")
+ .add("*.netty.io", "Netty")
+ .add("downloads.netty.io", "Netty-Downloads")
+ .map("downloads.netty.io"));
+
+ assertEquals("Netty-Downloads",
+ new DomainNameMapping("NotFound")
+ .add("downloads.netty.io", "Netty-Downloads")
+ .add("*.netty.io", "Netty")
+ .map("downloads.netty.io"));
+ }
+
+ @Test
+ public void testToStringInDeprecatedApi() {
+ DomainNameMapping mapping = new DomainNameMapping("NotFound")
+ .add("*.netty.io", "Netty")
+ .add("downloads.netty.io", "Netty-Downloads");
+
+ assertEquals(
+ "DomainNameMapping(default: NotFound, map: {*.netty.io=Netty, downloads.netty.io=Netty-Downloads})",
+ mapping.toString());
+ }
+
+ // Immutable DomainNameMapping Builder API
+
+ @Test(expected = NullPointerException.class)
+ public void testNullDefaultValue() {
+ new DomainMappingBuilder(null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testNullDomainNamePatternsAreForbidden() {
+ new DomainMappingBuilder("NotFound").add(null, "Some value");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testNullValuesAreForbidden() {
+ new DomainMappingBuilder("NotFound").add("Some key", null);
+ }
+
+ @Test
+ public void testDefaultValue() {
+ DomainNameMapping mapping = new DomainMappingBuilder("NotFound")
+ .add("*.netty.io", "Netty")
+ .build();
+
+ assertEquals("NotFound", mapping.map("not-existing"));
+ }
+
+ @Test
+ public void testStrictEquality() {
+ DomainNameMapping mapping = new DomainMappingBuilder("NotFound")
+ .add("netty.io", "Netty")
+ .add("downloads.netty.io", "Netty-Downloads")
+ .build();
+
+ assertEquals("Netty", mapping.map("netty.io"));
+ assertEquals("Netty-Downloads", mapping.map("downloads.netty.io"));
+
+ assertEquals("NotFound", mapping.map("x.y.z.netty.io"));
+ }
+
+ @Test
+ public void testWildcardMatchesAnyPrefix() {
+ DomainNameMapping mapping = new DomainMappingBuilder("NotFound")
+ .add("*.netty.io", "Netty")
+ .build();
+
+ assertEquals("Netty", mapping.map("netty.io"));
+ assertEquals("Netty", mapping.map("downloads.netty.io"));
+ assertEquals("Netty", mapping.map("x.y.z.netty.io"));
+
+ assertEquals("NotFound", mapping.map("netty.io.x"));
+ }
+
+ @Test
+ public void testFirstMatchWins() {
+ assertEquals("Netty",
+ new DomainMappingBuilder("NotFound")
+ .add("*.netty.io", "Netty")
+ .add("downloads.netty.io", "Netty-Downloads")
+ .build()
+ .map("downloads.netty.io"));
+
+ assertEquals("Netty-Downloads",
+ new DomainMappingBuilder("NotFound")
+ .add("downloads.netty.io", "Netty-Downloads")
+ .add("*.netty.io", "Netty")
+ .build()
+ .map("downloads.netty.io"));
+ }
+
+ @Test
+ public void testToString() {
+ DomainNameMapping mapping = new DomainMappingBuilder("NotFound")
+ .add("*.netty.io", "Netty")
+ .add("downloads.netty.io", "Netty-Download")
+ .build();
+
+ assertEquals(
+ "ImmutableDomainNameMapping(default: NotFound, map: {*.netty.io=Netty, downloads.netty.io=Netty-Download})",
+ mapping.toString());
+ }
+}
diff --git a/common/src/test/java/io/netty/util/internal/StringUtilTest.java b/common/src/test/java/io/netty/util/internal/StringUtilTest.java
index 7e15652ac8..66229dacb1 100644
--- a/common/src/test/java/io/netty/util/internal/StringUtilTest.java
+++ b/common/src/test/java/io/netty/util/internal/StringUtilTest.java
@@ -82,6 +82,245 @@ public class StringUtilTest {
assertEquals("bar:bar2", substringAfter("foo:bar:bar2", ':'));
}
+ @Test
+ public void commonSuffixOfLengthTest() {
+ // negative length suffixes are never common
+ checkNotCommonSuffix("abc", "abc", -1);
+
+ // null has no suffix
+ checkNotCommonSuffix("abc", null, 0);
+ checkNotCommonSuffix(null, null, 0);
+
+ // any non-null string has 0-length suffix
+ checkCommonSuffix("abc", "xx", 0);
+
+ checkCommonSuffix("abc", "abc", 0);
+ checkCommonSuffix("abc", "abc", 1);
+ checkCommonSuffix("abc", "abc", 2);
+ checkCommonSuffix("abc", "abc", 3);
+ checkNotCommonSuffix("abc", "abc", 4);
+
+ checkCommonSuffix("abcd", "cd", 1);
+ checkCommonSuffix("abcd", "cd", 2);
+ checkNotCommonSuffix("abcd", "cd", 3);
+
+ checkCommonSuffix("abcd", "axcd", 1);
+ checkCommonSuffix("abcd", "axcd", 2);
+ checkNotCommonSuffix("abcd", "axcd", 3);
+
+ checkNotCommonSuffix("abcx", "abcy", 1);
+ }
+
+ private static void checkNotCommonSuffix(String s, String p, int len) {
+ assertFalse(checkCommonSuffixSymmetric(s, p, len));
+ }
+
+ private static void checkCommonSuffix(String s, String p, int len) {
+ assertTrue(checkCommonSuffixSymmetric(s, p, len));
+ }
+
+ private static boolean checkCommonSuffixSymmetric(String s, String p, int len) {
+ boolean sp = commonSuffixOfLength(s, p, len);
+ boolean ps = commonSuffixOfLength(p, s, len);
+ assertEquals(sp, ps);
+ return sp;
+ }
+
+ @Test (expected = NullPointerException.class)
+ public void escapeCsvNull() {
+ StringUtil.escapeCsv(null);
+ }
+
+ @Test
+ public void escapeCsvEmpty() {
+ CharSequence value = "";
+ CharSequence expected = value;
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvUnquoted() {
+ CharSequence value = "something";
+ CharSequence expected = value;
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvAlreadyQuoted() {
+ CharSequence value = "\"something\"";
+ CharSequence expected = "\"something\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithQuote() {
+ CharSequence value = "s\"";
+ CharSequence expected = "\"s\"\"\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithQuoteInMiddle() {
+ CharSequence value = "some text\"and more text";
+ CharSequence expected = "\"some text\"\"and more text\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithQuoteInMiddleAlreadyQuoted() {
+ CharSequence value = "\"some text\"and more text\"";
+ CharSequence expected = "\"some text\"\"and more text\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithQuotedWords() {
+ CharSequence value = "\"foo\"\"goo\"";
+ CharSequence expected = "\"foo\"\"goo\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithAlreadyEscapedQuote() {
+ CharSequence value = "foo\"\"goo";
+ CharSequence expected = "foo\"\"goo";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvEndingWithQuote() {
+ CharSequence value = "some\"";
+ CharSequence expected = "\"some\"\"\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithSingleQuote() {
+ CharSequence value = "\"";
+ CharSequence expected = "\"\"\"\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithSingleQuoteAndCharacter() {
+ CharSequence value = "\"f";
+ CharSequence expected = "\"\"\"f\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvAlreadyEscapedQuote() {
+ CharSequence value = "\"some\"\"";
+ CharSequence expected = "\"some\"\"\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvQuoted() {
+ CharSequence value = "\"foo,goo\"";
+ CharSequence expected = value;
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithLineFeed() {
+ CharSequence value = "some text\n more text";
+ CharSequence expected = "\"some text\n more text\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithSingleLineFeedCharacter() {
+ CharSequence value = "\n";
+ CharSequence expected = "\"\n\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithMultipleLineFeedCharacter() {
+ CharSequence value = "\n\n";
+ CharSequence expected = "\"\n\n\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithQuotedAndLineFeedCharacter() {
+ CharSequence value = " \" \n ";
+ CharSequence expected = "\" \"\" \n \"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithLineFeedAtEnd() {
+ CharSequence value = "testing\n";
+ CharSequence expected = "\"testing\n\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithComma() {
+ CharSequence value = "test,ing";
+ CharSequence expected = "\"test,ing\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithSingleComma() {
+ CharSequence value = ",";
+ CharSequence expected = "\",\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithSingleCarriageReturn() {
+ CharSequence value = "\r";
+ CharSequence expected = "\"\r\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithMultipleCarriageReturn() {
+ CharSequence value = "\r\r";
+ CharSequence expected = "\"\r\r\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithCarriageReturn() {
+ CharSequence value = "some text\r more text";
+ CharSequence expected = "\"some text\r more text\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithQuotedAndCarriageReturnCharacter() {
+ CharSequence value = "\"\r";
+ CharSequence expected = "\"\"\"\r\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithCarriageReturnAtEnd() {
+ CharSequence value = "testing\r";
+ CharSequence expected = "\"testing\r\"";
+ escapeCsv(value, expected);
+ }
+
+ @Test
+ public void escapeCsvWithCRLFCharacter() {
+ CharSequence value = "\r\n";
+ CharSequence expected = "\"\r\n\"";
+ escapeCsv(value, expected);
+ }
+
+ private static void escapeCsv(CharSequence value, CharSequence expected) {
+ CharSequence escapedValue = value;
+ for (int i = 0; i < 10; ++i) {
+ escapedValue = StringUtil.escapeCsv(escapedValue);
+ assertEquals(expected, escapedValue.toString());
+ }
+ }
+
@Test
public void testSimpleClassName() throws Exception {
testSimpleClassName(String.class);