View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jxpath.ri.model.dynamic;
18  
19  import java.util.Arrays;
20  import java.util.Map;
21  
22  import org.apache.commons.jxpath.AbstractFactory;
23  import org.apache.commons.jxpath.DynamicPropertyHandler;
24  import org.apache.commons.jxpath.JXPathAbstractFactoryException;
25  import org.apache.commons.jxpath.JXPathContext;
26  import org.apache.commons.jxpath.JXPathInvalidAccessException;
27  import org.apache.commons.jxpath.ri.model.NodePointer;
28  import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
29  import org.apache.commons.jxpath.util.ValueUtils;
30  
31  /**
32   * Pointer pointing to a property of an object with dynamic properties.
33   *
34   * @author Dmitri Plotnikov
35   * @version $Revision: 652845 $ $Date: 2008-05-02 19:46:46 +0200 (Fr, 02 Mai 2008) $
36   */
37  public class DynamicPropertyPointer extends PropertyPointer {
38  
39      private static final long serialVersionUID = -5720585681149150822L;
40  
41      private DynamicPropertyHandler handler;
42      private String name;
43      private String[] names;
44      private String requiredPropertyName;
45  
46      /**
47       * Create a new DynamicPropertyPointer.
48       * @param parent pointer
49       * @param handler DynamicPropertyHandler
50       */
51      public DynamicPropertyPointer(NodePointer parent,
52              DynamicPropertyHandler handler) {
53          super(parent);
54          this.handler = handler;
55      }
56  
57      /**
58       * This type of node is auxiliary.
59       * @return true
60       */
61      public boolean isContainer() {
62          return true;
63      }
64  
65      /**
66       * Number of the DP object's properties.
67       * @return int
68       */
69      public int getPropertyCount() {
70          return getPropertyNames().length;
71      }
72  
73      /**
74       * Names of all properties, sorted alphabetically.
75       * @return String[]
76       */
77      public String[] getPropertyNames() {
78          if (names == null) {
79              String[] allNames = handler.getPropertyNames(getBean());
80              names = new String[allNames.length];
81              for (int i = 0; i < names.length; i++) {
82                  names[i] = allNames[i];
83              }
84              Arrays.sort(names);
85              if (requiredPropertyName != null) {
86                  int inx = Arrays.binarySearch(names, requiredPropertyName);
87                  if (inx < 0) {
88                      allNames = names;
89                      names = new String[allNames.length + 1];
90                      names[0] = requiredPropertyName;
91                      System.arraycopy(allNames, 0, names, 1, allNames.length);
92                      Arrays.sort(names);
93                  }
94              }
95          }
96          return names;
97      }
98  
99      /**
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 }