/* * 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 java.io.IOException; import org.exist.security.MessageDigester; import org.exist.storage.io.VariableByteInput; import org.exist.storage.io.VariableByteOutputStream; /** * 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(final String s) { bits = new byte[1]; final StringBuilder buf = new StringBuilder(16); boolean subValue = false; for(int p = 0; p < s.length(); p++) { final 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(final 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(final 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(final int units, final byte[] data, final int startOffset) { super(units, data, startOffset); } /** * Reads a DLN from the given {@link VariableByteInput} stream. * * @see #write(VariableByteOutputStream) * @param is * @throws IOException */ public DLN(final short bitCnt, final VariableByteInput is) throws IOException { super(bitCnt, is); } public DLN(final byte prefixLen, final DLN previous, final short bitCnt, final 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(final byte[] data, final int nbits) { super(data, nbits); } /** * Returns a new DLN representing the first child * node of this node. * * @return new child node id */ @Override public NodeId newChild() { final 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. */ @Override public NodeId nextSibling() { final DLN sibling = new DLN(this); sibling.incrementLevelId(); return sibling; } @Override public NodeId precedingSibling() { final DLN sibling = new DLN(this); sibling.decrementLevelId(); return sibling; } @Override public NodeId getChild(final int child) { final DLN nodeId = new DLN(this); nodeId.addLevelId(child, false); return nodeId; } @Override public NodeId insertNode(final NodeId right) { final DLN rightNode = (DLN) right; if (right == null) { return nextSibling(); } final int lastLeft = lastLevelOffset(); final int lastRight = rightNode.lastLevelOffset(); final int lenLeft = getSubLevelCount(lastLeft); final int lenRight = rightNode.getSubLevelCount(lastRight); final 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; } @Override public NodeId insertBefore() { final int lastPos = lastFieldPosition(); final int lastId = getLevelId(lastPos); final 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; } @Override public NodeId append(final NodeId otherId) { final DLN other = (DLN) otherId; final 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); } final int id = other.getLevelId(offset); newId.addLevelId(id, subLevel); offset += DLNBase.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() */ @Override public NodeId getParentId() { if(this == DOCUMENT_NODE) { return null; } final int last = lastLevelOffset(); if (last == 0) { return DOCUMENT_NODE; } return new DLN(bits, last - 1); } @Override public boolean isDescendantOf(final NodeId ancestor) { final DLN other = (DLN) ancestor; return startsWith(other) && bitIndex > other.bitIndex && isLevelSeparator(other.bitIndex + 1); } @Override public boolean isDescendantOrSelfOf(final NodeId other) { final DLN ancestor = (DLN) other; return startsWith(ancestor) && (bitIndex == ancestor.bitIndex || isLevelSeparator((ancestor).bitIndex + 1)); } @Override public boolean isChildOf(final NodeId parent) { final DLN other = (DLN) parent; if(!startsWith(other)) { return false; } final int levels = getLevelCount(other.bitIndex + 2); return levels == 1; } @Override public int computeRelation(final NodeId ancestor) { final 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; } @Override public boolean isSiblingOf(final NodeId sibling) { final NodeId parent = getParentId(); return sibling.isChildOf(parent); } /** * Returns the level within the document tree at which * this node occurs. */ @Override public int getTreeLevel() { return getLevelCount(0); } @Override public boolean equals(final NodeId other) { return super.equals((DLNBase) other); } @Override public int compareTo(final NodeId otherId) { if(otherId == null) { return 1; } final DLN other = (DLN) otherId; final int a1len = bits.length; final int a2len = other.bits.length; final int limit = a1len <= a2len ? a1len : a2len; final byte[] obits = other.bits; for(int i = 0; i < limit; i++) { final byte b1 = bits[i]; final byte b2 = obits[i]; if(b1 != b2) { return (b1 & 0xFF) - (b2 & 0xFF); } } return (a1len - a2len); } @Override public boolean after(final NodeId other, final boolean isFollowing) { if (compareTo(other) > 0) { if (isFollowing) { return !isDescendantOf(other); } return true; } return false; } @Override public boolean before(final NodeId other, final boolean isPreceding) { if (compareTo(other) < 0) { if (isPreceding) { return !other.isDescendantOf(this); } return true; } return false; } /** * Write the node id to a {@link VariableByteOutputStream}. * * @param os * @throws IOException */ @Override public void write(final VariableByteOutputStream os) throws IOException { os.writeShort((short) units()); os.write(bits, 0, bits.length); } @Override public NodeId write(final NodeId prevId, final VariableByteOutputStream os) throws IOException { int i = 0; if(prevId != null) { final DLN previous = (DLN) prevId; final int len = Math.min(bits.length, previous.bits.length); for( ; i < len; i++) { final 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(final String[] args) { final DLN left1 = new DLN("5.6.2.6"); final DLN left2 = new DLN("5.6.2.7"); final DLN right = new DLN("5.6.2.7.1"); final byte[] nodeIdData1 = new byte[left1.size()]; left1.serialize(nodeIdData1, 0); System.out.println(left1.units() + ": " +left1 + ": " + MessageDigester.byteArrayToHex(nodeIdData1) + Integer.toHexString(left1.units())); final byte[] nodeIdData2 = new byte[left2.size()]; left2.serialize(nodeIdData2, 0); System.out.println(left2.units() + ": " + left2 + ": " + MessageDigester.byteArrayToHex(nodeIdData2) + Integer.toHexString(left2.units())); final byte[] nodeIdData3 = new byte[right.size()]; right.serialize(nodeIdData3, 0); System.out.println(right.units() + ": " + right + ": " + MessageDigester.byteArrayToHex(nodeIdData3) + Integer.toHexString(right.units())); } }