com.feilong.lib.beanutils.converters.ArrayConverter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of feilong Show documentation
Show all versions of feilong Show documentation
feilong is a suite of core and expanded libraries that include utility classes, http, excel,cvs, io classes, and much much more.
/*
* 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 com.feilong.lib.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.feilong.lib.beanutils.ConversionException;
import com.feilong.lib.beanutils.Converter;
/**
* Generic {@link 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 {@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 - it is 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 $Id$
* @since 1.8.0
*/
public class ArrayConverter extends AbstractConverter{
/** The Constant log. */
private static final Logger LOGGER = LoggerFactory.getLogger(ArrayConverter.class);
//---------------------------------------------------------------
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.
*
* @param defaultType
* The default array type this
* Converter
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.
*
* @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(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.
*
* @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.
*
* @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.
*
* @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(final boolean onlyFirstToString){
this.onlyFirstToString = onlyFirstToString;
}
/**
* Return the default type this Converter
handles.
*
* @return The default type this Converter
handles.
*/
@Override
protected Class> getDefaultType(){
return defaultType;
}
/**
* 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.
*
* @param
* 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 convertToType(final Class 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.
*
* @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.
*
*
* - {@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(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