/* * eXist Open Source Native XML Database * Copyright (C) 2001-06 The eXist Project * 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * $Id$ */ package org.exist.numbering; import org.exist.storage.io.VariableByteInput; import org.exist.storage.io.VariableByteOutputStream; import java.io.IOException; /** * Represents a node id in the form of a dynamic level number (DLN). DLN's are * hierarchical ids, which borrow from Dewey's decimal classification. Examples for * node ids: 1, 1.1, 1.2, 1.2.1, 1.2.2, 1.3. In this case, 1 represents the root node, 1.1 is * the first node on the second level, 1.2 the second, and so on. * * To support efficient insertion of new nodes between existing nodes, we use the * concept of sublevel ids. Between two nodes 1.1 and 1.2, a new node can be inserted * as 1.1/1, where the / is the sublevel separator. The / does not start a new level. 1.1 and * 1.1/1 are thus on the same level of the tree. * * In the binary encoding, the '.' is represented by a 0-bit while '/' is written as a 1-bit. */ public class DLN extends DLNBase implements NodeId { /** * Constructs a new DLN with a single id with value 1. * */ public DLN() { this(1); } /** * Constructs a new DLN by parsing the string argument. * In the string, levels are separated by a '.', sublevels by * a '/'. For example, '1.2/1' or '1.2/1.2' are valid ids. * * @param s */ public DLN(String s) { bits = new byte[1]; StringBuilder buf = new StringBuilder(16); boolean subValue = false; for (int p = 0; p < s.length(); p++) { char ch = s.charAt(p); if (ch == '.' || ch == '/') { addLevelId(Integer.parseInt(buf.toString()), subValue); subValue = ch == '/'; buf.setLength(0); } else buf.append(ch); } if (buf.length() > 0) { addLevelId(Integer.parseInt(buf.toString()), subValue); } } /** * Constructs a new DLN, using the passed id as its * single level value. * * @param id */ public DLN(int id) { bits = new byte[1]; addLevelId(id, false); } /** * Constructs a new DLN by copying the data of the * passed DLN. * * @param other */ public DLN(DLN other) { super(other); } /** * Reads a DLN from the given byte[]. * * @param units number of bits to read * @param data the byte[] to read from * @param startOffset the start offset to start reading at */ public DLN(int units, byte[] data, int startOffset) { super(units, data, startOffset); } /** * Reads a DLN from the given {@link VariableByteInput} stream. * * @see #write(VariableByteOutputStream) * @param is * @throws IOException */ public DLN(short bitCnt, VariableByteInput is) throws IOException { super(bitCnt, is); } public DLN(byte prefixLen, DLN previous, short bitCnt, VariableByteInput is) throws IOException { super(prefixLen, previous, bitCnt, is); } /** * Create a new DLN by copying nbits bits from the given * byte[]. * * @param data * @param nbits */ protected DLN(byte[] data, int nbits) { super(data, nbits); } /** * Returns a new DLN representing the first child * node of this node. * * @return new child node id */ public NodeId newChild() { DLN child = new DLN(this); child.addLevelId(1, false); return child; } /** * Returns a new DLN representing the next following * sibling of this node. * * @return new sibling node id. */ public NodeId nextSibling() { DLN sibling = new DLN(this); sibling.incrementLevelId(); return sibling; } public NodeId precedingSibling() { DLN sibling = new DLN(this); sibling.decrementLevelId(); return sibling; } public NodeId getChild(int child) { DLN nodeId = new DLN(this); nodeId.addLevelId(child, false); return nodeId; } public NodeId insertNode(NodeId right) { DLN rightNode = (DLN) right; if (right == null) return nextSibling(); int lastLeft = lastLevelOffset(); int lastRight = rightNode.lastLevelOffset(); int lenLeft = getSubLevelCount(lastLeft); int lenRight = rightNode.getSubLevelCount(lastRight); DLN newNode; if (lenLeft > lenRight) { newNode = new DLN(this); newNode.incrementLevelId(); } else if (lenLeft < lenRight) { newNode = (DLN) rightNode.insertBefore(); } else { newNode = new DLN(this); newNode.addLevelId(1, true); } return newNode; } public NodeId insertBefore() { int lastPos = lastFieldPosition(); int lastId = getLevelId(lastPos); DLN newNode = new DLN(this); // System.out.println("insertBefore: " + newNode.toString() + " = " + newNode.bitIndex); if (lastId == 1) { newNode.setLevelId(lastPos, 0); newNode.addLevelId(35, true); } else { newNode.setLevelId(lastPos, lastId - 1); newNode.compact(); // System.out.println("newNode: " + newNode.toString() + " = " + newNode.bitIndex + "; last = " + lastPos); } return newNode; } public NodeId append(NodeId otherId) { DLN other = (DLN) otherId; DLN newId = new DLN(this); int offset = 0; while (offset <= other.bitIndex) { boolean subLevel = false; if (offset > 0) subLevel = ((other.bits[offset >> UNIT_SHIFT] & (1 << ((7 - offset++) & 7))) != 0); int id = other.getLevelId(offset); newId.addLevelId(id, subLevel); offset += DLN.getUnitsRequired(id) * BITS_PER_UNIT; } return newId; } /** * Returns a new DLN representing the parent of the * current node. If the current node is the root element * of the document, the method returns * {@link NodeId#DOCUMENT_NODE}. If the current node * is the document node, null is returned. * * @see NodeId#getParentId() */ public NodeId getParentId() { if (this == DOCUMENT_NODE) return null; int last = lastLevelOffset(); if (last == 0) return DOCUMENT_NODE; return new DLN(bits, last - 1); } public boolean isDescendantOf(NodeId ancestor) { DLN other = (DLN) ancestor; return startsWith(other) && bitIndex > other.bitIndex && isLevelSeparator(other.bitIndex + 1); } public boolean isDescendantOrSelfOf(NodeId other) { DLN ancestor = (DLN) other; return startsWith(ancestor) && (bitIndex == ancestor.bitIndex || isLevelSeparator((ancestor).bitIndex + 1)); } public boolean isChildOf(NodeId parent) { DLN other = (DLN) parent; if(!startsWith(other)) return false; int levels = getLevelCount(other.bitIndex + 2); return levels == 1; } public int computeRelation(NodeId ancestor) { DLN other = (DLN) ancestor; if (other == NodeId.DOCUMENT_NODE) return getLevelCount(0) == 1 ? IS_CHILD : IS_DESCENDANT; if (startsWith(other)) { if (bitIndex == other.bitIndex) return IS_SELF; if (bitIndex > other.bitIndex && isLevelSeparator(other.bitIndex + 1)) { if (getLevelCount(other.bitIndex + 2) == 1) return IS_CHILD; return IS_DESCENDANT; } } return -1; } public boolean isSiblingOf(NodeId sibling) { //DLN other = (DLN) sibling; NodeId parent = getParentId(); return sibling.isChildOf(parent); } /** * Returns the level within the document tree at which * this node occurs. */ public int getTreeLevel() { return getLevelCount(0); } public boolean equals(NodeId other) { return super.equals((DLNBase) other); } public int compareTo(Object other) { return compareTo((DLN) other); } public int compareTo(NodeId otherId) { if (otherId == null) return 1; final DLN other = (DLN) otherId; final int a1len = bits.length; final int a2len = other.bits.length; int limit = a1len <= a2len ? a1len : a2len; byte[] obits = other.bits; for (int i = 0; i < limit; i++) { byte b1 = bits[i]; byte b2 = obits[i]; if (b1 != b2) return (b1 & 0xFF) - (b2 & 0xFF); } return (a1len - a2len); } public boolean after(NodeId other, boolean isFollowing) { if (compareTo(other) > 0) { if (isFollowing) return !isDescendantOf(other); else return true; } return false; } public boolean before(NodeId other, boolean isPreceding) { if (compareTo(other) < 0) { if (isPreceding) return !other.isDescendantOf(this); else return true; } return false; } /** * Write the node id to a {@link VariableByteOutputStream}. * * @param os * @throws IOException */ public void write(VariableByteOutputStream os) throws IOException { os.writeShort((short) units()); os.write(bits, 0, bits.length); } public NodeId write(NodeId prevId, VariableByteOutputStream os) throws IOException { // if (prevId == null) { // write(os); // return this; // } int i = 0; if (prevId != null) { DLN previous = (DLN) prevId; final int len = Math.min(bits.length, previous.bits.length); for ( ; i < len; i++) { byte b = bits[i]; if (b != previous.bits[i]) break; } } os.writeByte((byte) i); os.writeShort((short) units()); os.write(bits, i, bits.length - i); return this; } public static void main(String[] args) throws IOException { DLN left = new DLN("1"); DLN right = new DLN("2.3.12"); NodeId r = left.append(right); System.out.println("r = " + r.toString()); } }