/* * 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.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.dom.QName; import org.exist.util.FastStringBuffer; import javax.xml.XMLConstants; /** * @author wolf */ public class NodePath implements Comparable<NodePath> { private final static Logger LOG = LogManager.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() { //Nothing to do } public NodePath(final NodePath other) { this(other, other.includeDescendants); } public NodePath(final NodePath other, final boolean includeDescendants) { components = new QName[other.components.length]; System.arraycopy(other.components, 0, components, 0, other.components.length); pos = other.pos; this.includeDescendants = includeDescendants; } /** * */ public NodePath(final Map<String, String> namespaces, final String path) { init(namespaces, path); } public NodePath(final Map<String, String> namespaces, final String path, final boolean includeDescendants) { this.includeDescendants = includeDescendants; init(namespaces, path); } public NodePath(final QName qname) { addComponent(qname); } public void setIncludeDescendants(final boolean includeDescendants) { this.includeDescendants = includeDescendants; } public void append(final NodePath other) { final QName[] newComponents = new QName[length() + other.length()]; System.arraycopy(components, 0, newComponents, 0, pos); System.arraycopy(other.components, 0, newComponents, pos, other.length()); pos = newComponents.length; components = newComponents; } public void addComponent(final QName component) { if (pos == components.length) { final QName[] t = new QName[pos + 1]; System.arraycopy(components, 0, t, 0, pos); components = t; } components[pos++] = component; } public void addComponentAtStart(final QName component) { if (pos == components.length) { final 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; } /** * @throws ArrayIndexOutOfBoundsException */ public QName getComponent(final 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(final QName qname) { if (pos > 0) { return components[pos - 1].equals(qname); } return false; } public final boolean match(final NodePath other) { return match(other, 0); } public final boolean match(final NodePath other, int j) { boolean skip = false; int i = 0; for( ; j < other.pos; j++) { if(i == pos) { if(includeDescendants) { return true; } return j == other.pos; } if(components[i] == SKIP) { ++i; skip = true; } if((components[i] == WILDCARD || other.components[j].compareTo(components[i]) == 0) && (!skip || 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; } return false; } public void reset() { for(int i = 0; i < pos; i++) { components[i] = null; } pos = 0; } @Override public String toString() { final 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(final Map<String, String> namespaces, String component) { boolean isAttribute = false; if (component.startsWith("@")) { isAttribute = true; component = component.substring(1); } String prefix = QName.extractPrefix(component); final String localName = QName.extractLocalName(component); String namespaceURI = null; //TODO simplify this code (AR) if (prefix != null) { namespaceURI = namespaces.get(prefix); if(namespaceURI == null) { LOG.error("No namespace URI defined for prefix: " + prefix); //TODO : throw exception ? -pb prefix = null; namespaceURI = ""; } } else if (namespaces != null) { namespaceURI = namespaces.get(XMLConstants.DEFAULT_NS_PREFIX); } final QName qn; if (isAttribute) { qn = new QName(localName, namespaceURI, prefix, ElementValue.ATTRIBUTE); } else { qn = new QName(localName, namespaceURI, prefix); } LOG.debug("URI = " + qn.getNamespaceURI()); addComponent(qn); } private void init(final Map<String, String> namespaces, final String path) { //TODO : compute better length final FastStringBuffer token = new FastStringBuffer(path.length()); int pos = 0; while (pos < path.length()) { final char ch = path.charAt(pos); switch (ch) { case '*': addComponent(WILDCARD); token.setLength(0); pos++; break; case '/': final 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()); } } @Override public boolean equals(final Object obj) { if (obj != null && obj instanceof NodePath) { final 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; } @Override public int hashCode() { int h = 0; for(int i = 0; i < pos; i++) { h = 31*h + components[i].hashCode(); } return h; } @Override public int compareTo(final NodePath other) { return toString().compareTo(other.toString()); //TODO: optimize } }