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 }