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

org.apache.commons.beanutils.converters.ArrayConverter Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
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 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 {@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 { 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 (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 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 (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); } } }