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  
18  package org.apache.commons.jxpath.ri.model.dynamic;
19  
20  import java.util.Arrays;
21  import java.util.Map;
22  
23  import org.apache.commons.jxpath.AbstractFactory;
24  import org.apache.commons.jxpath.DynamicPropertyHandler;
25  import org.apache.commons.jxpath.JXPathAbstractFactoryException;
26  import org.apache.commons.jxpath.JXPathContext;
27  import org.apache.commons.jxpath.JXPathInvalidAccessException;
28  import org.apache.commons.jxpath.ri.model.NodePointer;
29  import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
30  import org.apache.commons.jxpath.util.ValueUtils;
31  
32  /**
33   * Pointer to a property of an object with dynamic properties.
34   */
35  public class DynamicPropertyPointer extends PropertyPointer {
36  
37      private static final long serialVersionUID = -5720585681149150822L;
38  
39      /**
40       * Dynamic property handler.
41       */
42      private final DynamicPropertyHandler handler;
43  
44      /** The name of the currently selected property or "*" if none has been selected. */
45      private String name;
46  
47      /** The names of all properties, sorted alphabetically. */
48      private String[] names;
49  
50      /**
51       * The property name from {@link #setPropertyName(String)}.
52       */
53      private String requiredPropertyName;
54  
55      /**
56       * Constructs a new DynamicPropertyPointer.
57       *
58       * @param parent  pointer
59       * @param handler DynamicPropertyHandler
60       */
61      public DynamicPropertyPointer(final NodePointer parent, final DynamicPropertyHandler handler) {
62          super(parent);
63          this.handler = handler;
64      }
65  
66      @Override
67      public String asPath() {
68          final StringBuilder buffer = new StringBuilder();
69          buffer.append(getImmediateParentPointer().asPath());
70          if (buffer.length() == 0) {
71              buffer.append("/.");
72          } else if (buffer.charAt(buffer.length() - 1) == '/') {
73              buffer.append('.');
74          }
75          buffer.append("[@name='");
76          buffer.append(escape(getPropertyName()));
77          buffer.append("']");
78          if (index != WHOLE_COLLECTION && isCollection()) {
79              buffer.append('[').append(index + 1).append(']');
80          }
81          return buffer.toString();
82      }
83  
84      @Override
85      public NodePointer createPath(final JXPathContext context) {
86          // Ignore the name passed to us, use our own data
87          Object collection = getBaseValue();
88          if (collection == null) {
89              final AbstractFactory factory = getAbstractFactory(context);
90              final boolean success = factory.createObject(context, this, getBean(), getPropertyName(), 0);
91              if (!success) {
92                  throw new JXPathAbstractFactoryException("Factory could not create an object for path: " + asPath());
93              }
94              collection = getBaseValue();
95          }
96          if (index != WHOLE_COLLECTION) {
97              if (index < 0) {
98                  throw new JXPathInvalidAccessException("Index is less than 1: " + asPath());
99              }
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 }