    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.dynamic;
    018
    019    import java.util.Arrays;
    020    import java.util.Map;
    021
    022    import org.apache.commons.jxpath.AbstractFactory;
    023    import org.apache.commons.jxpath.DynamicPropertyHandler;
    024    import org.apache.commons.jxpath.JXPathAbstractFactoryException;
    025    import org.apache.commons.jxpath.JXPathContext;
    026    import org.apache.commons.jxpath.JXPathInvalidAccessException;
    027    import org.apache.commons.jxpath.ri.model.NodePointer;
    028    import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
    029    import org.apache.commons.jxpath.util.ValueUtils;
    030
    031    /**
    032     * Pointer pointing to a property of an object with dynamic properties.
    033     *
    034     * @author Dmitri Plotnikov
    035     * @version $Revision: 652845 $ $Date: 2008-05-02 12:46:46 -0500 (Fri, 02 May 2008) $
    036     */
    037    public class DynamicPropertyPointer extends PropertyPointer {
    038
    039        private static final long serialVersionUID = -5720585681149150822L;
    040
    041        private DynamicPropertyHandler handler;
    042        private String name;
    043        private String[] names;
    044        private String requiredPropertyName;
    045
    046        /**
    047         * Create a new DynamicPropertyPointer.
    048         * @param parent pointer
    049         * @param handler DynamicPropertyHandler
    050         */
    051        public DynamicPropertyPointer(NodePointer parent,
    052                DynamicPropertyHandler handler) {
    053            super(parent);
    054            this.handler = handler;
    055        }
    056
    057        /**
    058         * This type of node is auxiliary.
    059         * @return true
    060         */
    061        public boolean isContainer() {
    062            return true;
    063        }
    064
    065        /**
    066         * Number of the DP object's properties.
    067         * @return int
    068         */
    069        public int getPropertyCount() {
    070            return getPropertyNames().length;
    071        }
    072
    073        /**
    074         * Names of all properties, sorted alphabetically.
    075         * @return String[]
    076         */
    077        public String[] getPropertyNames() {
    078            if (names == null) {
    079                String[] allNames = handler.getPropertyNames(getBean());
    080                names = new String[allNames.length];
    081                for (int i = 0; i < names.length; i++) {
    082                    names[i] = allNames[i];
    083                }
    084                Arrays.sort(names);
    085                if (requiredPropertyName != null) {
    086                    int inx = Arrays.binarySearch(names, requiredPropertyName);
    087                    if (inx < 0) {
    088                        allNames = names;
    089                        names = new String[allNames.length + 1];
    090                        names[0] = requiredPropertyName;
    091                        System.arraycopy(allNames, 0, names, 1, allNames.length);
    092                        Arrays.sort(names);
    093                    }
    094                }
    095            }
    096            return names;
    097        }
    098
    099        /**
    100         * Returns the name of the currently selected property or "*"
    101         * if none has been selected.
    102         * @return String
    103         */
    104        public String getPropertyName() {
    105            if (name == null) {
    106                String[] names = getPropertyNames();
    107                name = propertyIndex >= 0 && propertyIndex < names.length ? names[propertyIndex] : "*";
    108            }
    109            return name;
    110        }
    111
    112        /**
    113         * Select a property by name.  If the supplied name is
    114         * not one of the object's existing properties, it implicitly
    115         * adds this name to the object's property name list. It does not
    116         * set the property value though. In order to set the property
    117         * value, call setValue().
    118         * @param propertyName to set
    119         */
    120        public void setPropertyName(String propertyName) {
    121            setPropertyIndex(UNSPECIFIED_PROPERTY);
    122            this.name = propertyName;
    123            requiredPropertyName = propertyName;
    124            if (names != null && Arrays.binarySearch(names, propertyName) < 0) {
    125                names = null;
    126            }
    127        }
    128
    129        /**
    130         * Index of the currently selected property in the list of all
    131         * properties sorted alphabetically.
    132         * @return int
    133         */
    134        public int getPropertyIndex() {
    135            if (propertyIndex == UNSPECIFIED_PROPERTY) {
    136                String[] names = getPropertyNames();
    137                for (int i = 0; i < names.length; i++) {
    138                    if (names[i].equals(name)) {
    139                        setPropertyIndex(i);
    140                        break;
    141                    }
    142                }
    143            }
    144            return super.getPropertyIndex();
    145        }
    146
    147        /**
    148         * Index a property by its index in the list of all
    149         * properties sorted alphabetically.
    150         * @param index to set
    151         */
    152        public void setPropertyIndex(int index) {
    153            if (propertyIndex != index) {
    154                super.setPropertyIndex(index);
    155                name = null;
    156            }
    157        }
    158
    159        /**
    160         * Returns the value of the property, not an element of the collection
    161         * represented by the property, if any.
    162         * @return Object
    163         */
    164        public Object getBaseValue() {
    165            return handler.getProperty(getBean(), getPropertyName());
    166        }
    167
    168        /**
    169         * If index == WHOLE_COLLECTION, the value of the property, otherwise
    170         * the value of the index'th element of the collection represented by the
    171         * property. If the property is not a collection, index should be zero
    172         * and the value will be the property itself.
    173         * @return Object
    174         */
    175        public Object getImmediateNode() {
    176            Object value;
    177            if (index == WHOLE_COLLECTION) {
    178                value = ValueUtils.getValue(handler.getProperty(
    179                        getBean(),
    180                        getPropertyName()));
    181            }
    182            else {
    183                value = ValueUtils.getValue(handler.getProperty(
    184                        getBean(),
    185                        getPropertyName()), index);
    186            }
    187            return value;
    188        }
    189
    190        /**
    191         * A dynamic property is always considered actual - all keys are apparently
    192         * existing with possibly the value of null.
    193         * @return boolean
    194         */
    195        protected boolean isActualProperty() {
    196            return true;
    197        }
    198
    199        /**
    200         * If index == WHOLE_COLLECTION, change the value of the property, otherwise
    201         * change the value of the index'th element of the collection
    202         * represented by the property.
    203         * @param value to set
    204         */
    205        public void setValue(Object value) {
    206            if (index == WHOLE_COLLECTION) {
    207                handler.setProperty(getBean(), getPropertyName(), value);
    208            }
    209            else {
    210                ValueUtils.setValue(
    211                    handler.getProperty(getBean(), getPropertyName()),
    212                    index,
    213                    value);
    214            }
    215        }
    216
    217        public NodePointer createPath(JXPathContext context) {
    218            // Ignore the name passed to us, use our own data
    219            Object collection = getBaseValue();
    220            if (collection == null) {
    221                AbstractFactory factory = getAbstractFactory(context);
    222                boolean success =
    223                    factory.createObject(
    224                        context,
    225                        this,
    226                        getBean(),
    227                        getPropertyName(),
    228                        0);
    229                if (!success) {
    230                    throw new JXPathAbstractFactoryException(
    231                        "Factory could not create an object for path: " + asPath());
    232                }
    233                collection = getBaseValue();
    234            }
    235
    236            if (index != WHOLE_COLLECTION) {
    237                if (index < 0) {
    238                    throw new JXPathInvalidAccessException("Index is less than 1: "
    239                            + asPath());
    240                }
    241
    242                if (index >= getLength()) {
    243                    collection = ValueUtils.expandCollection(collection, index + 1);
    244                    handler.setProperty(getBean(), getPropertyName(), collection);
    245                }
    246            }
    247
    248            return this;
    249        }
    250
    251        public NodePointer createPath(JXPathContext context, Object value) {
    252            if (index == WHOLE_COLLECTION) {
    253                handler.setProperty(getBean(), getPropertyName(), value);
    254            }
    255            else {
    256                createPath(context);
    257                ValueUtils.setValue(getBaseValue(), index, value);
    258            }
    259            return this;
    260        }
    261
    262        public void remove() {
    263            if (index == WHOLE_COLLECTION) {
    264                removeKey();
    265            }
    266            else if (isCollection()) {
    267                Object collection = ValueUtils.remove(getBaseValue(), index);
    268                handler.setProperty(getBean(), getPropertyName(), collection);
    269            }
    270            else if (index == 0) {
    271                removeKey();
    272            }
    273        }
    274
    275        /**
    276         * Remove the current property.
    277         */
    278        private void removeKey() {
    279            Object bean = getBean();
    280            if (bean instanceof Map) {
    281                ((Map) bean).remove(getPropertyName());
    282            }
    283            else {
    284                handler.setProperty(bean, getPropertyName(), null);
    285            }
    286        }
    287
    288        public String asPath() {
    289            StringBuffer buffer = new StringBuffer();
    290            buffer.append(getImmediateParentPointer().asPath());
    291            if (buffer.length() == 0) {
    292                buffer.append("/.");
    293            }
    294            else if (buffer.charAt(buffer.length() - 1) == '/') {
    295                buffer.append('.');
    296            }
    297            buffer.append("[@name='");
    298            buffer.append(escape(getPropertyName()));
    299            buffer.append("']");
    300            if (index != WHOLE_COLLECTION && isCollection()) {
    301                buffer.append('[').append(index + 1).append(']');
    302            }
    303            return buffer.toString();
    304        }
    305
    306    }