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