001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.jxpath.ri;
017:
018: import java.lang.ref.SoftReference;
019: import java.util.ArrayList;
020: import java.util.Arrays;
021: import java.util.Collections;
022: import java.util.Comparator;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.Map;
026: import java.util.Vector;
027: import java.util.Map.Entry;
028:
029: import org.apache.commons.jxpath.CompiledExpression;
030: import org.apache.commons.jxpath.Function;
031: import org.apache.commons.jxpath.Functions;
032: import org.apache.commons.jxpath.JXPathContext;
033: import org.apache.commons.jxpath.JXPathException;
034: import org.apache.commons.jxpath.Pointer;
035: import org.apache.commons.jxpath.Variables;
036: import org.apache.commons.jxpath.ri.axes.InitialContext;
037: import org.apache.commons.jxpath.ri.axes.RootContext;
038: import org.apache.commons.jxpath.ri.compiler.Expression;
039: import org.apache.commons.jxpath.ri.compiler.LocationPath;
040: import org.apache.commons.jxpath.ri.compiler.Path;
041: import org.apache.commons.jxpath.ri.compiler.TreeCompiler;
042: import org.apache.commons.jxpath.ri.model.NodePointer;
043: import org.apache.commons.jxpath.ri.model.NodePointerFactory;
044: import org.apache.commons.jxpath.ri.model.VariablePointer;
045: import org.apache.commons.jxpath.ri.model.beans.BeanPointerFactory;
046: import org.apache.commons.jxpath.ri.model.beans.CollectionPointerFactory;
047: import org.apache.commons.jxpath.ri.model.container.ContainerPointerFactory;
048: import org.apache.commons.jxpath.ri.model.dynamic.DynamicPointerFactory;
049: import org.apache.commons.jxpath.util.TypeUtils;
050:
051: /**
052: * The reference implementation of JXPathContext.
053: *
054: * @author Dmitri Plotnikov
055: * @version $Revision: 1.43 $ $Date: 2004/04/04 23:16:23 $
056: */
057: public class JXPathContextReferenceImpl extends JXPathContext {
058:
059: /**
060: * Change this to <code>false</code> to disable soft caching of
061: * CompiledExpressions.
062: */
063: public static final boolean USE_SOFT_CACHE = true;
064:
065: private static final Compiler COMPILER = new TreeCompiler();
066: private static Map compiled = new HashMap();
067: private static int cleanupCount = 0;
068:
069: private static Vector nodeFactories = new Vector();
070: private static NodePointerFactory nodeFactoryArray[] = null;
071: static {
072: nodeFactories.add(new CollectionPointerFactory());
073: nodeFactories.add(new BeanPointerFactory());
074: nodeFactories.add(new DynamicPointerFactory());
075:
076: // DOM factory is only registered if DOM support is on the classpath
077: Object domFactory = allocateConditionally(
078: "org.apache.commons.jxpath.ri.model.dom.DOMPointerFactory",
079: "org.w3c.dom.Node");
080: if (domFactory != null) {
081: nodeFactories.add(domFactory);
082: }
083:
084: // JDOM factory is only registered if JDOM is on the classpath
085: Object jdomFactory = allocateConditionally(
086: "org.apache.commons.jxpath.ri.model.jdom.JDOMPointerFactory",
087: "org.jdom.Document");
088: if (jdomFactory != null) {
089: nodeFactories.add(jdomFactory);
090: }
091:
092: // DynaBean factory is only registered if BeanUtils are on the classpath
093: Object dynaBeanFactory = allocateConditionally(
094: "org.apache.commons.jxpath.ri.model.dynabeans."
095: + "DynaBeanPointerFactory",
096: "org.apache.commons.beanutils.DynaBean");
097: if (dynaBeanFactory != null) {
098: nodeFactories.add(dynaBeanFactory);
099: }
100:
101: nodeFactories.add(new ContainerPointerFactory());
102: createNodeFactoryArray();
103: }
104:
105: private Pointer rootPointer;
106: private Pointer contextPointer;
107:
108: protected NamespaceResolver namespaceResolver;
109:
110: // The frequency of the cache cleanup
111: private static final int CLEANUP_THRESHOLD = 500;
112:
113: protected JXPathContextReferenceImpl(JXPathContext parentContext,
114: Object contextBean) {
115: this (parentContext, contextBean, null);
116: }
117:
118: public JXPathContextReferenceImpl(JXPathContext parentContext,
119: Object contextBean, Pointer contextPointer) {
120: super (parentContext, contextBean);
121:
122: synchronized (nodeFactories) {
123: createNodeFactoryArray();
124: }
125:
126: if (contextPointer != null) {
127: this .contextPointer = contextPointer;
128: this .rootPointer = NodePointer.newNodePointer(new QName(
129: null, "root"), contextPointer.getRootNode(),
130: getLocale());
131: } else {
132: this .contextPointer = NodePointer.newNodePointer(new QName(
133: null, "root"), contextBean, getLocale());
134: this .rootPointer = this .contextPointer;
135: }
136:
137: namespaceResolver = new NamespaceResolver();
138: namespaceResolver
139: .setNamespaceContextPointer((NodePointer) this .contextPointer);
140: }
141:
142: private static void createNodeFactoryArray() {
143: if (nodeFactoryArray == null) {
144: nodeFactoryArray = (NodePointerFactory[]) nodeFactories
145: .toArray(new NodePointerFactory[0]);
146: Arrays.sort(nodeFactoryArray, new Comparator() {
147: public int compare(Object a, Object b) {
148: int orderA = ((NodePointerFactory) a).getOrder();
149: int orderB = ((NodePointerFactory) b).getOrder();
150: return orderA - orderB;
151: }
152: });
153: }
154: }
155:
156: /**
157: * Call this with a custom NodePointerFactory to add support for
158: * additional types of objects. Make sure the factory returns
159: * a name that puts it in the right position on the list of factories.
160: */
161: public static void addNodePointerFactory(NodePointerFactory factory) {
162: synchronized (nodeFactories) {
163: nodeFactories.add(factory);
164: nodeFactoryArray = null;
165: }
166: }
167:
168: public static NodePointerFactory[] getNodePointerFactories() {
169: return nodeFactoryArray;
170: }
171:
172: /**
173: * Returns a static instance of TreeCompiler.
174: *
175: * Override this to return an aternate compiler.
176: */
177: protected Compiler getCompiler() {
178: return COMPILER;
179: }
180:
181: protected CompiledExpression compilePath(String xpath) {
182: return new JXPathCompiledExpression(xpath,
183: compileExpression(xpath));
184: }
185:
186: private Expression compileExpression(String xpath) {
187: Expression expr;
188:
189: synchronized (compiled) {
190: if (USE_SOFT_CACHE) {
191: expr = null;
192: SoftReference ref = (SoftReference) compiled.get(xpath);
193: if (ref != null) {
194: expr = (Expression) ref.get();
195: }
196: } else {
197: expr = (Expression) compiled.get(xpath);
198: }
199: }
200:
201: if (expr != null) {
202: return expr;
203: }
204:
205: expr = (Expression) Parser
206: .parseExpression(xpath, getCompiler());
207:
208: synchronized (compiled) {
209: if (USE_SOFT_CACHE) {
210: if (cleanupCount++ >= CLEANUP_THRESHOLD) {
211: Iterator it = compiled.entrySet().iterator();
212: while (it.hasNext()) {
213: Entry me = (Entry) it.next();
214: if (((SoftReference) me.getValue()).get() == null) {
215: it.remove();
216: }
217: }
218: cleanupCount = 0;
219: }
220: compiled.put(xpath, new SoftReference(expr));
221: } else {
222: compiled.put(xpath, expr);
223: }
224: }
225:
226: return expr;
227: }
228:
229: /**
230: * Traverses the xpath and returns the resulting object. Primitive
231: * types are wrapped into objects.
232: */
233: public Object getValue(String xpath) {
234: Expression expression = compileExpression(xpath);
235: // TODO: (work in progress) - trying to integrate with Xalan
236: // Object ctxNode = getNativeContextNode(expression);
237: // if (ctxNode != null) {
238: // System.err.println("WILL USE XALAN: " + xpath);
239: // CachedXPathAPI api = new CachedXPathAPI();
240: // try {
241: // if (expression instanceof Path) {
242: // Node node = api.selectSingleNode((Node)ctxNode, xpath);
243: // System.err.println("NODE: " + node);
244: // if (node == null) {
245: // return null;
246: // }
247: // return new DOMNodePointer(node, null).getValue();
248: // }
249: // else {
250: // XObject object = api.eval((Node)ctxNode, xpath);
251: // switch (object.getType()) {
252: // case XObject.CLASS_STRING: return object.str();
253: // case XObject.CLASS_NUMBER: return new Double(object.num());
254: // case XObject.CLASS_BOOLEAN: return new Boolean(object.bool());
255: // default:
256: // System.err.println("OTHER TYPE: " + object.getTypeString());
257: // }
258: // }
259: // }
260: // catch (TransformerException e) {
261: // // TODO Auto-generated catch block
262: // e.printStackTrace();
263: // }
264: // return
265: // }
266:
267: return getValue(xpath, expression);
268: }
269:
270: // private Object getNativeContextNode(Expression expression) {
271: // Object node = getNativeContextNode(getContextBean());
272: // if (node == null) {
273: // return null;
274: // }
275: //
276: // List vars = expression.getUsedVariables();
277: // if (vars != null) {
278: // return null;
279: // }
280: //
281: // return node;
282: // }
283:
284: // private Object getNativeContextNode(Object bean) {
285: // if (bean instanceof Number || bean instanceof String || bean instanceof Boolean) {
286: // return bean;
287: // }
288: // if (bean instanceof Node) {
289: // return (Node)bean;
290: // }
291: //
292: // if (bean instanceof Container) {
293: // bean = ((Container)bean).getValue();
294: // return getNativeContextNode(bean);
295: // }
296: //
297: // return null;
298: // }
299:
300: public Object getValue(String xpath, Expression expr) {
301: Object result = expr.computeValue(getEvalContext());
302: if (result == null) {
303: if (expr instanceof Path) {
304: if (!isLenient()) {
305: throw new JXPathException("No value for xpath: "
306: + xpath);
307: }
308: }
309: return null;
310: }
311: if (result instanceof EvalContext) {
312: EvalContext ctx = (EvalContext) result;
313: result = ctx.getSingleNodePointer();
314: if (!isLenient() && result == null) {
315: throw new JXPathException("No value for xpath: "
316: + xpath);
317: }
318: }
319: if (result instanceof NodePointer) {
320: result = ((NodePointer) result).getValuePointer();
321: if (!isLenient() && !((NodePointer) result).isActual()) {
322: // We need to differentiate between pointers representing
323: // a non-existing property and ones representing a property
324: // whose value is null. In the latter case, the pointer
325: // is going to have isActual == false, but its parent,
326: // which is a non-node pointer identifying the bean property,
327: // will return isActual() == true.
328: NodePointer parent = ((NodePointer) result)
329: .getImmediateParentPointer();
330: if (parent == null || !parent.isContainer()
331: || !parent.isActual()) {
332: throw new JXPathException("No value for xpath: "
333: + xpath);
334: }
335: }
336: result = ((NodePointer) result).getValue();
337: }
338: return result;
339: }
340:
341: /**
342: * Calls getValue(xpath), converts the result to the required type
343: * and returns the result of the conversion.
344: */
345: public Object getValue(String xpath, Class requiredType) {
346: Expression expr = compileExpression(xpath);
347: return getValue(xpath, expr, requiredType);
348: }
349:
350: public Object getValue(String xpath, Expression expr,
351: Class requiredType) {
352: Object value = getValue(xpath, expr);
353: if (value != null && requiredType != null) {
354: if (!TypeUtils.canConvert(value, requiredType)) {
355: throw new JXPathException("Invalid expression type. '"
356: + xpath + "' returns "
357: + value.getClass().getName()
358: + ". It cannot be converted to "
359: + requiredType.getName());
360: }
361: value = TypeUtils.convert(value, requiredType);
362: }
363: return value;
364: }
365:
366: /**
367: * Traverses the xpath and returns a Iterator of all results found
368: * for the path. If the xpath matches no properties
369: * in the graph, the Iterator will not be null.
370: */
371: public Iterator iterate(String xpath) {
372: return iterate(xpath, compileExpression(xpath));
373: }
374:
375: public Iterator iterate(String xpath, Expression expr) {
376: return expr.iterate(getEvalContext());
377: }
378:
379: public Pointer getPointer(String xpath) {
380: return getPointer(xpath, compileExpression(xpath));
381: }
382:
383: public Pointer getPointer(String xpath, Expression expr) {
384: Object result = expr.computeValue(getEvalContext());
385: if (result instanceof EvalContext) {
386: result = ((EvalContext) result).getSingleNodePointer();
387: }
388: if (result instanceof Pointer) {
389: if (!isLenient() && !((NodePointer) result).isActual()) {
390: throw new JXPathException("No pointer for xpath: "
391: + xpath);
392: }
393: return (Pointer) result;
394: } else {
395: return NodePointer
396: .newNodePointer(null, result, getLocale());
397: }
398: }
399:
400: public void setValue(String xpath, Object value) {
401: setValue(xpath, compileExpression(xpath), value);
402: }
403:
404: public void setValue(String xpath, Expression expr, Object value) {
405: try {
406: setValue(xpath, expr, value, false);
407: } catch (Throwable ex) {
408: throw new JXPathException(
409: "Exception trying to set value with xpath " + xpath,
410: ex);
411: }
412: }
413:
414: public Pointer createPath(String xpath) {
415: return createPath(xpath, compileExpression(xpath));
416: }
417:
418: public Pointer createPath(String xpath, Expression expr) {
419: try {
420: Object result = expr.computeValue(getEvalContext());
421: Pointer pointer = null;
422:
423: if (result instanceof Pointer) {
424: pointer = (Pointer) result;
425: } else if (result instanceof EvalContext) {
426: EvalContext ctx = (EvalContext) result;
427: pointer = ctx.getSingleNodePointer();
428: } else {
429: checkSimplePath(expr);
430: // This should never happen
431: throw new JXPathException("Cannot create path:" + xpath);
432: }
433: return ((NodePointer) pointer).createPath(this );
434: } catch (Throwable ex) {
435: throw new JXPathException(
436: "Exception trying to create xpath " + xpath, ex);
437: }
438: }
439:
440: public Pointer createPathAndSetValue(String xpath, Object value) {
441: return createPathAndSetValue(xpath, compileExpression(xpath),
442: value);
443: }
444:
445: public Pointer createPathAndSetValue(String xpath, Expression expr,
446: Object value) {
447: try {
448: return setValue(xpath, expr, value, true);
449: } catch (Throwable ex) {
450: throw new JXPathException(
451: "Exception trying to create xpath " + xpath, ex);
452: }
453: }
454:
455: private Pointer setValue(String xpath, Expression expr,
456: Object value, boolean create) {
457: Object result = expr.computeValue(getEvalContext());
458: Pointer pointer = null;
459:
460: if (result instanceof Pointer) {
461: pointer = (Pointer) result;
462: } else if (result instanceof EvalContext) {
463: EvalContext ctx = (EvalContext) result;
464: pointer = ctx.getSingleNodePointer();
465: } else {
466: if (create) {
467: checkSimplePath(expr);
468: }
469:
470: // This should never happen
471: throw new JXPathException("Cannot set value for xpath: "
472: + xpath);
473: }
474: if (create) {
475: pointer = ((NodePointer) pointer).createPath(this , value);
476: } else {
477: pointer.setValue(value);
478: }
479: return pointer;
480: }
481:
482: /**
483: * Checks if the path follows the JXPath restrictions on the type
484: * of path that can be passed to create... methods.
485: */
486: private void checkSimplePath(Expression expr) {
487: if (!(expr instanceof LocationPath)
488: || !((LocationPath) expr).isSimplePath()) {
489: throw new JXPathException(
490: "JXPath can only create a path if it uses exclusively "
491: + "the child:: and attribute:: axes and has "
492: + "no context-dependent predicates");
493: }
494: }
495:
496: /**
497: * Traverses the xpath and returns an Iterator of Pointers.
498: * A Pointer provides easy access to a property.
499: * If the xpath matches no properties
500: * in the graph, the Iterator be empty, but not null.
501: */
502: public Iterator iteratePointers(String xpath) {
503: return iteratePointers(xpath, compileExpression(xpath));
504: }
505:
506: public Iterator iteratePointers(String xpath, Expression expr) {
507: return expr.iteratePointers(getEvalContext());
508: }
509:
510: public void removePath(String xpath) {
511: removePath(xpath, compileExpression(xpath));
512: }
513:
514: public void removePath(String xpath, Expression expr) {
515: try {
516: NodePointer pointer = (NodePointer) getPointer(xpath, expr);
517: if (pointer != null) {
518: ((NodePointer) pointer).remove();
519: }
520: } catch (Throwable ex) {
521: throw new JXPathException(
522: "Exception trying to remove xpath " + xpath, ex);
523: }
524: }
525:
526: public void removeAll(String xpath) {
527: removeAll(xpath, compileExpression(xpath));
528: }
529:
530: public void removeAll(String xpath, Expression expr) {
531: try {
532: ArrayList list = new ArrayList();
533: Iterator it = expr.iteratePointers(getEvalContext());
534: while (it.hasNext()) {
535: list.add(it.next());
536: }
537: Collections.sort(list);
538: for (int i = list.size() - 1; i >= 0; i--) {
539: NodePointer pointer = (NodePointer) list.get(i);
540: pointer.remove();
541: }
542: } catch (Throwable ex) {
543: throw new JXPathException(
544: "Exception trying to remove all for xpath " + xpath,
545: ex);
546: }
547: }
548:
549: public JXPathContext getRelativeContext(Pointer pointer) {
550: Object contextBean = pointer.getNode();
551: if (contextBean == null) {
552: throw new JXPathException(
553: "Cannot create a relative context for a non-existent node: "
554: + pointer);
555: }
556: return new JXPathContextReferenceImpl(this , contextBean,
557: pointer);
558: }
559:
560: public Pointer getContextPointer() {
561: return contextPointer;
562: }
563:
564: private NodePointer getAbsoluteRootPointer() {
565: return (NodePointer) rootPointer;
566: }
567:
568: private EvalContext getEvalContext() {
569: return new InitialContext(new RootContext(this ,
570: (NodePointer) getContextPointer()));
571: }
572:
573: public EvalContext getAbsoluteRootContext() {
574: return new InitialContext(new RootContext(this ,
575: getAbsoluteRootPointer()));
576: }
577:
578: public NodePointer getVariablePointer(QName name) {
579: String varName = name.toString();
580: JXPathContext varCtx = this ;
581: Variables vars = null;
582: while (varCtx != null) {
583: vars = varCtx.getVariables();
584: if (vars.isDeclaredVariable(varName)) {
585: break;
586: }
587: varCtx = varCtx.getParentContext();
588: vars = null;
589: }
590: if (vars != null) {
591: return new VariablePointer(vars, name);
592: } else {
593: // The variable is not declared, but we will create
594: // a pointer anyway in case the user want to set, rather
595: // than get, the value of the variable.
596: return new VariablePointer(name);
597: }
598: }
599:
600: public Function getFunction(QName functionName, Object[] parameters) {
601: String namespace = functionName.getPrefix();
602: String name = functionName.getName();
603: JXPathContext funcCtx = this ;
604: Function func = null;
605: Functions funcs;
606: while (funcCtx != null) {
607: funcs = funcCtx.getFunctions();
608: if (funcs != null) {
609: func = funcs.getFunction(namespace, name, parameters);
610: if (func != null) {
611: return func;
612: }
613: }
614: funcCtx = funcCtx.getParentContext();
615: }
616: throw new JXPathException("Undefined function: "
617: + functionName.toString());
618: }
619:
620: public void registerNamespace(String prefix, String namespaceURI) {
621: if (namespaceResolver.isSealed()) {
622: namespaceResolver = (NamespaceResolver) namespaceResolver
623: .clone();
624: }
625: namespaceResolver.registerNamespace(prefix, namespaceURI);
626: }
627:
628: public String getNamespaceURI(String prefix) {
629: return namespaceResolver.getNamespaceURI(prefix);
630: }
631:
632: public void setNamespaceContextPointer(Pointer pointer) {
633: if (namespaceResolver.isSealed()) {
634: namespaceResolver = (NamespaceResolver) namespaceResolver
635: .clone();
636: }
637: namespaceResolver
638: .setNamespaceContextPointer((NodePointer) pointer);
639: }
640:
641: public Pointer getNamespaceContextPointer() {
642: return namespaceResolver.getNamespaceContextPointer();
643: }
644:
645: public NamespaceResolver getNamespaceResolver() {
646: namespaceResolver.seal();
647: return namespaceResolver;
648: }
649:
650: /**
651: * Checks if existenceCheckClass exists on the class path. If so, allocates
652: * an instance of the specified class, otherwise returns null.
653: */
654: public static Object allocateConditionally(String className,
655: String existenceCheckClassName) {
656: try {
657: try {
658: Class.forName(existenceCheckClassName);
659: } catch (ClassNotFoundException ex) {
660: return null;
661: }
662:
663: Class cls = Class.forName(className);
664: return cls.newInstance();
665: } catch (Exception ex) {
666: throw new JXPathException("Cannot allocate " + className,
667: ex);
668: }
669: }
670: }
|