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.dynabeans;
018
019 import java.util.Arrays;
020
021 import org.apache.commons.beanutils.DynaBean;
022 import org.apache.commons.beanutils.DynaClass;
023 import org.apache.commons.beanutils.DynaProperty;
024 import org.apache.commons.jxpath.JXPathTypeConversionException;
025 import org.apache.commons.jxpath.ri.model.NodePointer;
026 import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
027 import org.apache.commons.jxpath.util.TypeUtils;
028 import org.apache.commons.jxpath.util.ValueUtils;
029
030 /**
031 * Pointer pointing to a property of a {@link DynaBean}. If the target DynaBean is
032 * Serializable, so should this instance be.
033 *
034 * @author Dmitri Plotnikov
035 * @version $Revision: 668329 $ $Date: 2008-06-16 16:59:48 -0500 (Mon, 16 Jun 2008) $
036 */
037 public class DynaBeanPropertyPointer extends PropertyPointer {
038 private DynaBean dynaBean;
039 private String name;
040 private String[] names;
041
042 private static final long serialVersionUID = 2094421509141267239L;
043
044 /**
045 * Create a new DynaBeanPropertyPointer.
046 * @param parent pointer
047 * @param dynaBean pointed
048 */
049 public DynaBeanPropertyPointer(NodePointer parent, DynaBean dynaBean) {
050 super(parent);
051 this.dynaBean = dynaBean;
052 }
053
054 public Object getBaseValue() {
055 return dynaBean.get(getPropertyName());
056 }
057
058 /**
059 * This type of node is auxiliary.
060 * @return true
061 */
062 public boolean isContainer() {
063 return true;
064 }
065
066 public int getPropertyCount() {
067 return getPropertyNames().length;
068 }
069
070 public String[] getPropertyNames() {
071 /* @todo do something about the sorting - LIKE WHAT? - MJB */
072 if (names == null) {
073 DynaClass dynaClass = dynaBean.getDynaClass();
074 DynaProperty[] properties = dynaClass.getDynaProperties();
075 int count = properties.length;
076 boolean hasClass = dynaClass.getDynaProperty("class") != null;
077 if (hasClass) {
078 count--; // Exclude "class" from properties
079 }
080 names = new String[count];
081 for (int i = 0, j = 0; i < properties.length; i++) {
082 String name = properties[i].getName();
083 if (!hasClass || !name.equals("class")) {
084 names[j++] = name;
085 }
086 }
087 Arrays.sort(names);
088 }
089 return names;
090 }
091
092 /**
093 * Returns the name of the currently selected property or "*"
094 * if none has been selected.
095 * @return String
096 */
097 public String getPropertyName() {
098 if (name == null) {
099 String[] names = getPropertyNames();
100 name = propertyIndex >= 0 && propertyIndex < names.length ? names[propertyIndex] : "*";
101 }
102 return name;
103 }
104
105 /**
106 * Select a property by name.
107 * @param propertyName to select
108 */
109 public void setPropertyName(String propertyName) {
110 setPropertyIndex(UNSPECIFIED_PROPERTY);
111 this.name = propertyName;
112 }
113
114 /**
115 * Index of the currently selected property in the list of all
116 * properties sorted alphabetically.
117 * @return int
118 */
119 public int getPropertyIndex() {
120 if (propertyIndex == UNSPECIFIED_PROPERTY) {
121 String[] names = getPropertyNames();
122 for (int i = 0; i < names.length; i++) {
123 if (names[i].equals(name)) {
124 propertyIndex = i;
125 name = null;
126 break;
127 }
128 }
129 }
130 return super.getPropertyIndex();
131 }
132
133 /**
134 * Index a property by its index in the list of all
135 * properties sorted alphabetically.
136 * @param index to set
137 */
138 public void setPropertyIndex(int index) {
139 if (propertyIndex != index) {
140 super.setPropertyIndex(index);
141 name = null;
142 }
143 }
144
145 /**
146 * If index == WHOLE_COLLECTION, the value of the property, otherwise
147 * the value of the index'th element of the collection represented by the
148 * property. If the property is not a collection, index should be zero
149 * and the value will be the property itself.
150 * @return Object
151 */
152 public Object getImmediateNode() {
153 String name = getPropertyName();
154 if (name.equals("*")) {
155 return null;
156 }
157
158 Object value;
159 if (index == WHOLE_COLLECTION) {
160 value = ValueUtils.getValue(dynaBean.get(name));
161 }
162 else if (isIndexedProperty()) {
163 // DynaClass at this point is not based on whether
164 // the property is indeed indexed, but rather on
165 // whether it is an array or List. Therefore
166 // the indexed set may fail.
167 try {
168 value = ValueUtils.getValue(dynaBean.get(name, index));
169 }
170 catch (ArrayIndexOutOfBoundsException ex) {
171 value = null;
172 }
173 catch (IllegalArgumentException ex) {
174 value = dynaBean.get(name);
175 value = ValueUtils.getValue(value, index);
176 }
177 }
178 else {
179 value = dynaBean.get(name);
180 if (ValueUtils.isCollection(value)) {
181 value = ValueUtils.getValue(value, index);
182 }
183 else if (index != 0) {
184 value = null;
185 }
186 }
187 return value;
188 }
189
190 /**
191 * Returns true if the bean has the currently selected property.
192 * @return boolean
193 */
194 protected boolean isActualProperty() {
195 DynaClass dynaClass = dynaBean.getDynaClass();
196 return dynaClass.getDynaProperty(getPropertyName()) != null;
197 }
198
199 /**
200 * Learn whether the property referenced is an indexed property.
201 * @return boolean
202 */
203 protected boolean isIndexedProperty() {
204 DynaClass dynaClass = dynaBean.getDynaClass();
205 DynaProperty property = dynaClass.getDynaProperty(name);
206 return property.isIndexed();
207 }
208
209 /**
210 * If index == WHOLE_COLLECTION, change the value of the property, otherwise
211 * change the value of the index'th element of the collection
212 * represented by the property.
213 * @param value to set
214 */
215 public void setValue(Object value) {
216 setValue(index, value);
217 }
218
219 public void remove() {
220 if (index == WHOLE_COLLECTION) {
221 dynaBean.set(getPropertyName(), null);
222 }
223 else if (isIndexedProperty()) {
224 dynaBean.set(getPropertyName(), index, null);
225 }
226 else if (isCollection()) {
227 Object collection = ValueUtils.remove(getBaseValue(), index);
228 dynaBean.set(getPropertyName(), collection);
229 }
230 else if (index == 0) {
231 dynaBean.set(getPropertyName(), null);
232 }
233 }
234
235 /**
236 * Set an indexed value.
237 * @param index to change
238 * @param value to set
239 */
240 private void setValue(int index, Object value) {
241 if (index == WHOLE_COLLECTION) {
242 dynaBean.set(getPropertyName(), convert(value, false));
243 }
244 else if (isIndexedProperty()) {
245 dynaBean.set(getPropertyName(), index, convert(value, true));
246 }
247 else {
248 Object baseValue = dynaBean.get(getPropertyName());
249 ValueUtils.setValue(baseValue, index, value);
250 }
251 }
252
253
254 /**
255 * Convert a value to the appropriate property type.
256 * @param value to convert
257 * @param element whether this should be a collection element.
258 * @return conversion result
259 */
260 private Object convert(Object value, boolean element) {
261 DynaClass dynaClass = (DynaClass) dynaBean.getDynaClass();
262 DynaProperty property = dynaClass.getDynaProperty(getPropertyName());
263 Class type = property.getType();
264 if (element) {
265 if (type.isArray()) {
266 type = type.getComponentType();
267 }
268 else {
269 return value; // No need to convert
270 }
271 }
272
273 try {
274 return TypeUtils.convert(value, type);
275 }
276 catch (Exception ex) {
277 String string = value == null ? "null" : value.getClass().getName();
278 throw new JXPathTypeConversionException(
279 "Cannot convert value of class " + string + " to type "
280 + type, ex);
281 }
282 }
283 }