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 *      https://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}