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 java.beans.IndexedPropertyDescriptor;
020 import java.beans.PropertyDescriptor;
021
022 import org.apache.commons.jxpath.JXPathBeanInfo;
023 import org.apache.commons.jxpath.JXPathContext;
024 import org.apache.commons.jxpath.JXPathInvalidAccessException;
025 import org.apache.commons.jxpath.ri.model.NodePointer;
026 import 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: 670727 $ $Date: 2008-06-23 15:10:38 -0500 (Mon, 23 Jun 2008) $
033 */
034 public 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 ValueUtils.getLength(getBaseValue());
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 }