1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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 + "'"
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 + """
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 }