View Javadoc

1   /*
2    * Copyright 1999-2004 The Apache Software Foundation
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.commons.jxpath.ri.model.dynamic;
17  
18  import java.util.Arrays;
19  import java.util.Map;
20  
21  import org.apache.commons.jxpath.AbstractFactory;
22  import org.apache.commons.jxpath.DynamicPropertyHandler;
23  import org.apache.commons.jxpath.JXPathContext;
24  import org.apache.commons.jxpath.JXPathException;
25  import org.apache.commons.jxpath.ri.model.NodePointer;
26  import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
27  import org.apache.commons.jxpath.util.ValueUtils;
28  
29  /***
30   * Pointer pointing to a property of an object with dynamic properties.
31   *
32   * @author Dmitri Plotnikov
33   * @version $Revision: 1.8 $ $Date: 2004/04/04 22:06:36 $
34   */
35  public class DynamicPropertyPointer extends PropertyPointer {
36      private DynamicPropertyHandler handler;
37      private String name;
38      private String[] names;
39      private String requiredPropertyName;
40  
41      public DynamicPropertyPointer(
42              NodePointer parent,
43              DynamicPropertyHandler handler) 
44      {
45          super(parent);
46          this.handler = handler;
47      }
48      /***
49       * This type of node is auxiliary.
50       */
51      public boolean isContainer() {
52          return true;
53      }
54  
55      /***
56       * Number of the DP object's properties.
57       */
58      public int getPropertyCount() {
59          return getPropertyNames().length;
60      }
61  
62      /***
63       * Names of all properties, sorted alphabetically
64       */
65      public String[] getPropertyNames() {
66          if (names == null) {
67              String allNames[] = handler.getPropertyNames(getBean());
68              names = new String[allNames.length];
69              for (int i = 0; i < names.length; i++) {
70                  names[i] = allNames[i];
71              }
72              Arrays.sort(names);
73              if (requiredPropertyName != null) {
74                  int inx = Arrays.binarySearch(names, requiredPropertyName);
75                  if (inx < 0) {
76                      allNames = names;
77                      names = new String[allNames.length + 1];
78                      names[0] = requiredPropertyName;
79                      System.arraycopy(allNames, 0, names, 1, allNames.length);
80                      Arrays.sort(names);
81                  }
82              }
83          }
84          return names;
85      }
86  
87      /***
88       * Returns the name of the currently selected property or "*"
89       * if none has been selected.
90       */
91      public String getPropertyName() {
92          if (name == null) {
93              String names[] = getPropertyNames();
94              if (propertyIndex >= 0 && propertyIndex < names.length) {
95                  name = names[propertyIndex];
96              }
97              else {
98                  name = "*";
99              }
100         }
101         return name;
102     }
103 
104     /***
105      * Select a property by name.  If the supplied name is
106      * not one of the object's existing properties, it implicitly
107      * adds this name to the object's property name list. It does not
108      * set the property value though. In order to set the property
109      * value, call setValue().
110      */
111     public void setPropertyName(String propertyName) {
112         setPropertyIndex(UNSPECIFIED_PROPERTY);
113         this.name = propertyName;
114         requiredPropertyName = propertyName;
115         if (names != null && Arrays.binarySearch(names, propertyName) < 0) {
116             names = null;
117         }
118     }
119 
120     /***
121      * Index of the currently selected property in the list of all
122      * properties sorted alphabetically.
123      */
124     public int getPropertyIndex() {
125         if (propertyIndex == UNSPECIFIED_PROPERTY) {
126             String names[] = getPropertyNames();
127             for (int i = 0; i < names.length; i++) {
128                 if (names[i].equals(name)) {
129                     setPropertyIndex(i);
130                     break;
131                 }
132             }
133         }
134         return super.getPropertyIndex();
135     }
136 
137     /***
138      * Index a property by its index in the list of all
139      * properties sorted alphabetically.
140      */
141     public void setPropertyIndex(int index) {
142         if (propertyIndex != index) {
143             super.setPropertyIndex(index);
144             name = null;
145         }
146     }
147 
148     /***
149      * Returns the value of the property, not an element of the collection
150      * represented by the property, if any.
151      */
152     public Object getBaseValue() {
153         return handler.getProperty(getBean(), getPropertyName());
154     }
155 
156     /***
157      * If index == WHOLE_COLLECTION, the value of the property, otherwise
158      * the value of the index'th element of the collection represented by the
159      * property. If the property is not a collection, index should be zero
160      * and the value will be the property itself.
161      */
162     public Object getImmediateNode() {
163         Object value;
164         if (index == WHOLE_COLLECTION) {
165             value = ValueUtils.getValue(handler.getProperty(
166                     getBean(),
167                     getPropertyName()));
168         }
169         else {
170             value = ValueUtils.getValue(handler.getProperty(
171                     getBean(),
172                     getPropertyName()), index);
173         }
174         return value;
175     }
176 
177     /***
178      * A dynamic property is always considered actual - all keys are apparently
179      * existing with possibly the value of null.
180      */
181     protected boolean isActualProperty() {
182         return true;
183     }
184 
185     /***
186      * If index == WHOLE_COLLECTION, change the value of the property, otherwise
187      * change the value of the index'th element of the collection
188      * represented by the property.
189      */
190     public void setValue(Object value) {
191         if (index == WHOLE_COLLECTION) {
192             handler.setProperty(getBean(), getPropertyName(), value);
193         }
194         else {
195             ValueUtils.setValue(
196                 handler.getProperty(getBean(), getPropertyName()),
197                 index,
198                 value);
199         }
200     }
201 
202     public NodePointer createPath(JXPathContext context) {
203         // Ignore the name passed to us, use our own data
204         Object collection = getBaseValue();
205         if (collection == null) {
206             AbstractFactory factory = getAbstractFactory(context);
207             boolean success =
208                 factory.createObject(
209                     context,
210                     this,
211                     getBean(),
212                     getPropertyName(),
213                     0);
214             if (!success) {
215                 throw new JXPathException(
216                     "Factory could not create an object for path: " + asPath());
217             }
218             collection = getBaseValue();
219         }
220 
221         if (index != WHOLE_COLLECTION) {
222             if (index < 0) {
223                 throw new JXPathException("Index is less than 1: " + asPath());
224             }
225     
226             if (index >= getLength()) {
227                 collection = ValueUtils.expandCollection(collection, index + 1);
228                 handler.setProperty(getBean(), getPropertyName(), collection);
229             }
230         }
231         
232         return this;
233     }
234     
235     public NodePointer createPath(JXPathContext context, Object value) {
236         if (index == WHOLE_COLLECTION) {
237             handler.setProperty(getBean(), getPropertyName(), value);
238         }
239         else {
240             createPath(context);
241             ValueUtils.setValue(getBaseValue(), index, value);
242         }
243         return this;
244     }
245 
246     public void remove() {
247         if (index == WHOLE_COLLECTION) {
248             removeKey();
249         }
250         else if (isCollection()) {
251             Object collection = ValueUtils.remove(getBaseValue(), index);
252             handler.setProperty(getBean(), getPropertyName(), collection);
253         }
254         else if (index == 0) {
255             removeKey();
256         }
257     }
258 
259     private void removeKey() {
260         Object bean = getBean();
261         if (bean instanceof Map) {
262             ((Map) bean).remove(getPropertyName());
263         }
264         else {
265             handler.setProperty(bean, getPropertyName(), null);
266         }
267     }
268     
269     public String asPath() {
270         StringBuffer buffer = new StringBuffer();
271         buffer.append(getImmediateParentPointer().asPath());
272         if (buffer.length() == 0) {
273             buffer.append("/.");
274         }
275         else if (buffer.charAt(buffer.length() - 1) == '/') {
276             buffer.append('.');
277         }
278         buffer.append("[@name='");
279         buffer.append(escape(getPropertyName()));
280         buffer.append("']");
281         if (index != WHOLE_COLLECTION && isCollection()) {
282             buffer.append('[').append(index + 1).append(']');
283         }
284         return buffer.toString();
285     }
286 
287     private String escape(String string) {
288         int index = string.indexOf('\'');
289         while (index != -1) {
290             string =
291                 string.substring(0, index)
292                     + "&apos;"
293                     + string.substring(index + 1);
294             index = string.indexOf('\'');
295         }
296         index = string.indexOf('\"');
297         while (index != -1) {
298             string =
299                 string.substring(0, index)
300                     + "&quot;"
301                     + string.substring(index + 1);
302             index = string.indexOf('\"');
303         }
304         return string;
305     }
306 
307     private AbstractFactory getAbstractFactory(JXPathContext context) {
308         AbstractFactory factory = context.getFactory();
309         if (factory == null) {
310             throw new JXPathException(
311                 "Factory is not set on the JXPathContext - cannot create path: "
312                     + asPath());
313         }
314         return factory;
315     }
316 }