org.qedeq.kernel.xml.tracker.SimpleXPath Maven / Gradle / Ivy
/* This file is part of the project "Hilbert II" - http://www.qedeq.org
*
* Copyright 2000-2013, Michael Meyling .
*
* "Hilbert II" is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package org.qedeq.kernel.xml.tracker;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.qedeq.base.utility.EqualsUtility;
/**
* Simple XPath like description of a location in an XML file.
*
* @author Michael Meyling
*/
public class SimpleXPath {
/** List with element names. */
private final List elements;
/** List with element occurrence numbers. */
private final List numbers;
/** Attribute of element. */
private String attribute;
/**
* Constructor with simple XPath string as parameter.
* This is not the standard XPath definition but it is similar to a subset of
* the abbreviation XPath notation.
*
* /element1/element2[3]@attribute
is an example for such
* a notation. This selects from the first occurrence of element1
* and from the third occurrence of subnode element2
the attribute
* attribute
. The attribute is optional. It is always exactly one node or
* the attribute of one node specified.
*
* The general syntax could be described as follows:
* {"/"element"["index"]"}+
* ["@"attribute]
*
*
* @param xpath String with the syntax as described above. If the syntax is violated
* RuntimeExceptions may occur.
*/
public SimpleXPath(final String xpath) {
elements = new ArrayList();
numbers = new ArrayList();
attribute = null;
init(xpath);
}
/**
* Empty constructor.
*/
public SimpleXPath() {
elements = new ArrayList();
numbers = new ArrayList();
attribute = null;
}
/**
* Copy constructor.
*
* @param original XPath to copy.
*/
public SimpleXPath(final SimpleXPath original) {
elements = new ArrayList();
numbers = new ArrayList();
attribute = null;
init(original.toString());
}
/**
* Initialize all object attributes according to XPath parameter.
*
* @see SimpleXPath#SimpleXPath(String)
*
* @param xpath String with the syntax as described above. If the syntax is violated
* RuntimeExceptions may occur.
*/
private void init(final String xpath) {
if (xpath == null) {
throw new NullPointerException();
}
if (xpath.length() <= 0) {
throw new RuntimeException("XPath must not be empty");
}
if (xpath.charAt(0) != '/') {
throw new RuntimeException("XPath must start with '/': " + xpath);
}
if (xpath.indexOf("//") >= 0) {
throw new RuntimeException("empty tag not permitted: " + xpath);
}
if (xpath.endsWith("/")) {
throw new RuntimeException("XPath must not end with '/': " + xpath);
}
final StringTokenizer tokenizer = new StringTokenizer(xpath, "/");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (!tokenizer.hasMoreTokens() && token.indexOf('@') >= 0) {
attribute = token.substring(token.indexOf('@') + 1);
if (attribute.length() <= 0) {
throw new RuntimeException("empty attribute not permitted: " + xpath);
}
token = token.substring(0, token.indexOf('@'));
}
if (token.indexOf('[') < 0) {
elements.add(token);
numbers.add(new Integer(1));
} else {
final StringTokenizer getnu = new StringTokenizer(token, "[]");
elements.add(getnu.nextToken());
final Integer i;
try {
i = new Integer(getnu.nextToken());
} catch (RuntimeException e) {
throw new RuntimeException("not an integer: " + xpath, e);
}
if (i.intValue() <= 0) {
throw new RuntimeException("integer must be greater zero: " + xpath);
}
numbers.add(i);
}
}
}
/**
* Get number of collected exceptions.
*
* @return Number of collected exceptions.
*/
public final int size() {
return elements.size();
}
/**
* Get i
-th Element name.
*
* @param i Starts with 0 and must be smaller than {@link #size()}.
* @return Wanted element name.
*/
public final String getElementName(final int i) {
return (String) elements.get(i);
}
/**
* Get i
-th occurrence number.
*
* @param i Starts with 0 and must be smaller than {@link #size()}.
* @return Wanted element occurrence number.
*/
public final int getElementOccurrence(final int i) {
return ((Integer) numbers.get(i)).intValue();
}
/**
* Add new element to end of XPath.
*
* @param elementName element to add.
*/
public final void addElement(final String elementName) {
attribute = null;
elements.add(elementName);
numbers.add(new Integer(1));
}
/**
* Add new element to end of XPath.
*
* @param elementName element to add.
* @param occurrence Occurrence number of element. Starts with 1.
*/
public final void addElement(final String elementName, final int occurrence) {
attribute = null;
elements.add(elementName);
numbers.add(new Integer(occurrence));
}
/**
* Get last XPath element name.
*
* @return Last element name. Could be null
if no elements exist.
*/
public final String getLastElement() {
int size = elements.size();
if (size <= 0) {
return null;
}
return (String) elements.get(size - 1);
}
/**
* Get XPath element name before last.
*
* @return Before last element name. Could be null
if no more than one element
* exist.
*/
public final String getBeforeLastElement() {
int size = elements.size();
if (size <= 1) {
return null;
}
return (String) elements.get(size - 2);
}
/**
* Delete last XPath element if any.
*/
public void deleteLastElement() {
int size = elements.size();
if (size > 0) {
elements.remove(size - 1);
numbers.remove(size - 1);
attribute = null;
}
}
/**
* Set attribute.
*
* @param attribute Attribute, maybe null
.
*/
public final void setAttribute(final String attribute) {
this.attribute = attribute;
}
/**
* Get attribute.
*
* @return Attribute, maybe null
.
*/
public final String getAttribute() {
return attribute;
}
public final boolean equals(final Object obj) {
if (!(obj instanceof SimpleXPath)) {
return false;
}
final SimpleXPath other = (SimpleXPath) obj;
if (!EqualsUtility.equals(this.getAttribute(), other.getAttribute())) {
return false;
}
return equalsElements(other);
}
/**
* Are the elements and occurrences of this and another element equal? No special treatment
* of "*" elements.
*
* @param other Compare with this object.
* @return Are the elements of this and the parameter object equal?
*/
public final boolean equalsElements(final SimpleXPath other) {
final int size = this.size();
if (size != other.size()) {
return false;
}
for (int i = 0; i < size; i++) {
if (!EqualsUtility.equals(this.getElementName(i), other.getElementName(i))) {
return false;
}
if (getElementOccurrence(i) != other.getElementOccurrence(i)) {
return false;
}
}
return true;
}
/**
* Match the elements and occurrences of this finder object and current elements?
* This object may contain "*" elements.
*
* @param current Compare with this current elements. These elements should not
* contain "*" elements.
* @param currentSummary Contains only "*" elements. This parameter must be identify the same
* XPath as current
* @return Match the elements of this finder object and the parameter objects?
*/
public final boolean matchesElements(final SimpleXPath current,
final SimpleXPath currentSummary) {
final int size = current.size();
if (size != size()) {
return false;
}
if (size != currentSummary.size()) {
throw new IllegalArgumentException("summary size doesn't match");
}
for (int i = 0; i < size; i++) {
if ("*".equals(getElementName(i))) {
if (getElementOccurrence(i) != currentSummary.getElementOccurrence(i)) {
return false;
}
continue;
}
if (!EqualsUtility.equals(current.getElementName(i), getElementName(i))) {
return false;
}
if (current.getElementOccurrence(i) != getElementOccurrence(i)) {
return false;
}
}
return true;
}
/**
* Match the elements and occurrences of this finder object and current elements?
* This object may contain "*" elements. Checks only to current.size().
*
* @param current Compare with this current elements. These elements should not
* contain "*" elements.
* @param currentSummary Contains only "*" elements. This parameter must be identify the same
* XPath as current
* @return Match the elements of this finder object and the parameter objects?
*/
public final boolean matchesElementsBegining(final SimpleXPath current,
final SimpleXPath currentSummary) {
final int size = current.size();
if (size > size()) {
return false;
}
if (size != currentSummary.size()) {
throw new IllegalArgumentException("summary size doesn't match");
}
for (int i = 0; i < size; i++) {
if ("*".equals(getElementName(i))) {
if (getElementOccurrence(i) != currentSummary.getElementOccurrence(i)) {
return false;
}
continue;
}
if (!EqualsUtility.equals(current.getElementName(i), getElementName(i))) {
return false;
}
if (current.getElementOccurrence(i) != getElementOccurrence(i)) {
return false;
}
}
return true;
}
public final String toString() {
final StringBuffer buffer = new StringBuffer();
for (int i = 0; i < size(); i++) {
buffer.append("/");
buffer.append(getElementName(i));
if (getElementOccurrence(i) != 1) {
buffer.append("[");
buffer.append(getElementOccurrence(i));
buffer.append("]");
}
}
if (getAttribute() != null) {
buffer.append("@");
buffer.append(getAttribute());
}
return buffer.toString();
}
public final int hashCode() {
int code = 0;
if (attribute != null) {
code ^= attribute.hashCode();
}
for (int i = 0; i < size(); i++) {
code ^= i + 1;
code ^= getElementName(i).hashCode();
code ^= getElementOccurrence(i);
}
return code;
}
}