| /* |
| * 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.configuration2.convert; |
| |
| 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> |
| * |
| * @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(final char listDelimiter) { |
| delimiter = listDelimiter; |
| } |
| |
| @Override |
| public Object escapeList(final List<?> values, final ValueTransformer transformer) { |
| final Object[] escapedValues = new Object[values.size()]; |
| int idx = 0; |
| for (final Object v : values) { |
| escapedValues[idx++] = escape(v, transformer); |
| } |
| return StringUtils.join(escapedValues, getDelimiter()); |
| } |
| |
| @Override |
| protected String escapeString(final String s) { |
| final StringBuilder buf = new StringBuilder(s.length() + BUF_SIZE); |
| for (int i = 0; i < s.length(); i++) { |
| final char c = s.charAt(i); |
| if (c == getDelimiter() || c == ESCAPE) { |
| buf.append(ESCAPE); |
| } |
| buf.append(c); |
| } |
| return buf.toString(); |
| } |
| |
| /** |
| * Gets the list delimiter character used by this instance. |
| * |
| * @return the list delimiter character |
| */ |
| public char getDelimiter() { |
| return delimiter; |
| } |
| |
| /** |
| * {@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(final String s, final boolean trim) { |
| final List<String> list = new LinkedList<>(); |
| StringBuilder token = new StringBuilder(); |
| boolean inEscape = false; |
| |
| for (int i = 0; i < s.length(); i++) { |
| final 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; |
| } |
| } |