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