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