xbird.xquery.dm.instance.DocumentTableModel.java Source code

Java tutorial

Introduction

Here is the source code for xbird.xquery.dm.instance.DocumentTableModel.java

Source

/*
 * @(#)$Id: DocumentTableModel.java 3619 2008-03-26 07:23:03Z yui $
 *
 * Copyright 2006-2008 Makoto YUI
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * Contributors:
 *     Makoto YUI - initial implementation
 */
package xbird.xquery.dm.instance;

import static xbird.xquery.dm.dtm.IDocumentTable.BLOCKS_PER_NODE;

import java.io.Closeable;
import java.io.Externalizable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.ObjectStreamException;
import java.util.concurrent.ConcurrentMap;

import javax.xml.XMLConstants;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import xbird.config.Settings;
import xbird.storage.io.DiskPagedLongCache;
import xbird.util.cache.ILongCache;
import xbird.util.codec.IntCodec;
import xbird.util.concurrent.jsr166.ConcurrentReferenceHashMap;
import xbird.util.concurrent.jsr166.ConcurrentReferenceHashMap.ReferenceType;
import xbird.util.datetime.StopWatch;
import xbird.util.io.FastBufferedInputStream;
import xbird.util.io.FileUtils;
import xbird.util.lang.ClassResolver;
import xbird.util.lang.ObjectUtils;
import xbird.util.xml.XMLUtils;
import xbird.xquery.DynamicError;
import xbird.xquery.XQRTException;
import xbird.xquery.XQueryConstants;
import xbird.xquery.XQueryException;
import xbird.xquery.dm.NodeKind;
import xbird.xquery.dm.XQEventReceiver;
import xbird.xquery.dm.coder.SerializationContext;
import xbird.xquery.dm.coder.XQEventDecoder;
import xbird.xquery.dm.coder.XQEventEncoder;
import xbird.xquery.dm.dtm.DocumentTable;
import xbird.xquery.dm.dtm.DocumentTableBuilder;
import xbird.xquery.dm.dtm.IDocumentTable;
import xbird.xquery.dm.dtm.MemoryMappedDocumentTable;
import xbird.xquery.dm.dtm.PagingProfile;
import xbird.xquery.dm.dtm.PagingProfile.Strategy;
import xbird.xquery.dm.dtm.hooked.IProfiledDocumentTable;
import xbird.xquery.dm.dtm.hooked.ProfiledDocumentTable;
import xbird.xquery.dm.value.Item;
import xbird.xquery.dm.value.Sequence;
import xbird.xquery.dm.value.XQNode;
import xbird.xquery.dm.value.xsi.BooleanValue;
import xbird.xquery.expr.path.NodeTest;
import xbird.xquery.meta.DynamicContext;
import xbird.xquery.misc.QNameTable;
import xbird.xquery.misc.QNameTable.QualifiedName;

/**
 * 
 * <DIV lang="en"></DIV>
 * <DIV lang="ja"></DIV>
 * 
 * @author Makoto YUI (yuin405+xbird@gmail.com)
 */
public final class DocumentTableModel extends DataModel implements Externalizable {
    private static final long serialVersionUID = 6670281140109393291L;
    private static final Log LOG = LogFactory.getLog(DocumentTableModel.class);

    private static final String profileAccessPattern = System.getProperty("xbird.profile_dtm");
    private static final boolean resolveEntity = Boolean.parseBoolean(Settings.get("xbird.xml.resolve_entity"));
    private static final String HTML_PARSER_CLASS = Settings.get("xbird.xml.html_saxparser",
            "xbird.util.xml.HTMLSAXParser");
    private static final String TMP_DATA_DIR = Settings.get("xbird.database.tmpdir");

    private static final boolean hasSunXerces;
    static {
        hasSunXerces = ClassResolver.isPresent("com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
    }

    private transient final DocumentTableBuilder _handler;
    private transient final XMLReader _reader;
    private transient boolean _mmapedStore;

    private/* final */IDocumentTable _store;
    private int _docid = -1;

    private static ConcurrentMap<String, MemoryMappedDocumentTable> _remoteDoctblCache = null;

    /**
     * Only for {@link Externalizable}.
     */
    public DocumentTableModel() {
        this._handler = null;
        this._reader = null;
    }

    public DocumentTableModel(boolean parseAsHtml) {
        this(parseAsHtml, resolveEntity);
    }

    public DocumentTableModel(boolean parseAsHtml, boolean resolveEntity) {
        this(profileAccessPattern != null ? getProfiledDocumentTable() : new DocumentTable(), false, parseAsHtml,
                resolveEntity);
    }

    public DocumentTableModel(IDocumentTable store) {
        this(store, false, false, resolveEntity);
    }

    public DocumentTableModel(IDocumentTable store, boolean loaded) {
        this(store, loaded, false, resolveEntity);
    }

    private DocumentTableModel(IDocumentTable store, boolean loaded, boolean parseAsHtml, boolean resolveEntity) {
        super();
        this._mmapedStore = (store instanceof MemoryMappedDocumentTable);
        this._store = store;
        if (loaded) {
            this._handler = null;
            this._reader = null;
            this._docid = nextDocumentId();
        } else {
            this._handler = new DocumentTableBuilder(store);
            this._reader = getXMLReader(_handler, parseAsHtml, resolveEntity);
        }
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this._docid = in.readInt();
        final IDocumentTable doctbl = (IDocumentTable) in.readObject();
        if (doctbl instanceof MemoryMappedDocumentTable) {
            this._mmapedStore = true;
            MemoryMappedDocumentTable mmDoctbl = (MemoryMappedDocumentTable) doctbl;
            String docId = mmDoctbl.getDocumentIdentifer();
            if (docId != null) {
                setPool(mmDoctbl, docId);
            }
        }
        this._store = doctbl;
    }

    private static synchronized void setPool(MemoryMappedDocumentTable mmDoctbl, String docId) throws IOException {
        ConcurrentMap<String, MemoryMappedDocumentTable> cache = _remoteDoctblCache;
        if (cache == null) {
            cache = new ConcurrentReferenceHashMap<String, MemoryMappedDocumentTable>(16, ReferenceType.STRONG,
                    ReferenceType.SOFT);
            _remoteDoctblCache = cache;
        }
        MemoryMappedDocumentTable prevDoctbl = cache.putIfAbsent(docId, mmDoctbl);
        if (prevDoctbl != null) {
            ILongCache<int[]> prevCache = prevDoctbl.getBufferPool();
            if (prevCache != null) {
                mmDoctbl.setBufferPool(prevCache);
            }
        } else {
            File tmpDir = (TMP_DATA_DIR == null) ? null : new File(TMP_DATA_DIR);
            String docname = FileUtils.basename(docId);
            File tmpFile = File.createTempFile(docname, ".tmp", tmpDir);
            tmpFile.deleteOnExit();
            ILongCache<int[]> pool = new DiskPagedLongCache<int[]>(tmpFile, MemoryMappedDocumentTable.CACHED_PAGES,
                    new IntCodec());
            mmDoctbl.setBufferPool(pool);
        }
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(_docid);
        out.writeObject(_store);
    }

    @Deprecated
    public static DocumentTableModel read(ObjectInput in) throws IOException, ClassNotFoundException {
        DocumentTableModel model = new DocumentTableModel();
        model.readExternal(in);
        return model;
    }

    @Override
    public boolean isMemoryMappedStore() {
        return _mmapedStore;
    }

    private static DocumentTable getProfiledDocumentTable() {
        return new ProfiledDocumentTable(new File(profileAccessPattern));
    }

    public <T extends DTMNodeBase> T createNode(long id) {
        final byte kind = _store.getNodeKindAt(id);
        return this.<T>createNode(kind, id);
    }

    @SuppressWarnings("unchecked")
    public <T extends DTMNodeBase> T createNode(byte kind, long id) {
        switch (kind) {
        case NodeKind.DOCUMENT:
            return (T) new DTMDocument(this, id);
        case NodeKind.ELEMENT:
            return (T) new DTMElement(this, id);
        case NodeKind.ATTRIBUTE:
            return (T) new DTMAttribute(this, id);
        case NodeKind.NAMESPACE:
            return (T) new DTMNamespace(this, id);
        case NodeKind.COMMENT:
            return (T) new DTMComment(this, id);
        case NodeKind.TEXT:
            return (T) new DTMText(this, id);
        case NodeKind.PROCESSING_INSTRUCTION:
            return (T) new DTMProcessingInstruction(this, id);
        default:
            throw new IllegalStateException("Invalid node kind: " + kind + " for node#" + id);
        }
    }

    @SuppressWarnings("unchecked")
    public static <T extends DTMNodeBase> T getPrototype(DocumentTableModel model, byte kind, long nid) {
        switch (kind) {
        case NodeKind.ELEMENT:
            return (T) new DTMElement(model, nid);
        case NodeKind.TEXT:
            return (T) new DTMText(model, nid);
        case NodeKind.ATTRIBUTE:
            return (T) new DTMAttribute(model, nid);
        case NodeKind.DOCUMENT:
            return (T) new DTMDocument(model, nid);
        case NodeKind.NAMESPACE:
            return (T) new DTMNamespace(model, nid);
        case NodeKind.COMMENT:
            return (T) new DTMComment(model, nid);
        case NodeKind.PROCESSING_INSTRUCTION:
            return (T) new DTMProcessingInstruction(model, nid);
        default:
            throw new IllegalStateException("Invalid node kind " + kind + " for nid #" + nid);
        }
    }

    public DTMDocument documentNode() {
        return new DTMDocument(this, 0);
    }

    /**
     * Traverses tree in depth first order and reports events.
     */
    public void export(final XQNode curNode, final XQEventReceiver receiver) throws XQueryException {
        this.export(curNode.getPosition(), receiver);
    }

    public void export(final long nodeid, final XQEventReceiver receiver) throws XQueryException {
        final PagingProfile profile = _store.getPagingProfile();
        Strategy origStrategy = null;
        if (profile != null) {
            origStrategy = profile.getStrategy();
            profile.setStrategy(Strategy.serialization);
        }
        final IDocumentTable doctbl = _store;
        doctbl.ensureOpen();
        switch (doctbl.getNodeKindAt(nodeid)) {
        case NodeKind.DOCUMENT:
            receiver.evStartDocument();
            final long firstChild = doctbl.firstChild(nodeid);
            if (firstChild != -1L) {
                export(firstChild, receiver);
                long nextSib = doctbl.nextSibling(firstChild);
                while (nextSib != 0L) {
                    export(nextSib, receiver);
                    nextSib = doctbl.nextSibling(firstChild);
                }
            }
            receiver.evEndDocument();
            break;
        case NodeKind.ELEMENT:
            final QualifiedName qname = doctbl.getName(nodeid);
            receiver.evStartElement(nodeid, qname);
            // namespace decl
            final int nsdeclCnt = doctbl.getNamespaceCountAt(nodeid);
            for (int i = 0; i < nsdeclCnt; i++) {
                final long nsid = doctbl.getNamespaceDecl(nodeid, i);
                final QualifiedName nsName = doctbl.getAttributeName(nsid);
                final String uri = doctbl.getText(nsid);
                receiver.evNamespace(nsid, nsName, uri);
            }
            // attribute
            final int attrCnt = doctbl.getAttributeCountAt(nodeid);
            for (int i = 0; i < attrCnt; i++) {
                final long attid = doctbl.getAttribute(nodeid, i);
                final QualifiedName attName = doctbl.getAttributeName(attid);
                final String attValue = doctbl.getText(attid);
                receiver.evAttribute(attid, attName, attValue);
            }
            final long elemFirstChild = doctbl.firstChild(nodeid);
            if (elemFirstChild != -1L) {
                export(elemFirstChild, receiver);
                long nextSib = doctbl.nextSibling(elemFirstChild);
                while (nextSib != 0L) {
                    export(nextSib, receiver);
                    nextSib = doctbl.nextSibling(nextSib);
                }
            }
            receiver.evEndElement(nodeid, qname);
            break;
        case NodeKind.ATTRIBUTE:
            receiver.evAttribute(nodeid, doctbl.getAttributeName(nodeid), doctbl.getText(nodeid));
            break;
        case NodeKind.NAMESPACE:
            receiver.evNamespace(nodeid, doctbl.getAttributeName(nodeid), doctbl.getText(nodeid));
            break;
        case NodeKind.TEXT:
            receiver.evText(nodeid, doctbl.getText(nodeid));
            break;
        case NodeKind.COMMENT:
            receiver.evComment(nodeid, doctbl.getText(nodeid));
            break;
        case NodeKind.PROCESSING_INSTRUCTION:
            final QualifiedName pi = doctbl.getName(nodeid);
            receiver.evProcessingInstruction(nodeid, pi.getLocalPart(), pi.getNamespaceURI());
            break;
        default:
            throw new IllegalStateException("Invalid node kind '"
                    + NodeKind.resolveName(doctbl.getNodeKindAt(nodeid)) + "' for node#" + nodeid);
        }
        if (profile != null) {
            profile.setStrategy(origStrategy);
        }
    }

    public void loadDocument(InputStream is) throws XQueryException {
        if (!(is instanceof FastBufferedInputStream)) {
            is = new FastBufferedInputStream(is);
        }
        _handler.init();
        final StopWatch sw = new StopWatch();
        try {
            _reader.parse(new InputSource(is));
        } catch (IOException ie) {
            throw new DynamicError("Invalid source", ie);
        } catch (SAXException se) {
            throw new DynamicError("Parse failed", se);
        }
        if (LOG.isDebugEnabled()) {
            LOG.debug("Elasped time of building a DTM: " + sw.elapsed() + " msec");
        }
        this._docid = nextDocumentId();
        if (_store instanceof IProfiledDocumentTable) {
            ((IProfiledDocumentTable) _store).setProfiling(true);
        }
    }

    public void loadDocument(final InputStream is, final DynamicContext dynEnv) throws XQueryException {
        this.loadDocument(is);
    }

    private static final XMLReader getXMLReader(final DocumentTableBuilder handler, final boolean parseAsHtml,
            final boolean resolveEntity) {
        final XMLReader myReader;
        try {
            if (parseAsHtml) {
                Class clazz = ClassResolver.get(HTML_PARSER_CLASS);
                assert (clazz != null);
                myReader = ObjectUtils.<XMLReader>instantiate(clazz);
            } else {
                final SAXParserFactory factory;
                if (hasSunXerces) {
                    factory = ObjectUtils
                            .instantiate("com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl", null);
                } else {
                    factory = SAXParserFactory.newInstance();
                }
                factory.setNamespaceAware(true);
                SAXParser parser = factory.newSAXParser();
                myReader = parser.getXMLReader();
            }
        } catch (Exception e) {
            throw new XQRTException("Creating SAX XMLReader failed", e);
        }
        // setup handlers (requires saxHandler)
        myReader.setContentHandler(handler);
        try {
            myReader.setProperty("http://xml.org/sax/properties/lexical-handler", handler);
            myReader.setFeature("http://xml.org/sax/features/validation", true); // Validate the document and report validity errors.
            if (!parseAsHtml) {
                myReader.setFeature("http://apache.org/xml/features/validation/dynamic", true); // The parser will validate the document only if a grammar is specified.   
                myReader.setFeature("http://apache.org/xml/features/validation/schema", true); // Turn on XML Schema validation by inserting an XML Schema validator into the pipeline.   
            }
        } catch (Exception e) {
            throw new XQRTException("Configuaring SAX XMLReader failed.", e);
        }
        // setup entity resolver
        if (resolveEntity) {
            org.apache.xml.resolver.CatalogManager catalog = org.apache.xml.resolver.CatalogManager
                    .getStaticManager();
            catalog.setIgnoreMissingProperties(true);
            catalog.setPreferPublic(true);
            catalog.setUseStaticCatalog(false);
            EntityResolver resolver = new org.apache.xml.resolver.tools.CatalogResolver(catalog);
            myReader.setEntityResolver(resolver);
        }
        return myReader;
    }

    public IDocumentTable getDocumentTable() {
        return _store;
    }

    public static abstract class DTMNodeBase extends XQNode implements Externalizable {
        private static final long serialVersionUID = 1L;
        protected DocumentTableModel _model;
        protected IDocumentTable _store;

        private transient Sequence<Item> _toReplace;

        public DTMNodeBase(DocumentTableModel model, long id) {
            super(id);
            this._model = model;
            this._store = model._store;
        }

        public DTMNodeBase() {//for serialization
            super(-1L);
            this._model = null;
            this._store = null;
        }

        public void writeExternal(ObjectOutput out) throws IOException {
            final XQEventEncoder encoder = new XQEventEncoder(out);
            try {
                encoder.emit(this);
            } catch (XQueryException xqe) {
                throw new XQRTException(xqe);
            } catch (Throwable e) {
                LOG.fatal(e);
                throw new IllegalStateException("failed encoding", e);
            }
        }

        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            assert (_toReplace == null);
            final XQEventDecoder decoder = new XQEventDecoder(in);
            final Sequence<Item> decoded;
            try {
                decoded = decoder.decode();
            } catch (IOException ioe) {
                throw new XQRTException("decode failed", ioe);
            } catch (XQueryException xqe) {
                throw new XQRTException(xqe);
            } catch (Throwable e) {
                throw new IllegalStateException("decode failed", e);
            }
            this._toReplace = decoded;
        }

        public void writeTo(final ObjectOutput out, final SerializationContext context) throws IOException {
            out.writeObject(_model);
            out.writeByte(nodeKind());
            long curr = getPosition();
            out.writeLong(curr);
            long nextsib = _store.nextSibling(_id);
            long last = (nextsib == 0L) ? curr : nextsib;
            out.writeLong(last);
            long[] textBlocks = ((MemoryMappedDocumentTable) _store).referredTextBlocks(curr, last, context);
            out.writeInt(textBlocks.length);
            for (long tb : textBlocks) {
                out.writeLong(tb);
            }
        }

        public static DTMNodeBase readFrom(final ObjectInput in, SerializationContext serContext)
                throws IOException, ClassNotFoundException {
            DocumentTableModel model = (DocumentTableModel) in.readObject();
            byte nodeKind = in.readByte();
            long curr = in.readLong();
            DTMNodeBase node = getPrototype(model, nodeKind, curr);
            long last = in.readLong();
            final int tbSize = in.readInt();
            final long[] textBlocks = new long[tbSize];
            for (int i = 0; i < tbSize; i++) {
                textBlocks[i] = in.readLong();
            }
            MemoryMappedDocumentTable mmtable = (MemoryMappedDocumentTable) node._store;
            mmtable.markReferredBlocks(curr, last, textBlocks, serContext);
            return node;
        }

        public static void redirect(ObjectInput in, ObjectOutput out) throws IOException, ClassNotFoundException {
            DocumentTableModel model = (DocumentTableModel) in.readObject();
            out.writeObject(model);
            byte nodeKind = in.readByte();
            out.writeByte(nodeKind);
            long curr = in.readLong();
            out.writeLong(curr);
            long last = in.readLong();
            out.writeLong(last);
            final int tbsize = in.readInt();
            out.writeInt(tbsize);
            final byte[] b = new byte[80];
            int i = 0;
            final int limit = tbsize - 9;
            for (; i < limit; i += 10) {
                in.readFully(b, 0, 80);
                out.write(b, 0, 80);
            }
            for (; i < tbsize; i++) {
                in.readFully(b, 0, 8);
                out.write(b, 0, 8);
            }
        }

        protected Object readResolve() throws ObjectStreamException {
            Sequence replaced = _toReplace;
            this._toReplace = null;
            return replaced;
        }

        public void setInternalTable(DocumentTableModel model) {
            this._model = model;
            this._store = model._store;
        }

        public IDocumentTable documentTable() {
            return _store;
        }

        public DocumentTableModel getDataModel() {
            return _model;
        }

        public String baseUri() {
            return null;
        }

        public DTMNodeBase firstChild() {
            return null;
        }

        public String getContent() {
            return null;
        }

        public int getDocumentId() {
            final DocumentTableModel model = getDataModel();
            return model._docid;
        }

        public DTMDocument getDocumentNode() {
            final DocumentTableModel model = getDataModel();
            return model.createNode(NodeKind.DOCUMENT, 0);
        }

        public String getNamespaceURI() {
            return null;
        }

        public DTMNodeBase lastChild() {
            return null;
        }

        @Override
        public final XQNode following(final boolean firstcall) {
            final PagingProfile profile = _store.getPagingProfile();
            final XQNode node;
            if (profile != null) {
                Strategy origStrategy = profile.getStrategy();
                if (origStrategy != Strategy.nextsib) {
                    assert (origStrategy != null);
                    profile.setStrategy(Strategy.nextsib);
                    node = super.following(firstcall);
                    profile.setStrategy(origStrategy);
                } else {
                    node = super.following(firstcall);
                }
            } else {
                node = super.following(firstcall);
            }
            return node;
        }

        @Override
        public byte nodeKind() {
            return 0;
        }

        public DTMNodeBase nextSibling() {
            final IDocumentTable store = documentTable();
            final long nextsib = store.nextSibling(_id);
            if (nextsib == 0L) {
                return null;
            }
            final DocumentTableModel model = getDataModel();
            return model.createNode(store.getNodeKindAt(nextsib), nextsib);
        }

        public final DTMNodeBase nextSibling(NodeTest filter) {
            if (_id == 0L) {
                return null;
            }
            final IDocumentTable store = documentTable();
            final DocumentTableModel model = getDataModel();
            for (long nid = store.nextSibling(_id); nid != 0L; nid = store.nextSibling(nid)) {
                byte nodekind = store.getNodeKindAt(nid);
                DTMNodeBase probe = getPrototype(model, nodekind, nid);
                if (filter.accepts(probe)) {
                    return model.createNode(probe.nodeKind(), probe.getPosition());
                }
            }
            return null;
        }

        public QualifiedName nodeName() {
            return null;
        }

        public int getNameCode() {
            return -1;
        }

        public DTMElement parent() {
            final DocumentTableModel model = getDataModel();
            if (_id == BLOCKS_PER_NODE) {
                return model.createNode(NodeKind.DOCUMENT, 0);
            }
            final IDocumentTable store = documentTable();
            final long pid = store.parent(_id);
            if (pid == 0L) {
                return null;
            }
            return model.createNode(store.getNodeKindAt(pid), pid);
        }

        public DTMNodeBase previousSibling() {
            final IDocumentTable store = documentTable();
            final long prevSib = store.previousSibling(_id);
            if (prevSib == -1L) {
                return null;
            }
            final DocumentTableModel model = getDataModel();
            return model.createNode(store.getNodeKindAt(prevSib), prevSib);
        }

        public String stringValue() {
            final IDocumentTable store = documentTable();
            return store.stringValue(_id);
        }

        @Override
        public DTMNodeBase clone() {
            if (_cloned++ == 0) {
                return this;
            }
            return (DTMNodeBase) super.cloneWithoutIncr();
        }

    }

    public static class DTMElement extends DTMNodeBase {
        private static final long serialVersionUID = 7884779293497299039L;

        public DTMElement() {//for serialization
            super();
        }

        protected DTMElement(DocumentTableModel model, long id) {
            super(model, id);
        }

        public DTMAttribute attribute(int i) {
            final long attid = getAttribute(i);
            if (attid == -1) {
                return null;
            }
            final DocumentTableModel model = getDataModel();
            return new DTMAttribute(model, attid);
        }

        @Override
        public String baseUri() {
            DTMAttribute base = getAttribute(XMLConstants.XML_NS_URI, "base");
            if (base != null) {
                String uri = base.getContent();
                return uri;
            } else {
                XQNode p = parent();
                while (p != null) {
                    String uri = p.baseUri();
                    if (uri != null) {
                        return uri;
                    }
                    p = p.parent();
                }
                return null;
            }
        }

        @Override
        public DTMNodeBase firstChild() {
            final IDocumentTable store = documentTable();
            final long cid = store.firstChild(_id);
            if (cid == -1) {
                return null;
            }
            final DocumentTableModel model = getDataModel();
            return model.createNode(store.getNodeKindAt(cid), cid);
        }

        public long getAttribute(int i) {
            assert (i >= 0);
            final IDocumentTable store = documentTable();
            return store.getAttribute(_id, i);
        }

        public DTMAttribute getAttribute(String nsuri, String attname) {
            assert (attname != null);
            final IDocumentTable store = documentTable();
            int attcnt = store.getAttributeCountAt(_id);
            for (int i = 0; i < attcnt; i++) {
                long attid = store.getAttribute(_id, i);
                assert (attid != -1);
                assert (store.getNodeKindAt(attid) != NodeKind.ATTRIBUTE);
                QualifiedName qname = store.getAttributeName(attid);
                String attUri = qname.getNamespaceURI();
                String attName = qname.getLocalPart();
                if (nsuri == null || nsuri.equals(attUri)) {
                    if (attname.equals(attName)) {
                        final DocumentTableModel model = getDataModel();
                        return new DTMAttribute(model, attid);
                    }
                }
            }
            return null;
        }

        public int getAttributesCount() {
            final IDocumentTable store = documentTable();
            return store.getAttributeCountAt(_id);
        }

        public long getNamespaceDecl(int i) {
            assert (i >= 0);
            final IDocumentTable store = documentTable();
            return store.getNamespaceDecl(_id, i);
        }

        public DTMNamespace getNamespace(int i) {
            final long nsid = getNamespaceDecl(i);
            if (nsid == -1) {
                return null;
            }
            final DocumentTableModel model = getDataModel();
            return new DTMNamespace(model, nsid);
        }

        public int getNamespaceDeclCount() {
            final IDocumentTable store = documentTable();
            return store.getNamespaceCountAt(_id);
        }

        @Override
        public String getNamespaceURI() {
            return nodeName().getNamespaceURI();
        }

        @Override
        public DTMNodeBase lastChild() {
            final IDocumentTable store = documentTable();
            final long cid = store.lastChild(_id);
            if (cid == -1) {
                return null;
            }
            final DocumentTableModel model = getDataModel();
            return model.createNode(store.getNodeKindAt(cid), cid);
        }

        public boolean nilled() {
            final IDocumentTable store = documentTable();
            long attid;
            for (int i = 0; (attid = getAttribute(i)) != -1; i++) {
                assert (attid != -1);
                byte attkind = store.getNodeKindAt(attid);
                if (attkind == NodeKind.ATTRIBUTE) {
                    QualifiedName qname = store.getAttributeName(attid);
                    String nsuri = qname.getNamespaceURI();
                    String attname = qname.getLocalPart();
                    if (XQueryConstants.XSI_URI.equals(nsuri) && "nil".equals(attname)) {
                        String attval = store.getText(attid);
                        if (attval == null) {
                            return false;
                        }
                        return BooleanValue.toBoolean(attval);
                    }
                }
            }
            return false;
        }

        @Override
        public byte nodeKind() {
            return NodeKind.ELEMENT;
        }

        @Override
        public int getNameCode() {
            final IDocumentTable store = documentTable();
            return store.getNameCode(_id);
        }

        @Override
        public QualifiedName nodeName() {
            final IDocumentTable store = documentTable();
            return store.getName(_id);
        }
    }

    public static class DTMDocument extends DTMElement implements Closeable {
        private static final long serialVersionUID = -457402431467614608L;

        private String documentUri = null;

        public DTMDocument() {//for serialization
            super();
        }

        protected DTMDocument(DocumentTableModel model, long id) {
            super(model, id);
        }

        public DTMAttribute attribute(DTMElement elem, int i) {
            throw new UnsupportedOperationException();
        }

        @Override
        public DTMAttribute attribute(int i) {
            throw new UnsupportedOperationException();
        }

        public String documentUri() {
            return documentUri;
        }

        @Override
        public long getAttribute(int i) {
            throw new UnsupportedOperationException();
        }

        @Override
        public DTMAttribute getAttribute(String nsuri, String attname) {
            throw new UnsupportedOperationException();
        }

        @Override
        public int getDocumentId() {
            return getDataModel()._docid;
        }

        @Override
        public DTMDocument getDocumentNode() {
            return this;
        }

        @Override
        public String getNamespaceURI() {
            return null;
        }

        @Override
        public DTMElement getRoot() {
            return this;
        }

        @Override
        public DTMNodeBase nextSibling() {
            return null;
        }

        @Override
        public boolean nilled() {
            return false;
        }

        @Override
        public byte nodeKind() {
            return NodeKind.DOCUMENT;
        }

        @Override
        public QualifiedName nodeName() {
            return null;
        }

        @Override
        public DTMElement parent() {
            return null;
        }

        @Override
        public DTMNodeBase previousSibling() {
            return null;
        }

        public void setDocumentUri(String docUri) {
            this.documentUri = docUri;
        }

        public void close() throws IOException {
            _store.close();
        }
    }

    /**
     * Note: may be used within namespace node.
     */
    public static class DTMAttribute extends DTMNodeBase {
        private static final long serialVersionUID = 7263434327980382798L;

        public DTMAttribute() {//for serialization
            super();
        }

        protected DTMAttribute(DocumentTableModel model, long id) {
            super(model, id);
        }

        @Override
        public String getContent() {
            return _store.getText(_id);
        }

        @Override
        public String getNamespaceURI() {
            return nodeName().getNamespaceURI();
        }

        @Override
        public DTMNodeBase nextSibling() {
            return null;
        }

        @Override
        public byte nodeKind() {
            return NodeKind.ATTRIBUTE;
        }

        @Override
        public QualifiedName nodeName() {
            return _store.getAttributeName(_id);
        }

        @Override
        public int getNameCode() {
            return _store.getAttributeNameCode(_id);
        }

        @Override
        public DTMElement parent() {
            final long eid = _store.parent(_id);
            return _model.createNode(NodeKind.ELEMENT, eid);
        }

        @Override
        public DTMNodeBase previousSibling() {
            return null;
        }
    }

    public static final class DTMNamespace extends DTMAttribute {
        private static final long serialVersionUID = 5955223395627163938L;

        public DTMNamespace() {//for serialization
            super();
        }

        protected DTMNamespace(DocumentTableModel model, long id) {
            super(model, id);
        }

        @Override
        public byte nodeKind() {
            return NodeKind.NAMESPACE;
        }
    }

    public static final class DTMProcessingInstruction extends DTMNodeBase {
        private static final long serialVersionUID = -4129132047857524061L;

        public DTMProcessingInstruction() {//for serialization
            super();
        }

        protected DTMProcessingInstruction(DocumentTableModel model, long id) {
            super(model, id);
        }

        @Override
        public String getContent() {
            return _store.getName(_id).getNamespaceURI();
        }

        public String getTarget() {
            return _store.getName(_id).getLocalPart();
        }

        @Override
        public byte nodeKind() {
            return NodeKind.PROCESSING_INSTRUCTION;
        }

        @Override
        public QualifiedName nodeName() {
            final String target = getTarget();
            return QNameTable.instantiate(XMLUtils.NULL_NS_URI, target);
        }
    }

    public static final class DTMText extends DTMNodeBase {
        private static final long serialVersionUID = -1832518864998703501L;

        public DTMText() {//for serialization
            super();
        }

        protected DTMText(DocumentTableModel model, long id) {
            super(model, id);
        }

        @Override
        public String getContent() {
            return _store.getText(_id);
        }

        @Override
        public byte nodeKind() {
            return NodeKind.TEXT;
        }
    }

    public static final class DTMComment extends DTMNodeBase {
        private static final long serialVersionUID = -2555012444869523835L;

        public DTMComment() {//for serialization
            super();
        }

        protected DTMComment(DocumentTableModel model, long id) {
            super(model, id);
        }

        @Override
        public String getContent() {
            return _store.getText(_id);
        }

        @Override
        public byte nodeKind() {
            return NodeKind.COMMENT;
        }
    }

}