/*
 * 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.beanutils.converters;

import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.Converter;

Generic Converter implementation that handles conversion to and from array objects.

Can be configured to either return a default value or throw a ConversionException if a conversion error occurs.

The main features of this implementation are:

  • Element Conversion - delegates to a Converter, appropriate for the type, to convert individual elements of the array. This leverages the power of existing converters without having to replicate their functionality for converting to the element type and removes the need to create a specifc array type converters.
  • Arrays or Collections - can convert from either arrays or Collections to an array, limited only by the capability of the delegate Converter.
  • Delimited Lists - can Convert to and from a delimited list in String format.
  • Conversion to String - converts an array to a String in one of two ways: as a delimited list or by converting the first element in the array to a String - this is controlled by the setOnlyFirstToString(boolean) parameter.
  • Multi Dimensional Arrays - it is possible to convert a String to a multi-dimensional arrays, by embedding ArrayConverter within each other - see example below.
  • Default Value
    • No Default - use the ArrayConverter(Class<?>, Converter) constructor to create a converter which throws a ConversionException if the value is missing or invalid.
    • Default values - use the ArrayConverter(Class<?>, Converter, int) constructor to create a converter which returns a default value. The defaultSize parameter controls the default value in the following way:
      • defaultSize < 0 - default is null
      • defaultSize = 0 - default is an array of length zero
      • defaultSize > 0 - default is an array with a length specified by defaultSize (N.B. elements in the array will be null)

Parsing Delimited Lists

This implementation can convert a delimited list in String format into an array of the appropriate type. By default, it uses a comma as the delimiter but the following methods can be used to configure parsing:
  • setDelimiter(char) - allows the character used as the delimiter to be configured [default is a comma].
  • setAllowedChars(char[]) - adds additional characters (to the default alphabetic/numeric) to those considered to be valid token characters.

Multi Dimensional Arrays

It is possible to convert a String to mulit-dimensional arrays by using ArrayConverter as the element Converter within another ArrayConverter.

For example, the following code demonstrates how to construct a Converter to convert a delimited String into a two dimensional integer array:

   // Construct an Integer Converter
   IntegerConverter integerConverter = new IntegerConverter();
   // Construct an array Converter for an integer array (i.e. int[]) using
   // an IntegerConverter as the element converter.
   // N.B. Uses the default comma (i.e. ",") as the delimiter between individual numbers
   ArrayConverter arrayConverter = new ArrayConverter(int[].class, integerConverter);
   // Construct a "Matrix" Converter which converts arrays of integer arrays using
   // the pre-ceeding ArrayConverter as the element Converter.
   // N.B. Uses a semi-colon (i.e. ";") as the delimiter to separate the different sets of numbers.
   //      Also the delimiter used by the first ArrayConverter needs to be added to the
   //      "allowed characters" for this one.
   ArrayConverter matrixConverter = new ArrayConverter(int[][].class, arrayConverter);
   matrixConverter.setDelimiter(';');
   matrixConverter.setAllowedChars(new char[] {','});
   // Do the Conversion
   String matrixString = "11,12,13 ; 21,22,23 ; 31,32,33 ; 41,42,43";
   int[][] result = (int[][])matrixConverter.convert(int[][].class, matrixString);
Version:$Id$
Since:1.8.0
/** * Generic {@link Converter} implementation that handles conversion * to and from <b>array</b> objects. * <p> * Can be configured to either return a <i>default value</i> or throw a * <code>ConversionException</code> if a conversion error occurs. * <p> * The main features of this implementation are: * <ul> * <li><b>Element Conversion</b> - delegates to a {@link Converter}, * appropriate for the type, to convert individual elements * of the array. This leverages the power of existing converters * without having to replicate their functionality for converting * to the element type and removes the need to create a specifc * array type converters.</li> * <li><b>Arrays or Collections</b> - can convert from either arrays or * Collections to an array, limited only by the capability * of the delegate {@link Converter}.</li> * <li><b>Delimited Lists</b> - can Convert <b>to</b> and <b>from</b> a * delimited list in String format.</li> * <li><b>Conversion to String</b> - converts an array to a * <code>String</code> in one of two ways: as a <i>delimited list</i> * or by converting the first element in the array to a String - this * is controlled by the {@link ArrayConverter#setOnlyFirstToString(boolean)} * parameter.</li> * <li><b>Multi Dimensional Arrays</b> - it is possible to convert a <code>String</code> * to a multi-dimensional arrays, by embedding {@link ArrayConverter} * within each other - see example below.</li> * <li><b>Default Value</b></li> * <ul> * <li><b><i>No Default</b></i> - use the * {@link ArrayConverter#ArrayConverter(Class, Converter)} * constructor to create a converter which throws a * {@link ConversionException} if the value is missing or * invalid.</li> * <li><b><i>Default values</b></i> - use the * {@link ArrayConverter#ArrayConverter(Class, Converter, int)} * constructor to create a converter which returns a <i>default * value</i>. The <i>defaultSize</i> parameter controls the * <i>default value</i> in the following way:</li> * <ul> * <li><i>defaultSize &lt; 0</i> - default is <code>null</code></li> * <li><i>defaultSize = 0</i> - default is an array of length zero</li> * <li><i>defaultSize &gt; 0</i> - default is an array with a * length specified by <code>defaultSize</code> (N.B. elements * in the array will be <code>null</code>)</li> * </ul> * </ul> * </ul> * * <h3>Parsing Delimited Lists</h3> * This implementation can convert a delimited list in <code>String</code> format * into an array of the appropriate type. By default, it uses a comma as the delimiter * but the following methods can be used to configure parsing: * <ul> * <li><code>setDelimiter(char)</code> - allows the character used as * the delimiter to be configured [default is a comma].</li> * <li><code>setAllowedChars(char[])</code> - adds additional characters * (to the default alphabetic/numeric) to those considered to be * valid token characters. * </ul> * * <h3>Multi Dimensional Arrays</h3> * It is possible to convert a <code>String</code> to mulit-dimensional arrays by using * {@link ArrayConverter} as the element {@link Converter} * within another {@link ArrayConverter}. * <p> * For example, the following code demonstrates how to construct a {@link Converter} * to convert a delimited <code>String</code> into a two dimensional integer array: * <p> * <pre> * // Construct an Integer Converter * IntegerConverter integerConverter = new IntegerConverter(); * * // Construct an array Converter for an integer array (i.e. int[]) using * // an IntegerConverter as the element converter. * // N.B. Uses the default comma (i.e. ",") as the delimiter between individual numbers * ArrayConverter arrayConverter = new ArrayConverter(int[].class, integerConverter); * * // Construct a "Matrix" Converter which converts arrays of integer arrays using * // the pre-ceeding ArrayConverter as the element Converter. * // N.B. Uses a semi-colon (i.e. ";") as the delimiter to separate the different sets of numbers. * // Also the delimiter used by the first ArrayConverter needs to be added to the * // "allowed characters" for this one. * ArrayConverter matrixConverter = new ArrayConverter(int[][].class, arrayConverter); * matrixConverter.setDelimiter(';'); * matrixConverter.setAllowedChars(new char[] {','}); * * // Do the Conversion * String matrixString = "11,12,13 ; 21,22,23 ; 31,32,33 ; 41,42,43"; * int[][] result = (int[][])matrixConverter.convert(int[][].class, matrixString); * </pre> * * @version $Id$ * @since 1.8.0 */
public class ArrayConverter extends AbstractConverter { private final Class<?> defaultType; private final Converter elementConverter; private int defaultSize; private char delimiter = ','; private char[] allowedChars = new char[] {'.', '-'}; private boolean onlyFirstToString = true; // ----------------------------------------------------------- Constructors
Construct an array Converter with the specified component Converter that throws a ConversionException if an error occurs.
Params:
  • defaultType – The default array type this Converter handles
  • elementConverter – Converter used to convert individual array elements.
/** * Construct an <b>array</b> <code>Converter</code> with the specified * <b>component</b> <code>Converter</code> that throws a * <code>ConversionException</code> if an error occurs. * * @param defaultType The default array type this * <code>Converter</code> handles * @param elementConverter Converter used to convert * individual array elements. */
public ArrayConverter(final Class<?> defaultType, final Converter elementConverter) { super(); if (defaultType == null) { throw new IllegalArgumentException("Default type is missing"); } if (!defaultType.isArray()) { throw new IllegalArgumentException("Default type must be an array."); } if (elementConverter == null) { throw new IllegalArgumentException("Component Converter is missing."); } this.defaultType = defaultType; this.elementConverter = elementConverter; }
Construct an array Converter with the specified component Converter that returns a default array of the specified size (or null) if an error occurs.
Params:
  • defaultType – The default array type this Converter handles
  • elementConverter – Converter used to convert individual array elements.
  • defaultSize – Specifies the size of the default array value or if less than zero indicates that a null default value should be used.
/** * Construct an <b>array</b> <code>Converter</code> with the specified * <b>component</b> <code>Converter</code> that returns a default * array of the specified size (or <code>null</code>) if an error occurs. * * @param defaultType The default array type this * <code>Converter</code> handles * @param elementConverter Converter used to convert * individual array elements. * @param defaultSize Specifies the size of the default array value or if less * than zero indicates that a <code>null</code> default value should be used. */
public ArrayConverter(final Class<?> defaultType, final Converter elementConverter, final int defaultSize) { this(defaultType, elementConverter); this.defaultSize = defaultSize; Object defaultValue = null; if (defaultSize >= 0) { defaultValue = Array.newInstance(defaultType.getComponentType(), defaultSize); } setDefaultValue(defaultValue); }
Set the delimiter to be used for parsing a delimited String.
Params:
  • delimiter – The delimiter [default ',']
/** * Set the delimiter to be used for parsing a delimited String. * * @param delimiter The delimiter [default ','] */
public void setDelimiter(final char delimiter) { this.delimiter = delimiter; }
Set the allowed characters to be used for parsing a delimited String.
Params:
  • allowedChars – Characters which are to be considered as part of the tokens when parsing a delimited String [default is '.' and '-']
/** * Set the allowed characters to be used for parsing a delimited String. * * @param allowedChars Characters which are to be considered as part of * the tokens when parsing a delimited String [default is '.' and '-'] */
public void setAllowedChars(final char[] allowedChars) { this.allowedChars = allowedChars; }
Indicates whether converting to a String should create a delimited list or just convert the first value.
Params:
  • onlyFirstToString – true converts only the first value in the array to a String, false converts all values in the array into a delimited list (default is true
/** * Indicates whether converting to a String should create * a delimited list or just convert the first value. * * @param onlyFirstToString <code>true</code> converts only * the first value in the array to a String, <code>false</code> * converts all values in the array into a delimited list (default * is <code>true</code> */
public void setOnlyFirstToString(final boolean onlyFirstToString) { this.onlyFirstToString = onlyFirstToString; }
Return the default type this Converter handles.
Returns:The default type this Converter handles.
/** * Return the default type this <code>Converter</code> handles. * * @return The default type this <code>Converter</code> handles. */
@Override protected Class<?> getDefaultType() { return defaultType; }
Handles conversion to a String.
Params:
  • value – The value to be converted.
Throws:
  • Throwable – if an error occurs converting to a String
Returns:the converted String value.
/** * Handles conversion to a String. * * @param value The value to be converted. * @return the converted String value. * @throws Throwable if an error occurs converting to a String */
@Override protected String convertToString(final Object value) throws Throwable { int size = 0; Iterator<?> iterator = null; final Class<?> type = value.getClass(); if (type.isArray()) { size = Array.getLength(value); } else { final Collection<?> collection = convertToCollection(type, value); size = collection.size(); iterator = collection.iterator(); } if (size == 0) { return (String)getDefault(String.class); } if (onlyFirstToString) { size = 1; } // Create a StringBuffer containing a delimited list of the values final StringBuilder buffer = new StringBuilder(); for (int i = 0; i < size; i++) { if (i > 0) { buffer.append(delimiter); } Object element = iterator == null ? Array.get(value, i) : iterator.next(); element = elementConverter.convert(String.class, element); if (element != null) { buffer.append(element); } } return buffer.toString(); }
Handles conversion to an array of the specified type.
Params:
  • type – The type to which this value should be converted.
  • value – The input value to be converted.
Type parameters:
  • <T> – Target type of the conversion.
Throws:
  • Throwable – if an error occurs converting to the specified type
Returns:The converted value.
/** * Handles conversion to an array of the specified type. * * @param <T> Target type of the conversion. * @param type The type to which this value should be converted. * @param value The input value to be converted. * @return The converted value. * @throws Throwable if an error occurs converting to the specified type */
@Override protected <T> T convertToType(final Class<T> type, final Object value) throws Throwable { if (!type.isArray()) { throw new ConversionException(toString(getClass()) + " cannot handle conversion to '" + toString(type) + "' (not an array)."); } // Handle the source int size = 0; Iterator<?> iterator = null; if (value.getClass().isArray()) { size = Array.getLength(value); } else { final Collection<?> collection = convertToCollection(type, value); size = collection.size(); iterator = collection.iterator(); } // Allocate a new Array final Class<?> componentType = type.getComponentType(); final Object newArray = Array.newInstance(componentType, size); // Convert and set each element in the new Array for (int i = 0; i < size; i++) { Object element = iterator == null ? Array.get(value, i) : iterator.next(); // TODO - probably should catch conversion errors and throw // new exception providing better info back to the user element = elementConverter.convert(componentType, element); Array.set(newArray, i, element); } @SuppressWarnings("unchecked") final // This is safe because T is an array type and newArray is an array of // T's component type T result = (T) newArray; return result; }
Returns the value unchanged.
Params:
  • value – The value to convert
Returns:The value unchanged
/** * Returns the value unchanged. * * @param value The value to convert * @return The value unchanged */
@Override protected Object convertArray(final Object value) { return value; }
Converts non-array values to a Collection prior to being converted either to an array or a String.

  • Collection values are returned unchanged
  • Number, Boolean and Date values returned as a the only element in a List.
  • All other types are converted to a String and parsed as a delimited list.
N.B. The method is called by both the convertToType(Class<Object>, Object) and convertToString(Object) methods for non-array types.
Params:
  • type – The type to convert the value to
  • value – value to be converted
Returns:Collection elements.
/** * Converts non-array values to a Collection prior * to being converted either to an array or a String. * </p> * <ul> * <li>{@link Collection} values are returned unchanged</li> * <li>{@link Number}, {@link Boolean} and {@link java.util.Date} * values returned as a the only element in a List.</li> * <li>All other types are converted to a String and parsed * as a delimited list.</li> * </ul> * * <strong>N.B.</strong> The method is called by both the * {@link ArrayConverter#convertToType(Class, Object)} and * {@link ArrayConverter#convertToString(Object)} methods for * <i>non-array</i> types. * * @param type The type to convert the value to * @param value value to be converted * @return Collection elements. */
protected Collection<?> convertToCollection(final Class<?> type, final Object value) { if (value instanceof Collection) { return (Collection<?>)value; } if (value instanceof Number || value instanceof Boolean || value instanceof java.util.Date) { final List<Object> list = new ArrayList<Object>(1); list.add(value); return list; } return parseElements(type, value.toString()); }
Return the default value for conversions to the specified type.
Params:
  • type – Data type to which this value should be converted.
Returns:The default value for the specified type.
/** * Return the default value for conversions to the specified * type. * @param type Data type to which this value should be converted. * @return The default value for the specified type. */
@Override protected Object getDefault(final Class<?> type) { if (type.equals(String.class)) { return null; } final Object defaultValue = super.getDefault(type); if (defaultValue == null) { return null; } if (defaultValue.getClass().equals(type)) { return defaultValue; } else { return Array.newInstance(type.getComponentType(), defaultSize); } }
Provide a String representation of this array converter.
Returns:A String representation of this array converter
/** * Provide a String representation of this array converter. * * @return A String representation of this array converter */
@Override public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append(toString(getClass())); buffer.append("[UseDefault="); buffer.append(isUseDefault()); buffer.append(", "); buffer.append(elementConverter.toString()); buffer.append(']'); return buffer.toString(); }

Parse an incoming String of the form similar to an array initializer in the Java language into a List individual Strings for each element, according to the following rules.

  • The string is expected to be a comma-separated list of values.
  • The string may optionally have matching '{' and '}' delimiters around the list.
  • Whitespace before and after each element is stripped.
  • Elements in the list may be delimited by single or double quotes. Within a quoted elements, the normal Java escape sequences are valid.
Params:
  • type – The type to convert the value to
  • value – String value to be parsed
Throws:
Returns:List of parsed elements.
/** * <p>Parse an incoming String of the form similar to an array initializer * in the Java language into a <code>List</code> individual Strings * for each element, according to the following rules.</p> * <ul> * <li>The string is expected to be a comma-separated list of values.</li> * <li>The string may optionally have matching '{' and '}' delimiters * around the list.</li> * <li>Whitespace before and after each element is stripped.</li> * <li>Elements in the list may be delimited by single or double quotes. * Within a quoted elements, the normal Java escape sequences are valid.</li> * </ul> * * @param type The type to convert the value to * @param value String value to be parsed * @return List of parsed elements. * * @throws ConversionException if the syntax of <code>svalue</code> * is not syntactically valid * @throws NullPointerException if <code>svalue</code> * is <code>null</code> */
private List<String> parseElements(final Class<?> type, String value) { if (log().isDebugEnabled()) { log().debug("Parsing elements, delimiter=[" + delimiter + "], value=[" + value + "]"); } // Trim any matching '{' and '}' delimiters value = value.trim(); if (value.startsWith("{") && value.endsWith("}")) { value = value.substring(1, value.length() - 1); } try { // Set up a StreamTokenizer on the characters in this String final StreamTokenizer st = new StreamTokenizer(new StringReader(value)); st.whitespaceChars(delimiter , delimiter); // Set the delimiters st.ordinaryChars('0', '9'); // Needed to turn off numeric flag st.wordChars('0', '9'); // Needed to make part of tokens for (char allowedChar : allowedChars) { st.ordinaryChars(allowedChar, allowedChar); st.wordChars(allowedChar, allowedChar); } // Split comma-delimited tokens into a List List<String> list = null; while (true) { final int ttype = st.nextToken(); if ((ttype == StreamTokenizer.TT_WORD) || (ttype > 0)) { if (st.sval != null) { if (list == null) { list = new ArrayList<String>(); } list.add(st.sval); } } else if (ttype == StreamTokenizer.TT_EOF) { break; } else { throw new ConversionException("Encountered token of type " + ttype + " parsing elements to '" + toString(type) + "."); } } if (list == null) { list = Collections.emptyList(); } if (log().isDebugEnabled()) { log().debug(list.size() + " elements parsed"); } // Return the completed list return (list); } catch (final IOException e) { throw new ConversionException("Error converting from String to '" + toString(type) + "': " + e.getMessage(), e); } } }