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