001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.jxpath.ri;
019
020import java.lang.ref.SoftReference;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.Map;
027import java.util.Map.Entry;
028import java.util.Vector;
029
030import org.apache.commons.jxpath.CompiledExpression;
031import org.apache.commons.jxpath.ExceptionHandler;
032import org.apache.commons.jxpath.Function;
033import org.apache.commons.jxpath.Functions;
034import org.apache.commons.jxpath.JXPathContext;
035import org.apache.commons.jxpath.JXPathException;
036import org.apache.commons.jxpath.JXPathFunctionNotFoundException;
037import org.apache.commons.jxpath.JXPathInvalidSyntaxException;
038import org.apache.commons.jxpath.JXPathNotFoundException;
039import org.apache.commons.jxpath.JXPathTypeConversionException;
040import org.apache.commons.jxpath.Pointer;
041import org.apache.commons.jxpath.ri.axes.InitialContext;
042import org.apache.commons.jxpath.ri.axes.RootContext;
043import org.apache.commons.jxpath.ri.compiler.Expression;
044import org.apache.commons.jxpath.ri.compiler.LocationPath;
045import org.apache.commons.jxpath.ri.compiler.Path;
046import org.apache.commons.jxpath.ri.compiler.TreeCompiler;
047import org.apache.commons.jxpath.ri.model.NodePointer;
048import org.apache.commons.jxpath.ri.model.NodePointerFactory;
049import org.apache.commons.jxpath.ri.model.VariablePointerFactory;
050import org.apache.commons.jxpath.ri.model.beans.BeanPointerFactory;
051import org.apache.commons.jxpath.ri.model.beans.CollectionPointerFactory;
052import org.apache.commons.jxpath.ri.model.container.ContainerPointerFactory;
053import org.apache.commons.jxpath.ri.model.dynamic.DynamicPointerFactory;
054import org.apache.commons.jxpath.util.ClassLoaderUtil;
055import org.apache.commons.jxpath.util.ReverseComparator;
056import org.apache.commons.jxpath.util.TypeUtils;
057
058/**
059 * The reference implementation of JXPathContext.
060 */
061public class JXPathContextReferenceImpl extends JXPathContext {
062
063    /**
064     * Change this to {@code false} to disable soft caching of CompiledExpressions.
065     */
066    public static final boolean USE_SOFT_CACHE = true;
067    private static final Compiler COMPILER = new TreeCompiler();
068    private static Map<String, Object> compiled = new HashMap<>();
069    private static int cleanupCount;
070    private static NodePointerFactory[] nodeFactoryArray;
071    // The frequency of the cache cleanup
072    private static final int CLEANUP_THRESHOLD = 500;
073    private static final Vector<NodePointerFactory> nodeFactories = new Vector<>();
074    static {
075        nodeFactories.add(new CollectionPointerFactory());
076        nodeFactories.add(new BeanPointerFactory());
077        nodeFactories.add(new DynamicPointerFactory());
078        nodeFactories.add(new VariablePointerFactory());
079        // DOM factory is only registered if DOM support is on the classpath
080        final NodePointerFactory domFactory = (NodePointerFactory) allocateConditionally("org.apache.commons.jxpath.ri.model.dom.DOMPointerFactory",
081                "org.w3c.dom.Node");
082        if (domFactory != null) {
083            nodeFactories.add(domFactory);
084        }
085        // JDOM factory is only registered if JDOM is on the classpath
086        final NodePointerFactory jdomFactory = (NodePointerFactory) allocateConditionally("org.apache.commons.jxpath.ri.model.jdom.JDOMPointerFactory",
087                "org.jdom.Document");
088        if (jdomFactory != null) {
089            nodeFactories.add(jdomFactory);
090        }
091        // DynaBean factory is only registered if BeanUtils are on the classpath
092        final NodePointerFactory dynaBeanFactory = (NodePointerFactory) allocateConditionally(
093                "org.apache.commons.jxpath.ri.model.dynabeans." + "DynaBeanPointerFactory", "org.apache.commons.beanutils.DynaBean");
094        if (dynaBeanFactory != null) {
095            nodeFactories.add(dynaBeanFactory);
096        }
097        nodeFactories.add(new ContainerPointerFactory());
098        createNodeFactoryArray();
099    }
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}