You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2013/07/07 18:48:45 UTC
svn commit: r1500479 - in /commons/proper/configuration/trunk/src:
main/java/org/apache/commons/configuration/DefaultListDelimiterHandler.java
test/java/org/apache/commons/configuration/TestDefaultListDelimiterHandler.java
Author: oheger
Date: Sun Jul 7 16:48:44 2013
New Revision: 1500479
URL: http://svn.apache.org/r1500479
Log:
Added DefaultListDelimiterHandler class.
This is the list delimiter handler implementation to be used by new
applications that want to make use of the list splitting feature. It
implements a consistent algorithm for dealing with list delmiter and
backslash (escaping) characters.
Added:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/DefaultListDelimiterHandler.java
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestDefaultListDelimiterHandler.java
Added: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/DefaultListDelimiterHandler.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/DefaultListDelimiterHandler.java?rev=1500479&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/DefaultListDelimiterHandler.java (added)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/DefaultListDelimiterHandler.java Sun Jul 7 16:48:44 2013
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.commons.configuration;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * <p>
+ * The default implementation of the {@code ListDelimiterHandler} interface.
+ * </p>
+ * <p>
+ * This class supports list splitting and delimiter escaping using a delimiter
+ * character that can be specified when constructing an instance. Splitting of
+ * strings works by scanning the input for the list delimiter character. The
+ * list delimiter character can be escaped by a backslash. So, provided that a
+ * comma is configured as list delimiter, in the example {@code val1,val2,val3}
+ * three values are recognized. In {@code 3\,1415} the list delimiter is escaped
+ * so that only a single element is detected. (Note that when writing these
+ * examples in Java code, each backslash has to be doubled. This is also true
+ * for all other examples in this documentation.)
+ * </p>
+ * <p>
+ * Because the backslash has a special meaning as escaping character it is
+ * always treated in a special way. If it occurs as a normal character in a
+ * property value, it has to be escaped using another backslash (similar to the
+ * rules of the Java programming language). The following example shows the
+ * correct way to define windows network shares: {@code \\\\Server\\path}. Note
+ * that each backslash is doubled. When combining the list delimiter with
+ * backslashes the same escaping rules apply. For instance, in
+ * {@code C:\\Temp\\,D:\\data\\} the list delimiter is recognized; it is not
+ * escaped by the preceding backslash because this backslash is itself escaped.
+ * In contrast, {@code C:\\Temp\\\,D:\\data\\} defines a single element with a
+ * comma being part of the value; two backslashes after {@code Temp} result in a
+ * single one, the third backslash escapes the list delimiter.
+ * </p>
+ * <p>
+ * As can be seen, there are some constellations which are a bit tricky and
+ * cause a larger number of backslashes in sequence. Nevertheless, the escaping
+ * rules are consistent and do not cause ambiguous results.
+ * </p>
+ * <p>
+ * Implementation node: An instance of this class can safely be shared between
+ * multiple {@code Configuration} instances.
+ * </p>
+ *
+ * @version $Id: $
+ * @since 2.0
+ */
+public class DefaultListDelimiterHandler extends AbstractListDelimiterHandler
+{
+ /** Constant for the escape character. */
+ private static final char ESCAPE = '\\';
+
+ /**
+ * Constant for a buffer size for escaping strings. When a character is
+ * escaped the string becomes longer. Therefore, the output buffer is longer
+ * than the original string length. But we assume, that there are not too
+ * many characters that need to be escaped.
+ */
+ private static final int BUF_SIZE = 16;
+
+ /** Stores the list delimiter character. */
+ private final char delimiter;
+
+ /**
+ * Creates a new instance of {@code DefaultListDelimiterHandler} and sets
+ * the list delimiter character.
+ *
+ * @param listDelimiter the list delimiter character
+ */
+ public DefaultListDelimiterHandler(char listDelimiter)
+ {
+ delimiter = listDelimiter;
+ }
+
+ /**
+ * Returns the list delimiter character used by this instance.
+ *
+ * @return the list delimiter character
+ */
+ public char getDelimiter()
+ {
+ return delimiter;
+ }
+
+ public Object escapeList(List<?> values, ValueTransformer transformer)
+ {
+ Object[] escapedValues = new String[values.size()];
+ int idx = 0;
+ for (Object v : values)
+ {
+ escapedValues[idx++] = escape(v, transformer);
+ }
+ return StringUtils.join(escapedValues, getDelimiter());
+ }
+
+ @Override
+ protected String escapeString(String s)
+ {
+ StringBuilder buf = new StringBuilder(s.length() + BUF_SIZE);
+ for (int i = 0; i < s.length(); i++)
+ {
+ char c = s.charAt(i);
+ if (c == getDelimiter() || c == ESCAPE)
+ {
+ buf.append(ESCAPE);
+ }
+ buf.append(c);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * {@inheritDoc} This implementation reverses the escaping done by the
+ * {@code escape()} methods of this class. However, it tries to be tolerant
+ * with unexpected escaping sequences: If after the escape character "\" no
+ * allowed character follows, both the backslash and the following character
+ * are output.
+ */
+ @Override
+ protected Collection<String> splitString(String s, boolean trim)
+ {
+ List<String> list = new LinkedList<String>();
+ StringBuilder token = new StringBuilder();
+ boolean inEscape = false;
+
+ for (int i = 0; i < s.length(); i++)
+ {
+ char c = s.charAt(i);
+ if (inEscape)
+ {
+ // last character was the escape marker
+ // can current character be escaped?
+ if (c != getDelimiter() && c != ESCAPE)
+ {
+ // no, also add escape character
+ token.append(ESCAPE);
+ }
+ token.append(c);
+ inEscape = false;
+ }
+
+ else
+ {
+ if (c == getDelimiter())
+ {
+ // found a list delimiter -> add token and
+ // reset buffer
+ String t = token.toString();
+ if (trim)
+ {
+ t = t.trim();
+ }
+ list.add(t);
+ token = new StringBuilder();
+ }
+ else if (c == ESCAPE)
+ {
+ // potentially escape next character
+ inEscape = true;
+ }
+ else
+ {
+ token.append(c);
+ }
+ }
+ }
+
+ // Trailing delimiter?
+ if (inEscape)
+ {
+ token.append(ESCAPE);
+ }
+ // Add last token
+ String t = token.toString();
+ if (trim)
+ {
+ t = t.trim();
+ }
+ list.add(t);
+
+ return list;
+ }
+}
Added: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestDefaultListDelimiterHandler.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestDefaultListDelimiterHandler.java?rev=1500479&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestDefaultListDelimiterHandler.java (added)
+++ commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestDefaultListDelimiterHandler.java Sun Jul 7 16:48:44 2013
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.commons.configuration;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for {@code DefaultListDelimiterHandler}.
+ *
+ * @version $Id: $
+ */
+public class TestDefaultListDelimiterHandler
+{
+ /** The handler to be tested. */
+ private DefaultListDelimiterHandler handler;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ handler = new DefaultListDelimiterHandler(',');
+ }
+
+ /**
+ * Tests whether a string is correctly escaped which does not contain any
+ * special character.
+ */
+ @Test
+ public void testEscapeStringNoSpecialCharacter()
+ {
+ assertEquals("Wrong result", "test", handler.escapeString("test"));
+ }
+
+ /**
+ * Tests whether the list delimiter character is correctly escaped in a
+ * string.
+ */
+ @Test
+ public void testEscapeStringListDelimiter()
+ {
+ assertEquals("Wrong result", "3\\,1415", handler.escapeString("3,1415"));
+ }
+
+ /**
+ * Tests whether a backslash is correctly escaped.
+ */
+ @Test
+ public void testEscapeStringBackslash()
+ {
+ assertEquals("Wrong result", "C:\\\\Temp\\\\",
+ handler.escapeString("C:\\Temp\\"));
+ }
+
+ /**
+ * Tests whether combinations of list delimiters and backslashes are
+ * correctly escaped.
+ */
+ @Test
+ public void testEscapeStringListDelimiterAndBackslash()
+ {
+ assertEquals("Wrong result", "C:\\\\Temp\\\\\\,\\\\\\\\Share\\,/root",
+ handler.escapeString("C:\\Temp\\,\\\\Share,/root"));
+ }
+
+ /**
+ * Tests whether a value transformer is correctly called when escaping a
+ * single value.
+ */
+ @Test
+ public void testEscapeWithTransformer()
+ {
+ ValueTransformer trans = EasyMock.createMock(ValueTransformer.class);
+ EasyMock.expect(trans.transformValue("a\\,b")).andReturn("ok");
+ EasyMock.replay(trans);
+ assertEquals("Wrong result", "ok", handler.escape("a,b", trans));
+ EasyMock.verify(trans);
+ }
+
+ /**
+ * Tests whether a list is correctly escaped.
+ */
+ @Test
+ public void testEscapeList()
+ {
+ ValueTransformer trans = new ValueTransformer()
+ {
+ public Object transformValue(Object value)
+ {
+ return String.valueOf(value) + "_trans";
+ }
+ };
+ List<String> data =
+ Arrays.asList("simple", "Hello,world!", "\\,\\", "end");
+ assertEquals("Wrong result", "simple_trans,Hello\\,world!_trans,"
+ + "\\\\\\,\\\\_trans,end_trans",
+ handler.escapeList(data, trans));
+ }
+
+ /**
+ * Helper methods for testing a split operation. A split is executed with
+ * the passed in parameters. Then the results are compared to the expected
+ * elements.
+ *
+ * @param value the value to be split
+ * @param trim the trim flag
+ * @param expectedElements the expected results
+ */
+ private void checkSplit(String value, boolean trim,
+ String... expectedElements)
+ {
+ Collection<String> elems = handler.split(value, trim);
+ assertEquals("Wrong number of elements", expectedElements.length,
+ elems.size());
+ int idx = 0;
+ for (String elem : elems)
+ {
+ assertEquals("Wrong value at " + idx, expectedElements[idx++], elem);
+ }
+ }
+
+ /**
+ * Tests split() if there is only a single element.
+ */
+ @Test
+ public void testSplitSingleElement()
+ {
+ checkSplit("test", true, "test");
+ }
+
+ /**
+ * Tests whether a string list is split correctly.
+ */
+ @Test
+ public void testSplitList()
+ {
+ checkSplit("a, b,c , d", true, "a", "b", "c", "d");
+ }
+
+ /**
+ * Tests whether trimming can be disabled when splitting a list.
+ */
+ @Test
+ public void testSplitNoTrim()
+ {
+ checkSplit("a , b, c ,d", false, "a ", " b", " c ", "d");
+ }
+
+ /**
+ * Tests whether a line delimiter can be escaped when splitting a list.
+ */
+ @Test
+ public void testSplitEscapeLineDelimiter()
+ {
+ checkSplit("3\\,1415", true, "3,1415");
+ }
+
+ /**
+ * Tests whether split() deals correctly with escaped backslashes.
+ */
+ @Test
+ public void testSplitEscapeBackslash()
+ {
+ checkSplit("C:\\\\Temp\\\\", true, "C:\\Temp\\");
+ }
+
+ /**
+ * Tests a split operation with a complex combination of list delimiters and
+ * backslashes.
+ */
+ @Test
+ public void testSplitEscapeListDelimiterAndBackslashes()
+ {
+ checkSplit("C:\\\\Temp\\\\\\,\\\\\\\\Share\\\\,/root", false,
+ "C:\\Temp\\,\\\\Share\\", "/root");
+ }
+
+ /**
+ * Tests whether an unexpected escape character is handled properly.
+ */
+ @Test
+ public void testSplitUnexpectedEscape()
+ {
+ checkSplit("\\x, \\,y, \\", true, "\\x", ",y", "\\");
+ }
+}