    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.beanutils.converters;
    019
    020import java.io.IOException;
    021import java.io.StreamTokenizer;
    022import java.io.StringReader;
    023import java.util.ArrayList;
    024import java.util.List;
    025import java.util.Objects;
    026
    027import org.apache.commons.beanutils.ConversionException;
    028import org.apache.commons.beanutils.Converter;
    029
    030/**
    031 * <p>Convenience base class for converters that translate the String
    032 * representation of an array into a corresponding array of primitives
    033 * object.  This class encapsulates the functionality required to parse
    034 * the String into a list of String elements that can later be
    035 * individually converted to the appropriate primitive type.</p>
    036 *
    037 * <p>The input syntax accepted by the <code>parseElements()</code> method
    038 * is designed to be compatible with the syntax used to initialize arrays
    039 * in a Java source program, except that only String literal values are
    040 * supported.  For maximum flexibility, the surrounding '{' and '}'
    041 * characters are optional, and individual elements may be separated by
    042 * any combination of whitespace and comma characters.</p>
    043 *
    044 * @since 1.4
    045 * @deprecated Replaced by the new {@link ArrayConverter} implementation
    046 */
    047
    048@Deprecated
    049public abstract class AbstractArrayConverter implements Converter {
    050
    051    /**
    052     * This is a special reference that can be passed as the "default object"
    053     * to the constructor to indicate that no default is desired. Note that
    054     * the value 'null' cannot be used for this purpose, as the caller may
    055     * want a null to be returned as the default.
    056     * @since 1.8.0
    057     */
    058    public static final Object NO_DEFAULT = new Object();
    059
    060    /**
    061     * <p>Model object for string arrays.</p>
    062     */
    063    protected static String[] strings = {};
    064
    065    /**
    066     * The default value specified to our Constructor, if any.
    067     */
    068    protected Object defaultValue;
    069
    070    /**
    071     * Should we return the default value on conversion errors?
    072     */
    073    protected boolean useDefault = true;
    074
    075    /**
    076     * Create a {@link Converter} that will throw a {@link ConversionException}
    077     * if a conversion error occurs.
    078     */
    079    public AbstractArrayConverter() {
    080
    081        this.defaultValue = null;
    082        this.useDefault = false;
    083
    084    }
    085
    086    /**
    087     * Create a {@link Converter} that will return the specified default value
    088     * if a conversion error occurs.
    089     *
    090     * @param defaultValue The default value to be returned
    091     * @since 1.8.0
    092     */
    093    public AbstractArrayConverter(final Object defaultValue) {
    094
    095        if (defaultValue == NO_DEFAULT) {
    096            this.useDefault = false;
    097        } else {
    098            this.defaultValue = defaultValue;
    099            this.useDefault = true;
    100        }
    101
    102    }
    103
    104    /**
    105     * Convert the specified input object into an output object of the
    106     * specified type.  This method must be implemented by a concrete
    107     * subclass.
    108     *
    109     * @param type Data type to which this value should be converted
    110     * @param value The input value to be converted
    111     * @return The converted value
    112     * @throws ConversionException if conversion cannot be performed
    113     *  successfully
    114     */
    115    @Override
    116    public abstract Object convert(Class type, Object value);
    117
    118    /**
    119     * <p>Parse an incoming String of the form similar to an array initializer
    120     * in the Java language into a <code>List</code> individual Strings
    121     * for each element, according to the following rules.</p>
    122     * <ul>
    123     * <li>The string is expected to be a comma-separated list of values.</li>
    124     * <li>The string may optionally have matching '{' and '}' delimiters
    125     *   around the list.</li>
    126     * <li>Whitespace before and after each element is stripped.</li>
    127     * <li>Elements in the list may be delimited by single or double quotes.
    128     *  Within a quoted elements, the normal Java escape sequences are valid.</li>
    129     * </ul>
    130     *
    131     * @param svalue String value to be parsed
    132     * @return The parsed list of String values
    133     * @throws ConversionException if the syntax of <code>svalue</code>
    134     *  is not syntactically valid
    135     * @throws NullPointerException if <code>svalue</code>
    136     *  is <code>null</code>
    137     */
    138    protected List<String> parseElements(String svalue) {
    139        // Validate the passed argument
    140        Objects.requireNonNull(svalue, "svalue");
    141        // Trim any matching '{' and '}' delimiters
    142        svalue = svalue.trim();
    143        if (svalue.startsWith("{") && svalue.endsWith("}")) {
    144            svalue = svalue.substring(1, svalue.length() - 1);
    145        }
    146        try {
    147
    148            // Set up a StreamTokenizer on the characters in this String
    149            final StreamTokenizer st = new StreamTokenizer(new StringReader(svalue));
    150            st.whitespaceChars(',', ','); // Commas are delimiters
    151            st.ordinaryChars('0', '9'); // Needed to turn off numeric flag
    152            st.ordinaryChars('.', '.');
    153            st.ordinaryChars('-', '-');
    154            st.wordChars('0', '9'); // Needed to make part of tokens
    155            st.wordChars('.', '.');
    156            st.wordChars('-', '-');
    157            // Split comma-delimited tokens into a List
    158            final ArrayList<String> list = new ArrayList<>();
    159            while (true) {
    160                final int ttype = st.nextToken();
    161                if (ttype == StreamTokenizer.TT_WORD || ttype > 0) {
    162                    list.add(st.sval);
    163                } else if (ttype == StreamTokenizer.TT_EOF) {
    164                    break;
    165                } else {
    166                    throw new ConversionException("Encountered token of type " + ttype);
    167                }
    168            }
    169            // Return the completed list
    170            return list;
    171        } catch (final IOException e) {
    172            throw new ConversionException(e);
    173        }
    174    }
    175
    176}