Java tutorial
/* * 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.jdesigner.platform.web.converter; 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} implementaion 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> - its 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 < 0</i> - default is <code>null</code></li> * <li><i>defaultSize = 0</i> - default is an array of length zero</li> * <li><i>defaultSize > 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 $Revision: 1.1 $ $Date: 2010/04/08 05:41:59 $ * @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 <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(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 <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(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 * <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(boolean onlyFirstToString) { this.onlyFirstToString = onlyFirstToString; } /** * Return the default type this <code>Converter</code> handles. * * @return The default type this <code>Converter</code> 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(); // - 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. </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(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(); } /** * <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 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); } } }