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.jxpath.ri.model.beans;
019
020import org.apache.commons.jxpath.JXPathException;
021import org.apache.commons.jxpath.ri.model.NodeIterator;
022import org.apache.commons.jxpath.ri.model.NodePointer;
023
024/**
025 * Iterates property values of an object pointed at with a {@link PropertyOwnerPointer}. Examples of such objects are JavaBeans and objects with Dynamic
026 * Properties.
027 */
028public class PropertyIterator implements NodeIterator {
029
030    private boolean empty;
031    private final boolean reverse;
032    private final String name;
033    private int startIndex;
034    private boolean targetReady;
035    private int position;
036    private final PropertyPointer propertyNodePointer;
037    private int startPropertyIndex;
038    private boolean includeStart;
039
040    /**
041     * Constructs a new PropertyIterator.
042     *
043     * @param pointer   owning pointer
044     * @param name      property name
045     * @param reverse   iteration order
046     * @param startWith beginning pointer
047     */
048    public PropertyIterator(final PropertyOwnerPointer pointer, final String name, final boolean reverse, NodePointer startWith) {
049        propertyNodePointer = (PropertyPointer) pointer.getPropertyPointer().clone();
050        this.name = name;
051        this.reverse = reverse;
052        this.includeStart = true;
053        if (reverse) {
054            this.startPropertyIndex = PropertyPointer.UNSPECIFIED_PROPERTY;
055            this.startIndex = -1;
056        }
057        if (startWith != null) {
058            while (startWith != null && startWith.getImmediateParentPointer() != pointer) {
059                startWith = startWith.getImmediateParentPointer();
060            }
061            if (startWith == null) {
062                throw new JXPathException("PropertyIerator startWith parameter is " + "not a child of the supplied parent");
063            }
064            this.startPropertyIndex = ((PropertyPointer) startWith).getPropertyIndex();
065            this.startIndex = startWith.getIndex();
066            if (this.startIndex == NodePointer.WHOLE_COLLECTION) {
067                this.startIndex = 0;
068            }
069            this.includeStart = false;
070            if (reverse && startIndex == -1) {
071                this.includeStart = true;
072            }
073        }
074    }
075
076    /**
077     * Computes length for the current pointer - ignores any exceptions.
078     *
079     * @return length
080     */
081    private int getLength() {
082        int length;
083        try {
084            length = propertyNodePointer.getLength(); // TBD: cache length
085        } catch (final Throwable t) {
086            propertyNodePointer.handle(t);
087            length = 0;
088        }
089        return length;
090    }
091
092    @Override
093    public NodePointer getNodePointer() {
094        if (position == 0) {
095            if (name != null) {
096                if (!targetReady) {
097                    prepareForIndividualProperty(name);
098                }
099                // If there is no such property - return null
100                if (empty) {
101                    return null;
102                }
103            } else {
104                if (!setPosition(1)) {
105                    return null;
106                }
107                reset();
108            }
109        }
110        try {
111            return propertyNodePointer.getValuePointer();
112        } catch (final Throwable t) {
113            propertyNodePointer.handle(t);
114            final NullPropertyPointer npp = new NullPropertyPointer(propertyNodePointer.getImmediateParentPointer());
115            npp.setPropertyName(propertyNodePointer.getPropertyName());
116            npp.setIndex(propertyNodePointer.getIndex());
117            return npp.getValuePointer();
118        }
119    }
120
121    @Override
122    public int getPosition() {
123        return position;
124    }
125
126    /**
127     * Gets the property pointer.
128     *
129     * @return NodePointer
130     */
131    protected NodePointer getPropertyPointer() {
132        return propertyNodePointer;
133    }
134
135    /**
136     * Prepare for an individual property.
137     *
138     * @param name property name
139     */
140    protected void prepareForIndividualProperty(final String name) {
141        targetReady = true;
142        empty = true;
143        final String[] names = propertyNodePointer.getPropertyNames();
144        if (!reverse) {
145            if (startPropertyIndex == PropertyPointer.UNSPECIFIED_PROPERTY) {
146                startPropertyIndex = 0;
147            }
148            if (startIndex == NodePointer.WHOLE_COLLECTION) {
149                startIndex = 0;
150            }
151            for (int i = startPropertyIndex; i < names.length; i++) {
152                if (names[i].equals(name)) {
153                    propertyNodePointer.setPropertyIndex(i);
154                    if (i != startPropertyIndex) {
155                        startIndex = 0;
156                        includeStart = true;
157                    }
158                    empty = false;
159                    break;
160                }
161            }
162        } else {
163            if (startPropertyIndex == PropertyPointer.UNSPECIFIED_PROPERTY) {
164                startPropertyIndex = names.length - 1;
165            }
166            if (startIndex == NodePointer.WHOLE_COLLECTION) {
167                startIndex = -1;
168            }
169            for (int i = startPropertyIndex; i >= 0; i--) {
170                if (names[i].equals(name)) {
171                    propertyNodePointer.setPropertyIndex(i);
172                    if (i != startPropertyIndex) {
173                        startIndex = -1;
174                        includeStart = true;
175                    }
176                    empty = false;
177                    break;
178                }
179            }
180        }
181    }
182
183    /**
184     * Reset property iteration.
185     */
186    public void reset() {
187        position = 0;
188        targetReady = false;
189    }
190
191    @Override
192    public boolean setPosition(final int position) {
193        return name == null ? setPositionAllProperties(position) : setPositionIndividualProperty(position);
194    }
195
196    /**
197     * Sets position for all properties
198     *
199     * @param position int position
200     * @return whether this was a valid position
201     */
202    private boolean setPositionAllProperties(final int position) {
203        this.position = position;
204        if (position < 1) {
205            return false;
206        }
207        int offset;
208        final int count = propertyNodePointer.getPropertyCount();
209        if (!reverse) {
210            int index = 1;
211            for (int i = startPropertyIndex; i < count; i++) {
212                propertyNodePointer.setPropertyIndex(i);
213                int length = getLength();
214                if (i == startPropertyIndex) {
215                    length -= startIndex;
216                    if (!includeStart) {
217                        length--;
218                    }
219                    offset = startIndex + position - index;
220                    if (!includeStart) {
221                        offset++;
222                    }
223                } else {
224                    offset = position - index;
225                }
226                if (index <= position && position < index + length) {
227                    propertyNodePointer.setIndex(offset);
228                    return true;
229                }
230                index += length;
231            }
232        } else {
233            int index = 1;
234            int start = startPropertyIndex;
235            if (start == PropertyPointer.UNSPECIFIED_PROPERTY) {
236                start = count - 1;
237            }
238            for (int i = start; i >= 0; i--) {
239                propertyNodePointer.setPropertyIndex(i);
240                int length = getLength();
241                if (i == startPropertyIndex) {
242                    int end = startIndex;
243                    if (end == -1) {
244                        end = length - 1;
245                    }
246                    length = end + 1;
247                    offset = end - position + 1;
248                    if (!includeStart) {
249                        offset--;
250                        length--;
251                    }
252                } else {
253                    offset = length - (position - index) - 1;
254                }
255                if (index <= position && position < index + length) {
256                    propertyNodePointer.setIndex(offset);
257                    return true;
258                }
259                index += length;
260            }
261        }
262        return false;
263    }
264
265    /**
266     * Sets position for an individual property.
267     *
268     * @param position int position
269     * @return whether this was a valid position
270     */
271    private boolean setPositionIndividualProperty(final int position) {
272        this.position = position;
273        if (position < 1) {
274            return false;
275        }
276        if (!targetReady) {
277            prepareForIndividualProperty(name);
278        }
279        if (empty) {
280            return false;
281        }
282        final int length = getLength();
283        int index;
284        if (!reverse) {
285            index = position + startIndex;
286            if (!includeStart) {
287                index++;
288            }
289            if (index > length) {
290                return false;
291            }
292        } else {
293            int end = startIndex;
294            if (end == -1) {
295                end = length - 1;
296            }
297            index = end - position + 2;
298            if (!includeStart) {
299                index--;
300            }
301            if (index < 1) {
302                return false;
303            }
304        }
305        propertyNodePointer.setIndex(index - 1);
306        return true;
307    }
308}