/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.jxpath.ri; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Vector; import java.util.Map.Entry; import org.apache.commons.jxpath.CompiledExpression; import org.apache.commons.jxpath.Function; import org.apache.commons.jxpath.Functions; import org.apache.commons.jxpath.JXPathContext; import org.apache.commons.jxpath.JXPathException; import org.apache.commons.jxpath.JXPathFunctionNotFoundException; import org.apache.commons.jxpath.JXPathInvalidSyntaxException; import org.apache.commons.jxpath.JXPathNotFoundException; import org.apache.commons.jxpath.JXPathTypeConversionException; import org.apache.commons.jxpath.Pointer; import org.apache.commons.jxpath.ri.axes.InitialContext; import org.apache.commons.jxpath.ri.axes.RootContext; import org.apache.commons.jxpath.ri.compiler.Expression; import org.apache.commons.jxpath.ri.compiler.LocationPath; import org.apache.commons.jxpath.ri.compiler.Path; import org.apache.commons.jxpath.ri.compiler.TreeCompiler; import org.apache.commons.jxpath.ri.model.NodePointer; import org.apache.commons.jxpath.ri.model.NodePointerFactory; import org.apache.commons.jxpath.ri.model.VariablePointerFactory; import org.apache.commons.jxpath.ri.model.beans.BeanPointerFactory; import org.apache.commons.jxpath.ri.model.beans.CollectionPointerFactory; import org.apache.commons.jxpath.ri.model.container.ContainerPointerFactory; import org.apache.commons.jxpath.ri.model.dynamic.DynamicPointerFactory; import org.apache.commons.jxpath.util.ReverseComparator; import org.apache.commons.jxpath.util.TypeUtils; /** * The reference implementation of JXPathContext. * * @author Dmitri Plotnikov * @version $Revision: 670727 $ $Date: 2008-06-23 15:10:38 -0500 (Mon, 23 Jun 2008) $ */ public class JXPathContextReferenceImpl extends JXPathContext { /** * Change this to <code>false</code> to disable soft caching of * CompiledExpressions. */ public static final boolean USE_SOFT_CACHE = true; private static final Compiler COMPILER = new TreeCompiler(); private static Map compiled = new HashMap(); private static int cleanupCount = 0; private static NodePointerFactory[] nodeFactoryArray = null; // The frequency of the cache cleanup private static final int CLEANUP_THRESHOLD = 500; private static final Vector nodeFactories = new Vector(); static { nodeFactories.add(new CollectionPointerFactory()); nodeFactories.add(new BeanPointerFactory()); nodeFactories.add(new DynamicPointerFactory()); nodeFactories.add(new VariablePointerFactory()); // DOM factory is only registered if DOM support is on the classpath Object domFactory = allocateConditionally( "org.apache.commons.jxpath.ri.model.dom.DOMPointerFactory", "org.w3c.dom.Node"); if (domFactory != null) { nodeFactories.add(domFactory); } // JDOM factory is only registered if JDOM is on the classpath Object jdomFactory = allocateConditionally( "org.apache.commons.jxpath.ri.model.jdom.JDOMPointerFactory", "org.jdom.Document"); if (jdomFactory != null) { nodeFactories.add(jdomFactory); } // DynaBean factory is only registered if BeanUtils are on the classpath Object dynaBeanFactory = allocateConditionally( "org.apache.commons.jxpath.ri.model.dynabeans." + "DynaBeanPointerFactory", "org.apache.commons.beanutils.DynaBean"); if (dynaBeanFactory != null) { nodeFactories.add(dynaBeanFactory); } nodeFactories.add(new ContainerPointerFactory()); createNodeFactoryArray(); } /** * Create the default node factory array. */ private static synchronized void createNodeFactoryArray() { if (nodeFactoryArray == null) { nodeFactoryArray = (NodePointerFactory[]) nodeFactories. toArray(new NodePointerFactory[nodeFactories.size()]); Arrays.sort(nodeFactoryArray, new Comparator() { public int compare(Object a, Object b) { int orderA = ((NodePointerFactory) a).getOrder(); int orderB = ((NodePointerFactory) b).getOrder(); return orderA - orderB; } }); } } /** * 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 position on the list of factories. * @param factory NodePointerFactory to add */ public static void addNodePointerFactory(NodePointerFactory factory) { synchronized (nodeFactories) { nodeFactories.add(factory); nodeFactoryArray = null; } } /** * Get the registered NodePointerFactories. * @return NodePointerFactory[] */ public static NodePointerFactory[] getNodePointerFactories() { return nodeFactoryArray; } /** Namespace resolver */ protected NamespaceResolver namespaceResolver; private Pointer rootPointer; private Pointer contextPointer; /** * Create a new JXPathContextReferenceImpl. * @param parentContext parent context * @param contextBean Object */ protected JXPathContextReferenceImpl(JXPathContext parentContext, Object contextBean) { this(parentContext, contextBean, null); } /** * Create a new JXPathContextReferenceImpl. * @param parentContext parent context * @param contextBean Object * @param contextPointer context pointer */ public JXPathContextReferenceImpl(JXPathContext parentContext, Object contextBean, Pointer contextPointer) { super(parentContext, contextBean); synchronized (nodeFactories) { createNodeFactoryArray(); } if (contextPointer != null) { this.contextPointer = contextPointer; this.rootPointer = NodePointer.newNodePointer( new QName(null, "root"), contextPointer.getRootNode(), getLocale()); } else { this.contextPointer = NodePointer.newNodePointer( new QName(null, "root"), contextBean, getLocale()); this.rootPointer = this.contextPointer; } NamespaceResolver parentNR = null; if (parentContext instanceof JXPathContextReferenceImpl) { parentNR = ((JXPathContextReferenceImpl) parentContext).getNamespaceResolver(); } namespaceResolver = new NamespaceResolver(parentNR); namespaceResolver .setNamespaceContextPointer((NodePointer) this.contextPointer); } /** * Returns a static instance of TreeCompiler. * * Override this to return an alternate compiler. * @return Compiler */ protected Compiler getCompiler() { return COMPILER; } protected CompiledExpression compilePath(String xpath) { return new JXPathCompiledExpression(xpath, compileExpression(xpath)); } /** * Compile the given expression. * @param xpath to compile * @return Expression */ private Expression compileExpression(String xpath) { Expression expr; synchronized (compiled) { if (USE_SOFT_CACHE) { expr = null; SoftReference ref = (SoftReference) compiled.get(xpath); if (ref != null) { expr = (Expression) ref.get(); } } else { expr = (Expression) compiled.get(xpath); } } if (expr != null) { return expr; } expr = (Expression) Parser.parseExpression(xpath, getCompiler()); synchronized (compiled) { if (USE_SOFT_CACHE) { if (cleanupCount++ >= CLEANUP_THRESHOLD) { Iterator it = compiled.entrySet().iterator(); while (it.hasNext()) { Entry me = (Entry) it.next(); if (((SoftReference) me.getValue()).get() == null) { it.remove(); } } cleanupCount = 0; } compiled.put(xpath, new SoftReference(expr)); } else { compiled.put(xpath, expr); } } return expr; } /** * Traverses the xpath and returns the resulting object. Primitive * types are wrapped into objects. * @param xpath expression * @return Object found */ public Object getValue(String xpath) { Expression expression = compileExpression(xpath); // TODO: (work in progress) - trying to integrate with Xalan // Object ctxNode = getNativeContextNode(expression); // if (ctxNode != null) { // System.err.println("WILL USE XALAN: " + xpath); // CachedXPathAPI api = new CachedXPathAPI(); // try { // if (expression instanceof Path) { // Node node = api.selectSingleNode((Node)ctxNode, xpath); // System.err.println("NODE: " + node); // if (node == null) { // return null; // } // return new DOMNodePointer(node, null).getValue(); // } // else { // XObject object = api.eval((Node)ctxNode, xpath); // switch (object.getType()) { // case XObject.CLASS_STRING: return object.str(); // case XObject.CLASS_NUMBER: return new Double(object.num()); // case XObject.CLASS_BOOLEAN: return new Boolean(object.bool()); // default: // System.err.println("OTHER TYPE: " + object.getTypeString()); // } // } // } // catch (TransformerException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // return // } return getValue(xpath, expression); } // private Object getNativeContextNode(Expression expression) { // Object node = getNativeContextNode(getContextBean()); // if (node == null) { // return null; // } // // List vars = expression.getUsedVariables(); // if (vars != null) { // return null; // } // // return node; // } // private Object getNativeContextNode(Object bean) { // if (bean instanceof Number || bean instanceof String || bean instanceof Boolean) { // return bean; // } // if (bean instanceof Node) { // return (Node)bean; // } // // if (bean instanceof Container) { // bean = ((Container)bean).getValue(); // return getNativeContextNode(bean); // } // // return null; // } /** * Get the value indicated. * @param xpath String * @param expr Expression * @return Object */ public Object getValue(String xpath, Expression expr) { Object result = expr.computeValue(getEvalContext()); if (result == null) { if (expr instanceof Path && !isLenient()) { throw new JXPathNotFoundException("No value for xpath: " + xpath); } return null; } if (result instanceof EvalContext) { EvalContext ctx = (EvalContext) result; result = ctx.getSingleNodePointer(); if (!isLenient() && result == null) { throw new JXPathNotFoundException("No value for xpath: " + xpath); } } if (result instanceof NodePointer) { result = ((NodePointer) result).getValuePointer(); if (!isLenient() && !((NodePointer) result).isActual()) { // We need to differentiate between pointers representing // a non-existing property and ones representing a property // whose value is null. In the latter case, the pointer // is going to have isActual == false, but its parent, // which is a non-node pointer identifying the bean property, // will return isActual() == true. NodePointer parent = ((NodePointer) result).getImmediateParentPointer(); if (parent == null || !parent.isContainer() || !parent.isActual()) { throw new JXPathNotFoundException("No value for xpath: " + xpath); } } result = ((NodePointer) result).getValue(); } return result; } /** * Calls getValue(xpath), converts the result to the required type * and returns the result of the conversion. * @param xpath expression * @param requiredType Class * @return Object */ public Object getValue(String xpath, Class requiredType) { Expression expr = compileExpression(xpath); return getValue(xpath, expr, requiredType); } /** * Get the value indicated. * @param xpath expression * @param expr compiled Expression * @param requiredType Class * @return Object */ public Object getValue(String xpath, Expression expr, Class requiredType) { Object value = getValue(xpath, expr); if (value != null && requiredType != null) { if (!TypeUtils.canConvert(value, requiredType)) { throw new JXPathTypeConversionException( "Invalid expression type. '" + xpath + "' returns " + value.getClass().getName() + ". It cannot be converted to " + requiredType.getName()); } value = TypeUtils.convert(value, requiredType); } return value; } /** * 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 null. * @param xpath expression * @return Iterator */ public Iterator iterate(String xpath) { return iterate(xpath, compileExpression(xpath)); } /** * 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 null. * @param xpath expression * @param expr compiled Expression * @return Iterator */ public Iterator iterate(String xpath, Expression expr) { return expr.iterate(getEvalContext()); } public Pointer getPointer(String xpath) { return getPointer(xpath, compileExpression(xpath)); } /** * Get a pointer to the specified path/expression. * @param xpath String * @param expr compiled Expression * @return Pointer */ public Pointer getPointer(String xpath, Expression expr) { Object result = expr.computeValue(getEvalContext()); if (result instanceof EvalContext) { result = ((EvalContext) result).getSingleNodePointer(); } if (result instanceof Pointer) { if (!isLenient() && !((NodePointer) result).isActual()) { throw new JXPathNotFoundException("No pointer for xpath: " + xpath); } return (Pointer) result; } return NodePointer.newNodePointer(null, result, getLocale()); } public void setValue(String xpath, Object value) { setValue(xpath, compileExpression(xpath), value); } /** * Set the value of xpath to value. * @param xpath path * @param expr compiled Expression * @param value Object */ public void setValue(String xpath, Expression expr, Object value) { try { setValue(xpath, expr, value, false); } catch (Throwable ex) { throw new JXPathException( "Exception trying to set value with xpath " + xpath, ex); } } public Pointer createPath(String xpath) { return createPath(xpath, compileExpression(xpath)); } /** * Create the given path. * @param xpath String * @param expr compiled Expression * @return resulting Pointer */ public Pointer createPath(String xpath, Expression expr) { try { Object result = expr.computeValue(getEvalContext()); Pointer pointer = null; if (result instanceof Pointer) { pointer = (Pointer) result; } else if (result instanceof EvalContext) { EvalContext ctx = (EvalContext) result; pointer = ctx.getSingleNodePointer(); } else { checkSimplePath(expr); // This should never happen throw new JXPathException("Cannot create path:" + xpath); } return ((NodePointer) pointer).createPath(this); } catch (Throwable ex) { throw new JXPathException( "Exception trying to create xpath " + xpath, ex); } } public Pointer createPathAndSetValue(String xpath, Object value) { return createPathAndSetValue(xpath, compileExpression(xpath), value); } /** * Create the given path setting its value to value. * @param xpath String * @param expr compiled Expression * @param value Object * @return resulting Pointer */ public Pointer createPathAndSetValue(String xpath, Expression expr, Object value) { try { return setValue(xpath, expr, value, true); } catch (Throwable ex) { throw new JXPathException( "Exception trying to create xpath " + xpath, ex); } } /** * Set the specified value. * @param xpath path * @param expr compiled Expression * @param value destination value * @param create whether to create missing node(s) * @return Pointer created */ private Pointer setValue(String xpath, Expression expr, Object value, boolean create) { Object result = expr.computeValue(getEvalContext()); Pointer pointer = null; if (result instanceof Pointer) { pointer = (Pointer) result; } else if (result instanceof EvalContext) { EvalContext ctx = (EvalContext) result; pointer = ctx.getSingleNodePointer(); } else { if (create) { checkSimplePath(expr); } // This should never happen throw new JXPathException("Cannot set value for xpath: " + xpath); } if (create) { pointer = ((NodePointer) pointer).createPath(this, value); } else { pointer.setValue(value); } return pointer; } /** * Checks if the path follows the JXPath restrictions on the type * of path that can be passed to create... methods. * @param expr Expression to check */ private void checkSimplePath(Expression expr) { if (!(expr instanceof LocationPath) || !((LocationPath) expr).isSimplePath()) { throw new JXPathInvalidSyntaxException( "JXPath can only create a path if it uses exclusively " + "the child:: and attribute:: axes and has " + "no context-dependent predicates"); } } /** * 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, the Iterator be empty, but not null. * @param xpath expression * @return Iterator */ public Iterator iteratePointers(String xpath) { return iteratePointers(xpath, compileExpression(xpath)); } /** * 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, the Iterator be empty, but not null. * @param xpath expression * @param expr compiled Expression * @return Iterator */ public Iterator iteratePointers(String xpath, Expression expr) { return expr.iteratePointers(getEvalContext()); } public void removePath(String xpath) { removePath(xpath, compileExpression(xpath)); } /** * Remove the specified path. * @param xpath expression * @param expr compiled Expression */ public void removePath(String xpath, Expression expr) { try { NodePointer pointer = (NodePointer) getPointer(xpath, expr); if (pointer != null) { ((NodePointer) pointer).remove(); } } catch (Throwable ex) { throw new JXPathException( "Exception trying to remove xpath " + xpath, ex); } } public void removeAll(String xpath) { removeAll(xpath, compileExpression(xpath)); } /** * Remove all matching nodes. * @param xpath expression * @param expr compiled Expression */ public void removeAll(String xpath, Expression expr) { try { ArrayList list = new ArrayList(); Iterator it = expr.iteratePointers(getEvalContext()); while (it.hasNext()) { list.add(it.next()); } Collections.sort(list, ReverseComparator.INSTANCE); it = list.iterator(); if (it.hasNext()) { NodePointer pointer = (NodePointer) it.next(); pointer.remove(); while (it.hasNext()) { removePath(((NodePointer) it.next()).asPath()); } } } catch (Throwable ex) { throw new JXPathException( "Exception trying to remove all for xpath " + xpath, ex); } } public JXPathContext getRelativeContext(Pointer pointer) { Object contextBean = pointer.getNode(); if (contextBean == null) { throw new JXPathException( "Cannot create a relative context for a non-existent node: " + pointer); } return new JXPathContextReferenceImpl(this, contextBean, pointer); } public Pointer getContextPointer() { return contextPointer; } /** * Get absolute root pointer. * @return NodePointer */ private NodePointer getAbsoluteRootPointer() { return (NodePointer) rootPointer; } /** * Get the evaluation context. * @return EvalContext */ private EvalContext getEvalContext() { return new InitialContext(new RootContext(this, (NodePointer) getContextPointer())); } /** * Get the absolute root context. * @return EvalContext */ public EvalContext getAbsoluteRootContext() { return new InitialContext(new RootContext(this, getAbsoluteRootPointer())); } /** * Get a VariablePointer for the given variable name. * @param name variable name * @return NodePointer */ public NodePointer getVariablePointer(QName name) { return NodePointer.newNodePointer(name, VariablePointerFactory .contextWrapper(this), getLocale()); } /** * Get the named Function. * @param functionName name * @param parameters function args * @return Function */ public Function getFunction(QName functionName, Object[] parameters) { String namespace = functionName.getPrefix(); String name = functionName.getName(); JXPathContext funcCtx = this; Function func = null; Functions funcs; while (funcCtx != null) { funcs = funcCtx.getFunctions(); if (funcs != null) { func = funcs.getFunction(namespace, name, parameters); if (func != null) { return func; } } funcCtx = funcCtx.getParentContext(); } throw new JXPathFunctionNotFoundException( "Undefined function: " + functionName.toString()); } public void registerNamespace(String prefix, String namespaceURI) { if (namespaceResolver.isSealed()) { namespaceResolver = (NamespaceResolver) namespaceResolver.clone(); } namespaceResolver.registerNamespace(prefix, namespaceURI); } public String getNamespaceURI(String prefix) { return namespaceResolver.getNamespaceURI(prefix); } /** * {@inheritDoc} * @see org.apache.commons.jxpath.JXPathContext#getPrefix(java.lang.String) */ public String getPrefix(String namespaceURI) { return namespaceResolver.getPrefix(namespaceURI); } public void setNamespaceContextPointer(Pointer pointer) { if (namespaceResolver.isSealed()) { namespaceResolver = (NamespaceResolver) namespaceResolver.clone(); } namespaceResolver.setNamespaceContextPointer((NodePointer) pointer); } public Pointer getNamespaceContextPointer() { return namespaceResolver.getNamespaceContextPointer(); } /** * Get the namespace resolver. * @return NamespaceResolver */ public NamespaceResolver getNamespaceResolver() { namespaceResolver.seal(); return namespaceResolver; } /** * Checks if existenceCheckClass exists on the class path. If so, allocates * an instance of the specified class, otherwise returns null. * @param className to instantiate * @param existenceCheckClassName guard class * @return className instance */ public static Object allocateConditionally(String className, String existenceCheckClassName) { try { try { Class.forName(existenceCheckClassName); } catch (ClassNotFoundException ex) { return null; } Class cls = Class.forName(className); return cls.newInstance(); } catch (Exception ex) { throw new JXPathException("Cannot allocate " + className, ex); } } }