001: /*--
002:
003: $Id: XPath.java,v 1.2 2005/05/03 07:02:04 wittek Exp $
004:
005: Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
006: All rights reserved.
007:
008: Redistribution and use in source and binary forms, with or without
009: modification, are permitted provided that the following conditions
010: are met:
011:
012: 1. Redistributions of source code must retain the above copyright
013: notice, this list of conditions, and the following disclaimer.
014:
015: 2. Redistributions in binary form must reproduce the above copyright
016: notice, this list of conditions, and the disclaimer that follows
017: these conditions in the documentation and/or other materials
018: provided with the distribution.
019:
020: 3. The name "JDOM" must not be used to endorse or promote products
021: derived from this software without prior written permission. For
022: written permission, please contact <request_AT_jdom_DOT_org>.
023:
024: 4. Products derived from this software may not be called "JDOM", nor
025: may "JDOM" appear in their name, without prior written permission
026: from the JDOM Project Management <request_AT_jdom_DOT_org>.
027:
028: In addition, we request (but do not require) that you include in the
029: end-user documentation provided with the redistribution and/or in the
030: software itself an acknowledgement equivalent to the following:
031: "This product includes software developed by the
032: JDOM Project (http://www.jdom.org/)."
033: Alternatively, the acknowledgment may be graphical using the logos
034: available at http://www.jdom.org/images/logos.
035:
036: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
040: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: SUCH DAMAGE.
048:
049: This software consists of voluntary contributions made by many
050: individuals on behalf of the JDOM Project and was originally
051: created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
052: Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
053: on the JDOM Project, please see <http://www.jdom.org/>.
054:
055: */
056:
057: package org.jdom.xpath;
058:
059: import java.io.*;
060: import java.lang.reflect.*;
061: import java.util.*;
062:
063: import org.jdom.*;
064:
065: /**
066: * A utility class for performing XPath calls on JDOM nodes, with a factory
067: * interface for obtaining a first XPath instance. Users operate against this
068: * class while XPath vendors can plug-in implementations underneath. Users
069: * can choose an implementation using either {@link #setXPathClass} or
070: * the system property "org.jdom.xpath.class".
071: *
072: * @version $Revision: 1.2 $, $Date: 2005/05/03 07:02:04 $
073: * @author Laurent Bihanic
074: */
075: public abstract class XPath implements Serializable {
076:
077: private static final String CVS_ID = "@(#) $RCSfile: XPath.java,v $ $Revision: 1.2 $ $Date: 2005/05/03 07:02:04 $ $Name: $";
078:
079: /**
080: * The name of the system property from which to retrieve the
081: * name of the implementation class to use.
082: * <p>
083: * The property name is:
084: * "<code>org.jdom.xpath.class</code>".</p>
085: */
086: private final static String XPATH_CLASS_PROPERTY = "org.jdom.xpath.class";
087:
088: /**
089: * The default implementation class to use if none was configured.
090: */
091: private final static String DEFAULT_XPATH_CLASS = "org.jdom.xpath.JaxenXPath";
092:
093: /**
094: * The constructor to instanciate a new XPath concrete
095: * implementation.
096: *
097: * @see #newInstance
098: */
099: private static Constructor constructor = null;
100:
101: /**
102: * Creates a new XPath wrapper object, compiling the specified
103: * XPath expression.
104: *
105: * @param path the XPath expression to wrap.
106: *
107: * @throws JDOMException if the XPath expression is invalid.
108: */
109: public static XPath newInstance(String path) throws JDOMException {
110: try {
111: if (constructor == null) {
112: // First call => Determine implementation.
113: String className;
114: try {
115: className = System.getProperty(
116: XPATH_CLASS_PROPERTY, DEFAULT_XPATH_CLASS);
117: } catch (SecurityException ex1) {
118: // Access to system property denied. => Use default impl.
119: className = DEFAULT_XPATH_CLASS;
120: }
121: setXPathClass(Class.forName(className));
122: }
123: // Allocate and return new implementation instance.
124: return (XPath) constructor
125: .newInstance(new Object[] { path });
126: } catch (JDOMException ex1) {
127: throw ex1;
128: } catch (InvocationTargetException ex2) {
129: // Constructor threw an error on invocation.
130: Throwable t = ex2.getTargetException();
131:
132: throw (t instanceof JDOMException) ? (JDOMException) t
133: : new JDOMException(t.toString(), t);
134: } catch (Exception ex3) {
135: // Any reflection error (probably due to a configuration mistake).
136: throw new JDOMException(ex3.toString(), ex3);
137: }
138: }
139:
140: /**
141: * Sets the concrete XPath subclass to use when allocating XPath
142: * instances.
143: *
144: * @param aClass the concrete subclass of XPath.
145: *
146: * @throws IllegalArgumentException if <code>aClass</code> is
147: * <code>null</code>.
148: * @throws JDOMException if <code>aClass</code> is
149: * not a concrete subclass
150: * of XPath.
151: */
152: public static void setXPathClass(Class aClass) throws JDOMException {
153: if (aClass == null) {
154: throw new IllegalArgumentException("aClass");
155: }
156:
157: try {
158: if ((XPath.class.isAssignableFrom(aClass))
159: && (Modifier.isAbstract(aClass.getModifiers()) == false)) {
160: // Concrete subclass of XPath => Get constructor
161: constructor = aClass
162: .getConstructor(new Class[] { String.class });
163: } else {
164: throw new JDOMException(
165: aClass.getName()
166: + " is not a concrete JDOM XPath implementation");
167: }
168: } catch (JDOMException ex1) {
169: throw ex1;
170: } catch (Exception ex2) {
171: // Any reflection error (probably due to a configuration mistake).
172: throw new JDOMException(ex2.toString(), ex2);
173: }
174: }
175:
176: /**
177: * Evaluates the wrapped XPath expression and returns the list
178: * of selected items.
179: *
180: * @param context the node to use as context for evaluating
181: * the XPath expression.
182: *
183: * @return the list of selected items, which may be of types: {@link Element},
184: * {@link Attribute}, {@link Text}, {@link CDATA},
185: * {@link Comment}, {@link ProcessingInstruction}, Boolean,
186: * Double, or String.
187: *
188: * @throws JDOMException if the evaluation of the XPath
189: * expression on the specified context
190: * failed.
191: */
192: abstract public List selectNodes(Object context)
193: throws JDOMException;
194:
195: /**
196: * Evaluates the wrapped XPath expression and returns the first
197: * entry in the list of selected nodes (or atomics).
198: *
199: * @param context the node to use as context for evaluating
200: * the XPath expression.
201: *
202: * @return the first selected item, which may be of types: {@link Element},
203: * {@link Attribute}, {@link Text}, {@link CDATA},
204: * {@link Comment}, {@link ProcessingInstruction}, Boolean,
205: * Double, String, or <code>null</code> if no item was selected.
206: *
207: * @throws JDOMException if the evaluation of the XPath
208: * expression on the specified context
209: * failed.
210: */
211: abstract public Object selectSingleNode(Object context)
212: throws JDOMException;
213:
214: /**
215: * Returns the string value of the first node selected by applying
216: * the wrapped XPath expression to the given context.
217: *
218: * @param context the element to use as context for evaluating
219: * the XPath expression.
220: *
221: * @return the string value of the first node selected by applying
222: * the wrapped XPath expression to the given context.
223: *
224: * @throws JDOMException if the XPath expression is invalid or
225: * its evaluation on the specified context
226: * failed.
227: */
228: abstract public String valueOf(Object context) throws JDOMException;
229:
230: /**
231: * Returns the number value of the first node selected by applying
232: * the wrapped XPath expression to the given context.
233: *
234: * @param context the element to use as context for evaluating
235: * the XPath expression.
236: *
237: * @return the number value of the first node selected by applying
238: * the wrapped XPath expression to the given context,
239: * <code>null</code> if no node was selected or the
240: * special value {@link java.lang.Double#NaN}
241: * (NotElement-a-Number) if the selected value can not be
242: * converted into a number value.
243: *
244: * @throws JDOMException if the XPath expression is invalid or
245: * its evaluation on the specified context
246: * failed.
247: */
248: abstract public Number numberValueOf(Object context)
249: throws JDOMException;
250:
251: /**
252: * Defines an XPath variable and sets its value.
253: *
254: * @param name the variable name.
255: * @param value the variable value.
256: *
257: * @throws IllegalArgumentException if <code>name</code> is not
258: * a valid XPath variable name
259: * or if the value type is not
260: * supported by the underlying
261: * implementation
262: */
263: abstract public void setVariable(String name, Object value);
264:
265: /**
266: * Adds a namespace definition to the list of namespaces known of
267: * this XPath expression.
268: * <p>
269: * <strong>Note</strong>: In XPath, there is no such thing as a
270: * 'default namespace'. The empty prefix <b>always</b> resolves
271: * to the empty namespace URI.</p>
272: *
273: * @param namespace the namespace.
274: */
275: abstract public void addNamespace(Namespace namespace);
276:
277: /**
278: * Adds a namespace definition (prefix and URI) to the list of
279: * namespaces known of this XPath expression.
280: * <p>
281: * <strong>Note</strong>: In XPath, there is no such thing as a
282: * 'default namespace'. The empty prefix <b>always</b> resolves
283: * to the empty namespace URI.</p>
284: *
285: * @param prefix the namespace prefix.
286: * @param uri the namespace URI.
287: *
288: * @throws IllegalNameException if the prefix or uri are null or
289: * empty strings or if they contain
290: * illegal characters.
291: */
292: public void addNamespace(String prefix, String uri) {
293: addNamespace(Namespace.getNamespace(prefix, uri));
294: }
295:
296: /**
297: * Returns the wrapped XPath expression as a string.
298: *
299: * @return the wrapped XPath expression as a string.
300: */
301: abstract public String getXPath();
302:
303: /**
304: * Evaluates an XPath expression and returns the list of selected
305: * items.
306: * <p>
307: * <strong>Note</strong>: This method should not be used when the
308: * same XPath expression needs to be applied several times (on the
309: * same or different contexts) as it requires the expression to be
310: * compiled before being evaluated. In such cases,
311: * {@link #newInstance allocating} an XPath wrapper instance and
312: * {@link #selectNodes(java.lang.Object) evaluating} it several
313: * times is way more efficient.
314: * </p>
315: *
316: * @param context the node to use as context for evaluating
317: * the XPath expression.
318: * @param path the XPath expression to evaluate.
319: *
320: * @return the list of selected items, which may be of types: {@link Element},
321: * {@link Attribute}, {@link Text}, {@link CDATA},
322: * {@link Comment}, {@link ProcessingInstruction}, Boolean,
323: * Double, or String.
324: *
325: * @throws JDOMException if the XPath expression is invalid or
326: * its evaluation on the specified context
327: * failed.
328: */
329: public static List selectNodes(Object context, String path)
330: throws JDOMException {
331: return newInstance(path).selectNodes(context);
332: }
333:
334: /**
335: * Evaluates the wrapped XPath expression and returns the first
336: * entry in the list of selected nodes (or atomics).
337: * <p>
338: * <strong>Note</strong>: This method should not be used when the
339: * same XPath expression needs to be applied several times (on the
340: * same or different contexts) as it requires the expression to be
341: * compiled before being evaluated. In such cases,
342: * {@link #newInstance allocating} an XPath wrapper instance and
343: * {@link #selectSingleNode(java.lang.Object) evaluating} it
344: * several times is way more efficient.
345: * </p>
346: *
347: * @param context the element to use as context for evaluating
348: * the XPath expression.
349: * @param path the XPath expression to evaluate.
350: *
351: * @return the first selected item, which may be of types: {@link Element},
352: * {@link Attribute}, {@link Text}, {@link CDATA},
353: * {@link Comment}, {@link ProcessingInstruction}, Boolean,
354: * Double, String, or <code>null</code> if no item was selected.
355: *
356: * @throws JDOMException if the XPath expression is invalid or
357: * its evaluation on the specified context
358: * failed.
359: */
360: public static Object selectSingleNode(Object context, String path)
361: throws JDOMException {
362: return newInstance(path).selectSingleNode(context);
363: }
364:
365: //-------------------------------------------------------------------------
366: // Serialization support
367: //-------------------------------------------------------------------------
368:
369: /**
370: * <i>[Serialization support]</i> Returns the alternative object
371: * to write to the stream when serializing this object. This
372: * method returns an instance of a dedicated nested class to
373: * serialize XPath expressions independently of the concrete
374: * implementation being used.
375: * <p>
376: * <strong>Note</strong>: Subclasses are not allowed to override
377: * this method to ensure valid serialization of all
378: * implementations.</p>
379: *
380: * @return an XPathString instance configured with the wrapped
381: * XPath expression.
382: *
383: * @throws ObjectStreamException never.
384: */
385: protected final Object writeReplace() throws ObjectStreamException {
386: return new XPathString(this .getXPath());
387: }
388:
389: /**
390: * The XPathString is dedicated to serialize instances of
391: * XPath subclasses in a implementation-independent manner.
392: * <p>
393: * XPathString ensures that only string data are serialized. Upon
394: * deserialization, XPathString relies on XPath factory method to
395: * to create instances of the concrete XPath wrapper currently
396: * configured.</p>
397: */
398: private final static class XPathString implements Serializable {
399: /**
400: * The XPath expression as a string.
401: */
402: private String xPath = null;
403:
404: /**
405: * Creates a new XPathString instance from the specified
406: * XPath expression.
407: *
408: * @param xpath the XPath expression.
409: */
410: public XPathString(String xpath) {
411: super ();
412:
413: this .xPath = xpath;
414: }
415:
416: /**
417: * <i>[Serialization support]</i> Resolves the read XPathString
418: * objects into XPath implementations.
419: *
420: * @return an instance of a concrete implementation of
421: * XPath.
422: *
423: * @throws ObjectStreamException if no XPath could be built
424: * from the read object.
425: */
426: private Object readResolve() throws ObjectStreamException {
427: try {
428: return XPath.newInstance(this .xPath);
429: } catch (JDOMException ex1) {
430: throw new InvalidObjectException(
431: "Can't create XPath object for expression \""
432: + this .xPath + "\": " + ex1.toString());
433: }
434: }
435: }
436: }
|