| /* 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; |
| |
| // DOM Level 3 |
| |
| |
| 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; |
| |
| Cur(Locale l) { |
| _locale = l; |
| _pos = NO_POS; |
| |
| _tempFrame = -1; |
| |
| _state = POOLED; |
| |
| _stackTop = Locations.NULL; |
| _selectionFirst = -1; |
| _selectionN = -1; |
| _selectionLoc = Locations.NULL; |
| _selectionCount = 0; |
| } |
| |
| 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; |
| } |
| |
| int kind() { |
| assert isPositioned(); |
| int kind = _xobj.kind(); |
| return _pos == 0 ? kind : (_pos == END_POS ? -kind : TEXT); |
| } |
| |
| boolean isRoot() { |
| assert isPositioned(); |
| return _pos == 0 && _xobj.kind() == ROOT; |
| } |
| |
| boolean isElem() { |
| assert isPositioned(); |
| return _pos == 0 && _xobj.kind() == ELEM; |
| } |
| |
| boolean isAttr() { |
| assert isPositioned(); |
| return _pos == 0 && _xobj.kind() == ATTR; |
| } |
| |
| boolean isComment() { |
| assert isPositioned(); |
| return _pos == 0 && _xobj.kind() == COMMENT; |
| } |
| |
| boolean isProcinst() { |
| assert isPositioned(); |
| return _pos == 0 && _xobj.kind() == PROCINST; |
| } |
| |
| boolean isText() { |
| assert isPositioned(); |
| return _pos > 0; |
| } |
| |
| boolean isEnd() { |
| assert isPositioned(); |
| return _pos == END_POS && _xobj.kind() == ELEM; |
| } |
| |
| boolean isEndRoot() { |
| assert isPositioned(); |
| return _pos == END_POS && _xobj.kind() == ROOT; |
| } |
| |
| boolean isNode() { |
| assert isPositioned(); |
| return _pos == 0; |
| } |
| |
| boolean isContainer() { |
| assert isPositioned(); |
| return _pos == 0 && kindIsContainer(_xobj.kind()); |
| } |
| |
| boolean isFinish() { |
| assert isPositioned(); |
| return _pos == END_POS && kindIsContainer(_xobj.kind()); |
| } |
| |
| boolean isUserNode() { |
| assert isPositioned(); |
| int k = kind(); |
| return k == ELEM || k == ROOT || (k == ATTR && !isXmlns()); |
| } |
| |
| 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; |
| } |
| |
| boolean isNormalAttr() { |
| return isNode() && _xobj.isNormalAttr(); |
| } |
| |
| boolean isXmlns() { |
| return isNode() && _xobj.isXmlns(); |
| } |
| |
| boolean isTextCData() { |
| return _xobj.hasBookmark(CDataBookmark.class, _pos); |
| } |
| |
| QName getName() { |
| assert isNode() || isEnd(); |
| return _xobj._name; |
| } |
| |
| String getLocal() { |
| return getName().getLocalPart(); |
| } |
| |
| String getUri() { |
| return getName().getNamespaceURI(); |
| } |
| |
| String getXmlnsPrefix() { |
| assert isXmlns(); |
| return _xobj.getXmlnsPrefix(); |
| } |
| |
| String getXmlnsUri() { |
| assert isXmlns(); |
| return _xobj.getXmlnsUri(); |
| } |
| |
| boolean isDomDocRoot() { |
| return isRoot() && _xobj.getDom() instanceof Document; |
| } |
| |
| boolean isDomFragRoot() { |
| return isRoot() && _xobj.getDom() instanceof DocumentFragment; |
| } |
| |
| int cchRight() { |
| assert isPositioned(); |
| return _xobj.cchRight(_pos); |
| } |
| |
| 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 |
| } |
| |
| 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); |
| } |
| |
| boolean isAtEndOfLastPush() { |
| assert _stackTop != Locations.NULL; |
| |
| return _locale._locations.isAtEndOf(_stackTop, this); |
| } |
| |
| 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++; |
| } |
| |
| 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--; |
| } |
| |
| int selectionCount() { |
| return _selectionCount; |
| } |
| |
| void moveToSelection(int i) { |
| assert i >= 0 && i < _selectionCount; |
| |
| _locale._locations.moveTo(selectionIndex(i), this); |
| } |
| |
| void clearSelection() { |
| assert _selectionCount >= 0; |
| |
| while (_selectionCount > 0) { |
| removeFirstSelection(); |
| } |
| } |
| |
| boolean toParent() { |
| return toParent(false); |
| } |
| |
| boolean toParentRaw() { |
| return toParent(true); |
| } |
| |
| Xobj getParent() { |
| return getParent(false); |
| } |
| |
| Xobj getParentRaw() { |
| return getParent(true); |
| } |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| boolean toParent(boolean raw) { |
| Xobj parent = getParent(raw); |
| |
| if (parent == null) |
| return false; |
| |
| moveTo(parent); |
| |
| return true; |
| } |
| |
| 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); |
| } |
| |
| boolean hasText() { |
| assert isNode(); |
| |
| return _xobj.hasTextEnsureOccupancy(); |
| } |
| |
| boolean hasAttrs() { |
| assert isNode(); |
| |
| return _xobj.hasAttrs(); |
| } |
| |
| boolean hasChildren() { |
| assert isNode(); |
| |
| return _xobj.hasChildren(); |
| } |
| |
| boolean toFirstChild() { |
| assert isNode(); |
| |
| if (!_xobj.hasChildren()) |
| return false; |
| |
| for (Xobj x = _xobj._firstChild; ; x = x._nextSibling) { |
| if (!x.isAttr()) { |
| moveTo(x); |
| return true; |
| } |
| } |
| } |
| |
| protected boolean toLastChild() { |
| assert isNode(); |
| |
| if (!_xobj.hasChildren()) |
| return false; |
| |
| moveTo(_xobj._lastChild); |
| |
| return true; |
| } |
| |
| 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; |
| } |
| |
| 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); |
| } |
| |
| void setValue(String value) { |
| assert isNode(); |
| |
| moveNodeContents(null, false); |
| |
| next(); |
| |
| insertString(value); |
| |
| toParent(); |
| } |
| |
| void removeFollowingAttrs() { |
| assert isAttr(); |
| |
| QName attrName = getName(); |
| |
| push(); |
| |
| if (toNextAttr()) { |
| while (isAttr()) { |
| if (getName().equals(attrName)) |
| moveNode(null); |
| else if (!toNextAttr()) |
| break; |
| } |
| } |
| |
| pop(); |
| } |
| |
| String getAttrValue(QName name) { |
| String s = null; |
| |
| push(); |
| |
| if (toAttr(name)) { |
| s = getValueAsString(); |
| } |
| |
| pop(); |
| |
| return s; |
| } |
| |
| 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(); |
| } |
| |
| boolean removeAttr(QName name) { |
| assert isContainer(); |
| |
| return _xobj.removeAttr(name); |
| } |
| |
| void setAttrValue(QName name, String value) { |
| assert isContainer(); |
| |
| _xobj.setAttr(name, value); |
| } |
| |
| boolean toAttr(QName name) { |
| assert isNode(); |
| |
| Xobj a = _xobj.getAttr(name); |
| |
| if (a == null) |
| return false; |
| |
| moveTo(a); |
| |
| return true; |
| } |
| |
| boolean toFirstAttr() { |
| assert isNode(); |
| |
| Xobj firstAttr = _xobj.firstAttr(); |
| |
| if (firstAttr == null) { |
| return false; |
| } |
| |
| moveTo(firstAttr); |
| |
| return true; |
| } |
| |
| boolean toLastAttr() { |
| assert isNode(); |
| |
| if (!toFirstAttr()) { |
| return false; |
| } |
| |
| //noinspection StatementWithEmptyBody |
| while (toNextAttr()) ; |
| |
| return true; |
| } |
| |
| boolean toNextAttr() { |
| assert isAttr() || isContainer(); |
| |
| Xobj nextAttr = _xobj.nextAttr(); |
| |
| if (nextAttr == null) { |
| return false; |
| } |
| |
| moveTo(nextAttr); |
| |
| return true; |
| } |
| |
| @SuppressWarnings("UnusedReturnValue") |
| 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") |
| boolean skipWithAttrs() { |
| assert isNode(); |
| |
| if (skip()) { |
| return true; |
| } |
| |
| if (_xobj.isRoot()) { |
| return false; |
| } |
| |
| assert _xobj.isAttr(); |
| |
| toParent(); |
| |
| next(); |
| |
| return true; |
| } |
| |
| 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; |
| } |
| |
| void toEnd() { |
| assert isNode(); |
| |
| moveTo(_xobj, END_POS); |
| } |
| |
| 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") |
| boolean prevWithAttrs() { |
| if (prev()) { |
| return true; |
| } |
| |
| if (!isAttr()) { |
| return false; |
| } |
| |
| toParent(); |
| |
| return true; |
| } |
| |
| 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") |
| boolean next(boolean withAttrs) { |
| return withAttrs ? nextWithAttrs() : next(); |
| } |
| |
| 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(); |
| } |
| |
| 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(Locale.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); |
| } |
| } |
| |
| 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; |
| } |
| |
| XmlObject getObject() { |
| return isUserNode() ? (XmlObject) getUser() : null; |
| } |
| |
| TypeStoreUser getUser() { |
| assert isUserNode(); |
| |
| return _xobj.getUser(); |
| } |
| |
| 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(); |
| } |
| |
| 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); |
| } |
| |
| static final String LOAD_USE_LOCALE_CHAR_UTIL = "LOAD_USE_LOCALE_CHAR_UTIL"; |
| |
| static final class CurLoadContext extends LoadContext { |
| CurLoadContext(Locale l, XmlOptions options) { |
| options = XmlOptions.maskNull(options); |
| |
| _locale = l; |
| |
| _charUtil = |
| options.hasOption(LOAD_USE_LOCALE_CHAR_UTIL) |
| ? _locale.getCharUtil() |
| : CharUtil.getThreadLocalCharUtil(); |
| |
| _frontier = createDomDocumentRootXobj(_locale); |
| _after = false; |
| |
| _lastXobj = _frontier; |
| _lastPos = 0; |
| |
| if (options.hasOption(XmlOptions.LOAD_REPLACE_DOCUMENT_ELEMENT)) { |
| _replaceDocElem = (QName) options.get(XmlOptions.LOAD_REPLACE_DOCUMENT_ELEMENT); |
| _discardDocElem = true; |
| } |
| |
| _stripWhitespace = options.hasOption(XmlOptions.LOAD_STRIP_WHITESPACE); |
| _stripComments = options.hasOption(XmlOptions.LOAD_STRIP_COMMENTS); |
| _stripProcinsts = options.hasOption(XmlOptions.LOAD_STRIP_PROCINSTS); |
| |
| _substituteNamespaces = (Map) options.get(XmlOptions.LOAD_SUBSTITUTE_NAMESPACES); |
| _additionalNamespaces = (Map) options.get(XmlOptions.LOAD_ADDITIONAL_NAMESPACES); |
| |
| _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 = (String) _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 than regular attrs -- I don't change their name, |
| // I change their value! |
| |
| if (_substituteNamespaces != null) { |
| String substituteUri = (String) _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; |
| } |
| |
| protected 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) { |
| Cur c1 = x.tempCur(); |
| c1.toRoot(); |
| Xobj doc = c1._xobj; |
| c1.release(); |
| 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 & 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(); |
| } |
| |
| protected 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 boolean _discardDocElem; |
| private QName _replaceDocElem; |
| private final boolean _stripWhitespace; |
| private final boolean _stripComments; |
| private final boolean _stripProcinsts; |
| private final Map _substituteNamespaces; |
| private final Map _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; |
| } |
| |
| // |
| // |
| // |
| |
| 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; |
| } |