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    package org.apache.commons.jxpath.ri.model.beans;
018    
019    import org.apache.commons.jxpath.JXPathException;
020    import org.apache.commons.jxpath.ri.model.NodeIterator;
021    import 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: 652845 $ $Date: 2008-05-02 12:46:46 -0500 (Fri, 02 May 2008) $
029     */
030    public 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 ex) {
124                // @todo: should this exception be reported in any way?
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                // @todo: should this exception be reported in any way?
322                length = 0;
323            }
324            return length;
325        }
326    }