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.dynamic;
018
019 import java.util.Arrays;
020 import java.util.Map;
021
022 import org.apache.commons.jxpath.AbstractFactory;
023 import org.apache.commons.jxpath.DynamicPropertyHandler;
024 import org.apache.commons.jxpath.JXPathAbstractFactoryException;
025 import org.apache.commons.jxpath.JXPathContext;
026 import org.apache.commons.jxpath.JXPathInvalidAccessException;
027 import org.apache.commons.jxpath.ri.model.NodePointer;
028 import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
029 import 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 12:46:46 -0500 (Fri, 02 May 2008) $
036 */
037 public 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 }