/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-04 Wolfgang M. Meier
* wolfgang@exist-db.org
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package org.exist.storage;
import java.util.Map;
import org.apache.log4j.Logger;
import org.exist.dom.QName;
import org.exist.util.FastStringBuffer;
/**
* @author wolf
*/
public class NodePath implements Comparable<NodePath>{
private final static Logger LOG = Logger.getLogger(NodePath.class);
/**
* (Illegal) QNames used as a marker for arbitrary path steps.
*/
public final static QName SKIP = new QName("//", "");
public final static QName WILDCARD = new QName("*", "");
private QName[] components = new QName[5];
private int pos = 0;
private boolean includeDescendants = true;
public NodePath() {
}
public NodePath(NodePath other) {
components = new QName[other.components.length];
System.arraycopy(other.components, 0, components, 0, other.components.length);
pos = other.pos;
includeDescendants = other.includeDescendants;
}
/**
*
*/
public NodePath(Map<String, String> namespaces, String path) {
init(namespaces, path);
}
public NodePath(Map<String, String> namespaces, String path, boolean includeDescendants) {
this.includeDescendants = includeDescendants;
init(namespaces, path);
}
public NodePath(QName qname) {
addComponent(qname);
}
public void addComponent(QName component) {
if(pos == components.length) {
QName[] t = new QName[pos + 1];
System.arraycopy(components, 0, t, 0, pos);
components = t;
}
components[pos++] = component;
}
public void addComponentAtStart(QName component) {
if(pos == components.length) {
QName[] t = new QName[pos + 1];
System.arraycopy(components, 0, t, 1, pos);
components = t;
components[0] = component;
} else {
System.arraycopy(components, 0, components, 1, pos);
components[0] = component;
}
pos++;
}
public void removeLastComponent() {
if (pos > 0)
components[--pos] = null;
}
public int length() {
return pos;
}
public QName getComponent(int at) {
if (at < 0 || at >= pos)
throw new ArrayIndexOutOfBoundsException(at);
return components[at];
}
public QName getLastComponent() {
if (pos > 0) {
return components[pos - 1];
}
return null;
}
public boolean hasWildcard() {
for (int i = 0; i < pos; i++) {
if (components[i] == WILDCARD)
return true;
}
return false;
}
public boolean match(QName qname) {
if (pos > 0) {
return components[pos - 1].equals(qname);
}
return false;
}
public final boolean match(NodePath other) {
boolean skip = false;
int i = 0, j = 0;
for( ; j < other.pos; j++) {
if(i == pos) {
if(includeDescendants)
return true;
return j == other.pos ? true : false;
}
if(components[i] == SKIP) {
++i;
skip = true;
}
if((components[i] == WILDCARD || other.components[j].compareTo(components[i]) == 0) &&
(j + 1 == other.pos || other.components[j + 1].compareTo(components[i]) != 0)) {
++i;
skip = false;
} else if(skip) {
continue;
} else
return false;
}
if(i == pos) {
if(includeDescendants)
return true;
return j == other.pos ? true : false;
}
return false;
}
public void reset() {
for(int i = 0; i < pos; i++)
components[i] = null;
pos = 0;
}
public String toString() {
StringBuilder buf = new StringBuilder();
for(int i = 0; i < pos; i++) {
buf.append("/");
if (components[i].getNameType() == ElementValue.ATTRIBUTE)
buf.append("@");
buf.append(components[i]);
}
return buf.toString();
}
private void addComponent(Map<String, String> namespaces, String component) {
boolean isAttribute = false;
if (component.startsWith("@")) {
isAttribute = true;
component = component.substring(1);
}
String prefix = QName.extractPrefix(component);
String localName = QName.extractLocalName(component);
String namespaceURI = "";
if (prefix != null) {
namespaceURI = namespaces.get(prefix);
if(namespaceURI == null) {
LOG.error("No namespace URI defined for prefix: " + prefix);
prefix = null;
namespaceURI = "";
}
}
QName qn = new QName(localName, namespaceURI, prefix);
if (isAttribute)
qn.setNameType(ElementValue.ATTRIBUTE);
addComponent(qn);
}
private void init( Map<String, String> namespaces, String path ) {
//TODO : compute better length
FastStringBuffer token = new FastStringBuffer(path.length());
int pos = 0;
while (pos < path.length()) {
char ch = path.charAt(pos);
switch (ch) {
case '*':
addComponent(WILDCARD);
token.setLength(0);
pos++;
break;
case '/':
String next = token.toString();
token.setLength(0);
if (next.length() > 0)
addComponent(namespaces, next);
if (path.charAt(++pos ) == '/')
addComponent(SKIP);
break;
default:
token.append(ch);
pos++;
}
}
if (token.length() > 0)
addComponent(namespaces, token.toString());
}
public boolean equals(Object obj) {
if (obj == null) return false;
if (obj instanceof NodePath) {
NodePath nodePath = (NodePath) obj;
if (nodePath.pos == pos) {
for(int i = 0; i < pos; i++) {
if (!nodePath.components[i].equals(components[i])) {
return false;
}
}
return true;
}
}
return false;
}
public int hashCode() {
int h = 0;
for(int i = 0; i < pos; i++) {
h = 31*h + components[i].hashCode();
}
return h;
}
//s@Override
public int compareTo(NodePath o) {
return toString().compareTo(o.toString()); //TODO: optimize
}
}