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.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 RuntimeException("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 RuntimeException("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 RuntimeException("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 RuntimeException("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}