1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.jxpath.ri;
17
18
19 import java.lang.ref.SoftReference;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.Map;
27 import java.util.Vector;
28 import java.util.Map.Entry;
29
30 import org.apache.commons.jxpath.CompiledExpression;
31 import org.apache.commons.jxpath.Function;
32 import org.apache.commons.jxpath.Functions;
33 import org.apache.commons.jxpath.JXPathContext;
34 import org.apache.commons.jxpath.JXPathException;
35 import org.apache.commons.jxpath.Pointer;
36 import org.apache.commons.jxpath.Variables;
37 import org.apache.commons.jxpath.ri.axes.InitialContext;
38 import org.apache.commons.jxpath.ri.axes.RootContext;
39 import org.apache.commons.jxpath.ri.compiler.Expression;
40 import org.apache.commons.jxpath.ri.compiler.LocationPath;
41 import org.apache.commons.jxpath.ri.compiler.Path;
42 import org.apache.commons.jxpath.ri.compiler.TreeCompiler;
43 import org.apache.commons.jxpath.ri.model.NodePointer;
44 import org.apache.commons.jxpath.ri.model.NodePointerFactory;
45 import org.apache.commons.jxpath.ri.model.VariablePointer;
46 import org.apache.commons.jxpath.ri.model.beans.BeanPointerFactory;
47 import org.apache.commons.jxpath.ri.model.beans.CollectionPointerFactory;
48 import org.apache.commons.jxpath.ri.model.container.ContainerPointerFactory;
49 import org.apache.commons.jxpath.ri.model.dynamic.DynamicPointerFactory;
50 import org.apache.commons.jxpath.util.TypeUtils;
51
52 /***
53 * The reference implementation of JXPathContext.
54 *
55 * @author Dmitri Plotnikov
56 * @version $Revision: 1.43 $ $Date: 2004/04/04 23:16:23 $
57 */
58 public class JXPathContextReferenceImpl extends JXPathContext {
59
60 /***
61 * Change this to <code>false</code> to disable soft caching of
62 * CompiledExpressions.
63 */
64 public static final boolean USE_SOFT_CACHE = true;
65
66 private static final Compiler COMPILER = new TreeCompiler();
67 private static Map compiled = new HashMap();
68 private static int cleanupCount = 0;
69
70 private static Vector nodeFactories = new Vector();
71 private static NodePointerFactory nodeFactoryArray[] = null;
72 static {
73 nodeFactories.add(new CollectionPointerFactory());
74 nodeFactories.add(new BeanPointerFactory());
75 nodeFactories.add(new DynamicPointerFactory());
76
77
78 Object domFactory = allocateConditionally(
79 "org.apache.commons.jxpath.ri.model.dom.DOMPointerFactory",
80 "org.w3c.dom.Node");
81 if (domFactory != null) {
82 nodeFactories.add(domFactory);
83 }
84
85
86 Object jdomFactory = allocateConditionally(
87 "org.apache.commons.jxpath.ri.model.jdom.JDOMPointerFactory",
88 "org.jdom.Document");
89 if (jdomFactory != null) {
90 nodeFactories.add(jdomFactory);
91 }
92
93
94 Object dynaBeanFactory =
95 allocateConditionally(
96 "org.apache.commons.jxpath.ri.model.dynabeans."
97 + "DynaBeanPointerFactory",
98 "org.apache.commons.beanutils.DynaBean");
99 if (dynaBeanFactory != null) {
100 nodeFactories.add(dynaBeanFactory);
101 }
102
103 nodeFactories.add(new ContainerPointerFactory());
104 createNodeFactoryArray();
105 }
106
107 private Pointer rootPointer;
108 private Pointer contextPointer;
109
110 protected NamespaceResolver namespaceResolver;
111
112
113 private static final int CLEANUP_THRESHOLD = 500;
114
115 protected JXPathContextReferenceImpl(JXPathContext parentContext,
116 Object contextBean)
117 {
118 this(parentContext, contextBean, null);
119 }
120
121 public JXPathContextReferenceImpl(
122 JXPathContext parentContext,
123 Object contextBean,
124 Pointer contextPointer)
125 {
126 super(parentContext, contextBean);
127
128 synchronized (nodeFactories) {
129 createNodeFactoryArray();
130 }
131
132 if (contextPointer != null) {
133 this.contextPointer = contextPointer;
134 this.rootPointer =
135 NodePointer.newNodePointer(
136 new QName(null, "root"),
137 contextPointer.getRootNode(),
138 getLocale());
139 }
140 else {
141 this.contextPointer =
142 NodePointer.newNodePointer(
143 new QName(null, "root"),
144 contextBean,
145 getLocale());
146 this.rootPointer = this.contextPointer;
147 }
148
149 namespaceResolver = new NamespaceResolver();
150 namespaceResolver
151 .setNamespaceContextPointer((NodePointer) this.contextPointer);
152 }
153
154 private static void createNodeFactoryArray() {
155 if (nodeFactoryArray == null) {
156 nodeFactoryArray =
157 (NodePointerFactory[]) nodeFactories.
158 toArray(new NodePointerFactory[0]);
159 Arrays.sort(nodeFactoryArray, new Comparator() {
160 public int compare(Object a, Object b) {
161 int orderA = ((NodePointerFactory) a).getOrder();
162 int orderB = ((NodePointerFactory) b).getOrder();
163 return orderA - orderB;
164 }
165 });
166 }
167 }
168
169 /***
170 * Call this with a custom NodePointerFactory to add support for
171 * additional types of objects. Make sure the factory returns
172 * a name that puts it in the right position on the list of factories.
173 */
174 public static void addNodePointerFactory(NodePointerFactory factory) {
175 synchronized (nodeFactories) {
176 nodeFactories.add(factory);
177 nodeFactoryArray = null;
178 }
179 }
180
181 public static NodePointerFactory[] getNodePointerFactories() {
182 return nodeFactoryArray;
183 }
184
185 /***
186 * Returns a static instance of TreeCompiler.
187 *
188 * Override this to return an aternate compiler.
189 */
190 protected Compiler getCompiler() {
191 return COMPILER;
192 }
193
194 protected CompiledExpression compilePath(String xpath) {
195 return new JXPathCompiledExpression(xpath, compileExpression(xpath));
196 }
197
198 private Expression compileExpression(String xpath) {
199 Expression expr;
200
201 synchronized (compiled) {
202 if (USE_SOFT_CACHE) {
203 expr = null;
204 SoftReference ref = (SoftReference) compiled.get(xpath);
205 if (ref != null) {
206 expr = (Expression) ref.get();
207 }
208 }
209 else {
210 expr = (Expression) compiled.get(xpath);
211 }
212 }
213
214 if (expr != null) {
215 return expr;
216 }
217
218 expr = (Expression) Parser.parseExpression(xpath, getCompiler());
219
220 synchronized (compiled) {
221 if (USE_SOFT_CACHE) {
222 if (cleanupCount++ >= CLEANUP_THRESHOLD) {
223 Iterator it = compiled.entrySet().iterator();
224 while (it.hasNext()) {
225 Entry me = (Entry) it.next();
226 if (((SoftReference) me.getValue()).get() == null) {
227 it.remove();
228 }
229 }
230 cleanupCount = 0;
231 }
232 compiled.put(xpath, new SoftReference(expr));
233 }
234 else {
235 compiled.put(xpath, expr);
236 }
237 }
238
239 return expr;
240 }
241
242 /***
243 * Traverses the xpath and returns the resulting object. Primitive
244 * types are wrapped into objects.
245 */
246 public Object getValue(String xpath) {
247 Expression expression = compileExpression(xpath);
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280 return getValue(xpath, expression);
281 }
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313 public Object getValue(String xpath, Expression expr) {
314 Object result = expr.computeValue(getEvalContext());
315 if (result == null) {
316 if (expr instanceof Path) {
317 if (!isLenient()) {
318 throw new JXPathException("No value for xpath: " + xpath);
319 }
320 }
321 return null;
322 }
323 if (result instanceof EvalContext) {
324 EvalContext ctx = (EvalContext) result;
325 result = ctx.getSingleNodePointer();
326 if (!isLenient() && result == null) {
327 throw new JXPathException("No value for xpath: " + xpath);
328 }
329 }
330 if (result instanceof NodePointer) {
331 result = ((NodePointer) result).getValuePointer();
332 if (!isLenient() && !((NodePointer) result).isActual()) {
333
334
335
336
337
338
339 NodePointer parent =
340 ((NodePointer) result).getImmediateParentPointer();
341 if (parent == null
342 || !parent.isContainer()
343 || !parent.isActual()) {
344 throw new JXPathException("No value for xpath: " + xpath);
345 }
346 }
347 result = ((NodePointer) result).getValue();
348 }
349 return result;
350 }
351
352 /***
353 * Calls getValue(xpath), converts the result to the required type
354 * and returns the result of the conversion.
355 */
356 public Object getValue(String xpath, Class requiredType) {
357 Expression expr = compileExpression(xpath);
358 return getValue(xpath, expr, requiredType);
359 }
360
361 public Object getValue(String xpath, Expression expr, Class requiredType) {
362 Object value = getValue(xpath, expr);
363 if (value != null && requiredType != null) {
364 if (!TypeUtils.canConvert(value, requiredType)) {
365 throw new JXPathException(
366 "Invalid expression type. '"
367 + xpath
368 + "' returns "
369 + value.getClass().getName()
370 + ". It cannot be converted to "
371 + requiredType.getName());
372 }
373 value = TypeUtils.convert(value, requiredType);
374 }
375 return value;
376 }
377
378 /***
379 * Traverses the xpath and returns a Iterator of all results found
380 * for the path. If the xpath matches no properties
381 * in the graph, the Iterator will not be null.
382 */
383 public Iterator iterate(String xpath) {
384 return iterate(xpath, compileExpression(xpath));
385 }
386
387 public Iterator iterate(String xpath, Expression expr) {
388 return expr.iterate(getEvalContext());
389 }
390
391 public Pointer getPointer(String xpath) {
392 return getPointer(xpath, compileExpression(xpath));
393 }
394
395 public Pointer getPointer(String xpath, Expression expr) {
396 Object result = expr.computeValue(getEvalContext());
397 if (result instanceof EvalContext) {
398 result = ((EvalContext) result).getSingleNodePointer();
399 }
400 if (result instanceof Pointer) {
401 if (!isLenient() && !((NodePointer) result).isActual()) {
402 throw new JXPathException("No pointer for xpath: " + xpath);
403 }
404 return (Pointer) result;
405 }
406 else {
407 return NodePointer.newNodePointer(null, result, getLocale());
408 }
409 }
410
411 public void setValue(String xpath, Object value) {
412 setValue(xpath, compileExpression(xpath), value);
413 }
414
415
416 public void setValue(String xpath, Expression expr, Object value) {
417 try {
418 setValue(xpath, expr, value, false);
419 }
420 catch (Throwable ex) {
421 throw new JXPathException(
422 "Exception trying to set value with xpath " + xpath, ex);
423 }
424 }
425
426 public Pointer createPath(String xpath) {
427 return createPath(xpath, compileExpression(xpath));
428 }
429
430 public Pointer createPath(String xpath, Expression expr) {
431 try {
432 Object result = expr.computeValue(getEvalContext());
433 Pointer pointer = null;
434
435 if (result instanceof Pointer) {
436 pointer = (Pointer) result;
437 }
438 else if (result instanceof EvalContext) {
439 EvalContext ctx = (EvalContext) result;
440 pointer = ctx.getSingleNodePointer();
441 }
442 else {
443 checkSimplePath(expr);
444
445 throw new JXPathException("Cannot create path:" + xpath);
446 }
447 return ((NodePointer) pointer).createPath(this);
448 }
449 catch (Throwable ex) {
450 throw new JXPathException(
451 "Exception trying to create xpath " + xpath,
452 ex);
453 }
454 }
455
456 public Pointer createPathAndSetValue(String xpath, Object value) {
457 return createPathAndSetValue(xpath, compileExpression(xpath), value);
458 }
459
460 public Pointer createPathAndSetValue(
461 String xpath,
462 Expression expr,
463 Object value)
464 {
465 try {
466 return setValue(xpath, expr, value, true);
467 }
468 catch (Throwable ex) {
469 throw new JXPathException(
470 "Exception trying to create xpath " + xpath,
471 ex);
472 }
473 }
474
475 private Pointer setValue(
476 String xpath,
477 Expression expr,
478 Object value,
479 boolean create)
480 {
481 Object result = expr.computeValue(getEvalContext());
482 Pointer pointer = null;
483
484 if (result instanceof Pointer) {
485 pointer = (Pointer) result;
486 }
487 else if (result instanceof EvalContext) {
488 EvalContext ctx = (EvalContext) result;
489 pointer = ctx.getSingleNodePointer();
490 }
491 else {
492 if (create) {
493 checkSimplePath(expr);
494 }
495
496
497 throw new JXPathException("Cannot set value for xpath: " + xpath);
498 }
499 if (create) {
500 pointer = ((NodePointer) pointer).createPath(this, value);
501 }
502 else {
503 pointer.setValue(value);
504 }
505 return pointer;
506 }
507
508 /***
509 * Checks if the path follows the JXPath restrictions on the type
510 * of path that can be passed to create... methods.
511 */
512 private void checkSimplePath(Expression expr) {
513 if (!(expr instanceof LocationPath)
514 || !((LocationPath) expr).isSimplePath()) {
515 throw new JXPathException(
516 "JXPath can only create a path if it uses exclusively "
517 + "the child:: and attribute:: axes and has "
518 + "no context-dependent predicates");
519 }
520 }
521
522 /***
523 * Traverses the xpath and returns an Iterator of Pointers.
524 * A Pointer provides easy access to a property.
525 * If the xpath matches no properties
526 * in the graph, the Iterator be empty, but not null.
527 */
528 public Iterator iteratePointers(String xpath) {
529 return iteratePointers(xpath, compileExpression(xpath));
530 }
531
532 public Iterator iteratePointers(String xpath, Expression expr) {
533 return expr.iteratePointers(getEvalContext());
534 }
535
536 public void removePath(String xpath) {
537 removePath(xpath, compileExpression(xpath));
538 }
539
540 public void removePath(String xpath, Expression expr) {
541 try {
542 NodePointer pointer = (NodePointer) getPointer(xpath, expr);
543 if (pointer != null) {
544 ((NodePointer) pointer).remove();
545 }
546 }
547 catch (Throwable ex) {
548 throw new JXPathException(
549 "Exception trying to remove xpath " + xpath,
550 ex);
551 }
552 }
553
554 public void removeAll(String xpath) {
555 removeAll(xpath, compileExpression(xpath));
556 }
557
558 public void removeAll(String xpath, Expression expr) {
559 try {
560 ArrayList list = new ArrayList();
561 Iterator it = expr.iteratePointers(getEvalContext());
562 while (it.hasNext()) {
563 list.add(it.next());
564 }
565 Collections.sort(list);
566 for (int i = list.size() - 1; i >= 0; i--) {
567 NodePointer pointer = (NodePointer) list.get(i);
568 pointer.remove();
569 }
570 }
571 catch (Throwable ex) {
572 throw new JXPathException(
573 "Exception trying to remove all for xpath " + xpath,
574 ex);
575 }
576 }
577
578 public JXPathContext getRelativeContext(Pointer pointer) {
579 Object contextBean = pointer.getNode();
580 if (contextBean == null) {
581 throw new JXPathException(
582 "Cannot create a relative context for a non-existent node: "
583 + pointer);
584 }
585 return new JXPathContextReferenceImpl(this, contextBean, pointer);
586 }
587
588 public Pointer getContextPointer() {
589 return contextPointer;
590 }
591
592 private NodePointer getAbsoluteRootPointer() {
593 return (NodePointer) rootPointer;
594 }
595
596 private EvalContext getEvalContext() {
597 return new InitialContext(new RootContext(this,
598 (NodePointer) getContextPointer()));
599 }
600
601 public EvalContext getAbsoluteRootContext() {
602 return new InitialContext(new RootContext(this,
603 getAbsoluteRootPointer()));
604 }
605
606 public NodePointer getVariablePointer(QName name) {
607 String varName = name.toString();
608 JXPathContext varCtx = this;
609 Variables vars = null;
610 while (varCtx != null) {
611 vars = varCtx.getVariables();
612 if (vars.isDeclaredVariable(varName)) {
613 break;
614 }
615 varCtx = varCtx.getParentContext();
616 vars = null;
617 }
618 if (vars != null) {
619 return new VariablePointer(vars, name);
620 }
621 else {
622
623
624
625 return new VariablePointer(name);
626 }
627 }
628
629 public Function getFunction(QName functionName, Object[] parameters) {
630 String namespace = functionName.getPrefix();
631 String name = functionName.getName();
632 JXPathContext funcCtx = this;
633 Function func = null;
634 Functions funcs;
635 while (funcCtx != null) {
636 funcs = funcCtx.getFunctions();
637 if (funcs != null) {
638 func = funcs.getFunction(namespace, name, parameters);
639 if (func != null) {
640 return func;
641 }
642 }
643 funcCtx = funcCtx.getParentContext();
644 }
645 throw new JXPathException(
646 "Undefined function: " + functionName.toString());
647 }
648
649 public void registerNamespace(String prefix, String namespaceURI) {
650 if (namespaceResolver.isSealed()) {
651 namespaceResolver = (NamespaceResolver) namespaceResolver.clone();
652 }
653 namespaceResolver.registerNamespace(prefix, namespaceURI);
654 }
655
656 public String getNamespaceURI(String prefix) {
657 return namespaceResolver.getNamespaceURI(prefix);
658 }
659
660 public void setNamespaceContextPointer(Pointer pointer) {
661 if (namespaceResolver.isSealed()) {
662 namespaceResolver = (NamespaceResolver) namespaceResolver.clone();
663 }
664 namespaceResolver.setNamespaceContextPointer((NodePointer) pointer);
665 }
666
667 public Pointer getNamespaceContextPointer() {
668 return namespaceResolver.getNamespaceContextPointer();
669 }
670
671 public NamespaceResolver getNamespaceResolver() {
672 namespaceResolver.seal();
673 return namespaceResolver;
674 }
675
676 /***
677 * Checks if existenceCheckClass exists on the class path. If so, allocates
678 * an instance of the specified class, otherwise returns null.
679 */
680 public static Object allocateConditionally(
681 String className,
682 String existenceCheckClassName)
683 {
684 try {
685 try {
686 Class.forName(existenceCheckClassName);
687 }
688 catch (ClassNotFoundException ex) {
689 return null;
690 }
691
692 Class cls = Class.forName(className);
693 return cls.newInstance();
694 }
695 catch (Exception ex) {
696 throw new JXPathException("Cannot allocate " + className, ex);
697 }
698 }
699 }