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