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