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