    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}