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