/*   Copyright 2004 The Apache Software Foundation
 *
 *   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.
 */

package org.apache.xmlbeans.impl.store;

import org.apache.xmlbeans.*;
import org.apache.xmlbeans.XmlCursor.XmlBookmark;
import org.apache.xmlbeans.impl.soap.*;
import org.apache.xmlbeans.impl.store.DomImpl.Dom;
import org.apache.xmlbeans.impl.store.Locale.LoadContext;
import org.apache.xmlbeans.impl.values.TypeStoreUser;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;

import javax.xml.namespace.QName;
import java.io.PrintStream;
import java.util.Map;

import static org.apache.xmlbeans.impl.values.TypeStore.WS_PRESERVE;

// DOM Level 3


public final class Cur {
    static final int TEXT = 0; // Must be 0
    static final int ROOT = 1;
    static final int ELEM = 2;
    static final int ATTR = 3;
    static final int COMMENT = 4;
    static final int PROCINST = 5;

    static final int POOLED = 0;
    static final int REGISTERED = 1;
    static final int EMBEDDED = 2;
    static final int DISPOSED = 3;

    static final int END_POS = -1;
    static final int NO_POS = -2;

    Locale _locale;

    Xobj _xobj;
    int _pos;

    int _state;

    String _id;

    Cur _nextTemp;
    Cur _prevTemp;
    int _tempFrame;

    Cur _next;
    Cur _prev;

    Locale.Ref _ref;

    int _stackTop;

    int _selectionFirst;
    int _selectionN;
    int _selectionLoc;
    int _selectionCount;

    private int _posTemp;

    int _offSrc;
    int _cchSrc;


    Cur(Locale l) {
        _locale = l;
        _pos = NO_POS;

        _tempFrame = -1;

        _state = POOLED;

        _stackTop = Locations.NULL;
        _selectionFirst = -1;
        _selectionN = -1;
        _selectionLoc = Locations.NULL;
        _selectionCount = 0;
    }

    public boolean isPositioned() {
        assert isNormal();
        return _xobj != null;
    }

    static boolean kindIsContainer(int k) {
        return k == ELEM || k == ROOT;
    }

    static boolean kindIsFinish(int k) {
        return k == -ELEM || k == -ROOT;
    }

    public int kind() {
        assert isPositioned();
        int kind = _xobj.kind();
        return _pos == 0 ? kind : (_pos == END_POS ? -kind : TEXT);
    }

    public boolean isRoot() {
        assert isPositioned();
        return _pos == 0 && _xobj.kind() == ROOT;
    }

    public boolean isElem() {
        assert isPositioned();
        return _pos == 0 && _xobj.kind() == ELEM;
    }

    public boolean isAttr() {
        assert isPositioned();
        return _pos == 0 && _xobj.kind() == ATTR;
    }

    public boolean isComment() {
        assert isPositioned();
        return _pos == 0 && _xobj.kind() == COMMENT;
    }

    public boolean isProcinst() {
        assert isPositioned();
        return _pos == 0 && _xobj.kind() == PROCINST;
    }

    public boolean isText() {
        assert isPositioned();
        return _pos > 0;
    }

    public boolean isEnd() {
        assert isPositioned();
        return _pos == END_POS && _xobj.kind() == ELEM;
    }

    public boolean isEndRoot() {
        assert isPositioned();
        return _pos == END_POS && _xobj.kind() == ROOT;
    }

    public boolean isNode() {
        assert isPositioned();
        return _pos == 0;
    }

    public boolean isContainer() {
        assert isPositioned();
        return _pos == 0 && kindIsContainer(_xobj.kind());
    }

    public boolean isFinish() {
        assert isPositioned();
        return _pos == END_POS && kindIsContainer(_xobj.kind());
    }

    public boolean isUserNode() {
        assert isPositioned();
        int k = kind();
        return k == ELEM || k == ROOT || (k == ATTR && !isXmlns());
    }

    public boolean isContainerOrFinish() {
        assert isPositioned();

        if (_pos != 0 && _pos != END_POS) {
            return false;
        }

        int kind = _xobj.kind();
        return kind == ELEM || kind == -ELEM || kind == ROOT || kind == -ROOT;
    }

    public boolean isNormalAttr() {
        return isNode() && _xobj.isNormalAttr();
    }

    public boolean isXmlns() {
        return isNode() && _xobj.isXmlns();
    }

    public boolean isTextCData() {
        return _xobj.hasBookmark(CDataBookmark.class, _pos);
    }

    public QName getName() {
        assert isNode() || isEnd();
        return _xobj._name;
    }

    public String getLocal() {
        return getName().getLocalPart();
    }

    public String getUri() {
        return getName().getNamespaceURI();
    }

    public String getXmlnsPrefix() {
        assert isXmlns();
        return _xobj.getXmlnsPrefix();
    }

    public String getXmlnsUri() {
        assert isXmlns();
        return _xobj.getXmlnsUri();
    }

    public boolean isDomDocRoot() {
        return isRoot() && _xobj.getDom() instanceof Document;
    }

    public boolean isDomFragRoot() {
        return isRoot() && _xobj.getDom() instanceof DocumentFragment;
    }

    public int cchRight() {
        assert isPositioned();
        return _xobj.cchRight(_pos);
    }

    public int cchLeft() {
        assert isPositioned();
        return _xobj.cchLeft(_pos);
    }

    //
    // Creation methods
    //

    void createRoot() {
        createDomDocFragRoot();
    }

    void createDomDocFragRoot() {
        moveTo(new DocumentFragXobj(_locale));
    }

    void createDomDocumentRoot() {
        moveTo(createDomDocumentRootXobj(_locale));
    }

    void createAttr(QName name) {
        createHelper(new AttrXobj(_locale, name));
    }

    void createComment() {
        createHelper(new CommentXobj(_locale));
    }

    void createProcinst(String target) {
        createHelper(new ProcInstXobj(_locale, target));
    }

    void createElement(QName name) {
        createElement(name, null);
    }

    void createElement(QName name, QName parentName) {
        createHelper(createElementXobj(_locale, name, parentName));
    }

    static Xobj createDomDocumentRootXobj(Locale l) {
        return createDomDocumentRootXobj(l, false);
    }

    static Xobj createDomDocumentRootXobj(Locale l, boolean fragment) {
        Xobj xo;

        if (l._saaj == null) {
            if (fragment) {
                xo = new DocumentFragXobj(l);
            } else {
                xo = new DocumentXobj(l);
            }
        } else {
            xo = new SoapPartDocXobj(l);
        }

        if (l._ownerDoc == null) {
            l._ownerDoc = xo.getDom();
        }

        return xo;
    }

    static Xobj createElementXobj(Locale l, QName name, QName parentName) {
        if (l._saaj == null) {
            return new ElementXobj(l, name);
        }

        Class<?> c = l._saaj.identifyElement(name, parentName);

        if (c == SOAPElement.class) {
            return new SoapElementXobj(l, name);
        }
        if (c == SOAPBody.class) {
            return new SoapBodyXobj(l, name);
        }
        if (c == SOAPBodyElement.class) {
            return new SoapBodyElementXobj(l, name);
        }
        if (c == SOAPEnvelope.class) {
            return new SoapEnvelopeXobj(l, name);
        }
        if (c == SOAPHeader.class) {
            return new SoapHeaderXobj(l, name);
        }
        if (c == SOAPHeaderElement.class) {
            return new SoapHeaderElementXobj(l, name);
        }
        if (c == SOAPFaultElement.class) {
            return new SoapFaultElementXobj(l, name);
        }
        if (c == Detail.class) {
            return new DetailXobj(l, name);
        }
        if (c == DetailEntry.class) {
            return new DetailEntryXobj(l, name);
        }
        if (c == SOAPFault.class) {
            return new SoapFaultXobj(l, name);
        }

        throw new IllegalStateException("Unknown SAAJ element class: " + c);
    }

    private void createHelper(Xobj x) {
        assert x._locale == _locale;

        // insert the new Xobj into an exisiting tree.

        if (isPositioned()) {
            Cur from = tempCur(x, 0);
            from.moveNode(this);
            from.release();
        }

        moveTo(x);
    }

    //
    // General operations
    //

    boolean isSamePos(Cur that) {
        assert isNormal() && (that == null || that.isNormal());

        return _xobj == that._xobj && _pos == that._pos;
    }

    // is this just after the end of that (that must be the start of a node)

    boolean isJustAfterEnd(Cur that) {
        assert isNormal() && that != null && that.isNormal() && that.isNode();

        return that._xobj.isJustAfterEnd(_xobj, _pos);
    }

    boolean isJustAfterEnd(Xobj x) {
        return x.isJustAfterEnd(_xobj, _pos);
    }

    boolean isAtEndOf(Cur that) {
        assert that != null && that.isNormal() && that.isNode();

        return _xobj == that._xobj && _pos == END_POS;
    }

    boolean isInSameTree(Cur that) {
        assert isPositioned() && that.isPositioned();

        return _xobj.isInSameTree(that._xobj);
    }

    // Retunr -1, 0 or 1 for relative cursor positions.  Return 2 is not in sames trees.

    int comparePosition(Cur that) {
        assert isPositioned() && that.isPositioned();

        // If in differnet locales, then can't comapre

        if (_locale != that._locale) {
            return 2;
        }

        // No need to denormalize, but I want positions which I can compare (no END_POS)

        Xobj xThis = _xobj;
        int pThis = _pos == END_POS ? xThis.posAfter() - 1 : _pos;

        Xobj xThat = that._xobj;
        int pThat = that._pos == END_POS ? xThat.posAfter() - 1 : that._pos;

        // There are several cases:
        //
        // 1. Cursors are on the same xobj
        // 2. One cursor is a child of the other
        // 3. Cursors share a common parent
        // 4. Cursors are not in the same trees
        //
        // Check for the first, trivial, case.  Then, compute the depths of the nodes the
        // cursors are on, checkin for case 2
        //

        if (xThis == xThat) {
            return Integer.compare(pThis, pThat);
        }

        // Compute the depth of xThis.  See if I hit xThat (case 2)

        int dThis = 0;

        for (Xobj x = xThis._parent; x != null; x = x._parent) {
            dThis++;

            if (x == xThat) {
                return pThat < xThat.posAfter() - 1 ? 1 : -1;
            }
        }

        // Compute the depth of xThat.  See if I hit xThis (case 2)

        int dThat = 0;

        for (Xobj x = xThat._parent; x != null; x = x._parent) {
            dThat++;

            if (x == xThis) {
                return pThis < xThis.posAfter() - 1 ? -1 : 1;
            }
        }

        // Must be case 3 or 4 now.  Find a common parent.  If none, then it's case 4

        while (dThis > dThat) {
            dThis--;
            assert (xThis != null);
            xThis = xThis._parent;
        }
        while (dThat > dThis) {
            dThat--;
            assert (xThat != null);
            xThat = xThat._parent;
        }

        assert dThat == dThis;

        if (dThat == 0) {
            return 2;
        }

        assert xThis != null && xThis._parent != null && xThat != null && xThat._parent != null;

        while (xThis._parent != xThat._parent) {
            if ((xThis = xThis._parent) == null) {
                return 2;
            }

            xThat = xThat._parent;
        }

        // Now, see where xThis and XThat are relative to eachother in the childlist.  Apply
        // some quick common checks before iterating.

        if (xThis._prevSibling == null || xThat._nextSibling == null) {
            return -1;
        }

        if (xThis._nextSibling == null || xThat._prevSibling == null) {
            return 1;
        }

        while (xThis != null) {
            if ((xThis = xThis._prevSibling) == xThat) {
                return 1;
            }
        }

        return -1;
    }

    void setName(QName newName) {
        assert isNode() && newName != null;

        _xobj.setName(newName);
    }

    void moveTo(Xobj x) {
        moveTo(x, 0);
    }

    void moveTo(Xobj x, int p) {
        // This cursor may not be normalized upon entry, don't assert isNormal() here

        assert x == null || _locale == x._locale;
        assert x != null || p == NO_POS;
        assert x == null || x.isNormal(p) || (x.isVacant() && x._cchValue == 0 && x._user == null);
        assert _state == REGISTERED || _state == EMBEDDED;
        assert _state == EMBEDDED || (_xobj == null || !isOnList(_xobj._embedded));
        assert _state == REGISTERED || (_xobj != null && isOnList(_xobj._embedded));

        moveToNoCheck(x, p);

        assert isNormal() || (_xobj.isVacant() && _xobj._cchValue == 0 && _xobj._user == null);
    }

    void moveToNoCheck(Xobj x, int p) {
        if (_state == EMBEDDED && x != _xobj) {
            _xobj._embedded = listRemove(_xobj._embedded);
            _locale._registered = listInsert(_locale._registered);
            _state = REGISTERED;
        }

        _xobj = x;
        _pos = p;
    }

    void moveToCur(Cur to) {
        assert isNormal() && (to == null || to.isNormal());

        if (to == null) {
            moveTo(null, NO_POS);
        } else {
            moveTo(to._xobj, to._pos);
        }
    }

    void moveToDom(Dom d) {
        assert _locale == d.locale();
        assert d instanceof Xobj || d instanceof SoapPartDom;

        moveTo(d instanceof Xobj ? (Xobj) d : ((SoapPartDom) d)._docXobj);
    }

    static final class Locations {
        private static final int NULL = -1;

        Locations(Locale l) {
            _locale = l;

            _xobjs = new Xobj[_initialSize];
            _poses = new int[_initialSize];
            _curs = new Cur[_initialSize];
            _next = new int[_initialSize];
            _prev = new int[_initialSize];
            _nextN = new int[_initialSize];
            _prevN = new int[_initialSize];

            for (int i = _initialSize - 1; i >= 0; i--) {
                assert _xobjs[i] == null;
                _poses[i] = NO_POS;
                _next[i] = i + 1;
                _prev[i] = NULL;
                _nextN[i] = NULL;
                _prevN[i] = NULL;
            }

            _next[_initialSize - 1] = NULL;

            _free = 0;
            _naked = NULL;
        }

        boolean isSamePos(int i, Cur c) {
            if (_curs[i] == null) {
                return c._xobj == _xobjs[i] && c._pos == _poses[i];
            } else {
                return c.isSamePos(_curs[i]);
            }
        }

        boolean isAtEndOf(int i, Cur c) {
            assert _curs[i] != null || _poses[i] == 0;
            assert _curs[i] == null || _curs[i].isNode();

            if (_curs[i] == null) {
                return c._xobj == _xobjs[i] && c._pos == END_POS;
            } else {
                return c.isAtEndOf(_curs[i]);
            }
        }

        void moveTo(int i, Cur c) {
            if (_curs[i] == null) {
                c.moveTo(_xobjs[i], _poses[i]);
            } else {
                c.moveToCur(_curs[i]);
            }
        }

        int insert(int head, int before, int i) {
            return insert(head, before, i, _next, _prev);
        }

        int remove(int head, int i) {
            Cur c = _curs[i];

            assert c != null || _xobjs[i] != null;
            assert c != null || _xobjs[i] != null;

            if (c != null) {
                _curs[i].release();
                _curs[i] = null;

                assert _xobjs[i] == null;
                assert _poses[i] == NO_POS;
            } else {
                assert _xobjs[i] != null && _poses[i] != NO_POS;

                _xobjs[i] = null;
                _poses[i] = NO_POS;

                _naked = remove(_naked, i, _nextN, _prevN);
            }

            head = remove(head, i, _next, _prev);

            _next[i] = _free;
            _free = i;

            return head;
        }

        int allocate(Cur addThis) {
            assert addThis.isPositioned();

            if (_free == NULL) {
                makeRoom();
            }

            int i = _free;

            _free = _next[i];

            _next[i] = NULL;
            assert _prev[i] == NULL;

            assert _curs[i] == null;
            assert _xobjs[i] == null;
            assert _poses[i] == NO_POS;

            _xobjs[i] = addThis._xobj;
            _poses[i] = addThis._pos;

            _naked = insert(_naked, NULL, i, _nextN, _prevN);

            return i;
        }

        private static int insert(int head, int before, int i, int[] next, int[] prev) {
            if (head == NULL) {
                assert before == NULL;
                prev[i] = i;
                head = i;
            } else if (before != NULL) {
                prev[i] = prev[before];
                next[i] = before;
                prev[before] = i;

                if (head == before) {
                    head = i;
                }
            } else {
                prev[i] = prev[head];
                assert next[i] == NULL;
                next[prev[head]] = i;
                prev[head] = i;
            }

            return head;
        }

        private static int remove(int head, int i, int[] next, int[] prev) {
            if (prev[i] == i) {
                assert head == i;
                head = NULL;
            } else {
                if (head == i) {
                    head = next[i];
                } else {
                    next[prev[i]] = next[i];
                }

                if (next[i] == NULL) {
                    prev[head] = prev[i];
                } else {
                    prev[next[i]] = prev[i];
                    next[i] = NULL;
                }
            }

            prev[i] = NULL;
            assert next[i] == NULL;

            return head;
        }

        void notifyChange() {
            for (int i; (i = _naked) != NULL; ) {
                assert _curs[i] == null && _xobjs[i] != null && _poses[i] != NO_POS;

                _naked = remove(_naked, i, _nextN, _prevN);

                _curs[i] = _locale.getCur();
                _curs[i].moveTo(_xobjs[i], _poses[i]);

                _xobjs[i] = null;
                _poses[i] = NO_POS;
            }
        }

        int next(int i) {
            return _next[i];
        }

        int prev(int i) {
            return _prev[i];
        }

        private void makeRoom() {
            assert _free == NULL;

            int l = _xobjs.length;

            Xobj[] oldXobjs = _xobjs;
            int[] oldPoses = _poses;
            Cur[] oldCurs = _curs;
            int[] oldNext = _next;
            int[] oldPrev = _prev;
            int[] oldNextN = _nextN;
            int[] oldPrevN = _prevN;

            _xobjs = new Xobj[l * 2];
            _poses = new int[l * 2];
            _curs = new Cur[l * 2];
            _next = new int[l * 2];
            _prev = new int[l * 2];
            _nextN = new int[l * 2];
            _prevN = new int[l * 2];

            System.arraycopy(oldXobjs, 0, _xobjs, 0, l);
            System.arraycopy(oldPoses, 0, _poses, 0, l);
            System.arraycopy(oldCurs, 0, _curs, 0, l);
            System.arraycopy(oldNext, 0, _next, 0, l);
            System.arraycopy(oldPrev, 0, _prev, 0, l);
            System.arraycopy(oldNextN, 0, _nextN, 0, l);
            System.arraycopy(oldPrevN, 0, _prevN, 0, l);

            for (int i = l * 2 - 1; i >= l; i--) {
                _next[i] = i + 1;
                _prev[i] = NULL;
                _nextN[i] = NULL;
                _prevN[i] = NULL;
                _poses[i] = NO_POS;
            }

            _next[l * 2 - 1] = NULL;

            _free = l;
        }

        private static final int _initialSize = 32;

        private final Locale _locale;

        private Xobj[] _xobjs;
        private int[] _poses;
        private Cur[] _curs;
        private int[] _next;
        private int[] _prev;
        private int[] _nextN;
        private int[] _prevN;

        private int _free;   // Unused entries
        private int _naked;  // Entries without Curs
    }

    public void push() {
        assert isPositioned();

        int i = _locale._locations.allocate(this);
        _stackTop = _locale._locations.insert(_stackTop, _stackTop, i);
    }

    void popButStay() {
        if (_stackTop != Locations.NULL) {
            _stackTop = _locale._locations.remove(_stackTop, _stackTop);
        }
    }

    boolean pop() {
        if (_stackTop == Locations.NULL) {
            return false;
        }

        _locale._locations.moveTo(_stackTop, this);
        _stackTop = _locale._locations.remove(_stackTop, _stackTop);

        return true;
    }

    boolean isAtLastPush() {
        assert _stackTop != Locations.NULL;

        return _locale._locations.isSamePos(_stackTop, this);
    }

    public boolean isAtEndOfLastPush() {
        assert _stackTop != Locations.NULL;

        return _locale._locations.isAtEndOf(_stackTop, this);
    }

    public void addToSelection(Cur that) {
        assert that != null && that.isNormal();
        assert isPositioned() && that.isPositioned();

        int i = _locale._locations.allocate(that);
        _selectionFirst = _locale._locations.insert(_selectionFirst, Locations.NULL, i);

        _selectionCount++;
    }

    public void addToSelection() {
        assert isPositioned();

        int i = _locale._locations.allocate(this);
        _selectionFirst = _locale._locations.insert(_selectionFirst, Locations.NULL, i);

        _selectionCount++;
    }

    private int selectionIndex(int i) {
        assert _selectionN >= -1 && i >= 0 && i < _selectionCount;

        if (_selectionN == -1) {
            _selectionN = 0;
            _selectionLoc = _selectionFirst;
        }

        while (_selectionN < i) {
            _selectionLoc = _locale._locations.next(_selectionLoc);
            _selectionN++;
        }

        while (_selectionN > i) {
            _selectionLoc = _locale._locations.prev(_selectionLoc);
            _selectionN--;
        }

        return _selectionLoc;
    }

    void removeFirstSelection() {
        final int i = 0;
        assert i < _selectionCount;

        int j = selectionIndex(i);

        // Update the nth selection indices to accomodate the deletion

        if (i < _selectionN) {
            _selectionN--;
        } else if (i == _selectionN) {
            _selectionN--;
            _selectionLoc = Locations.NULL;
        }

        _selectionFirst = _locale._locations.remove(_selectionFirst, j);

        _selectionCount--;
    }

    public int selectionCount() {
        return _selectionCount;
    }

    public void moveToSelection(int i) {
        assert i >= 0 && i < _selectionCount;

        _locale._locations.moveTo(selectionIndex(i), this);
    }

    public void clearSelection() {
        assert _selectionCount >= 0;

        while (_selectionCount > 0) {
            removeFirstSelection();
        }
    }

    public boolean toParent() {
        return toParent(false);
    }

    public boolean toParentRaw() {
        return toParent(true);
    }

    public Xobj getParent() {
        return getParent(false);
    }

    public Xobj getParentRaw() {
        return getParent(true);
    }

    public boolean hasParent() {
        assert isPositioned();

        if (_pos == END_POS || (_pos >= 1 && _pos < _xobj.posAfter())) {
            return true;
        }

        assert _pos == 0 || _xobj._parent != null;

        return _xobj._parent != null;
    }

    public Xobj getParentNoRoot() {
        assert isPositioned();

        if (_pos == END_POS || (_pos >= 1 && _pos < _xobj.posAfter())) {
            return _xobj;
        }

        assert _pos == 0 || _xobj._parent != null;

        if (_xobj._parent != null) {
            return _xobj._parent;
        }

        return null;
    }

    public Xobj getParent(boolean raw) {
        assert isPositioned();

        if (_pos == END_POS || (_pos >= 1 && _pos < _xobj.posAfter())) {
            return _xobj;
        }

        assert _pos == 0 || _xobj._parent != null;

        if (_xobj._parent != null) {
            return _xobj._parent;
        }

        if (raw || _xobj.isRoot()) {
            return null;
        }

        Cur r = _locale.tempCur();

        r.createRoot();

        Xobj root = r._xobj;

        r.next();
        moveNode(r);
        r.release();

        return root;
    }

    public boolean toParent(boolean raw) {
        Xobj parent = getParent(raw);

        if (parent == null) {
            return false;
        }

        moveTo(parent);

        return true;
    }

    public void toRoot() {
        Xobj xobj = _xobj;
        while (!xobj.isRoot()) {
            if (xobj._parent == null) {
                Cur r = _locale.tempCur();

                r.createRoot();

                Xobj root = r._xobj;

                r.next();
                moveNode(r);
                r.release();

                xobj = root;
                break;
            }
            xobj = xobj._parent;
        }
        moveTo(xobj);
    }

    public boolean hasText() {
        assert isNode();

        return _xobj.hasTextEnsureOccupancy();
    }

    public boolean hasAttrs() {
        assert isNode();

        return _xobj.hasAttrs();
    }

    public boolean hasChildren() {
        assert isNode();

        return _xobj.hasChildren();
    }

    public boolean toFirstChild() {
        assert isNode();

        if (!_xobj.hasChildren()) {
            return false;
        }

        for (Xobj x = _xobj._firstChild; ; x = x._nextSibling) {
            if (!x.isAttr()) {
                moveTo(x);
                return true;
            }
        }
    }

    public boolean toLastChild() {
        assert isNode();

        if (!_xobj.hasChildren()) {
            return false;
        }

        moveTo(_xobj._lastChild);

        return true;
    }

    public boolean toNextSibling() {
        assert isNode();

        if (_xobj.isAttr()) {
            if (_xobj._nextSibling != null && _xobj._nextSibling.isAttr()) {
                moveTo(_xobj._nextSibling);
                return true;
            }
        } else if (_xobj._nextSibling != null) {
            moveTo(_xobj._nextSibling);
            return true;
        }

        return false;
    }

    public void setValueAsQName(QName qname) {
        assert isNode();

        String value = qname.getLocalPart();
        String ns = qname.getNamespaceURI();

        String prefix =
            prefixForNamespace(
                ns, qname.getPrefix().length() > 0 ? qname.getPrefix() : null, true);

        if (prefix.length() > 0) {
            value = prefix + ":" + value;
        }

        setValue(value);
    }

    public void setValue(String value) {
        assert isNode();

        moveNodeContents(null, false);

        next();

        insertString(value);

        toParent();
    }

    public void removeFollowingAttrs() {
        assert isAttr();

        QName attrName = getName();

        push();

        if (toNextAttr()) {
            while (isAttr()) {
                if (getName().equals(attrName)) {
                    moveNode(null);
                } else if (!toNextAttr()) {
                    break;
                }
            }
        }

        pop();
    }

    public String getAttrValue(QName name) {
        String s = null;

        push();

        if (toAttr(name)) {
            s = getValueAsString();
        }

        pop();

        return s;
    }

    public void setAttrValueAsQName(QName value) {
        assert isContainer();

        final QName name = Locale._xsiType;

        if (value == null) {
            _xobj.removeAttr(name);
            return;
        }

        if (toAttr(name)) {
            removeFollowingAttrs();
        } else {
            next();
            createAttr(name);
        }
        setValueAsQName(value);
        toParent();
    }

    public boolean removeAttr(QName name) {
        assert isContainer();

        return _xobj.removeAttr(name);
    }

    public void setAttrValue(QName name, String value) {
        assert isContainer();

        _xobj.setAttr(name, value);
    }

    public boolean toAttr(QName name) {
        assert isNode();

        Xobj a = _xobj.getAttr(name);

        if (a == null) {
            return false;
        }

        moveTo(a);

        return true;
    }

    public boolean toFirstAttr() {
        assert isNode();

        Xobj firstAttr = _xobj.firstAttr();

        if (firstAttr == null) {
            return false;
        }

        moveTo(firstAttr);

        return true;
    }

    public boolean toLastAttr() {
        assert isNode();

        if (!toFirstAttr()) {
            return false;
        }

        //noinspection StatementWithEmptyBody
        while (toNextAttr()) {
        }

        return true;
    }

    public boolean toNextAttr() {
        assert isAttr() || isContainer();

        Xobj nextAttr = _xobj.nextAttr();

        if (nextAttr == null) {
            return false;
        }

        moveTo(nextAttr);

        return true;
    }

    @SuppressWarnings("UnusedReturnValue")
    public boolean toPrevAttr() {
        if (isAttr()) {
            if (_xobj._prevSibling == null) {
                moveTo(_xobj.ensureParent());
            } else {
                moveTo(_xobj._prevSibling);
            }

            return true;
        }

        prev();

        if (!isContainer()) {
            next();
            return false;
        }

        return toLastAttr();
    }

    @SuppressWarnings("UnusedReturnValue")
    public boolean skipWithAttrs() {
        assert isNode();

        if (skip()) {
            return true;
        }

        if (_xobj.isRoot()) {
            return false;
        }

        assert _xobj.isAttr();

        toParent();

        next();

        return true;
    }

    public boolean skip() {
        assert isNode();

        if (_xobj.isRoot()) {
            return false;
        }

        if (_xobj.isAttr()) {
            if (_xobj._nextSibling == null || !_xobj._nextSibling.isAttr()) {
                return false;
            }

            moveTo(_xobj._nextSibling, 0);
        } else {
            moveTo(getNormal(_xobj, _xobj.posAfter()), _posTemp);
        }

        return true;
    }

    public void toEnd() {
        assert isNode();

        moveTo(_xobj, END_POS);
    }

    public void moveToCharNode(CharNode node) {
        assert node.getDom() != null && node.getDom().locale() == _locale;

        moveToDom(node.getDom());

        CharNode n;

        _xobj.ensureOccupancy();

        n = _xobj._charNodesValue =
            updateCharNodes(_locale, _xobj, _xobj._charNodesValue, _xobj._cchValue);

        for (; n != null; n = n._next) {
            if (node == n) {
                moveTo(getNormal(_xobj, n._off + 1), _posTemp);
                return;
            }
        }

        n = _xobj._charNodesAfter =
            updateCharNodes(_locale, _xobj, _xobj._charNodesAfter, _xobj._cchAfter);

        for (; n != null; n = n._next) {
            if (node == n) {
                moveTo(getNormal(_xobj, n._off + _xobj._cchValue + 2), _posTemp);
                return;
            }
        }

        assert false;
    }

    @SuppressWarnings("UnusedReturnValue")
    public boolean prevWithAttrs() {
        if (prev()) {
            return true;
        }

        if (!isAttr()) {
            return false;
        }

        toParent();

        return true;
    }

    public boolean prev() {
        assert isPositioned();

        if (_xobj.isRoot() && _pos == 0) {
            return false;
        }

        if (_xobj.isAttr() && _pos == 0 && _xobj._prevSibling == null) {
            return false;
        }

        Xobj x = getDenormal();
        int p = _posTemp;

        assert p > 0;

        int pa = x.posAfter();

        if (p > pa) {
            p = pa;
        } else if (p == pa) {
            // Text after an attr is allowed only on the last attr,
            // and that text belongs to the parent container..
            //
            // If we're a thte end of the last attr, then we were just
            // inside the container, and we need to skip the attrs.

            if (x.isAttr() &&
                (x._cchAfter > 0 || x._nextSibling == null || !x._nextSibling.isAttr())) {
                x = x.ensureParent();
                p = 0;
            } else {
                p = END_POS;
            }
        } else if (p == pa - 1) {
            x.ensureOccupancy();
            p = x._cchValue > 0 ? 1 : 0;
        } else if (p > 1) {
            p = 1;
        } else {
            //noinspection ConstantConditions
            assert p == 1;
            p = 0;
        }

        moveTo(getNormal(x, p), _posTemp);

        return true;
    }

    @SuppressWarnings("UnusedReturnValue")
    public boolean next(boolean withAttrs) {
        return withAttrs ? nextWithAttrs() : next();
    }

    public boolean nextWithAttrs() {
        int k = kind();

        if (kindIsContainer(k)) {
            if (toFirstAttr()) {
                return true;
            }
        } else if (k == -ATTR) {
            if (next()) {
                return true;
            }

            toParent();

            if (!toParentRaw()) {
                return false;
            }
        }

        return next();
    }

    public boolean next() {
        assert isNormal();

        Xobj x = _xobj;
        int p = _pos;

        int pa = x.posAfter();

        if (p >= pa) {
            p = _xobj.posMax();
        } else if (p == END_POS) {
            if (x.isRoot() || (x.isAttr() && (x._nextSibling == null || !x._nextSibling.isAttr()))) {
                return false;
            }

            p = pa;
        } else if (p > 0) {
            assert x._firstChild == null || !x._firstChild.isAttr();

            if (x._firstChild != null) {
                x = x._firstChild;
                p = 0;
            } else {
                p = END_POS;
            }
        } else {
            assert p == 0;

            x.ensureOccupancy();

            p = 1;

            if (x._cchValue == 0) {
                if (x._firstChild != null) {
                    if (x._firstChild.isAttr()) {
                        Xobj a = x._firstChild;

                        while (a._nextSibling != null && a._nextSibling.isAttr()) {
                            a = a._nextSibling;
                        }

                        if (a._cchAfter > 0) {
                            x = a;
                            p = a.posAfter();
                        } else if (a._nextSibling != null) {
                            x = a._nextSibling;
                            p = 0;
                        }
                    } else {
                        x = x._firstChild;
                        p = 0;
                    }
                }
            }
        }

        moveTo(getNormal(x, p), _posTemp);

        return true;
    }

    int prevChars(int cch) {
        assert isPositioned();

        int cchLeft = cchLeft();

        if (cch < 0 || cch > cchLeft) {
            cch = cchLeft;
        }

        // Dang, I love this stmt :-)

        if (cch != 0) {
            moveTo(getNormal(getDenormal(), _posTemp - cch), _posTemp);
        }

        return cch;
    }

    int nextChars(int cch) {
        assert isPositioned();

        int cchRight = cchRight();

        if (cchRight == 0) {
            return 0;
        }

        if (cch < 0 || cch >= cchRight) {
            // Use next to not skip over children
            next();
            return cchRight;
        }

        moveTo(getNormal(_xobj, _pos + cch), _posTemp);

        return cch;
    }

    void setCharNodes(CharNode nodes) {
        assert nodes == null || _locale == nodes.locale();
        assert isPositioned();

        Xobj x = getDenormal();
        int p = _posTemp;

        assert !x.isRoot() || (p > 0 && p < x.posAfter());

        if (p >= x.posAfter()) {
            x._charNodesAfter = nodes;
        } else {
            x._charNodesValue = nodes;
        }

        for (; nodes != null; nodes = nodes._next) {
            nodes.setDom((Dom) x);
        }

        // No Need to notify text change or alter version, text nodes are
        // not part of the infoset
    }

    CharNode getCharNodes() {
        assert isPositioned();
        assert !isRoot();

        Xobj x = getDenormal();

        CharNode nodes;

        if (_posTemp >= x.posAfter()) {
            nodes = x._charNodesAfter =
                updateCharNodes(_locale, x, x._charNodesAfter, x._cchAfter);
        } else {
            x.ensureOccupancy();

            nodes = x._charNodesValue =
                updateCharNodes(_locale, x, x._charNodesValue, x._cchValue);
        }

        return nodes;
    }

    // private
    static CharNode updateCharNodes(Locale l, Xobj x, CharNode nodes, int cch) {
        assert nodes == null || nodes.locale() == l;

        CharNode node = nodes;
        int i = 0;

        while (node != null && cch > 0) {
            assert node.getDom() == x;

            if (node._cch > cch) {
                node._cch = cch;
            }

            node._off = i;
            i += node._cch;
            cch -= node._cch;

            node = node._next;
        }

        if (cch <= 0) {
            for (; node != null; node = node._next) {
                assert node.getDom() == x;

                if (node._cch != 0) {
                    node._cch = 0;
                }

                node._off = i;
            }
        } else {
            node = l.createTextNode();
            node.setDom((Dom) x);
            node._cch = cch;
            node._off = i;
            nodes = CharNode.appendNode(nodes, node);
        }

        return nodes;
    }

    final QName getXsiTypeName() {
        assert isNode();

        return _xobj.getXsiTypeName();
    }

    final void setXsiType(QName value) {
        assert isContainer();

        setAttrValueAsQName(value);
    }

    final String namespaceForPrefix(String prefix, boolean defaultAlwaysMapped) {
        return _xobj.namespaceForPrefix(prefix, defaultAlwaysMapped);
    }

    final String prefixForNamespace(String ns, String suggestion, boolean createIfMissing) {
        return
            (isContainer() ? _xobj : getParent()).
                prefixForNamespace(ns, suggestion, createIfMissing);
    }

    // Does the node at this cursor properly contain the position specified by the argument

    boolean contains(Cur that) {
        assert isNode();
        assert that != null && that.isPositioned();

        return _xobj.contains(that);
    }

    void insertString(String s) {
        if (s != null) {
            insertChars(s, 0, s.length());
        }
    }

    void insertChars(Object src, int off, int cch) {
        assert isPositioned() && !isRoot();
        assert CharUtil.isValid(src, off, cch);

        // Check for nothing to insert

        if (cch <= 0) {
            return;
        }

        _locale.notifyChange();

        // The only situation where I need to ensure occupancy is when I'm at the end of a node.
        // All other positions will require occupancy.  For example, if I'm at the beginning of a
        // node, then I will either insert in the after text of the previous sibling, or I will
        // insert in the value of the parent.  In the latter case, because the parent has a child,
        // it cannot be vacant.

        if (_pos == END_POS) {
            _xobj.ensureOccupancy();
        }

        // Get the denormailized Xobj and pos.  This is the Xobj which will actually receive
        // the new chars.  Note that a denormalized position can never be <= 0.

        Xobj x = getDenormal();
        int p = _posTemp;

        assert p > 0;

        // This will move "this" cursor to be after the inserted text. No worries, I'll update its
        // position after.  This insertChars takes care of all the appropriate invalidations
        // (passing true as last arg).

        x.insertCharsHelper(p, src, off, cch, true);

        // Reposition the cursor to be just before the newly inserted text.  It's current
        // position could have been shifted, or it may have been just before the end tag, or
        // normalized on another Xobj.

        moveTo(x, p);

        _locale._versionAll++;
    }

    // Move the chars just after this Cur to the "to" Cur.  If no "to" Cur is specified,
    // then remove the chars.

    Object moveChars(Cur to, int cchMove) {
        assert isPositioned();
        assert cchMove <= 0 || cchMove <= cchRight();
        assert to == null || (to.isPositioned() && !to.isRoot());

        if (cchMove < 0) {
            cchMove = cchRight();
        }

        // If we're instructed to move 0 characters, then return the null triple.

        if (cchMove == 0) {
            _offSrc = 0;
            _cchSrc = 0;

            return null;
        }

        // Here I record the triple of the chars to move.  I will return this.  No need to save
        // cch 'cause cchMove will be that value.

        Object srcMoved = getChars(cchMove);
        int offMoved = _offSrc;

        // Either I'm moving text from the value or the after text.  If after, then the container
        // must be occupied.  If in the value, because we're just before text, it must be occupied.

        assert isText() && (_pos >= _xobj.posAfter() ? _xobj._parent : _xobj).isOccupied();

        if (to == null) {
            // In this case, I'm removing chars vs moving them.  Normally I would like to blow
            // them away entirely, but if there are any references to those chars via a bookmark
            // I need to keep them alive.  I do this by moving these chars to a new root.  Note
            // that because Curs will stay behind, I don't have to check for them.

            for (Bookmark b = _xobj._bookmarks; b != null; b = b._next) {
                if (inChars(b, cchMove, false)) {
                    Cur c = _locale.tempCur();

                    c.createRoot();
                    c.next();

                    Object chars = moveChars(c, cchMove);

                    c.release();

                    return chars;
                }
            }
        } else {
            // If the target, "to", is inside or on the edge of the text to be moved, then this
            // is a no-op.  In this case, I still want to return the text "moved".
            //
            // Note how I move "to" and this cur around.  I move "to" to be at the beginning of the
            // chars moved and "this" to be at the end.  If the text were really moving to a
            // different location, then "to" would be at the beginning of the newly moved chars,
            // and "this" would be at the gap left by the newly removed chars.

            if (inChars(to, cchMove, true)) {
                // BUGBUG - may want to consider shuffling the interior cursors to the right just
                // like I move "this" to the right...

                to.moveToCur(this);
                nextChars(cchMove);

                _offSrc = offMoved;
                _cchSrc = cchMove;

                return srcMoved;
            }

            // Copy the chars here, I'll remove the originals next

            to.insertChars(srcMoved, offMoved, cchMove);
        }

        // Notice that I can delay the general change notification to this point because any
        // modifications up to this point are made by calling other high level operations which
        // generate this notification themselves.  Also, no need to notify of general change in
        // the "to" locale because the insertion of chars above handles that.

        _locale.notifyChange();

        //
        //if ( _xobj != null )
        {
            if (to == null) {
                _xobj.removeCharsHelper(_pos, cchMove, null, NO_POS, false, true);
            } else {
                _xobj.removeCharsHelper(_pos, cchMove, to._xobj, to._pos, false, true);
            }
        }

        // Need to update the position of this cursor even though it did not move anywhere.  This
        // needs to happen because it may not be properly normalized anymore.  Note that because
        // of the removal of the text, this cur may not be normal any more, thus I call moveTo
        // which does not assume this.

        _locale._versionAll++;

        _offSrc = offMoved;
        _cchSrc = cchMove;

        return srcMoved;
    }

    void moveNode(Cur to) {
        assert isNode() && !isRoot();
        assert to == null || to.isPositioned();
        assert to == null || !contains(to);
        assert to == null || !to.isRoot();

        // TODO - should assert that is an attr is being moved, it is ok there


        // Record the node to move and skip this cur past it.  This moves this cur to be after
        // the move to move/remove -- it's final resting place.  The only piece of information
        // about the source of the move is the node itself.

        Xobj x = _xobj;

        skip();

        // I call another function here to move the node.  I do this because  I don't have to
        // worry about messing with "this" here given that it not should be treated like any other
        // cursor after this point.

        moveNode(x, to);
    }

    // Moves text from one place to another in a low-level way, used as a helper for the higher
    // level functions.  Takes care of moving bookmarks and cursors.  In the high level content
    // manipulation functions, cursors do not follow content, but this helper moves them.  The
    // arguments are denormalized.  The Xobj's must be different from eachother but from the same
    // locale.  The destination must not be not be vacant.

    private static void transferChars(Xobj xFrom, int pFrom, Xobj xTo, int pTo, int cch) {
        assert xFrom != xTo;
        assert xFrom._locale == xTo._locale;
        assert pFrom > 0 && pFrom < xFrom.posMax();
        assert pTo > 0 && pTo <= xTo.posMax();
        assert cch > 0 && cch <= xFrom.cchRight(pFrom);
        assert pTo >= xTo.posAfter() || xTo.isOccupied();

        // Copy the chars from -> to without performing any invalidations.  This will scoot curs
        // and marks around appropriately.  Note that I get the cars with getCharsHelper which
        // does not check for normalization because the state of the tree at this moment may not
        // exactly be "correct" here.

        xTo.insertCharsHelper(
            pTo, xFrom.getCharsHelper(pFrom, cch),
            xFrom._locale._offSrc, xFrom._locale._cchSrc, false);

        xFrom.removeCharsHelper(pFrom, cch, xTo, pTo, true, false);
    }

    // Moves the node x to "to", or removes it if to is null.

    static void moveNode(Xobj x, Cur to) {
        assert x != null && !x.isRoot();
        assert to == null || to.isPositioned();
        assert to == null || !x.contains(to);
        assert to == null || !to.isRoot();

        if (to != null) {
            // Before I go much further, I want to make sure that if "to" is in the container of
            // a vacant node, I get it occupied.  I do not need to worry about the source being
            // vacant.

            if (to._pos == END_POS) {
                to._xobj.ensureOccupancy();
            }

            // See if the destination is on the edge of the node to be moved (a no-op).  It is
            // illegal to call this fcn when to is contained within the node to be moved.  Note
            // that I make sure that to gets oved to the beginning of the node.  The position of
            // to in all operations should leave to just before the content moved/inserted.

            if ((to._pos == 0 && to._xobj == x) || to.isJustAfterEnd(x)) {
                // TODO - should shuffle contained curs to the right???

                to.moveTo(x);
                return;
            }
        }

        // Notify the locale(s) about the change I am about to make.

        x._locale.notifyChange();

        x._locale._versionAll++;
        x._locale._versionSansText++;

        if (to != null && to._locale != x._locale) {
            to._locale.notifyChange();

            to._locale._versionAll++;
            to._locale._versionSansText++;
        }

        // Node is going away.  Invalidate the parent (the text around the node is merging).
        // Also, this node may be an attribute -- invalidate special attrs ...

        if (x.isAttr()) {
            x.invalidateSpecialAttr(to == null ? null : to.getParentRaw());
        } else {
            if (x._parent != null) {
                x._parent.invalidateUser();
            }

            if (to != null && to.hasParent()) {
                to.getParent().invalidateUser();
            }
        }

        // If there is any text after x, I move it to be before x.  This frees me to extract x
        // and it's contents with out this text coming along for the ride.  Note that if this
        // node is the last attr and there is text after it, transferText will move the text
        // to a potential previous attr.  This is an invalid state for a short period of time.
        // I need to move this text away here so that when I walk the tree next, *all* curs
        // embedded in this node or deeper will be moved off this node.

        if (x._cchAfter > 0) {
            transferChars(x, x.posAfter(), x.getDenormal(0), x.posTemp(), x._cchAfter);
        }

        assert x._cchAfter == 0;

        // Walk the node tree, moving curs out, disconnecting users and relocating to a, possibly,
        // new locale.  I embed the cursors in this locale before itersting to just cause the
        // embed to happen once.

        x._locale.embedCurs();

        for (Xobj y = x; y != null; y = y.walk(x, true)) {
            while (y._embedded != null) {
                y._embedded.moveTo(x.getNormal(x.posAfter()));
            }

            y.disconnectUser();

            if (to != null) {
                y._locale = to._locale;
            }
        }

        // Now, actually remove the node

        x.removeXobj();

        // Now, if there is a destination, insert the node there and shuffle the text in the
        // vicinity of the destination appropriately.

        if (to != null) {
            // To know where I should insert/append the node to move, I need to see where "to"
            // would be if there were no text after it.  However, I need to keep "to" where it
            // is when I move the text after it later.

            Xobj here = to._xobj;
            boolean append = to._pos != 0;

            int cchRight = to.cchRight();

            if (cchRight > 0) {
                to.push();
                to.next();
                here = to._xobj;
                append = to._pos != 0;
                to.pop();
            }

            if (append) {
                here.appendXobj(x);
            } else {
                here.insertXobj(x);
            }

            // The only text I need to move is that to the right of "to".  Even considering all
            // the cases where an attribute is involed!

            if (cchRight > 0) {
                transferChars(to._xobj, to._pos, x, x.posAfter(), cchRight);
            }

            to.moveTo(x);
        }
    }

    void moveNodeContents(Cur to, boolean moveAttrs) {
        assert _pos == 0;
        assert to == null || !to.isRoot();

        // By calling this helper, I do not have to deal with this Cur any longer.  Basically,
        // this Cur is out of the picture, it behaves like any other cur at this point.

        moveNodeContents(_xobj, to, moveAttrs);
    }

    static void moveNodeContents(Xobj x, Cur to, boolean moveAttrs) {
        // TODO - should assert that is an attr is being moved, it is ok there

        assert to == null || !to.isRoot();

        // Collect a bit of information about the contents to move first.  Note that the collection
        // of this info must not cause a vacant value to become occupied.

        boolean hasAttrs = x.hasAttrs();
        boolean noSubNodesToMove = !x.hasChildren() && (!moveAttrs || !hasAttrs);

        // Deal with the cases where only text is involved in the move

        if (noSubNodesToMove) {
            // If we're vacant and there is no place to move a potential value, then I can avoid
            // acquiring the text from the TypeStoreUser.  Otherwise, there may be text here I
            // need to move somewhere else.

            if (x.isVacant() && to == null) {
                x.clearBit(Xobj.VACANT);

                x.invalidateUser();
                x.invalidateSpecialAttr(null);
                x._locale._versionAll++;
            } else if (x.hasTextEnsureOccupancy()) {
                Cur c = x.tempCur();
                c.next();
                c.moveChars(to, -1);
                c.release();
            }

            return;
        }

        // Here I check to see if "to" is just inside x.  In this case this is a no-op.  Note that
        // the value of x may still be vacant.

        if (to != null) {
            // Quick check of the right edge.  If it is there, I need to move "to" to the left edge
            // so that it is positioned at the beginning of the "moved" content.

            if (x == to._xobj && to._pos == END_POS) {
                // TODO - shuffle interior curs?

                to.moveTo(x);
                to.next(moveAttrs && hasAttrs);

                return;
            }

            // Here I need to see if to is at the left edge.  I push to's current position and
            // then navigate it to the left edge then compare it to the pushed position...
            // Note: gotta be careful to make sure to and x are not in different locales, curs
            // may not go to a different locale.

            boolean isAtLeftEdge = false;

            if (to._locale == x._locale) {
                to.push();
                to.moveTo(x);
                to.next(moveAttrs && hasAttrs);
                isAtLeftEdge = to.isAtLastPush();
                to.pop();
            }

            // TODO - shuffle interior curs?

            if (isAtLeftEdge) {
                return;
            }

            // Now, after dealing with the edge condition, I can assert that to is not inside x

            assert !x.contains(to);

            // So, at this point, I've taken case of the no-op cases and the movement of just text.
            // Also, to must be occupied because I took care of the text only and nothing to move
            // cases.

            assert to.getParent().isOccupied();
        }

        // TODO - did I forget to put a changeNotification here?  Look more closely ...

        // Deal with the value text of x which is either on x or the last attribute of x.
        // I need to get it out of the way to properly deal with the walk of the contents.
        // In order to reposition "to" properly later, I need to record how many chars were moved.

        int valueMovedCch = 0;

        if (x.hasTextNoEnsureOccupancy()) {
            Cur c = x.tempCur();
            c.next();
            c.moveChars(to, -1);
            c.release();

            if (to != null) {
                to.nextChars(valueMovedCch = c._cchSrc);
            }
        }

        // Now, walk all the contents, invalidating special attrs, reportioning cursors,
        // disconnecting users and relocating to a potentially different locale.  Because I moved
        // the value text above, no top level attrs should have any text.

        x._locale.embedCurs();

        Xobj firstToMove = x.walk(x, true);
        boolean sawBookmark = false;

        for (Xobj y = firstToMove; y != null; y = y.walk(x, true)) {
            if (y._parent == x && y.isAttr()) {
                assert y._cchAfter == 0;

                if (!moveAttrs) {
                    firstToMove = y._nextSibling;
                    continue;
                }

                y.invalidateSpecialAttr(to == null ? null : to.getParent());
            }

            for (Cur c; (c = y._embedded) != null; ) {
                c.moveTo(x, END_POS);
            }

            y.disconnectUser();

            if (to != null) {
                y._locale = to._locale;
            }

            sawBookmark = sawBookmark || y._bookmarks != null;
        }

        Xobj lastToMove = x._lastChild;

        // If there were any bookmarks in the tree to remove, to preserve the content that these
        // bookmarks reference, move the contents to a new root.  Note that I already moved the
        // first piece of text above elsewhere.  Note: this has the effect of keeping all of the
        // contents alive even if there is one bookmark deep into the tree.  I should really
        // disband all the content, except for the pieces which are bookmarked.

        Cur surragateTo = null;

        if (sawBookmark && to == null) {
            surragateTo = to = x._locale.tempCur();
            to.createRoot();
            to.next();
        }

        // Perform the rest of the invalidations.  If only attrs are moving, then no user
        // invalidation needed.  If I've move text to "to" already, no need to invalidate
        // again.

        if (!lastToMove.isAttr()) {
            x.invalidateUser();
        }

        x._locale._versionAll++;
        x._locale._versionSansText++;

        if (to != null && valueMovedCch == 0) {
            to.getParent().invalidateUser();
            to._locale._versionAll++;
            to._locale._versionSansText++;
        }

        // Remove the children and, if needed, move them

        x.removeXobjs(firstToMove, lastToMove);

        if (to != null) {
            // To know where I should insert/append the contents to move, I need to see where "to"
            // would be if there were no text after it.

            Xobj here = to._xobj;
            boolean append = to._pos != 0;

            int cchRight = to.cchRight();

            if (cchRight > 0) {
                to.push();
                to.next();
                here = to._xobj;
                append = to._pos != 0;
                to.pop();
            }

            // Now, I have to shuffle the text around "to" in special ways.  A complication is
            // the insertion of attributes.  First, if I'm inserting attrs here then, logically,
            // there can be no text to the left because attrs can only live after another attr
            // or just inside a container.  So, If attrs are being inserted and there is value
            // text on the target container, I will need to move this value text to be after
            // the lew last attribute.  Note that this value text may already live on a current
            // last attr (before the inserting).  Also, I need to figure this all out before I
            // move the text after "to" because this text may end up being sent to the same place
            // as the containers value text when the last new node being inserted is an attr!
            // Whew!

            if (firstToMove.isAttr()) {
                Xobj lastNewAttr = firstToMove;

                while (lastNewAttr._nextSibling != null && lastNewAttr._nextSibling.isAttr()) {
                    lastNewAttr = lastNewAttr._nextSibling;
                }

                // Get to's parnet now before I potentially move him with the next transfer

                Xobj y = to.getParent();

                if (cchRight > 0) {
                    transferChars(to._xobj, to._pos, lastNewAttr, lastNewAttr.posMax(), cchRight);
                }

                if (y.hasTextNoEnsureOccupancy()) {
                    int p, cch;

                    if (y._cchValue > 0) {
                        p = 1;
                        cch = y._cchValue;
                    } else {
                        y = y.lastAttr();
                        assert (y != null);
                        p = y.posAfter();
                        cch = y._cchAfter;
                    }

                    transferChars(y, p, lastNewAttr, lastNewAttr.posAfter(), cch);
                }
            } else if (cchRight > 0) {
                transferChars(to._xobj, to._pos, lastToMove, lastToMove.posMax(), cchRight);
            }

            // After mucking with the text, splice the new tree in

            if (append) {
                here.appendXobjs(firstToMove, lastToMove);
            } else {
                here.insertXobjs(firstToMove, lastToMove);
            }

            // Position "to" to be at the beginning of the newly inserted contents

            to.moveTo(firstToMove);
            to.prevChars(valueMovedCch);
        }

        // If I consed up a to, release it here

        if (surragateTo != null) {
            surragateTo.release();
        }
    }

    protected final Bookmark setBookmark(Object key, Object value) {
        assert isNormal();
        assert key != null;

        return _xobj.setBookmark(_pos, key, value);
    }

    Object getBookmark(Object key) {
        assert isNormal();
        assert key != null;

        for (Bookmark b = _xobj._bookmarks; b != null; b = b._next) {
            if (b._pos == _pos && b._key == key) {
                return b._value;
            }
        }

        return null;
    }

    int firstBookmarkInChars(Object key, int cch) {
        assert isNormal();
        assert key != null;
        assert cch > 0;
        assert cch <= cchRight();

        int d = -1;

        if (isText()) {
            for (Bookmark b = _xobj._bookmarks; b != null; b = b._next) {
                if (b._key == key && inChars(b, cch, false)) {
                    d = (d == -1 || b._pos - _pos < d) ? b._pos - _pos : d;
                }
            }
        }

        return d;
    }

    int firstBookmarkInCharsLeft(Object key, int cch) {
        assert isNormal();
        assert key != null;
        assert cch > 0;
        assert cch <= cchLeft();

        int d = -1;

        if (cchLeft() > 0) {
            Xobj x = getDenormal();
            int p = _posTemp - cch;

            for (Bookmark b = x._bookmarks; b != null; b = b._next) {
                if (b._key == key && x.inChars(p, b._xobj, b._pos, cch, false)) {
                    d = (d == -1 || b._pos - p < d) ? b._pos - p : d;
                }
            }
        }

        return d;
    }

    String getCharsAsString() {
        assert isNormal() && _xobj != null;

        return getCharsAsString(WS_PRESERVE);
    }

    String getCharsAsString(int wsr) {
        return _xobj.getCharsAsString(_pos, -1, wsr);
    }

    String getValueAsString(int wsr) {
        assert isNode();

        return _xobj.getValueAsString(wsr);
    }

    String getValueAsString() {
        assert isNode();
        assert !hasChildren();

        return _xobj.getValueAsString();
    }

    Object getChars(int cch) {
        assert isPositioned();

        return _xobj.getChars(_pos, cch, this);
    }

    Object getFirstChars() {
        assert isNode();

        Object src = _xobj.getFirstChars();

        _offSrc = _locale._offSrc;
        _cchSrc = _locale._cchSrc;

        return src;
    }

    void copyNode(Cur to) {
        assert to != null;
        assert isNode();

        Xobj copy = _xobj.copyNode(to._locale);

        // TODO - in the moveNode case, I would not have to walk the tree for cursors ... optimize

        if (to.isPositioned()) {
            Cur.moveNode(copy, to);
        } else {
            to.moveTo(copy);
        }
    }

    public Cur weakCur(Object o) {
        Cur c = _locale.weakCur(o);
        c.moveToCur(this);
        return c;
    }

    Cur tempCur() {
        String id = null;
        Cur c = _locale.tempCur(id);
        c.moveToCur(this);
        return c;
    }

    private Cur tempCur(Xobj x, int p) {
        assert (x == null || _locale == x._locale);
        assert (x != null || p == NO_POS);

        Cur c = _locale.tempCur();

        if (x != null) {
            c.moveTo(getNormal(x, p), _posTemp);
        }

        return c;
    }

    // Is a cursor (c) in the chars defined by cch chars after where this Cur is positioned.
    // Is inclusive on the left, and inclusive/exclusive on the right depending on the value
    // of includeEnd.

    boolean inChars(Cur c, int cch, boolean includeEnd) {
        assert isPositioned() && isText() && cchRight() >= cch;
        assert c.isNormal();

        return _xobj.inChars(_pos, c._xobj, c._pos, cch, includeEnd);
    }

    boolean inChars(Bookmark b, int cch, boolean includeEnd) {
        assert isPositioned() && isText() && cchRight() >= cch;
        assert b._xobj.isNormal(b._pos);

        return _xobj.inChars(_pos, b._xobj, b._pos, cch, includeEnd);
    }

    // Can't be static because I need to communicate pos in _posTemp :-(
    // I wish I had multiple return vars ...

    private Xobj getNormal(Xobj x, int p) {
        Xobj nx = x.getNormal(p);
        _posTemp = x._locale._posTemp;
        return nx;
    }

    private Xobj getDenormal() {
        assert isPositioned();

        return getDenormal(_xobj, _pos);
    }

    private Xobj getDenormal(Xobj x, int p) {
        Xobj dx = x.getDenormal(p);
        _posTemp = x._locale._posTemp;
        return dx;
    }

    // May throw IllegalArgumentException if can't change the type

    void setType(SchemaType type) {
        setType(type, true);
    }

    void setType(SchemaType type, boolean complain) {
        assert type != null;
        assert isUserNode();

        TypeStoreUser user = peekUser();

        if (user != null && user.get_schema_type() == type) {
            return;
        }

        if (isRoot()) {
            _xobj.setStableType(type);
            return;
        }

        // Gotta get the parent user to make sure this type is ok here

        TypeStoreUser parentUser = _xobj.ensureParent().getUser();

        // One may only set the type of an attribute to its 'natural' type because
        // attributes cannot take advantage of the xsiType attribute.

        if (isAttr()) {
            if (complain && parentUser.get_attribute_type(getName()) != type) {
                throw new IllegalArgumentException("Can't set type of attribute to " + type.toString());
            }

            return;
        }

        assert isElem();

        // First check to see if this type can be here sans xsi:type.
        // If so, make sure there is no xsi:type

        if (parentUser.get_element_type(getName(), null) == type) {
            removeAttr(Locale._xsiType);
            return;
        }

        // If the desired type has no name, then it cannot be
        // referenced via xsi:type

        QName typeName = type.getName();

        if (typeName == null) {
            if (complain) {
                throw new IllegalArgumentException("Can't set type of element, type is un-named");
            } else {
                return;
            }
        }

        // See if setting xsiType would result in the target type

        if (parentUser.get_element_type(getName(), typeName) != type) {
            if (complain) {
                throw new IllegalArgumentException("Can't set type of element, invalid type");
            } else {
                return;
            }
        }

        setAttrValueAsQName(typeName);
    }

    void setSubstitution(QName name, SchemaType type) {
        assert name != null;
        assert type != null;
        assert isUserNode();

        TypeStoreUser user = peekUser();

        if (user != null && user.get_schema_type() == type && name.equals(getName())) {
            return;
        }

        if (isRoot()) {
            // If this is the root node, we can't set its name, so the whole
            // operation is aborted
            return;
        }

        // Gotta get the parent user to make sure this type is ok here

        TypeStoreUser parentUser = _xobj.ensureParent().getUser();

        // One may only set the type of an attribute to its 'natural' type because
        // attributes cannot take advantage of the xsiType attribute.

        if (isAttr()) {
            // Can't use substitution with attributes
            return;
        }

        assert isElem();

        // First check to see if this type can be here sans xsi:type.
        // If so, make sure there is no xsi:type

        if (parentUser.get_element_type(name, null) == type) {
            setName(name);
            removeAttr(Locale._xsiType);
            return;
        }

        // If the desired type has no name, then it cannot be
        // referenced via xsi:type

        QName typeName = type.getName();

        if (typeName == null) {
            // Can't set xsi:type on element, type is un-named
            return;
        }

        // See if setting xsiType would result in the target type

        if (parentUser.get_element_type(name, typeName) != type) {
            // Can't set xsi:type on element, invalid type
            return;
        }

        setName(name);
        setAttrValueAsQName(typeName);
    }

    TypeStoreUser peekUser() {
        assert isUserNode();

        return _xobj._user;
    }

    public XmlObject getObject() {
        return isUserNode() ? (XmlObject) getUser() : null;
    }

    TypeStoreUser getUser() {
        assert isUserNode();

        return _xobj.getUser();
    }

    public Dom getDom() {
        assert isNormal();
        assert isPositioned();

        if (isText()) {
            int cch = cchLeft();

            for (CharNode cn = getCharNodes(); ; cn = cn._next) {
                if ((cch -= cn._cch) < 0) {
                    return cn;
                }
            }
        }

        return _xobj.getDom();
    }

    public void release() {
        if (_tempFrame >= 0) {
            if (_nextTemp != null) {
                _nextTemp._prevTemp = _prevTemp;
            }

            if (_prevTemp == null) {
                _locale._tempFrames[_tempFrame] = _nextTemp;
            } else {
                _prevTemp._nextTemp = _nextTemp;
            }

            _prevTemp = _nextTemp = null;
            _tempFrame = -1;
        }

        if (_state != POOLED && _state != DISPOSED) {
            // Clean up any state

            while (_stackTop != -1) {
                popButStay();
            }

            clearSelection();

            _id = null;

            // Unposition

            moveToCur(null);

            assert isNormal();

            assert _xobj == null;
            assert _pos == NO_POS;

            // Release weak reference and attacked value

            if (_ref != null) {
                _ref.clear();
                _ref._cur = null;
            }

            _ref = null;

            // Unregister and either diapose of cursor or add it back to pool

            assert _state == REGISTERED;
            _locale._registered = listRemove(_locale._registered);

            if (_locale._curPoolCount < 16) {
                _locale._curPool = listInsert(_locale._curPool);
                _state = POOLED;
                _locale._curPoolCount++;
            } else {
                _locale = null;
                _state = DISPOSED;
            }
        }
    }

    boolean isOnList(Cur head) {
        for (; head != null; head = head._next) {
            if (head == this) {
                return true;
            }
        }

        return false;
    }

    Cur listInsert(Cur head) {
        assert _next == null && _prev == null;

        if (head == null) {
            head = _prev = this;
        } else {
            _prev = head._prev;
            head._prev = head._prev._next = this;
        }

        return head;
    }

    Cur listRemove(Cur head) {
        assert _prev != null && isOnList(head);

        if (_prev == this) {
            head = null;
        } else {
            if (head == this) {
                head = _next;
            } else {
                _prev._next = _next;
            }

            if (_next == null) {
                if (head != null) {
                    head._prev = _prev;
                }
            } else {
                _next._prev = _prev;
                _next = null;
            }
        }

        _prev = null;
        assert _next == null;

        return head;
    }

//    boolean isNormal ( Cur that )
//    {
//        return isNormal() && (that == null || (_locale == that._locale && that.isNormal()));
//    }

    boolean isNormal() {
        if (_state == POOLED || _state == DISPOSED) {
            return false;
        }

        if (_xobj == null) {
            return _pos == NO_POS;
        }

        if (!_xobj.isNormal(_pos)) {
            return false;
        }

        if (_state == EMBEDDED) {
            return isOnList(_xobj._embedded);
        }

        assert _state == REGISTERED;

        return isOnList(_locale._registered);
    }

    public static final class CurLoadContext extends LoadContext {
        public CurLoadContext(Locale l, XmlOptions options) {
            options = XmlOptions.maskNull(options);

            _locale = l;

            _charUtil =
                options.isLoadUseLocaleCharUtil()
                    ? _locale.getCharUtil()
                    : CharUtil.getThreadLocalCharUtil();

            _frontier = createDomDocumentRootXobj(_locale);
            _after = false;

            _lastXobj = _frontier;
            _lastPos = 0;

            _replaceDocElem = options.getLoadReplaceDocumentElement();
            _discardDocElem = options.hasOption(XmlOptions.XmlOptionsKeys.LOAD_REPLACE_DOCUMENT_ELEMENT);

            _stripWhitespace = options.isSetLoadStripWhitespace();
            _stripComments = options.isLoadStripComments();
            _stripProcinsts = options.isLoadStripProcinsts();

            _substituteNamespaces = options.getLoadSubstituteNamespaces();
            _additionalNamespaces = options.getLoadAdditionalNamespaces();

            _locale._versionAll++;
            _locale._versionSansText++;
        }

        //
        // Really primitive load context operations
        //

        private void start(Xobj xo) {
            assert _frontier != null;
            assert !_after || _frontier._parent != null;

            flushText();

            if (_after) {
                _frontier = _frontier._parent;
                _after = false;
            }

            _frontier.appendXobj(xo);
            _frontier = xo;

            _lastXobj = xo;
            _lastPos = 0;
        }

        private void end() {
            assert _frontier != null;
            assert !_after || _frontier._parent != null;

            flushText();

            if (_after) {
                _frontier = _frontier._parent;
            } else {
                _after = true;
            }

            _lastXobj = _frontier;
            _lastPos = END_POS;
        }

        private void text(Object src, int off, int cch) {
            if (cch <= 0) {
                return;
            }

            _lastXobj = _frontier;
            _lastPos = _frontier._cchValue + 1;

            if (_after) {
                _lastPos += _frontier._cchAfter + 1;

                _frontier._srcAfter =
                    _charUtil.saveChars(
                        src, off, cch,
                        _frontier._srcAfter, _frontier._offAfter, _frontier._cchAfter);

                _frontier._offAfter = _charUtil._offSrc;
                _frontier._cchAfter = _charUtil._cchSrc;

            } else {
                _frontier._srcValue =
                    _charUtil.saveChars(
                        src, off, cch,
                        _frontier._srcValue, _frontier._offValue, _frontier._cchValue);

                _frontier._offValue = _charUtil._offSrc;
                _frontier._cchValue = _charUtil._cchSrc;
            }
        }

        private void flushText() {
            if (_stripWhitespace) {
                if (_after) {
                    _frontier._srcAfter =
                        _charUtil.stripRight(
                            _frontier._srcAfter, _frontier._offAfter, _frontier._cchAfter);

                    _frontier._offAfter = _charUtil._offSrc;
                    _frontier._cchAfter = _charUtil._cchSrc;
                } else {
                    _frontier._srcValue =
                        _charUtil.stripRight(
                            _frontier._srcValue, _frontier._offValue, _frontier._cchValue);

                    _frontier._offValue = _charUtil._offSrc;
                    _frontier._cchValue = _charUtil._cchSrc;
                }
            }
        }

        private Xobj parent() {
            return _after ? _frontier._parent : _frontier;
        }

        private QName checkName(QName name, boolean local) {
            if (_substituteNamespaces != null && (!local || name.getNamespaceURI().length() > 0)) {
                String substituteUri = _substituteNamespaces.get(name.getNamespaceURI());

                if (substituteUri != null) {
                    name = _locale.makeQName(substituteUri, name.getLocalPart(), name.getPrefix());
                }
            }

            return name;
        }

        protected void startDTD(String name, String publicId, String systemId) {
            _doctypeName = name;
            _doctypePublicId = publicId;
            _doctypeSystemId = systemId;
        }

        protected void endDTD() {
        }

        protected void startElement(QName name) {
            start(createElementXobj(_locale, checkName(name, false), parent()._name));
            _stripLeft = true;
        }

        protected void endElement() {
            assert parent().isElem();

            end();
            _stripLeft = true;
        }

        protected void xmlns(String prefix, String uri) {
            assert parent().isContainer();
            // BUGBUG - should assert there that there is no text before this attr

            // Namespace attrs are different to regular attrs -- I don't change their name,
            // I change their value!

            if (_substituteNamespaces != null) {
                String substituteUri = _substituteNamespaces.get(uri);

                if (substituteUri != null) {
                    uri = substituteUri;
                }
            }

            Xobj x = new AttrXobj(_locale, _locale.createXmlns(prefix));

            start(x);
            text(uri, 0, uri.length());
            end();

            _lastXobj = x;
            _lastPos = 0;
        }

        public void attr(QName name, String value) {
            assert parent().isContainer();
            // BUGBUG - should assert there that there is no text before this attr

            QName parentName = _after ?
                _lastXobj._parent.getQName() : _lastXobj.getQName();
            boolean isId = isAttrOfTypeId(name, parentName);

            Xobj x = isId ?
                new AttrIdXobj(_locale, checkName(name, true)) :
                new AttrXobj(_locale, checkName(name, true));
            start(x);
            text(value, 0, value.length());
            end();
            if (isId) {
                _locale.enter();
                Cur c1 = x.tempCur();
                c1.toRoot();
                Xobj doc = c1._xobj;
                c1.release();
                _locale.exit();
                if (doc instanceof DocumentXobj) {
                    ((DocumentXobj) doc).addIdElement(value,
                        x._parent.getDom());
                }
            }
            _lastXobj = x;
            _lastPos = 0;
        }

        protected void attr(String local, String uri, String prefix, String value) {
            attr(_locale.makeQName(uri, local, prefix), value);
        }

        protected void procInst(String target, String value) {
            if (!_stripProcinsts) {
                Xobj x = new ProcInstXobj(_locale, target);

                start(x);
                text(value, 0, value.length());
                end();

                _lastXobj = x;
                _lastPos = 0;
            }
            _stripLeft = true;
        }

        protected void comment(String comment) {
            if (!_stripComments) {
                comment(comment, 0, comment.length());
            }
            _stripLeft = true;
        }

        protected void comment(char[] chars, int off, int cch) {
            if (!_stripComments) {
                comment(
                    _charUtil.saveChars(chars, off, cch),
                    _charUtil._offSrc, _charUtil._cchSrc);
            }
            _stripLeft = true;
        }

        private void comment(Object src, int off, int cch) {
            Xobj x = new CommentXobj(_locale);

            start(x);
            text(src, off, cch);
            end();

            _lastXobj = x;
            _lastPos = 0;
        }

        private boolean _stripLeft = true;

        private void stripText(Object src, int off, int cch) {
            if (_stripWhitespace) {
                // this is to avoid bug in cases like <company>Procter &amp; Gamble</company>
                if (_stripLeft) {
                    src = _charUtil.stripLeft(src, off, cch);
                    _stripLeft = false;
                    off = _charUtil._offSrc;
                    cch = _charUtil._cchSrc;
                }
            }

            text(src, off, cch);
        }

        protected void text(String s) {
            if (s == null) {
                return;
            }

            stripText(s, 0, s.length());
        }

        protected void text(char[] src, int off, int cch) {
            stripText(src, off, cch);
        }

        protected void bookmark(XmlBookmark bm) {
            _lastXobj.setBookmark(_lastPos, bm.getKey(), bm);
        }

        protected void bookmarkLastNonAttr(XmlBookmark bm) {
            if (_lastPos > 0 || !_lastXobj.isAttr()) {
                _lastXobj.setBookmark(_lastPos, bm.getKey(), bm);
            } else {
                assert _lastXobj._parent != null;

                _lastXobj._parent.setBookmark(0, bm.getKey(), bm);
            }
        }

        protected void bookmarkLastAttr(QName attrName, XmlBookmark bm) {
            if (_lastPos == 0 && _lastXobj.isAttr()) {
                assert _lastXobj._parent != null;

                Xobj a = _lastXobj._parent.getAttr(attrName);

                if (a != null) {
                    a.setBookmark(0, bm.getKey(), bm);
                }
            }
        }

        protected void lineNumber(int line, int column, int offset) {
            _lastXobj.setBookmark(
                _lastPos,
                XmlLineNumber.class,
                new XmlLineNumber(line, column, offset));
        }

        protected void abort() {
            _stripLeft = true;
            while (!parent().isRoot()) {
                end();
            }

            finish().release();
        }

        public Cur finish() {
            flushText();

            if (_after) {
                _frontier = _frontier._parent;
            }

            assert _frontier != null && _frontier._parent == null && _frontier.isRoot();

            Cur c = _frontier.tempCur();

            if (!Locale.toFirstChildElement(c)) {
                return c;
            }

            // See if the document element is a fragment

            boolean isFrag = Locale.isFragmentQName(c.getName());

            if (_discardDocElem || isFrag) {
                if (_replaceDocElem != null) {
                    c.setName(_replaceDocElem);
                } else {
                    // Remove the content around the element to remove so that that content
                    // does not appear to have been the contents of the removed element.

                    //noinspection StatementWithEmptyBody
                    while (c.toParent()) {
                    }

                    c.next();

                    while (!c.isElem()) {
                        if (c.isText()) {
                            c.moveChars(null, -1);
                        } else {
                            c.moveNode(null);
                        }
                    }

                    assert c.isElem();
                    c.skip();

                    while (!c.isFinish()) {
                        if (c.isText()) {
                            c.moveChars(null, -1);
                        } else {
                            c.moveNode(null);
                        }
                    }

                    c.toParent();

                    c.next();

                    assert c.isElem();

                    Cur c2 = c.tempCur();

                    c.moveNodeContents(c, true);

                    c.moveToCur(c2);

                    c2.release();

                    c.moveNode(null);
                }

                // Remove the fragment namespace decl

                if (isFrag) {
                    c.moveTo(_frontier);

                    if (c.toFirstAttr()) {
                        for (; ; ) {
                            if (c.isXmlns() && c.getXmlnsUri().equals(Locale._openFragUri)) {
                                c.moveNode(null);

                                if (!c.isAttr()) {
                                    break;
                                }
                            } else if (!c.toNextAttr()) {
                                break;
                            }
                        }
                    }

                    c.moveTo(_frontier);
                    _frontier = createDomDocumentRootXobj(_locale, true);

                    Cur c2 = _frontier.tempCur();
                    c2.next();
                    c.moveNodeContents(c2, true);
                    c.moveTo(_frontier);
                    c2.release();
                }
            }


            if (_additionalNamespaces != null) {
                c.moveTo(_frontier);
                Locale.toFirstChildElement(c);
                Locale.applyNamespaces(c, _additionalNamespaces);
            }

            if (_doctypeName != null && (_doctypePublicId != null || _doctypeSystemId != null)) {
                XmlDocumentProperties props = Locale.getDocProps(c, true);
                props.setDoctypeName(_doctypeName);
                if (_doctypePublicId != null) {
                    props.setDoctypePublicId(_doctypePublicId);
                }
                if (_doctypeSystemId != null) {
                    props.setDoctypeSystemId(_doctypeSystemId);
                }
            }

            c.moveTo(_frontier);

            assert c.isRoot();

            return c;
        }

        public void dump() {
            _frontier.dump();
        }

        private final Locale _locale;
        private final CharUtil _charUtil;

        private Xobj _frontier;
        private boolean _after;

        private Xobj _lastXobj;
        private int _lastPos;

        private final boolean _discardDocElem;
        private final QName _replaceDocElem;
        private final boolean _stripWhitespace;
        private final boolean _stripComments;
        private final boolean _stripProcinsts;
        private final Map<String, String> _substituteNamespaces;
        private final Map<String, String> _additionalNamespaces;

        private String _doctypeName;
        private String _doctypePublicId;
        private String _doctypeSystemId;
    }

    static String kindName(int kind) {
        switch (kind) {
            case ROOT:
                return "ROOT";
            case ELEM:
                return "ELEM";
            case ATTR:
                return "ATTR";
            case COMMENT:
                return "COMMENT";
            case PROCINST:
                return "PROCINST";
            case TEXT:
                return "TEXT";
            default:
                return "<< Unknown Kind (" + kind + ") >>";
        }
    }

    void dump() {
        dump(System.out, _xobj, this);
    }

    void dump(PrintStream o) {
        if (_xobj == null) {
            o.println("Unpositioned xptr");
            return;
        }

        dump(o, _xobj, this);
    }

    public static void dump(PrintStream o, Xobj xo, Object ref) {
        if (ref == null) {
            ref = xo;
        }

        while (xo._parent != null) {
            xo = xo._parent;
        }

        dumpXobj(o, xo, 0, ref);

        o.println();
    }

    private static void dumpCur(PrintStream o, String prefix, Cur c, Object ref) {
        o.print(" ");

        if (ref == c) {
            o.print("*:");
        }

        o.print(prefix + (c._id == null ? "<cur>" : c._id) + "[" + c._pos + "]");
    }

    private static void dumpCurs(PrintStream o, Xobj xo, Object ref) {
        for (Cur c = xo._embedded; c != null; c = c._next) {
            dumpCur(o, "E:", c, ref);
        }

        for (Cur c = xo._locale._registered; c != null; c = c._next) {
            if (c._xobj == xo) {
                dumpCur(o, "R:", c, ref);
            }
        }
    }

    private static void dumpBookmarks(PrintStream o, Xobj xo, Object ref) {
        for (Bookmark b = xo._bookmarks; b != null; b = b._next) {
            o.print(" ");

            if (ref == b) {
                o.print("*:");
            }

            if (b._value instanceof XmlLineNumber) {
                XmlLineNumber l = (XmlLineNumber) b._value;
                o.print("<line:" + l.getLine() + ">" + "[" + b._pos + "]");
            } else {
                o.print("<mark>" + "[" + b._pos + "]");
            }
        }
    }

    private static void dumpCharNodes(PrintStream o, CharNode nodes, Object ref) {
        for (CharNode n = nodes; n != null; n = n._next) {
            o.print(" ");

            if (n == ref) {
                o.print("*");
            }

            o.print((n instanceof TextNode ? "TEXT" : "CDATA") + "[" + n._cch + "]");
        }
    }

    private static void dumpChars(PrintStream o, Object src, int off, int cch) {

        o.print("\"");

        String s = CharUtil.getString(src, off, cch);

        for (int i = 0; i < s.length(); ) {
            if (i == 36) {
                o.print("...");
                break;
            }

            int codePoint = s.codePointAt(i);
            char[] chars = Character.toChars(codePoint);

            if (chars.length == 1) {
                char ch = chars[0];
                if (ch >= 32 && ch < 127 && ch != '\"') {
                    o.print(ch);
                } else if (ch == '\n') {
                    o.print("\\n");
                } else if (ch == '\r') {
                    o.print("\\r");
                } else if (ch == '\t') {
                    o.print("\\t");
                } else if (ch == '\"') {
                    o.print("\\\"");
                } else {
                    o.print("<#" + ((int) ch) + ">");
                }
            } else {
                o.print("<#" + codePoint + ">");
            }

            i += Character.charCount(codePoint);
        }

        o.print("\"");
    }

    private static void dumpXobj(PrintStream o, Xobj xo, int level, Object ref) {
        if (xo == null) {
            return;
        }

        if (xo == ref) {
            o.print("* ");
        } else {
            o.print("  ");
        }

        for (int i = 0; i < level; i++) {
            o.print("  ");
        }

        o.print(kindName(xo.kind()));

        if (xo._name != null) {
            o.print(" ");

            if (xo._name.getPrefix().length() > 0) {
                o.print(xo._name.getPrefix() + ":");
            }

            o.print(xo._name.getLocalPart());

            if (xo._name.getNamespaceURI().length() > 0) {
                o.print("@" + xo._name.getNamespaceURI());
            }
        }

        if (xo._srcValue != null || xo._charNodesValue != null) {
            o.print(" Value( ");
            dumpChars(o, xo._srcValue, xo._offValue, xo._cchValue);
            dumpCharNodes(o, xo._charNodesValue, ref);
            o.print(" )");
        }

        if (xo._user != null) {
            o.print(" (USER)");
        }

        if (xo.isVacant()) {
            o.print(" (VACANT)");
        }

        if (xo._srcAfter != null || xo._charNodesAfter != null) {
            o.print(" After( ");
            dumpChars(o, xo._srcAfter, xo._offAfter, xo._cchAfter);
            dumpCharNodes(o, xo._charNodesAfter, ref);
            o.print(" )");
        }

        dumpCurs(o, xo, ref);
        dumpBookmarks(o, xo, ref);

        String className = xo.getClass().getName();

        int i = className.lastIndexOf('.');

        if (i > 0) {
            className = className.substring(i + 1);

            i = className.lastIndexOf('$');

            if (i > 0) {
                className = className.substring(i + 1);
            }
        }

        o.print(" (");
        o.print(className);
        o.print(")");

        o.println();

        for (xo = xo._firstChild; xo != null; xo = xo._nextSibling) {
            dumpXobj(o, xo, level + 1, ref);
        }
    }

    void setId(String id) {
        _id = id;
    }

    public Locale getLocale() {
        return _locale;
    }
}
