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.dynamic; 018 019import java.util.Arrays; 020import java.util.Map; 021 022import org.apache.commons.jxpath.AbstractFactory; 023import org.apache.commons.jxpath.DynamicPropertyHandler; 024import org.apache.commons.jxpath.JXPathAbstractFactoryException; 025import org.apache.commons.jxpath.JXPathContext; 026import org.apache.commons.jxpath.JXPathInvalidAccessException; 027import org.apache.commons.jxpath.ri.model.NodePointer; 028import org.apache.commons.jxpath.ri.model.beans.PropertyPointer; 029import org.apache.commons.jxpath.util.ValueUtils; 030 031/** 032 * Pointer pointing to a property of an object with dynamic properties. 033 * 034 * @author Dmitri Plotnikov 035 * @version $Revision: 652845 $ $Date: 2008-05-02 19:46:46 +0200 (Fr, 02 Mai 2008) $ 036 */ 037public class DynamicPropertyPointer extends PropertyPointer { 038 039 private static final long serialVersionUID = -5720585681149150822L; 040 041 private DynamicPropertyHandler handler; 042 private String name; 043 private String[] names; 044 private String requiredPropertyName; 045 046 /** 047 * Create a new DynamicPropertyPointer. 048 * @param parent pointer 049 * @param handler DynamicPropertyHandler 050 */ 051 public DynamicPropertyPointer(NodePointer parent, 052 DynamicPropertyHandler handler) { 053 super(parent); 054 this.handler = handler; 055 } 056 057 /** 058 * This type of node is auxiliary. 059 * @return true 060 */ 061 public boolean isContainer() { 062 return true; 063 } 064 065 /** 066 * Number of the DP object's properties. 067 * @return int 068 */ 069 public int getPropertyCount() { 070 return getPropertyNames().length; 071 } 072 073 /** 074 * Names of all properties, sorted alphabetically. 075 * @return String[] 076 */ 077 public String[] getPropertyNames() { 078 if (names == null) { 079 String[] allNames = handler.getPropertyNames(getBean()); 080 names = new String[allNames.length]; 081 for (int i = 0; i < names.length; i++) { 082 names[i] = allNames[i]; 083 } 084 Arrays.sort(names); 085 if (requiredPropertyName != null) { 086 int inx = Arrays.binarySearch(names, requiredPropertyName); 087 if (inx < 0) { 088 allNames = names; 089 names = new String[allNames.length + 1]; 090 names[0] = requiredPropertyName; 091 System.arraycopy(allNames, 0, names, 1, allNames.length); 092 Arrays.sort(names); 093 } 094 } 095 } 096 return names; 097 } 098 099 /** 100 * Returns the name of the currently selected property or "*" 101 * if none has been selected. 102 * @return String 103 */ 104 public String getPropertyName() { 105 if (name == null) { 106 String[] names = getPropertyNames(); 107 name = propertyIndex >= 0 && propertyIndex < names.length ? names[propertyIndex] : "*"; 108 } 109 return name; 110 } 111 112 /** 113 * Select a property by name. If the supplied name is 114 * not one of the object's existing properties, it implicitly 115 * adds this name to the object's property name list. It does not 116 * set the property value though. In order to set the property 117 * value, call setValue(). 118 * @param propertyName to set 119 */ 120 public void setPropertyName(String propertyName) { 121 setPropertyIndex(UNSPECIFIED_PROPERTY); 122 this.name = propertyName; 123 requiredPropertyName = propertyName; 124 if (names != null && Arrays.binarySearch(names, propertyName) < 0) { 125 names = null; 126 } 127 } 128 129 /** 130 * Index of the currently selected property in the list of all 131 * properties sorted alphabetically. 132 * @return int 133 */ 134 public int getPropertyIndex() { 135 if (propertyIndex == UNSPECIFIED_PROPERTY) { 136 String[] names = getPropertyNames(); 137 for (int i = 0; i < names.length; i++) { 138 if (names[i].equals(name)) { 139 setPropertyIndex(i); 140 break; 141 } 142 } 143 } 144 return super.getPropertyIndex(); 145 } 146 147 /** 148 * Index a property by its index in the list of all 149 * properties sorted alphabetically. 150 * @param index to set 151 */ 152 public void setPropertyIndex(int index) { 153 if (propertyIndex != index) { 154 super.setPropertyIndex(index); 155 name = null; 156 } 157 } 158 159 /** 160 * Returns the value of the property, not an element of the collection 161 * represented by the property, if any. 162 * @return Object 163 */ 164 public Object getBaseValue() { 165 return handler.getProperty(getBean(), getPropertyName()); 166 } 167 168 /** 169 * If index == WHOLE_COLLECTION, the value of the property, otherwise 170 * the value of the index'th element of the collection represented by the 171 * property. If the property is not a collection, index should be zero 172 * and the value will be the property itself. 173 * @return Object 174 */ 175 public Object getImmediateNode() { 176 Object value; 177 if (index == WHOLE_COLLECTION) { 178 value = ValueUtils.getValue(handler.getProperty( 179 getBean(), 180 getPropertyName())); 181 } 182 else { 183 value = ValueUtils.getValue(handler.getProperty( 184 getBean(), 185 getPropertyName()), index); 186 } 187 return value; 188 } 189 190 /** 191 * A dynamic property is always considered actual - all keys are apparently 192 * existing with possibly the value of null. 193 * @return boolean 194 */ 195 protected boolean isActualProperty() { 196 return true; 197 } 198 199 /** 200 * If index == WHOLE_COLLECTION, change the value of the property, otherwise 201 * change the value of the index'th element of the collection 202 * represented by the property. 203 * @param value to set 204 */ 205 public void setValue(Object value) { 206 if (index == WHOLE_COLLECTION) { 207 handler.setProperty(getBean(), getPropertyName(), value); 208 } 209 else { 210 ValueUtils.setValue( 211 handler.getProperty(getBean(), getPropertyName()), 212 index, 213 value); 214 } 215 } 216 217 public NodePointer createPath(JXPathContext context) { 218 // Ignore the name passed to us, use our own data 219 Object collection = getBaseValue(); 220 if (collection == null) { 221 AbstractFactory factory = getAbstractFactory(context); 222 boolean success = 223 factory.createObject( 224 context, 225 this, 226 getBean(), 227 getPropertyName(), 228 0); 229 if (!success) { 230 throw new JXPathAbstractFactoryException( 231 "Factory could not create an object for path: " + asPath()); 232 } 233 collection = getBaseValue(); 234 } 235 236 if (index != WHOLE_COLLECTION) { 237 if (index < 0) { 238 throw new JXPathInvalidAccessException("Index is less than 1: " 239 + asPath()); 240 } 241 242 if (index >= getLength()) { 243 collection = ValueUtils.expandCollection(collection, index + 1); 244 handler.setProperty(getBean(), getPropertyName(), collection); 245 } 246 } 247 248 return this; 249 } 250 251 public NodePointer createPath(JXPathContext context, Object value) { 252 if (index == WHOLE_COLLECTION) { 253 handler.setProperty(getBean(), getPropertyName(), value); 254 } 255 else { 256 createPath(context); 257 ValueUtils.setValue(getBaseValue(), index, value); 258 } 259 return this; 260 } 261 262 public void remove() { 263 if (index == WHOLE_COLLECTION) { 264 removeKey(); 265 } 266 else if (isCollection()) { 267 Object collection = ValueUtils.remove(getBaseValue(), index); 268 handler.setProperty(getBean(), getPropertyName(), collection); 269 } 270 else if (index == 0) { 271 removeKey(); 272 } 273 } 274 275 /** 276 * Remove the current property. 277 */ 278 private void removeKey() { 279 Object bean = getBean(); 280 if (bean instanceof Map) { 281 ((Map) bean).remove(getPropertyName()); 282 } 283 else { 284 handler.setProperty(bean, getPropertyName(), null); 285 } 286 } 287 288 public String asPath() { 289 StringBuffer buffer = new StringBuffer(); 290 buffer.append(getImmediateParentPointer().asPath()); 291 if (buffer.length() == 0) { 292 buffer.append("/."); 293 } 294 else if (buffer.charAt(buffer.length() - 1) == '/') { 295 buffer.append('.'); 296 } 297 buffer.append("[@name='"); 298 buffer.append(escape(getPropertyName())); 299 buffer.append("']"); 300 if (index != WHOLE_COLLECTION && isCollection()) { 301 buffer.append('[').append(index + 1).append(']'); 302 } 303 return buffer.toString(); 304 } 305 306}