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 java.beans.IndexedPropertyDescriptor;
020import java.beans.PropertyDescriptor;
021
022import org.apache.commons.jxpath.JXPathBeanInfo;
023import org.apache.commons.jxpath.JXPathContext;
024import org.apache.commons.jxpath.JXPathInvalidAccessException;
025import org.apache.commons.jxpath.ri.model.NodePointer;
026import org.apache.commons.jxpath.util.ValueUtils;
027
028/**
029 * Pointer pointing to a property of a JavaBean.
030 *
031 * @author Dmitri Plotnikov
032 * @version $Revision: 1133499 $ $Date: 2011-06-08 20:19:50 +0200 (Mi, 08 Jun 2011) $
033 */
034public class BeanPropertyPointer extends PropertyPointer {
035    private static final long serialVersionUID = -6008991447676468786L;
036
037    private static final Object UNINITIALIZED = new Object();
038
039    private String propertyName;
040    private JXPathBeanInfo beanInfo;
041    private Object baseValue = UNINITIALIZED;
042    private Object value = UNINITIALIZED;
043    private transient String[] names;
044    private transient PropertyDescriptor[] propertyDescriptors;
045    private transient PropertyDescriptor propertyDescriptor;
046
047    /**
048     * Create a new BeanPropertyPointer.
049     * @param parent parent pointer
050     * @param beanInfo describes the target property/ies.
051     */
052    public BeanPropertyPointer(NodePointer parent, JXPathBeanInfo beanInfo) {
053        super(parent);
054        this.beanInfo = beanInfo;
055    }
056
057    /**
058     * This type of node is auxiliary.
059     * @return true
060     */
061    public boolean isContainer() {
062        return true;
063    }
064
065    public int getPropertyCount() {
066        if (beanInfo.isAtomic()) {
067            return 0;
068        }
069        return getPropertyDescriptors().length;
070    }
071
072    /**
073     * Get the names of all properties, sorted alphabetically
074     * @return String[]
075     */
076    public String[] getPropertyNames() {
077        if (names == null) {
078            PropertyDescriptor[] pds = getPropertyDescriptors();
079            names = new String[pds.length];
080            for (int i = 0; i < names.length; i++) {
081                names[i] = pds[i].getName();
082            }
083        }
084        return names;
085    }
086
087    /**
088     * Select a property by name.
089     * @param propertyName String name
090     */
091    public void setPropertyName(String propertyName) {
092        setPropertyIndex(UNSPECIFIED_PROPERTY);
093        this.propertyName = propertyName;
094    }
095
096    /**
097     * Selects a property by its offset in the alphabetically sorted list.
098     * @param index property index
099     */
100    public void setPropertyIndex(int index) {
101        if (propertyIndex != index) {
102            super.setPropertyIndex(index);
103            propertyName = null;
104            propertyDescriptor = null;
105            baseValue = UNINITIALIZED;
106            value = UNINITIALIZED;
107        }
108    }
109
110    /**
111     * Get the value of the currently selected property.
112     * @return Object value
113     */
114    public Object getBaseValue() {
115        if (baseValue == UNINITIALIZED) {
116            PropertyDescriptor pd = getPropertyDescriptor();
117            if (pd == null) {
118                return null;
119            }
120            baseValue = ValueUtils.getValue(getBean(), pd);
121        }
122        return baseValue;
123    }
124
125    public void setIndex(int index) {
126        if (this.index == index) {
127            return;
128        }
129        // When dealing with a scalar, index == 0 is equivalent to
130        // WHOLE_COLLECTION, so do not change it.
131        if (this.index != WHOLE_COLLECTION
132                || index != 0
133                || isCollection()) {
134            super.setIndex(index);
135            value = UNINITIALIZED;
136        }
137    }
138
139    /**
140     * If index == WHOLE_COLLECTION, the value of the property, otherwise
141     * the value of the index'th element of the collection represented by the
142     * property. If the property is not a collection, index should be zero
143     * and the value will be the property itself.
144     * @return Object
145     */
146    public Object getImmediateNode() {
147        if (value == UNINITIALIZED) {
148            if (index == WHOLE_COLLECTION) {
149                value = ValueUtils.getValue(getBaseValue());
150            }
151            else {
152                PropertyDescriptor pd = getPropertyDescriptor();
153                if (pd == null) {
154                    value = null;
155                }
156                else {
157                    value = ValueUtils.getValue(getBean(), pd, index);
158                }
159            }
160        }
161        return value;
162    }
163
164    protected boolean isActualProperty() {
165        return getPropertyDescriptor() != null;
166    }
167
168    public boolean isCollection() {
169        PropertyDescriptor pd = getPropertyDescriptor();
170        if (pd == null) {
171            return false;
172        }
173
174        if (pd instanceof IndexedPropertyDescriptor) {
175            return true;
176        }
177
178        int hint = ValueUtils.getCollectionHint(pd.getPropertyType());
179        if (hint == -1) {
180            return false;
181        }
182        if (hint == 1) {
183            return true;
184        }
185
186        Object value = getBaseValue();
187        return value != null && ValueUtils.isCollection(value);
188    }
189
190    /**
191     * If the property contains a collection, then the length of that
192     * collection, otherwise - 1.
193     * @return int length
194     */
195    public int getLength() {
196        PropertyDescriptor pd = getPropertyDescriptor();
197        if (pd == null) {
198            return 1;
199        }
200
201        if (pd instanceof IndexedPropertyDescriptor) {
202            return ValueUtils.getIndexedPropertyLength(
203                getBean(),
204                (IndexedPropertyDescriptor) pd);
205        }
206
207        int hint = ValueUtils.getCollectionHint(pd.getPropertyType());
208        if (hint == -1) {
209            return 1;
210        }
211        return super.getLength();
212    }
213
214    /**
215     * If index == WHOLE_COLLECTION, change the value of the property, otherwise
216     * change the value of the index'th element of the collection
217     * represented by the property.
218     * @param value value to set
219     */
220    public void setValue(Object value) {
221        PropertyDescriptor pd = getPropertyDescriptor();
222        if (pd == null) {
223            throw new JXPathInvalidAccessException(
224                "Cannot set property: " + asPath() + " - no such property");
225        }
226
227        if (index == WHOLE_COLLECTION) {
228            ValueUtils.setValue(getBean(), pd, value);
229        }
230        else {
231            ValueUtils.setValue(getBean(), pd, index, value);
232        }
233        this.value = value;
234    }
235
236    public NodePointer createPath(JXPathContext context) {
237        if (getImmediateNode() == null) {
238            super.createPath(context);
239            baseValue = UNINITIALIZED;
240            value = UNINITIALIZED;
241        }
242        return this;
243    }
244
245    public void remove() {
246        if (index == WHOLE_COLLECTION) {
247            setValue(null);
248        }
249        else if (isCollection()) {
250            Object o = getBaseValue();
251            Object collection = ValueUtils.remove(getBaseValue(), index);
252            if (collection != o) {
253                ValueUtils.setValue(getBean(), getPropertyDescriptor(), collection);
254            }
255        }
256        else if (index == 0) {
257            index = WHOLE_COLLECTION;
258            setValue(null);
259        }
260    }
261
262    /**
263     * Get the name of the currently selected property.
264     * @return String property name
265     */
266    public String getPropertyName() {
267        if (propertyName == null) {
268            PropertyDescriptor pd = getPropertyDescriptor();
269            if (pd != null) {
270                propertyName = pd.getName();
271            }
272        }
273        return propertyName != null ? propertyName : "*";
274    }
275
276    /**
277     * Finds the property descriptor corresponding to the current property
278     * index.
279     * @return PropertyDescriptor
280     */
281    private PropertyDescriptor getPropertyDescriptor() {
282        if (propertyDescriptor == null) {
283            int inx = getPropertyIndex();
284            if (inx == UNSPECIFIED_PROPERTY) {
285                propertyDescriptor =
286                    beanInfo.getPropertyDescriptor(propertyName);
287            }
288            else {
289                PropertyDescriptor[] propertyDescriptors =
290                    getPropertyDescriptors();
291                if (inx >= 0 && inx < propertyDescriptors.length) {
292                    propertyDescriptor = propertyDescriptors[inx];
293                }
294                else {
295                    propertyDescriptor = null;
296                }
297            }
298        }
299        return propertyDescriptor;
300    }
301
302    /**
303     * Get all PropertyDescriptors.
304     * @return PropertyDescriptor[]
305     */
306    protected synchronized PropertyDescriptor[] getPropertyDescriptors() {
307        if (propertyDescriptors == null) {
308            propertyDescriptors = beanInfo.getPropertyDescriptors();
309        }
310        return propertyDescriptors;
311    }
312}