All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.feilong.lib.beanutils.converters.ArrayConverter Maven / Gradle / Ivy

Go to download

feilong is a suite of core and expanded libraries that include utility classes, http, excel,cvs, io classes, and much much more.

There is a newer version: 4.3.0
Show newest version
/*
 * 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 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. */ @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. * * @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.
  • *
* * @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(final Class type,String value){ if (LOGGER.isDebugEnabled()){ LOGGER.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 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<>(); } 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 (LOGGER.isDebugEnabled()){ LOGGER.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); } } }