org.apache.commons.beanutils.converters.ArrayConverter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-beanutils Show documentation
Show all versions of commons-beanutils Show documentation
BeanUtils provides an easy-to-use but flexible wrapper around reflection and introspection.
/*
* 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.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Collection;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.io.IOException;
import java.lang.reflect.Array;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.Converter;
/**
* Generic {@link Converter} implementaion 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 {@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.
* - Arrays or Collections - can convert from either arrays or
* Collections to an array, limited only by the capability
* of the delegate {@link 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 {@link ArrayConverter#setOnlyFirstToString(boolean)}
* parameter.
* - Multi Dimensional Arrays - its possible to convert a
String
* to a multi-dimensional arrays, by embedding {@link ArrayConverter}
* within each other - see example below.
* - Default Value
*
* - No Default - use the
* {@link ArrayConverter#ArrayConverter(Class, Converter)}
* constructor to create a converter which throws a
* {@link ConversionException} if the value is missing or
* invalid.
* - Default values - use the
* {@link ArrayConverter#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
* {@link ArrayConverter} as the element {@link Converter}
* within another {@link ArrayConverter}.
*
* For example, the following code demonstrates how to construct a {@link 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 $Revision: 640131 $ $Date: 2008-03-22 22:10:31 -0400 (Sat, 22 Mar 2008) $
* @since 1.8.0
*/
public class ArrayConverter extends AbstractConverter {
private Object defaultTypeInstance;
private 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.
*
* @param defaultType The default array type this
* Converter
handles
* @param elementConverter Converter used to convert
* individual array elements.
*/
public ArrayConverter(Class defaultType, 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.defaultTypeInstance = Array.newInstance(defaultType.getComponentType(), 0);
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.
*
* @param defaultType The default array type this
* Converter
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 null
default value should be used.
*/
public ArrayConverter(Class defaultType, Converter elementConverter, 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.
*
* @param delimiter The delimiter [default ',']
*/
public void setDelimiter(char delimiter) {
this.delimiter = delimiter;
}
/**
* 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(char[] allowedChars) {
this.allowedChars = allowedChars;
}
/**
* Indicates whether converting to a String should create
* a delimited list or just convert the first value.
*
* @param 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
*/
public void setOnlyFirstToString(boolean onlyFirstToString) {
this.onlyFirstToString = onlyFirstToString;
}
/**
* Return the default type this Converter
handles.
*
* @return The default type this Converter
handles.
*/
protected Class getDefaultType() {
return defaultTypeInstance.getClass();
}
/**
* 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
*/
protected String convertToString(Object value) throws Throwable {
int size = 0;
Iterator iterator = null;
Class type = value.getClass();
if (type.isArray()) {
size = Array.getLength(value);
} else {
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
StringBuffer buffer = new StringBuffer();
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.
*
* @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
*/
protected Object convertToType(Class type, 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 {
Collection collection = convertToCollection(type, value);
size = collection.size();
iterator = collection.iterator();
}
// Allocate a new Array
Class componentType = type.getComponentType();
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);
}
return newArray;
}
/**
* Returns the value unchanged.
*
* @param value The value to convert
* @return The value unchanged
*/
protected Object convertArray(Object value) {
return value;
}
/**
* Converts non-array values to a Collection prior
* to being converted either to an array or a String.
*
*
* - {@link Collection} values are returned unchanged
* - {@link Number}, {@link Boolean} and {@link java.util.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
* {@link ArrayConverter#convertToType(Class, Object)} and
* {@link ArrayConverter#convertToString(Object)} methods for
* non-array types.
*
* @param type The type to convert the value to
* @param value value to be converted
* @return Collection elements.
*/
protected Collection convertToCollection(Class type, Object value) {
if (value instanceof Collection) {
return (Collection)value;
}
if (value instanceof Number ||
value instanceof Boolean ||
value instanceof java.util.Date) {
List list = new ArrayList(1);
list.add(value);
return list;
}
return parseElements(type, value.toString());
}
/**
* 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.
*/
protected Object getDefault(Class type) {
if (type.equals(String.class)) {
return null;
}
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.
*
* @return A String representation of this array converter
*/
public String toString() {
StringBuffer buffer = new StringBuffer();
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.
*
*
* @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 svalue
* is not syntactically valid
* @throws NullPointerException if svalue
* is null
*/
private List parseElements(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
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 (int i = 0; i < allowedChars.length; i++) {
st.ordinaryChars(allowedChars[i], allowedChars[i]);
st.wordChars(allowedChars[i], allowedChars[i]);
}
// Split comma-delimited tokens into a List
List list = null;
while (true) {
int ttype = st.nextToken();
if ((ttype == StreamTokenizer.TT_WORD) || (ttype > 0)) {
if (st.sval != null) {
if (list == null) {
list = new ArrayList();
}
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.EMPTY_LIST;
}
if (log().isDebugEnabled()) {
log().debug(list.size() + " elements parsed");
}
// Return the completed list
return (list);
} catch (IOException e) {
throw new ConversionException("Error converting from String to '"
+ toString(type) + "': " + e.getMessage(), e);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy