KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jdom > contrib > input > scanner > XPathMatcher


1 /*--
2
3  $Id: XPathMatcher.java,v 1.3 2004/09/07 06:39:46 jhunter Exp $
4
5  Copyright (C) 2001-2004 Jason Hunter & Brett McLaughlin.
6  All rights reserved.
7
8  Redistribution and use in source and binary forms, with or without
9  modification, are permitted provided that the following conditions
10  are met:
11
12  1. Redistributions of source code must retain the above copyright
13     notice, this list of conditions, and the following disclaimer.
14
15  2. Redistributions in binary form must reproduce the above copyright
16     notice, this list of conditions, and the disclaimer that follows
17     these conditions in the documentation and/or other materials
18     provided with the distribution.
19
20  3. The name "JDOM" must not be used to endorse or promote products
21     derived from this software without prior written permission. For
22     written permission, please contact <request_AT_jdom_DOT_org>.
23
24  4. Products derived from this software may not be called "JDOM", nor
25     may "JDOM" appear in their name, without prior written permission
26     from the JDOM Project Management <request_AT_jdom_DOT_org>.
27
28  In addition, we request (but do not require) that you include in the
29  end-user documentation provided with the redistribution and/or in the
30  software itself an acknowledgement equivalent to the following:
31      "This product includes software developed by the
32       JDOM Project (http://www.jdom.org/)."
33  Alternatively, the acknowledgment may be graphical using the logos
34  available at http://www.jdom.org/images/logos.
35
36  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39  DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
40  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47  SUCH DAMAGE.
48
49  This software consists of voluntary contributions made by many
50  individuals on behalf of the JDOM Project and was originally
51  created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
52  Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
53  on the JDOM Project, please see <http://www.jdom.org/>.
54
55  */

56
57 package org.jdom.contrib.input.scanner;
58
59
60 import java.lang.reflect.InvocationTargetException JavaDoc;
61 import java.lang.reflect.Constructor JavaDoc;
62 import java.lang.reflect.Modifier JavaDoc;
63
64 import org.jdom.Element;
65 import org.jdom.JDOMException;
66
67 import org.xml.sax.Attributes JavaDoc;
68
69
70 /**
71  * The base class for all XPath matchers used by
72  * {@link ElementScanner}.
73  * <p>
74  * This class also plays the role of factory for concrete
75  * implementations: The system property
76  * "<code>org.jdom.XPathMatcher.class</code>" shall contain the
77  * fully-qualified name of a concrete subclass of XPatchMatcher with
78  * a public {@link #XPathMatcher two argument constructor}. If this
79  * property is not defined, the default concrete implementation
80  * (supporting only node matching patterns) will be used.</p>
81  * <p>
82  * As the default implementation relies on Perl5-like regular
83  * expression to match nodes, any regular expression can be used as
84  * "<i>XPath expression</i>" with the restriction that any '*'
85  * character be escaped (i&#46;e&#46; preceded with a '\' character).</p>
86  *
87  * @author Laurent Bihanic
88  */

89 public abstract class XPathMatcher {
90
91    /**
92     * The name of the system property from which to retrieve the
93     * name of the implementation class to use.
94     * <p>
95     * The property name is:
96     * "<code>org.jdom.XPathMatcher.class</code>".</p>
97     */

98    private final static String JavaDoc IMPLEMENTATION_CLASS_PROPERTY =
99                                         "org.jdom.XPathMatcher.class";
100
101    /**
102     * The default implementation class to use if none was configured.
103     */

104    private final static String JavaDoc DEFAULT_IMPLEMENTATION_CLASS =
105                 "org.jdom.contrib.input.scanner.JakartaRegExpXPathMatcher";
106
107    /**
108     * The constructor to instanciate a new XPathMatcher concrete
109     * implementation.
110     *
111     * @see #newXPathMatcher
112     */

113    private static Constructor JavaDoc constructor = null;
114
115    /**
116     * Whether debug traces are active.
117     */

118    private static boolean debug = false;
119
120    /**
121     * The XPath expression this matcher matches!
122     */

123    private final String JavaDoc expression;
124
125    /**
126     * The element listener.
127     */

128    private final ElementListener listener;
129
130    /**
131     * Default constructor, protected on purpose.
132     *
133     * @param expression the XPath-like expression to match.
134     * @param listener the element listener to notify when an
135     * element matches the expression.
136     *
137     * @throws JDOMException if one of the arguments is invalid.
138     */

139    protected XPathMatcher(String JavaDoc expression, ElementListener listener)
140                                                         throws JDOMException {
141       if ((expression == null) || (expression.length() == 0)) {
142          throw (new JDOMException(
143                         "Invalid XPath expression: \"" + expression + "\""));
144       }
145       if (listener == null) {
146          throw (new JDOMException("Invalid ElementListener: <null>"));
147       }
148       this.expression = expression;
149       this.listener = listener;
150    }
151
152    /**
153     * Returns the XPath expression this matcher matches.
154     *
155     * @return the XPath expression this matcher matches.
156     */

157    public String JavaDoc getExpression() {
158       return (this.expression);
159    }
160
161    /**
162     * Returns the element listener associated to this matcher object.
163     *
164     * @return the element listener.
165     */

166    public ElementListener getListener() {
167       return (this.listener);
168    }
169
170    /**
171     * Tries to match an element path and attributes with the XPath
172     * expression this matcher matches.
173     * <p>
174     * This method is invoked when processing the
175     * <code>startElement</code> SAX event.</p>
176     * <p>
177     * <strong>Note</strong>: The default implementation ignores the
178     * attributes.</p>
179     *
180     * @param path the path to the element.
181     * @param attrs the SAX attributes of the element.
182     *
183     * @return <code>true</code> is the element matches the XPath
184     * expression, <code>false</code> otherwise.
185     */

186    abstract public boolean match(String JavaDoc path, Attributes JavaDoc attrs);
187
188    /**
189     * Tries to match an element with the XPath expression this
190     * matcher matches.
191     * <p>
192     * This method is invoked when processing the
193     * <code>endElement</code> SAX event. It allows matching on
194     * data not part of the <code>startElement</code> event such
195     * as the presence of child elements.</p>
196     * <p>
197     * <strong>Note</strong>: The default implementation always
198     * returns <code>true</code> as it only supports filtering on
199     * the node path.</p>
200     *
201     * @param path the path to the element.
202     * @param elt the JDOM element.
203     *
204     * @return <code>true</code> is the element matches the XPath
205     * expression, <code>false</code> otherwise.
206     */

207    abstract public boolean match(String JavaDoc path, Element elt);
208
209    /**
210     * Extracts the node matching pattern part from an XPath
211     * expression and converts it into a Perl5-like regular
212     * expression.
213     *
214     * @param expr an XPath expression.
215     *
216     * @return the RE to match the element path.
217     *
218     * @throws JDOMException if <code>expression</code> is invalid.
219     */

220    protected static String JavaDoc getPathPatternAsRE(String JavaDoc expr)
221                                                         throws JDOMException {
222       if ((expr == null) || (expr.length() == 0)) {
223          expr = "/*";
224       }
225
226       // It the expression ends with a square backet, a test part is
227
// present. => Strip it!
228
// Note: Any other sub-expression between square backet is view as
229
// a RE alphabet definition and proccessed. OK, that's not
230
// XPath compliant but that's very convenient!
231
String JavaDoc path = (expr.endsWith("]"))?
232                      expr.substring(0, expr.lastIndexOf('[')): expr;
233
234       int length = path.length();
235       StringBuffer JavaDoc re = new StringBuffer JavaDoc(2 * length);
236
237       char previous = (char)0;
238       for (int i=0; i<length; i++) {
239          char current = path.charAt(i);
240
241          if (i == 0) {
242             re.append((current == '/')? '^': '/');
243          }
244          if (current == '*') {
245             if (previous == '\\') {
246                // Escaped wildcard. => Remove previous '\' and insert '*'.
247
re.setLength(re.length() - 1);
248             }
249             else {
250                // Regular XPath wildcard.
251
// => "*" -> ".[^/]*", i.e. all chars but the separator '/'
252
re.append(".[^/]");
253             }
254             re.append('*');
255          }
256          else {
257             if ((current == '/') && (previous == '/')) {
258                // "//" -> "/.*/" or "/" !!!
259
// => Remove previous '/'
260
re.setLength(re.length() - 1);
261  
262                // And insert RE.
263
re.append("(/.*/|/)");
264             }
265             else {
266                re.append(current);
267             }
268          }
269          previous = current;
270       }
271       re.append('$');
272
273       return (re.toString());
274    }
275
276    /**
277     * Extracts the test part from an XPath expression. The test
278     * part if the part of the XPath expression between square
279     * backets.
280     *
281     * @param expr an XPath expression.
282     *
283     * @return the test part of the expression of <code>null</code>
284     * if no test was specified.
285     *
286     * @throws JDOMException if <code>expression</code> is invalid.
287     */

288    protected static String JavaDoc getTestPattern(String JavaDoc expr) throws JDOMException {
289       if (expr.endsWith("]")) {
290          return (expr.substring(expr.lastIndexOf('[')));
291       }
292       else {
293          return (null);
294       }
295    }
296
297
298    //-------------------------------------------------------------------------
299
// XPathMatcher Factory methods
300
//-------------------------------------------------------------------------
301

302    /**
303     * Activates or deactivates debug traces on the process standard
304     * output. Debug traces allow to trace the mapping between the
305     * XPath-like expressions provided when registering a listener
306     * and the regular expressions and/or XPath expressions actually
307     * used to filter elements.
308     *
309     * @param value whether to activate debug traces.
310     */

311    public static void setDebug(boolean value) {
312       debug = value;
313    }
314
315    /**
316     * Returns whether debug traces are active.
317     *
318     * @return <code>true</code> if debug traces are active;
319     * <code>false</code> otherwise.
320     */

321    public static boolean isDebug() {
322       return (debug);
323    }
324
325    /**
326     * Sets the concrete XPathMatcher subclass to be used when
327     * allocating XPathMatcher instances.
328     *
329     * @param aClass the concrete subclass of XPathMatcher.
330     *
331     * @throws IllegalArgumentException if <code>aClass</code> is
332     * <code>null</code>.
333     * @throws JDOMException if <code>aClass</code> is
334     * not a concrete subclass
335     * of XPathMatcher.
336     */

337    public static void setXPathMatcherClass(Class JavaDoc aClass)
338                         throws IllegalArgumentException JavaDoc, JDOMException {
339       if (aClass != null) {
340          try {
341             if ((XPathMatcher.class.isAssignableFrom(aClass)) &&
342                 (Modifier.isAbstract(aClass.getModifiers()) == false)) {
343                // Concrete subclass pf XPathMatcher.
344
// => Get the constructor to use.
345
constructor = aClass.getConstructor(
346                         new Class JavaDoc[] { String JavaDoc.class, ElementListener.class });
347             }
348             else {
349                throw (new JDOMException(
350                         aClass.getName() + " is not a concrete XPathMatcher"));
351             }
352          }
353          catch (Exception JavaDoc ex1) {
354             // Any reflection error (probably due to a configuration mistake).
355
throw (new JDOMException(ex1.toString(), ex1));
356          }
357       }
358       else {
359          throw (new IllegalArgumentException JavaDoc("aClass"));
360       }
361    }
362
363    /**
364     * Creates a new XPath matcher matching the specified XPath
365     * expression.
366     *
367     * @param expression the XPath-like expression to match.
368     * @param listener the element listener to notify when an
369     * element matches the expression.
370     *
371     * @throws JDOMException if <code>expression</code> is invalid.
372     */

373    public static final XPathMatcher newXPathMatcher(
374                                 String JavaDoc expression, ElementListener listener)
375                                                         throws JDOMException {
376       try {
377          if (constructor == null) {
378             // First call.
379
// => Load configuration to determine implementation.
380
String JavaDoc className = System.getProperty(
381                                         IMPLEMENTATION_CLASS_PROPERTY,
382                                         DEFAULT_IMPLEMENTATION_CLASS);
383
384             setXPathMatcherClass(Class.forName(className));
385          }
386          return ((XPathMatcher)(constructor.newInstance(new Object JavaDoc[]
387                                                 { expression, listener })));
388       }
389       catch (InvocationTargetException JavaDoc ex1) {
390          // Constructor threw an error on invocation.
391
Throwable JavaDoc te = ex1.getTargetException();
392
393          throw ((te instanceof JDOMException)? (JDOMException)te:
394                                         new JDOMException(te.toString(), te));
395       }
396       catch (Exception JavaDoc ex3) {
397          // Any reflection error (probably due to a configuration mistake).
398
throw (new JDOMException(ex3.toString(), ex3));
399       }
400    }
401 }
402
403
Popular Tags