001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.configuration2.convert; 018 019import java.lang.reflect.Array; 020import java.util.Collection; 021import java.util.Iterator; 022import java.util.LinkedList; 023import java.util.List; 024 025/** 026 * <p> 027 * Definition of an interface that controls the handling of list delimiters in configuration properties. 028 * </p> 029 * <p> 030 * {@link org.apache.commons.configuration2.AbstractConfiguration AbstractConfiguration} supports list delimiters in 031 * property values. If such a delimiter is found, the value actually contains multiple values and has to be split. This 032 * is useful for instance for {@link org.apache.commons.configuration2.PropertiesConfiguration PropertiesConfiguration}: 033 * properties files that have to be compatible with the {@code java.util.Properties} class cannot have multiple 034 * occurrences of a single property key, therefore a different storage scheme for multi-valued properties is needed. A 035 * possible storage scheme could look as follows: 036 * </p> 037 * 038 * <pre> 039 * myProperty=value1,value2,value3 040 * </pre> 041 * 042 * <p> 043 * Here a comma is used as list delimiter. When parsing this property (and using a corresponding 044 * {@code ListDelimiterHandler} implementation) the string value is split, and three values are added for the property 045 * key. 046 * </p> 047 * <p> 048 * A {@code ListDelimiterHandler} knows how to parse and to escape property values. It is called by concrete 049 * {@code Configuration} implementations when they have to deal with properties with multiple values. 050 * </p> 051 * 052 * @since 2.0 053 */ 054public interface ListDelimiterHandler { 055 /** 056 * A specialized {@code ValueTransformer} implementation which does no transformation. The {@code transformValue()} 057 * method just returns the passed in object without changes. This instance can be used by configurations which do not 058 * require additional encoding. 059 */ 060 ValueTransformer NOOP_TRANSFORMER = value -> value; 061 062 /** 063 * Escapes the specified single value object. This method is called for properties containing only a single value. So 064 * this method can rely on the fact that the passed in object is not a list. An implementation has to check whether the 065 * value contains list delimiter characters and - if so - escape them accordingly. 066 * 067 * @param value the value to be escaped 068 * @param transformer a {@code ValueTransformer} for an additional encoding (must not be <b>null</b>) 069 * @return the escaped value 070 */ 071 Object escape(Object value, ValueTransformer transformer); 072 073 /** 074 * Escapes all values in the given list and concatenates them to a single string. This operation is required by 075 * configurations that have to represent properties with multiple values in a single line in their external 076 * configuration representation. This may require an advanced escaping in some cases. 077 * 078 * @param values the list with all the values to be converted to a single value 079 * @param transformer a {@code ValueTransformer} for an additional encoding (must not be <b>null</b>) 080 * @return the resulting escaped value 081 */ 082 Object escapeList(List<?> values, ValueTransformer transformer); 083 084 /** 085 * Parses the specified value for list delimiters and splits it if necessary. The passed in object can be either a 086 * single value or a complex one, e.g. a collection, an array, or an {@code Iterable}. It is the responsibility of this 087 * method to return an {@code Iterable} which contains all extracted values. 088 * 089 * @param value the value to be parsed 090 * @return an {@code Iterable} allowing access to all extracted values 091 */ 092 Iterable<?> parse(Object value); 093 094 /** 095 * Splits the specified string at the list delimiter and returns a collection with all extracted components. A concrete 096 * implementation also has to deal with escape characters which might mask a list delimiter character at certain 097 * positions. The boolean {@code trim} flag determines whether each extracted component should be trimmed. This 098 * typically makes sense as the list delimiter may be surrounded by whitespace. However, there may be specific use cases 099 * in which automatic trimming is not desired. 100 * 101 * @param s the string to be split 102 * @param trim a flag whether each component of the string is to be trimmed 103 * @return a collection with all components extracted from the string 104 */ 105 Collection<String> split(String s, boolean trim); 106 107 /** 108 * Extracts all values contained in the specified object up to the given limit. The passed in object is evaluated (if 109 * necessary in a recursive way). If it is a complex object (e.g. a collection or an array), all its elements are 110 * processed recursively and added to a target collection. The process stops if the limit is reached, but depending on 111 * the input object, it might be exceeded. (The limit is just an indicator to stop the process to avoid unnecessary work 112 * if the caller is only interested in a few values.) 113 * 114 * @param value the value to be processed 115 * @param limit the limit for aborting the processing 116 * @return a "flat" collection containing all primitive values of the passed in object 117 * @since 2.9.0 118 */ 119 default Collection<?> flatten(final Object value, final int limit) { 120 if (value instanceof String) { 121 return split((String) value, true); 122 } 123 final Collection<Object> result = new LinkedList<>(); 124 if (value instanceof Iterable) { 125 AbstractListDelimiterHandler.flattenIterator(this, result, ((Iterable<?>) value).iterator(), limit); 126 } else if (value instanceof Iterator) { 127 AbstractListDelimiterHandler.flattenIterator(this, result, (Iterator<?>) value, limit); 128 } else if (value != null) { 129 if (value.getClass().isArray()) { 130 for (int len = Array.getLength(value), idx = 0, size = 0; idx < len && size < limit; idx++, size = result.size()) { 131 result.addAll(flatten(Array.get(value, idx), limit - size)); 132 } 133 } else { 134 result.add(value); 135 } 136 } 137 return result; 138 } 139 140}