    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}