    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.beanutils2.sql;
    019
    020import java.sql.SQLException;
    021import java.util.Iterator;
    022import java.util.NoSuchElementException;
    023
    024import org.apache.commons.beanutils2.ConversionException;
    025import org.apache.commons.beanutils2.DynaBean;
    026import org.apache.commons.beanutils2.DynaClass;
    027
    028/**
    029 * <p>
    030 * Implements {@link Iterator} returned by the {@code iterator()} method of {@link ResultSetDynaClass}. Each object returned by this iterator will be a
    031 * {@link DynaBean} that represents a single row from the result set being wrapped.
    032 * </p>
    033 */
    034public class ResultSetIterator implements DynaBean, Iterator<DynaBean> {
    035
    036    /**
    037     * <p>
    038     * Flag indicating whether the result set is currently positioned at a row for which we have not yet returned an element in the iteration.
    039     * </p>
    040     */
    041    protected boolean current;
    042
    043    /**
    044     * <p>
    045     * The {@link ResultSetDynaClass} we are associated with.
    046     * </p>
    047     */
    048    protected ResultSetDynaClass dynaClass;
    049
    050    /**
    051     * <p>
    052     * Flag indicating whether the result set has indicated that there are no further rows.
    053     * </p>
    054     */
    055    protected boolean eof;
    056
    057    /**
    058     * <p>
    059     * Constructs an {@code Iterator} for the result set being wrapped by the specified {@link ResultSetDynaClass}.
    060     * </p>
    061     *
    062     * @param dynaClass The {@link ResultSetDynaClass} wrapping the result set we will iterate over
    063     */
    064    ResultSetIterator(final ResultSetDynaClass dynaClass) {
    065        this.dynaClass = dynaClass;
    066    }
    067
    068    /**
    069     * <p>
    070     * Advance the result set to the next row, if there is not a current row (and if we are not already at eof).
    071     * </p>
    072     *
    073     * @throws SQLException if the result set throws an exception
    074     */
    075    @SuppressWarnings("resource") // getResultSet() does not allocate.
    076    protected void advance() throws SQLException {
    077        if (!current && !eof) {
    078            if (dynaClass.getResultSet().next()) {
    079                current = true;
    080                eof = false;
    081            } else {
    082                current = false;
    083                eof = true;
    084            }
    085        }
    086    }
    087
    088    /**
    089     * Does the specified mapped property contain a value for the specified key value?
    090     *
    091     * @param name Name of the property to check
    092     * @param key  Name of the key to check
    093     * @return {@code true} if the mapped property contains a value for the specified key, otherwise {@code false}
    094     * @throws IllegalArgumentException if there is no property of the specified name
    095     */
    096    @Override
    097    public boolean contains(final String name, final String key) {
    098        throw new UnsupportedOperationException("FIXME - mapped properties not currently supported");
    099    }
    100
    101    /**
    102     * Gets the value of a simple property with the specified name.
    103     *
    104     * @param name Name of the property whose value is to be retrieved
    105     * @return The property's value
    106     * @throws IllegalArgumentException if there is no property of the specified name
    107     */
    108    @Override
    109    public Object get(final String name) {
    110        if (dynaClass.getDynaProperty(name) == null) {
    111            throw new IllegalArgumentException(name);
    112        }
    113        try {
    114            return dynaClass.getObjectFromResultSet(name);
    115        } catch (final SQLException e) {
    116            throw new IllegalArgumentException("get(" + name + "): SQLException: " + e);
    117        }
    118    }
    119
    120    /**
    121     * Gets the value of an indexed property with the specified name.
    122     *
    123     * @param name  Name of the property whose value is to be retrieved
    124     * @param index Index of the value to be retrieved
    125     * @return The indexed property's value
    126     * @throws IllegalArgumentException  if there is no property of the specified name
    127     * @throws IllegalArgumentException  if the specified property exists, but is not indexed
    128     * @throws IndexOutOfBoundsException if the specified index is outside the range of the underlying property
    129     * @throws NullPointerException      if no array or List has been initialized for this property
    130     */
    131    @Override
    132    public Object get(final String name, final int index) {
    133        throw new UnsupportedOperationException("FIXME - indexed properties not currently supported");
    134    }
    135
    136    /**
    137     * Gets the value of a mapped property with the specified name, or {@code null} if there is no value for the specified key.
    138     *
    139     * @param name Name of the property whose value is to be retrieved
    140     * @param key  Key of the value to be retrieved
    141     * @return The mapped property's value
    142     * @throws IllegalArgumentException if there is no property of the specified name
    143     * @throws IllegalArgumentException if the specified property exists, but is not mapped
    144     */
    145    @Override
    146    public Object get(final String name, final String key) {
    147        throw new UnsupportedOperationException("FIXME - mapped properties not currently supported");
    148    }
    149
    150    /**
    151     * Gets the {@code DynaClass} instance that describes the set of properties available for this DynaBean.
    152     *
    153     * @return The associated DynaClass
    154     */
    155    @Override
    156    public DynaClass getDynaClass() {
    157        return this.dynaClass;
    158    }
    159
    160    /**
    161     * <p>
    162     * Gets {@code true} if the iteration has more elements.
    163     * </p>
    164     *
    165     * @return {@code true} if the result set has another row, otherwise {@code false}
    166     */
    167    @Override
    168    public boolean hasNext() {
    169        try {
    170            advance();
    171            return !eof;
    172        } catch (final SQLException e) {
    173            throw new IllegalStateException("hasNext():  SQLException:  " + e);
    174        }
    175    }
    176
    177    /**
    178     * <p>
    179     * Gets the next element in the iteration.
    180     * </p>
    181     *
    182     * @return advance to the new row and return this
    183     */
    184    @Override
    185    public DynaBean next() {
    186        try {
    187            advance();
    188            if (eof) {
    189                throw new NoSuchElementException();
    190            }
    191            current = false;
    192            return this;
    193        } catch (final SQLException e) {
    194            throw new IllegalStateException("next():  SQLException:  " + e);
    195        }
    196    }
    197
    198    /**
    199     * <p>
    200     * Remove the current element from the iteration. This method is not supported.
    201     * </p>
    202     */
    203    @Override
    204    public void remove() {
    205        throw new UnsupportedOperationException("remove()");
    206    }
    207
    208    /**
    209     * Remove any existing value for the specified key on the specified mapped property.
    210     *
    211     * @param name Name of the property for which a value is to be removed
    212     * @param key  Key of the value to be removed
    213     * @throws IllegalArgumentException if there is no property of the specified name
    214     */
    215    @Override
    216    public void remove(final String name, final String key) {
    217        throw new UnsupportedOperationException("FIXME - mapped operations not currently supported");
    218    }
    219
    220    /**
    221     * Sets the value of an indexed property with the specified name.
    222     *
    223     * @param name  Name of the property whose value is to be set
    224     * @param index Index of the property to be set
    225     * @param value Value to which this property is to be set
    226     * @throws ConversionException       if the specified value cannot be converted to the type required for this property
    227     * @throws IllegalArgumentException  if there is no property of the specified name
    228     * @throws IllegalArgumentException  if the specified property exists, but is not indexed
    229     * @throws IndexOutOfBoundsException if the specified index is outside the range of the underlying property
    230     */
    231    @Override
    232    public void set(final String name, final int index, final Object value) {
    233        throw new UnsupportedOperationException("FIXME - indexed properties not currently supported");
    234    }
    235
    236    /**
    237     * Sets the value of a simple property with the specified name.
    238     *
    239     * @param name  Name of the property whose value is to be set
    240     * @param value Value to which this property is to be set
    241     * @throws ConversionException      if the specified value cannot be converted to the type required for this property
    242     * @throws IllegalArgumentException if there is no property of the specified name
    243     * @throws NullPointerException     if an attempt is made to set a primitive property to null
    244     */
    245    @SuppressWarnings("resource") // getResultSet() does not allocate.
    246    @Override
    247    public void set(final String name, final Object value) {
    248        if (dynaClass.getDynaProperty(name) == null) {
    249            throw new IllegalArgumentException(name);
    250        }
    251        try {
    252            dynaClass.getResultSet().updateObject(name, value);
    253        } catch (final SQLException e) {
    254            throw new IllegalArgumentException("set(" + name + "): SQLException: " + e);
    255        }
    256    }
    257
    258    /**
    259     * Sets the value of a mapped property with the specified name.
    260     *
    261     * @param name  Name of the property whose value is to be set
    262     * @param key   Key of the property to be set
    263     * @param value Value to which this property is to be set
    264     * @throws ConversionException      if the specified value cannot be converted to the type required for this property
    265     * @throws IllegalArgumentException if there is no property of the specified name
    266     * @throws IllegalArgumentException if the specified property exists, but is not mapped
    267     */
    268    @Override
    269    public void set(final String name, final String key, final Object value) {
    270        throw new UnsupportedOperationException("FIXME - mapped properties not currently supported");
    271    }
    272
    273}