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 }