View Javadoc

1   /* This file is part of the project "Hilbert II" - http://www.qedeq.org" target="alexandria_uri">http://www.qedeq.org
2    *
3    * Copyright 2000-2014,  Michael Meyling <mime@qedeq.org>.
4    *
5    * "Hilbert II" is free software; you can redistribute
6    * it and/or modify it under the terms of the GNU General Public
7    * License as published by the Free Software Foundation; either
8    * version 2 of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13   * GNU General Public License for more details.
14   */
15  
16  package org.qedeq.kernel.xml.tracker;
17  
18  import java.util.ArrayList;
19  import java.util.List;
20  import java.util.StringTokenizer;
21  
22  import org.qedeq.base.utility.EqualsUtility;
23  
24  
25  /**
26   * Simple XPath like description of a location in an XML file.
27   *
28   * @author    Michael Meyling
29   */
30  public class SimpleXPath {
31  
32      /** List with element names. */
33      private final List elements;
34  
35      /** List with element occurrence numbers. */
36      private final List numbers;
37  
38      /** Attribute of element. */
39      private String attribute;
40  
41      /**
42       * Constructor with simple XPath string as parameter.
43       * This is not the standard XPath definition but it is similar to a subset of
44       * the abbreviation XPath notation.
45       * <p>
46       * <code>/element1/element2[3]@attribute</code> is an example for such
47       * a notation. This selects from the first occurrence of <code>element1</code>
48       * and from the third occurrence of subnode <code>element2</code> the attribute
49       * <code>attribute</code>. The attribute is optional. It is always exactly one node or
50       * the attribute of one node specified.
51       * <p>
52       * The general syntax could be described as follows:
53       * {"/"<em>element</em>"["<em>index</em>"]"}+
54       * ["@"<em>attribute</em>]
55       *
56       *
57       * @param   xpath   String with the syntax as described above. If the syntax is violated
58       *                  RuntimeExceptions may occur.
59       */
60      public SimpleXPath(final String xpath) {
61          elements = new ArrayList();
62          numbers = new ArrayList();
63          attribute = null;
64          init(xpath);
65      }
66  
67      /**
68       * Empty constructor.
69       */
70      public SimpleXPath() {
71          elements = new ArrayList();
72          numbers = new ArrayList();
73          attribute = null;
74      }
75  
76      /**
77       * Copy constructor.
78       *
79       * @param   original    XPath to copy.
80       */
81      public SimpleXPath(final SimpleXPath original) {
82          elements = new ArrayList();
83          numbers = new ArrayList();
84          attribute = null;
85          init(original.toString());
86      }
87  
88      /**
89       * Initialize all object attributes according to XPath parameter.
90       *
91       * @see SimpleXPath#SimpleXPath(String)
92       *
93       * @param   xpath   String with the syntax as described above. If the syntax is violated
94       *                  RuntimeExceptions may occur.
95       */
96      private void init(final String xpath) {
97          if (xpath == null) {
98              throw new NullPointerException();
99          }
100         if (xpath.length() <= 0) {
101             throw new RuntimeException("XPath must not be empty");
102         }
103         if (xpath.charAt(0) != '/') {
104             throw new RuntimeException("XPath must start with '/': " + xpath);
105         }
106         if (xpath.indexOf("//") >= 0) {
107             throw new RuntimeException("empty tag not permitted: " + xpath);
108         }
109         if (xpath.endsWith("/")) {
110             throw new RuntimeException("XPath must not end with '/': " + xpath);
111         }
112         final StringTokenizer tokenizer = new StringTokenizer(xpath, "/");
113         while (tokenizer.hasMoreTokens()) {
114             String token = tokenizer.nextToken();
115             if (!tokenizer.hasMoreTokens() && token.indexOf('@') >= 0) {
116                 attribute = token.substring(token.indexOf('@') + 1);
117                 if (attribute.length() <= 0) {
118                     throw new RuntimeException("empty attribute not permitted: " + xpath);
119                 }
120                 token = token.substring(0, token.indexOf('@'));
121             }
122             if (token.indexOf('[') < 0) {
123                 elements.add(token);
124                 numbers.add(new Integer(1));
125             } else {
126                 final StringTokenizer getnu = new StringTokenizer(token, "[]");
127                 elements.add(getnu.nextToken());
128                 final Integer i;
129                 try {
130                     i = new Integer(getnu.nextToken());
131                 } catch (RuntimeException e) {
132                     throw new RuntimeException("not an integer: " + xpath, e);
133                 }
134                 if (i.intValue() <= 0) {
135                     throw new RuntimeException("integer must be greater zero: " + xpath);
136                 }
137                 numbers.add(i);
138             }
139         }
140     }
141 
142     /**
143      * Get number of collected exceptions.
144      *
145      * @return  Number of collected exceptions.
146      */
147     public final int size() {
148         return elements.size();
149     }
150 
151     /**
152      * Get <code>i</code>-th Element name.
153      *
154      * @param   i   Starts with 0 and must be smaller than {@link #size()}.
155      * @return  Wanted element name.
156      */
157     public final String getElementName(final int i) {
158         return (String) elements.get(i);
159     }
160 
161     /**
162      * Get <code>i</code>-th occurrence number.
163      *
164      * @param   i   Starts with 0 and must be smaller than {@link #size()}.
165      * @return  Wanted element occurrence number.
166      */
167     public final int getElementOccurrence(final int i) {
168         return ((Integer) numbers.get(i)).intValue();
169     }
170 
171     /**
172      * Add new element to end of XPath.
173      *
174      * @param   elementName element to add.
175      */
176     public final void addElement(final String elementName) {
177         attribute = null;
178         elements.add(elementName);
179         numbers.add(new Integer(1));
180     }
181 
182     /**
183      * Add new element to end of XPath.
184      *
185      * @param   elementName element to add.
186      * @param   occurrence  Occurrence number of element. Starts with 1.
187      */
188     public final void addElement(final String elementName, final int occurrence) {
189         attribute = null;
190         elements.add(elementName);
191         numbers.add(new Integer(occurrence));
192     }
193 
194     /**
195      * Get last XPath element name.
196      *
197      * @return  Last element name. Could be <code>null</code> if no elements exist.
198      */
199     public final String getLastElement() {
200         int size = elements.size();
201         if (size <= 0) {
202             return null;
203         }
204         return (String) elements.get(size - 1);
205     }
206 
207     /**
208      * Get XPath element name before last.
209      *
210      * @return  Before last element name. Could be <code>null</code> if no more than one element
211      *          exist.
212      */
213     public final String getBeforeLastElement() {
214         int size = elements.size();
215         if (size <= 1) {
216             return null;
217         }
218         return (String) elements.get(size - 2);
219     }
220 
221     /**
222      * Delete last XPath element if any.
223      */
224     public void deleteLastElement() {
225         int size = elements.size();
226         if (size > 0) {
227             elements.remove(size - 1);
228             numbers.remove(size - 1);
229             attribute = null;
230         }
231     }
232 
233     /**
234      * Set attribute.
235      *
236      * @param   attribute   Attribute, maybe <code>null</code>.
237      */
238     public final void setAttribute(final String attribute) {
239         this.attribute = attribute;
240     }
241 
242     /**
243      * Get attribute.
244      *
245      * @return  Attribute, maybe <code>null</code>.
246      */
247     public final String getAttribute() {
248         return attribute;
249     }
250 
251 
252     public final boolean equals(final Object obj) {
253         if (!(obj instanceof SimpleXPath)) {
254             return false;
255         }
256         final SimpleXPath other = (SimpleXPath) obj;
257         if (!EqualsUtility.equals(this.getAttribute(), other.getAttribute())) {
258             return false;
259         }
260         return equalsElements(other);
261     }
262 
263     /**
264      * Are the elements and occurrences of this and another element equal? No special treatment
265      * of "*" elements.
266      *
267      * @param   other   Compare with this object.
268      * @return  Are the elements of this and the parameter object equal?
269      */
270     public final boolean equalsElements(final SimpleXPath other) {
271         final int size = this.size();
272         if (size != other.size()) {
273             return false;
274         }
275 
276         for (int i = 0; i < size; i++) {
277             if (!EqualsUtility.equals(this.getElementName(i), other.getElementName(i))) {
278                 return false;
279             }
280             if (getElementOccurrence(i) != other.getElementOccurrence(i)) {
281                 return false;
282             }
283         }
284         return true;
285     }
286 
287     /**
288      * Match the elements and occurrences of this finder object and current elements?
289      * This object may contain "*" elements.
290      *
291      * @param   current         Compare with this current elements. These elements should not
292      *                          contain "*" elements.
293      * @param   currentSummary  Contains only "*" elements. This parameter must be identify the same
294      *                          XPath as <code>current</code>
295      * @return  Match the elements of this finder object and the parameter objects?
296      */
297     public final boolean matchesElements(final SimpleXPath current,
298             final SimpleXPath currentSummary) {
299         final int size = current.size();
300         if (size != size()) {
301             return false;
302         }
303         if (size != currentSummary.size()) {
304             throw new IllegalArgumentException("summary size doesn't match");
305         }
306 
307         for (int i = 0; i < size; i++) {
308             if ("*".equals(getElementName(i))) {
309                 if (getElementOccurrence(i) != currentSummary.getElementOccurrence(i)) {
310                     return false;
311                 }
312                 continue;
313             }
314             if (!EqualsUtility.equals(current.getElementName(i), getElementName(i))) {
315                 return false;
316             }
317             if (current.getElementOccurrence(i) != getElementOccurrence(i)) {
318                 return false;
319             }
320         }
321         return true;
322     }
323 
324     /**
325      * Match the elements and occurrences of this finder object and current elements?
326      * This object may contain "*" elements. Checks only to current.size().
327      *
328      * @param   current         Compare with this current elements. These elements should not
329      *                          contain "*" elements.
330      * @param   currentSummary  Contains only "*" elements. This parameter must be identify the same
331      *                          XPath as <code>current</code>
332      * @return  Match the elements of this finder object and the parameter objects?
333      */
334     public final boolean matchesElementsBegining(final SimpleXPath current,
335             final SimpleXPath currentSummary) {
336         final int size = current.size();
337         if (size > size()) {
338             return false;
339         }
340         if (size != currentSummary.size()) {
341             throw new IllegalArgumentException("summary size doesn't match");
342         }
343 
344         for (int i = 0; i < size; i++) {
345             if ("*".equals(getElementName(i))) {
346                 if (getElementOccurrence(i) != currentSummary.getElementOccurrence(i)) {
347                     return false;
348                 }
349                 continue;
350             }
351             if (!EqualsUtility.equals(current.getElementName(i), getElementName(i))) {
352                 return false;
353             }
354             if (current.getElementOccurrence(i) != getElementOccurrence(i)) {
355                 return false;
356             }
357         }
358         return true;
359     }
360 
361     public final String toString() {
362         final StringBuffer buffer = new StringBuffer();
363         for (int i = 0; i < size(); i++) {
364             buffer.append("/");
365             buffer.append(getElementName(i));
366             if (getElementOccurrence(i) != 1) {
367                 buffer.append("[");
368                 buffer.append(getElementOccurrence(i));
369                 buffer.append("]");
370             }
371         }
372         if (getAttribute() != null) {
373             buffer.append("@");
374             buffer.append(getAttribute());
375         }
376         return buffer.toString();
377     }
378 
379     public final int hashCode() {
380         int code = 0;
381         if (attribute != null) {
382             code ^= attribute.hashCode();
383         }
384         for (int i = 0; i < size(); i++) {
385             code ^= i + 1;
386             code ^= getElementName(i).hashCode();
387             code ^= getElementOccurrence(i);
388         }
389         return code;
390     }
391 
392 }