| /* 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.impl.values.TypeStore; |
| import org.apache.xmlbeans.impl.values.TypeStoreUser; |
| import org.apache.xmlbeans.impl.common.QNameHelper; |
| import org.apache.xmlbeans.SchemaType; |
| import org.apache.xmlbeans.XmlCursor.TokenType; |
| import org.apache.xmlbeans.XmlCursor.XmlBookmark; |
| import org.apache.xmlbeans.XmlCursor.XmlMark; |
| import org.apache.xmlbeans.XmlCursor; |
| import java.lang.ref.Reference; |
| import java.lang.ref.WeakReference; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Iterator; |
| import javax.xml.namespace.QName; |
| |
| public abstract class Splay extends Goobers |
| { |
| // |
| // Can't change the kind of a base token |
| // |
| |
| Splay ( int kind, boolean is ) |
| { |
| _bits = kind; |
| |
| if (is) |
| _bits += 0x8; |
| } |
| |
| // |
| // Splay kinds |
| // |
| |
| static final int DOC = 0; // DOC must be 0 |
| static final int BEGIN = 1; // BEGIN must be 1 |
| static final int ATTR = 2; |
| static final int COMMENT = 3; |
| static final int PROCINST = 4; |
| static final int ROOT = 5; // ROOT must be second to last |
| static final int END = 6; // END must be last |
| |
| // |
| // |
| // |
| |
| final int getKind ( ) { return _bits & 0x7; } |
| |
| final boolean isDoc ( ) { return getKind() == DOC; } |
| final boolean isRoot ( ) { return getKind() == ROOT; } |
| final boolean isBegin ( ) { return getKind() == BEGIN; } |
| final boolean isEnd ( ) { return getKind() == END; } |
| final boolean isAttr ( ) { return getKind() == ATTR; } |
| final boolean isComment ( ) { return getKind() == COMMENT; } |
| final boolean isProcinst ( ) { return getKind() == PROCINST; } |
| final boolean isContainer ( ) { return getKind() <= BEGIN; } |
| final boolean isFinish ( ) { return getKind() >= ROOT; } |
| |
| final int getCch ( ) { return _cch; } |
| |
| final int getCdocBegin ( ) { return 1; } |
| final int getCchLeft ( ) { return _cchLeft; } |
| final int getCdocBeginLeft ( ) { return _bits >> 5; } |
| final int getCchAfter ( ) { return _cchAfter; } |
| final int getCchValue ( ) { return _cch - _cchAfter; } |
| |
| QName getName ( ) { throw new IllegalStateException(); } |
| String getUri ( ) { throw new IllegalStateException(); } |
| String getLocal ( ) { throw new IllegalStateException(); } |
| |
| // Fourth bit in _bits has several meanings, depending on splay kind |
| |
| final boolean isNormalAttr ( ) { return (_bits & 0xF) == (0x0 + ATTR); } |
| final boolean isXmlns ( ) { return (_bits & 0xF) == (0x8 + ATTR); } |
| final boolean isLeaf ( ) { return (_bits & 0xF) == (0x8 + BEGIN); } |
| final boolean isFragment ( ) { return (_bits & 0xF) == (0x8 + COMMENT); } |
| final boolean isXsiAttr ( ) { return isNormalAttr() && getUri().equals( _xsi ); } |
| final boolean isXsiNil ( ) { return isNormalAttr() && getName().equals(_xsiNil ); } |
| final boolean isXsiType ( ) { return isNormalAttr() && getName().equals( _xsiType ); } |
| final boolean isTypeable ( ) { return isContainer() || isNormalAttr(); } |
| |
| final void toggleIsLeaf ( ) |
| { |
| assert isBegin(); |
| _bits ^= 0x8; |
| } |
| |
| final boolean isValid ( ) |
| { |
| assert ((_bits & 0x10) == 0) || isTypeable(); |
| return (_bits & 0x10) == 0; |
| } |
| |
| final boolean isInvalid ( ) |
| { |
| assert ((_bits & 0x10) == 0) || isTypeable(); |
| return (_bits & 0x10) != 0; |
| } |
| |
| final void toggleIsInvalid ( ) |
| { |
| assert isTypeable(); |
| _bits ^= 0x10; |
| } |
| |
| final void adjustCch ( int delta ) |
| { |
| _cch += delta; |
| assert _cch >= 0; |
| } |
| |
| final void adjustCchAfter ( int delta ) |
| { |
| _cchAfter += delta; |
| assert _cchAfter >= 0; |
| } |
| |
| final void adjustCchLeft ( int delta ) |
| { |
| _cchLeft += delta; |
| assert _cchLeft >= 0; |
| } |
| |
| final void adjustCdocBeginLeft ( int d ) |
| { |
| _bits += d * 32; |
| assert getCdocBeginLeft() >= 0; |
| } |
| |
| final Splay getFinishSplay ( ) |
| { |
| assert isContainer(); |
| return isLeaf() ? this : ((Container) this).getFinish(); |
| } |
| |
| final int getPosAfter ( ) |
| { |
| return getEndPos() - getCchAfter(); |
| } |
| |
| final int getPosLeafEnd ( ) |
| { |
| assert isLeaf(); |
| return 1 + getCchValue(); |
| } |
| |
| final int getCpForPos ( Root r, int p ) |
| { |
| int cp = r.getCp( this ); |
| |
| if (p == 0) |
| return cp; |
| |
| return |
| isLeaf() |
| ? p - (p <= getPosLeafEnd() ? 1 : 2) + cp |
| : p - 1 + getCchValue() + cp; |
| } |
| |
| final int getPostCch ( int p ) |
| { |
| assert p >= 0 && p <= getEndPos(); |
| |
| return |
| isLeaf() && p <= getPosLeafEnd() |
| ? getPosLeafEnd() - p |
| : getEndPos() - p; |
| } |
| |
| static final String _xsi = |
| "http://www.w3.org/2001/XMLSchema-instance"; |
| |
| static final String _schema = |
| "http://www.w3.org/2001/XMLSchema"; |
| |
| static final String _openFragUri = |
| "http://www.openuri.org/fragment"; |
| |
| static final String _xml1998Uri = |
| "http://www.w3.org/XML/1998/namespace"; |
| |
| static final String _xmlnsUri = |
| "http://www.w3.org/2000/xmlns/"; |
| |
| static final QName _xsiNil = new QName( _xsi, "nil" ); |
| static final QName _xsiType = new QName( _xsi, "type" ); |
| static final QName _openuriFragment = new QName( _openFragUri, "fragment" ); |
| static final QName _xmlFragment = new QName( "", "xml-fragment" ); |
| |
| static final boolean isXmlFragment ( QName name ) |
| { |
| return _openuriFragment.equals( name ) || _xmlFragment.equals( name ); |
| } |
| |
| static boolean beginsWithXml ( String name ) |
| { |
| if (name.length() < 3) |
| return false; |
| |
| char ch; |
| |
| if (((ch = name.charAt( 0 )) == 'x' || ch == 'X') && |
| ((ch = name.charAt( 1 )) == 'm' || ch == 'M') && |
| ((ch = name.charAt( 2 )) == 'l' || ch == 'L')) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * This returns the pos of the very end of the splay. This is not |
| * generally a valid position to exist because in these cases, I |
| * force the position to be 0 on the next splay. |
| */ |
| |
| final int getEndPos ( ) |
| { |
| return getMaxPos() + 1; |
| } |
| |
| /** |
| * This returns the largest pos possible without actually being at |
| * the end of the splay. |
| */ |
| |
| final int getMaxPos ( ) |
| { |
| switch ( getKind() ) |
| { |
| case ATTR : |
| case ROOT : return 0; |
| case BEGIN : return (isLeaf() ? 1 : 0) + getCch(); |
| case DOC : |
| case COMMENT : |
| case PROCINST : |
| case END : return getCchAfter(); |
| |
| default : |
| assert false: "Unexpected splay kind " +getKind(); |
| return 0; |
| } |
| } |
| |
| static final boolean isWhiteSpace ( char ch ) |
| { |
| switch ( ch ) |
| { |
| case ' ': |
| case '\t': |
| case '\n': |
| case '\r': |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static final boolean isWhiteSpace ( String s ) |
| { |
| int l = s.length(); |
| |
| while ( l-- > 0) |
| if (!isWhiteSpace( s.charAt( l ))) |
| return false; |
| |
| return true; |
| } |
| |
| static final boolean isWhiteSpace ( StringBuffer sb ) |
| { |
| int l = sb.length(); |
| |
| while ( l-- > 0) |
| if (!isWhiteSpace( sb.charAt( l ))) |
| return false; |
| |
| return true; |
| } |
| |
| final boolean isAfterWhiteSpace ( Root r ) |
| { |
| int cchAfter = getCchAfter(); |
| |
| if (cchAfter == 0) |
| return true; |
| |
| int off = |
| r._text.unObscure( |
| r.getCp( this ) + getCch() - cchAfter, cchAfter ); |
| |
| while ( cchAfter-- > 0 ) |
| if (!isWhiteSpace( r._text._buf[ off + cchAfter ] )) |
| return false; |
| |
| return true; |
| } |
| |
| final void setName ( Root r, QName newName ) |
| { |
| assert isBegin() || isAttr() || isProcinst(); |
| |
| // BUGBUG - deal with xsi:nil here too |
| |
| QName oldName = getName(); |
| |
| if (!newName.equals( oldName )) |
| { |
| r.startChange(); |
| |
| ((QNameSplay) this).changeName( newName ); |
| |
| if (!isProcinst() && !isXmlns()) |
| { |
| assert isAttr() || isBegin(); |
| |
| if (isBegin()) |
| disconnectTypes( r ); |
| |
| if (_xsiType.equals( oldName ) || _xsiType.equals( newName )) |
| getContainer().disconnectTypes( r ); |
| else if (_xsiNil.equals( oldName ) || _xsiNil.equals( newName )) |
| getContainer().invalidateNil(); |
| else |
| disconnectTypes( r ); |
| } |
| |
| r.invalidateVersion(); |
| } |
| } |
| |
| /** |
| * If this is an invalid leaf, then refresh it's value and return the |
| * number of characters added. |
| */ |
| |
| final int ensureContentValid ( ) |
| { |
| assert isContainer(); |
| |
| if (isValid()) |
| return 0; |
| |
| Type t = peekType(); |
| |
| return t.validateContent( this, t ); |
| } |
| |
| /** |
| * Ensure that the value (ATTR, COMMENT and PROCINST) is valid. Really |
| * only does anything for attrs. |
| */ |
| |
| final void ensureValueValid ( ) |
| { |
| assert isNormalAttr(); |
| |
| if (isInvalid()) |
| { |
| Type t = peekType(); |
| t.validateValue( this, t ); |
| } |
| } |
| |
| /** |
| * Given a position in this splay, return the TokenType associated with |
| * the content to the left of the position. |
| */ |
| |
| final TokenType getTokenType ( int p ) |
| { |
| assert p >= 0 && p <= getMaxPos(); |
| |
| // No need to revalidate text value here because there can be no pos |
| // which could exists when this splay is invalid which could be on |
| // text. |
| |
| // First, see if we're in/at the text after the splay |
| |
| if (p >= getPosAfter()) |
| return TokenType.TEXT; |
| |
| switch ( getKind() ) |
| { |
| case ATTR : |
| return isXmlns() ? TokenType.NAMESPACE : TokenType.ATTR; |
| |
| case BEGIN : |
| if (p == 0) |
| return TokenType.START; |
| |
| assert isLeaf(); |
| |
| return p == getPosLeafEnd() ? TokenType.END : TokenType.TEXT; |
| |
| case ROOT : return TokenType.ENDDOC; |
| case DOC : return TokenType.STARTDOC; |
| case COMMENT : return TokenType.COMMENT; |
| case PROCINST : return TokenType.PROCINST; |
| case END : return TokenType.END; |
| |
| default : |
| assert false: "Unexpected splay kind " + getKind(); |
| return null; |
| } |
| } |
| |
| /** |
| * Cause this splay to become a leaf. This does not logically |
| * alter the document, but does alter the physical splay tree which |
| * demands that I issue a change notice. |
| */ |
| |
| final void foliate ( Root r ) |
| { |
| assert isBegin(); |
| assert !isLeaf(); |
| |
| Begin b = (Begin) this; |
| Splay e = b.getFinish(); |
| |
| assert b.nextNonAttrSplay() == e; |
| assert b._end == e; |
| assert e.isEnd(); |
| assert !b.isLeaf(); |
| |
| // Caller must initiate change |
| assert r.validateChangeStarted(); |
| |
| int cchBefore = getCch(); |
| |
| e.saveTextAfter( r ); |
| |
| b._end = null; |
| b.toggleIsLeaf(); |
| |
| b.adjustCchAfter( - cchBefore ); |
| |
| // Move the goobers from the end to the begin |
| |
| Goober nextGoober; |
| for ( Goober g = e.firstGoober() ; g != null ; g = nextGoober ) |
| { |
| nextGoober = e.nextGoober( g ); |
| |
| assert g.getPos() == 0; |
| g.set( this, cchBefore + 1 ); |
| } |
| |
| for ( Goober g = firstGoober() ; g != null ; g = nextGoober ) |
| { |
| nextGoober = nextGoober( g ); |
| |
| int gp = g.getPos(); |
| |
| if (gp > cchBefore + 1) |
| g.set( this, gp + 1 ); |
| } |
| |
| e.removeSplay( r ); |
| } |
| |
| final void defoliate ( Root r ) |
| { |
| assert isLeaf(); |
| |
| // Caller must initiate change |
| assert r.validateChangeStarted(); |
| |
| // Caller must ensure leaf is valid |
| assert isValid(); |
| |
| Begin b = (Begin) this; |
| |
| int posLeafEnd = getPosLeafEnd(); |
| int cchAfter = getCchAfter(); |
| int cchValue = getCchValue(); |
| |
| Splay a = this; |
| |
| int cchAttr = 0; |
| |
| for ( Splay t = nextSplay() ; t.isAttr() ; t = t.nextSplay() ) |
| { |
| cchAttr += t.getCch(); |
| a = t; |
| } |
| |
| r.insertSplay( b._end = new End( (Begin) this ), a ); |
| |
| b.toggleIsLeaf(); |
| |
| b.adjustCchAfter( cchValue - cchAfter ); |
| b._end.adjustCchAfter( cchAfter ); |
| |
| r.updateCch( b, - cchAfter ); |
| r.updateCch( b._end, cchAfter ); |
| |
| if (cchAttr > 0) |
| { |
| int cp = r.getCp( b ); |
| |
| r._text.move( |
| cp + cchValue + cchAfter + cchAttr, |
| r._text, cp + cchValue, cchAfter ); |
| } |
| |
| Goober nextGoober; |
| for ( Goober g = firstGoober() ; g != null ; g = nextGoober ) |
| { |
| nextGoober = nextGoober( g ); |
| |
| int gp = g.getPos(); |
| |
| if (gp >= posLeafEnd) |
| g.set( b._end, gp - posLeafEnd ); |
| } |
| |
| assert validate(); |
| } |
| |
| /** |
| * Remove the text after *this* splay. |
| * No validation is done, caller must do that |
| */ |
| |
| private final void removeTextAfter ( Root r ) |
| { |
| assert r != null; |
| assert Root.dv > 0 || getRootSlow() == r; |
| assert !isRoot() && !isAttr(); |
| |
| // Caller must initiate change |
| assert r.validateChangeStarted(); |
| |
| int cchTextAfter = getCchAfter(); |
| |
| if (cchTextAfter == 0) |
| return; |
| |
| int cpTextAfter = r.getCp( this ) + getCch() - cchTextAfter; |
| int posTextAfter = getPosAfter(); |
| |
| adjustCchAfter( - cchTextAfter ); |
| |
| r.updateCch( this, - cchTextAfter ); |
| |
| r._text.remove( cpTextAfter, cchTextAfter ); |
| |
| Goober nextGoober; |
| for ( Goober g = firstGoober() ; g != null ; g = nextGoober ) |
| { |
| nextGoober = nextGoober( g ); |
| |
| int k = g.getKind(); |
| |
| if (g.getPos() >= posTextAfter) |
| { |
| if (k == ANNOTATION) |
| g.disconnect( r ); |
| else if (k == CURSOR) |
| g.set( nextSplay(), 0 ); |
| } |
| } |
| |
| assert validate(); |
| } |
| |
| /** |
| * Transfers the text after this "node" to be after the "node" before it. |
| * No type invalidation is done here. No checking for isInvalid is needed |
| * either. No validation is done, caller must do that. |
| */ |
| |
| private final int saveTextAfter ( Root r ) |
| { |
| assert r != null; |
| assert Root.dv > 0 || getRootSlow() == r; |
| |
| // Caller must initiate change |
| assert r.validateChangeStarted(); |
| |
| Splay endText = this; |
| |
| if (isBegin() && !isLeaf()) |
| endText = getFinishSplay(); |
| |
| int cchEndText = endText.getCchAfter(); |
| |
| if (cchEndText == 0) |
| return 0; |
| |
| assert !isRoot() && !isDoc() && !isAttr(); |
| |
| Splay getsText = prevNonAttrSplay(); |
| int getsTextLastPos = getsText.getEndPos(); |
| |
| int cpGetsText = r.getCp( getsText ) + getsText.getCch(); |
| int cpEndText = r.getCp( endText ) + endText.getCch() - cchEndText; |
| |
| getsText.adjustCchAfter( cchEndText ); |
| endText.adjustCchAfter( - cchEndText ); |
| |
| r.updateCch( getsText, cchEndText ); |
| r.updateCch( endText, - cchEndText ); |
| |
| assert cpGetsText <= cpEndText; |
| |
| if (cpGetsText != cpEndText) |
| { |
| r._text.insert( cpGetsText, r._text, cpEndText, cchEndText ); |
| r._text.remove( cpEndText + cchEndText, cchEndText ); |
| } |
| |
| int posEndText = endText.getPosAfter(); |
| |
| Goober nextGoober; |
| for ( Goober g = endText.firstGoober() ; g != null ; g = nextGoober ) |
| { |
| nextGoober = endText.nextGoober( g ); |
| |
| int gp = g.getPos(); |
| |
| if (gp >= posEndText) |
| g.set( getsText, getsTextLastPos + gp - posEndText ); |
| } |
| |
| assert validate(); |
| |
| return cchEndText; |
| } |
| |
| /** |
| * Moves this splay, all of its content and attributes. If no destination |
| * is provided, then only a removal is performed. |
| */ |
| |
| final void move ( |
| Root r, Root rDst, Splay sDst, int pDst, boolean invalidate ) |
| { |
| assert r != null; |
| assert Root.dv > 0 || getRootSlow() == r; |
| assert !isDoc() && !isFinish(); |
| |
| assert rDst == null || (r != rDst || !sDst.between( r, pDst, this ) ); |
| assert rDst == null || sDst != null; |
| assert rDst == null || Root.dv > 0 || sDst.getRootSlow() == rDst; |
| assert rDst == null || pDst >= 0 && pDst < sDst.getEndPos(); |
| |
| assert |
| rDst == null || |
| (true || checkInsertionValidity( 0, sDst, pDst, false )); |
| |
| r.startChange(); |
| |
| if (rDst != null) |
| rDst.startChange(); |
| |
| // The text after the logical entity to move/remove is not part of |
| // this operation. Transfer it to a previous splay. No need to |
| // fret about invalidity here, because it's text *after*. |
| |
| int cchSaved = saveTextAfter( r ); |
| |
| // If any text was saved, I need to move the cursors at the beginning |
| // of this splay to where the text was saved. I do this because this |
| // content is going away and cursors need to collapse to the right |
| // location which, in this case, is before the after text. |
| // |
| // No need to deal with goobers other than cursor because annotations |
| // and types will be dealt with appropriately later. |
| // |
| // Also, update the target if it was in the moved text! |
| // |
| // TODO - if moving xsi:type or xsi:nil must do stuff |
| |
| if (cchSaved > 0) |
| { |
| Splay gotText = prevNonAttrSplay(); |
| |
| Goober nextGoober; |
| for ( Goober g = firstGoober() ; g != null ; g = nextGoober ) |
| { |
| nextGoober = nextGoober( g ); |
| |
| if (g.getKind() == CURSOR) |
| g.set( gotText, gotText.getEndPos() - cchSaved ); |
| } |
| |
| if (rDst != null) |
| { |
| Splay hadText = |
| isBegin() && !isLeaf() ? getFinishSplay() : this; |
| |
| if (sDst == hadText && pDst >= hadText.getEndPos()) |
| { |
| sDst = gotText; |
| |
| pDst = |
| gotText.getEndPos() - |
| (cchSaved - (pDst - hadText.getEndPos())); |
| } |
| } |
| } |
| |
| assert sDst != this; |
| |
| // Compute the splay, up to which, I will remove |
| |
| Splay last = |
| isBegin() ? getFinishSplay().nextNonAttrSplay() : nextSplay(); |
| |
| // |
| // |
| // |
| |
| Container container = getContainer(); |
| |
| int cp = r.getCp( this ); |
| int cch = 0; |
| |
| for ( Splay s = this ; s != last ; s = s.nextSplay() ) |
| { |
| // I'm going to blow away the Types here, make sure I have |
| // all the text. |
| |
| if (s.isInvalid()) |
| { |
| assert s.isLeaf() || s.isNormalAttr(); |
| |
| if (s.isNormalAttr()) |
| s.ensureValueValid(); |
| else if (s.isLeaf()) |
| s.ensureContentValid(); |
| } |
| |
| cch += s.getCch(); |
| |
| Goober nextGoober; |
| for ( Goober g = s.firstGoober() ; g != null ; g = nextGoober ) |
| { |
| nextGoober = s.nextGoober( g ); |
| |
| switch ( g.getKind() ) |
| { |
| case TYPE : |
| g.disconnect( r ); |
| break; |
| |
| case ANNOTATION : |
| if (rDst == null) |
| g.disconnect( r ); |
| else |
| g.set( rDst ); // Potential move to a new tree |
| break; |
| |
| case CURSOR : |
| g.set( last, 0 ); |
| break; |
| } |
| } |
| } |
| |
| // Remove the splays and the text. Unfortunately, I need to remove |
| // the text here. I can't think of a way to keep the tree valid and |
| // have the text moved in one copy .... Can you? |
| |
| Splay sRemoved = r.removeSplays( this, last ); |
| |
| Splay first = |
| sRemoved._leftSplay == null ? sRemoved : sRemoved._leftSplay; |
| |
| if (first.isBegin()) |
| ((Begin) first)._container = null; |
| |
| char[] textRemoved = rDst == null || cch == 0 ? null : new char [ cch ]; |
| |
| r._text.remove( cp, cch, textRemoved, 0 ); |
| |
| r.invalidateVersion(); |
| |
| if (rDst != null) |
| { |
| sDst.insert( |
| rDst, pDst, sRemoved, textRemoved, 0, cch, invalidate ); |
| } |
| |
| if (invalidate) |
| { |
| Type parentType = container.peekType(); |
| |
| if (parentType != null) |
| { |
| if (isBegin()) |
| parentType.invalidateElement( container, last ); |
| else if (isAttr() && isXsiNil()) |
| parentType.invalidateNil(); |
| } |
| } |
| |
| assert r.validate(); |
| assert rDst == null || rDst.validate(); |
| } |
| |
| /** |
| * Removes this splay, all of its content and attributes. |
| */ |
| |
| final void remove ( Root r, boolean invalidate ) |
| { |
| move( r, null, null, 0, invalidate ); |
| } |
| |
| private final void complain ( String msg ) |
| { |
| throw new IllegalArgumentException( msg ); |
| } |
| |
| /** |
| * Check to if the insertion of a "node" into the destination is allowed. |
| * This assumes that the source is a single piece of contents (like a node). |
| * Fragments are strings of content, not contained, and must not be |
| * validated by this function. |
| * |
| * Returns true if the src to copy is characters (potentially 0 chars too) |
| */ |
| |
| final boolean checkInsertionValidity ( |
| int p, Splay sDst, int pDst, boolean endOk ) |
| { |
| assert p >= 0; |
| assert p < getEndPos(); |
| assert pDst >= 0; |
| assert pDst < sDst.getEndPos(); |
| |
| boolean srcChars = false; |
| |
| if (p > 0) |
| { |
| if (!endOk && isLeaf() && p == getPosLeafEnd()) |
| complain( "Can't move/copy/insert an end token." ); |
| |
| srcChars = true; |
| |
| // valid text target will be tested for further down |
| } |
| else |
| { |
| if (isDoc()) |
| complain( "Can't move/copy/insert a whole document." ); |
| |
| if (isFinish()) |
| { |
| if (!endOk) |
| complain( "Can't move/copy/insert an end token." ); |
| |
| srcChars = true; |
| } |
| else if (isFragment()) |
| srcChars = true; |
| } |
| |
| if (pDst == 0 && sDst.isDoc()) |
| complain( "Can't insert before the start of the document." ); |
| |
| if (p > 0 && sDst.isAttr()) |
| complain( "Can't insert text before an attribute." ); |
| |
| if (isAttr() && pDst > 0 && (!sDst.isContainer() || pDst != 1)) |
| complain( "Can't insert an attribute in text." ); |
| |
| if (p <= 0 && pDst <= 0) |
| { |
| if (isAttr()) |
| { |
| boolean isOk = sDst.isAttr(); |
| |
| if (!isOk) |
| { |
| Splay t = sDst.prevNonAttrSplay(); |
| |
| if (t.isContainer() && t.getCchAfter() == 0) |
| isOk = true; |
| } |
| |
| if (!isOk) |
| { |
| complain( |
| "Can only move/copy/insert attribute after another " + |
| "attribute or start token." ); |
| } |
| } |
| else if (sDst.isAttr()) |
| { |
| complain( |
| "Can't move/copy/insert a non-attribute before " + |
| "an attribute." ); |
| } |
| } |
| |
| return srcChars; |
| } |
| |
| /** |
| * Insert in this splay at position p whose root is r the splays rootInsert |
| * with text txt at offset cpTxt and length cchTxt |
| */ |
| |
| final void insert ( |
| Root r, int p, Splay rootInsert, |
| Object txt, int offTxt, int cchTxt, |
| boolean invalidate ) |
| { |
| // BUGBUG - what is a new xsitype attr is inserted? -- must disconnect types |
| |
| assert p < getEndPos(); |
| assert cchTxt >= 0; |
| assert rootInsert != null; |
| assert rootInsert._parentSplay == null; |
| assert rootInsert.getCchLeft() + rootInsert.getCch() == cchTxt; |
| |
| // We are inserting a tree here. Get the first splay in the tree |
| // It must either be at the root or just to the left of the root. |
| // This is the "classic" dog leg pattern I use to pass splay trees. |
| |
| assert |
| rootInsert._leftSplay == null || |
| rootInsert._leftSplay._leftSplay == null; |
| |
| r.startChange(); |
| |
| Splay sInsert = |
| rootInsert._leftSplay == null ? rootInsert : rootInsert._leftSplay; |
| |
| assert !sInsert.isDoc() && !sInsert.isFinish(); |
| assert Root.dv > 0 || getRootSlow() == r; |
| |
| // When this member is called, we better be able to do the insert. |
| |
| assert true || sInsert.checkInsertionValidity( 0, this, p, false ); |
| |
| // If there is a fragment, there better be text in it |
| |
| assert !sInsert.isFragment() || sInsert.getCch() > 0; |
| |
| // |
| |
| Splay s = this; |
| |
| assert Root.dv > 0 || sInsert.getRootSlow() == null; |
| |
| Splay sOrig = null; |
| int pOrig = 0; |
| int cchBypassed = 0; |
| |
| // Prepare for inserting into the middle of a splay |
| |
| if (p > 0) |
| { |
| int ple; |
| |
| if (s.isLeaf() && p <= (ple = s.getPosLeafEnd())) |
| { |
| if (s.isInvalid()) |
| { |
| assert ple == 1; |
| assert p == 1; |
| |
| int cchValid = s.ensureContentValid(); |
| |
| p += cchValid; |
| ple += cchValid; |
| } |
| |
| assert s.getFinishSplay() == s; |
| |
| defoliate( r ); |
| |
| assert s.getFinishSplay() != s; |
| |
| if (p == ple) |
| { |
| s = getFinishSplay(); |
| p = 0; |
| } |
| |
| s.insert( r, p, rootInsert, txt, offTxt, cchTxt, invalidate ); |
| |
| return; |
| } |
| |
| // You can copy an attr to a dst where it is the first attr, or |
| // after all pre-exisitng attrs. This these cases, p == 1 on |
| // the begin. In this case, there is no need to move text later. |
| |
| assert !sInsert.isAttr() || (p == 1 && s.isContainer()); |
| |
| sOrig = s; |
| pOrig = p; |
| cchBypassed = getEndPos() - p; |
| |
| s = s.nextNonAttrSplay(); |
| p = 0; |
| } |
| |
| assert p == 0; |
| assert !s.isDoc(); |
| |
| // Get the container receiving all of this |
| |
| Container c = s.getContainer(); |
| |
| // Run over the top level content, parenting any BEGINS. Also, |
| // characterize the content being inserted for future invalidation. |
| // Only fragments will have multiple top level begins. |
| |
| boolean insertingElements = false; |
| boolean insertingText = false; |
| boolean insertingNilAttr = false; |
| Splay insertingFragment = null; |
| |
| for ( Splay t = sInsert ; t != null ; t = t.nextSplay() ) |
| { |
| if (t.isBegin()) |
| { |
| insertingElements = true; |
| |
| Begin b = (Begin) t; |
| |
| assert b._container == null; |
| |
| b._container = c; |
| |
| t = t.getFinishSplay(); |
| } |
| else if (t.isFragment()) |
| { |
| assert insertingFragment == null; |
| insertingFragment = t; |
| } |
| |
| if (t.isXsiNil()) |
| insertingNilAttr = true; |
| |
| if (t.getCchAfter() > 0) |
| insertingText = true; |
| } |
| |
| // Now, insert the text (if any) |
| |
| r._text.insert( r.getCp( s ), txt, offTxt, cchTxt ); |
| |
| // Insert the tree |
| |
| r.insertSplay( rootInsert, s.prevSplay() ); |
| |
| // If the first splay to isnert was a fragment, remove it, leaving |
| // the text it carried in the tree. Don't perform invalidation. |
| |
| if (insertingFragment != null) |
| insertingFragment.remove( r, false ); |
| |
| // Invalidate the parent if requested |
| |
| if (invalidate) |
| { |
| Type cType = c.peekType(); |
| |
| if (cType != null) |
| { |
| if (insertingElements) |
| { |
| cType.invalidateElement( c, s ); |
| insertingText = true; |
| } |
| |
| if (insertingText) |
| cType.invalidateText(); |
| |
| if (insertingNilAttr) |
| cType.invalidateNil(); |
| } |
| } |
| |
| // If there is post text to move, move it to the end of the stuff |
| // inserted. Don't do this is if an attr was inserted because in this |
| // case, the attr is logically after the text to be moved anyways. |
| |
| if (sOrig != null && !sInsert.isAttr()) |
| { |
| int cchMoved = sOrig.moveChars( r, pOrig, -1, r, s, 0, true ); |
| assert cchMoved > 0; |
| assert cchMoved == cchBypassed; |
| } |
| |
| // build a leaf if possible |
| |
| if (s.isEnd() && s.getContainer().nextNonAttrSplay() == s) |
| s.getContainer().foliate( r ); |
| |
| r.invalidateVersion(); |
| |
| assert r.validate(); |
| } |
| |
| /** |
| * Replaces the contents of this (attributes as well) with a copy of the |
| * contents of sSrc. If preserveType is true, then the xsi:type attr |
| * of the source is not copied, and the xsi:type of the target (this) |
| * is not altered. |
| */ |
| |
| final void replaceContents ( |
| Root r, Splay sSrc, Root rSrc, |
| boolean preserveType, boolean preserveNamespaces ) |
| { |
| assert !isFinish() && !sSrc.isFinish(); |
| assert Root.dv > 0 || getRootSlow() == r; |
| assert Root.dv > 0 || sSrc.getRootSlow() == rSrc; |
| |
| // If the src and dst are the same splay, then there is nothing to do |
| |
| if (this == sSrc) |
| return; |
| |
| // If we are to preserve namespace mappings, make a copy of the |
| // namspaces in scope here |
| |
| Map sourceNamespaces = |
| preserveNamespaces ? sSrc.lookupAllPrefixMappings() : null; |
| |
| // If the destination is a simple container of text, then the content |
| // model is limited to text. Handle this specially. |
| |
| if (isAttr() || isComment() || isProcinst()) |
| { |
| // TODO - Do this without creating a string? |
| String str = sSrc.getText( rSrc ); |
| setText( r, str, 0, str.length() ); |
| } |
| else |
| { |
| // No need to startChange() here, fcns I call will do it for me |
| |
| assert isContainer(); |
| |
| // Make copy of source |
| |
| CopyContext copyContext = sSrc.copySplayContents( rSrc ); |
| |
| // If we need to preserve the xsi:type attribute, then fetch it. |
| // If there is more than one, the first one wins. |
| |
| String targetXsiType = null; |
| |
| if (preserveType) |
| { |
| for ( Splay s = nextSplay() ; s.isAttr() ; s = s.nextSplay() ) |
| { |
| if (s.isXsiType()) |
| { |
| targetXsiType = s.getText( r ); |
| break; |
| } |
| } |
| } |
| |
| // Because I have removed the entire contents of the target, I can |
| // insert fragment or node. |
| |
| removeContent( r, true ); |
| |
| assert isLeaf() || getFinishSplay() == nextSplay(); |
| assert getCchValue() == 0; |
| |
| Splay copy = copyContext.getTree(); |
| |
| if (copy != null) |
| { |
| // Need to compute a normalized splay/pos to insert at. Only |
| // two cases, either we're inserting in a leaf or a |
| // container/finish pair. |
| |
| Splay insertSplay = this; |
| int insertPos = 1; |
| |
| if (!isLeaf()) |
| { |
| insertSplay = nextSplay(); |
| insertPos = 0; |
| } |
| |
| char[] textCopy = copyContext.getText(); |
| |
| insertSplay.insert( |
| r, insertPos, copy, |
| textCopy, 0, textCopy == null ? 0 : textCopy.length, true ); |
| |
| // Now, go thorugh all the attrs, removing types and place the |
| // original back in. |
| |
| if (preserveType) |
| { |
| Splay next = nextSplay(); |
| |
| for ( Splay s = next ; s.isAttr() ; s = next ) |
| { |
| next = s.nextSplay(); |
| |
| if (s.isXsiType()) |
| s.remove( r, true ); |
| } |
| |
| if (targetXsiType != null) |
| { |
| Attr a = new Attr( _xsiType ); |
| |
| int cchType = targetXsiType.length(); |
| |
| a.adjustCch( cchType ); |
| |
| if (getEndPos() > 1) |
| insert( r, 1, a, targetXsiType, 0, cchType, true ); |
| else |
| { |
| nextNonAttrSplay().insert( |
| r, 0, a, targetXsiType, 0, cchType, true ); |
| } |
| } |
| } |
| } |
| } |
| |
| if (sourceNamespaces != null) |
| applyNamespaces( r, sourceNamespaces ); |
| } |
| |
| static final class CopyContext |
| { |
| void copyText ( char[] text ) |
| { |
| _text = text; |
| } |
| |
| void copySplay ( Splay s, boolean copyTextAfter ) |
| { |
| assert !s.isDoc() && !s.isRoot(); |
| |
| Splay t; |
| |
| switch ( s.getKind() ) |
| { |
| case BEGIN : |
| { |
| s.ensureContentValid(); |
| |
| t = _frontier = new Begin( s.getName(), _frontier ); |
| |
| if (s.isLeaf()) |
| { |
| t.toggleIsLeaf(); |
| _frontier = _frontier.getContainer(); |
| } |
| |
| break; |
| } |
| case ATTR : |
| { |
| if (s.isXmlns()) |
| t = new Xmlns( s.getName() ); |
| else |
| { |
| s.ensureValueValid(); |
| t = new Attr( s.getName() ); |
| } |
| break; |
| } |
| case COMMENT : |
| { |
| t = new Comment(); |
| break; |
| } |
| case PROCINST : |
| { |
| t = new Procinst( s.getName() ); |
| break; |
| } |
| case END : |
| { |
| Begin b = (Begin) _frontier; |
| t = b._end = new End( b ); |
| _frontier = _frontier.getContainer(); |
| break; |
| } |
| default : |
| throw new IllegalStateException(); |
| } |
| |
| assert s.isValid(); |
| |
| int cch = s.getCchValue(); |
| |
| if (copyTextAfter) |
| { |
| int cchAfter = s.getCchAfter(); |
| cch += cchAfter; |
| t.adjustCchAfter( cchAfter ); |
| } |
| |
| if (cch > 0) |
| t.adjustCch( cch ); |
| |
| copy( t ); |
| } |
| |
| /** |
| * May be called once before any other copy |
| */ |
| |
| void copyFragment ( int cch ) |
| { |
| assert cch > 0; |
| |
| Splay s = new Fragment(); |
| |
| s.adjustCchAfter( cch ); |
| s.adjustCch( cch ); |
| |
| copy( s ); |
| } |
| |
| Splay getTree ( ) |
| { |
| assert _frontier == null; |
| |
| if (_last == null) |
| return null; |
| |
| if (_first != null) |
| { |
| _last.adjustCchLeft( _first.getCchLeft() + _first.getCch() ); |
| |
| _last.adjustCdocBeginLeft( |
| _first.getCdocBeginLeft() + _first.getCdocBegin() ); |
| |
| _last._leftSplay = _first; |
| _first._parentSplay = _last; |
| |
| if (_tail != null) |
| { |
| _last.adjustCchLeft( _tail.getCchLeft() + _tail.getCch() ); |
| |
| _last.adjustCdocBeginLeft( |
| _tail.getCdocBeginLeft() + _tail.getCdocBegin() ); |
| |
| _first._rightSplay = _tail; |
| _tail._parentSplay = _first; |
| } |
| } |
| |
| return _last; |
| } |
| |
| char[] getText ( ) |
| { |
| return _text; |
| } |
| |
| private void copy ( Splay s ) |
| { |
| // |
| // Here I make sure that the structure of the splay tree returned |
| // by the copy is such that the last splay copied is at the root, |
| // the first splay copied is the left child of the last copied, |
| // the second to last splay copied is the right child of the first, |
| // and the third through the second to last are a left only list |
| // off of the left child of the second! |
| // |
| // You may be asking your self, "Self, why all this madness?" |
| // |
| // Several reasons. First, by making the first hang off the |
| // left child of the last and have the right child of last be null, |
| // the root will have the total splay statistics for the whole |
| // tree. Second, you have quick access to the first splay copied. |
| // Third, building the third through the second to last do |
| // not require walking to update statictics. |
| // |
| // Ultimately, I do this to be consistent with the format of the |
| // tree when a range of splays are removed from a tree. To avoid |
| // an additional splay, the removal of a sub tree is shaped like |
| // this. |
| // |
| |
| if (_last != null) |
| { |
| if (_first == null) |
| _first = _last; |
| else if (_tail == null) |
| _tail = _last; |
| else |
| { |
| _last.adjustCchLeft( _tail.getCchLeft() + _tail.getCch() ); |
| |
| _last.adjustCdocBeginLeft( |
| _tail.getCdocBeginLeft() + _tail.getCdocBegin() ); |
| |
| _last._leftSplay = _tail; |
| _tail._parentSplay = _last; |
| _tail = _last; |
| } |
| } |
| |
| _last = s; |
| } |
| |
| private char[] _text; |
| private Splay _first; |
| private Splay _tail; |
| private Splay _last; |
| private Container _frontier; |
| } |
| |
| /** |
| * Copies the contents of this splay (deeply). |
| * This will update any invalid content. |
| * |
| * Because this can return only text, if there is text at the beginning |
| * of the contents, I will create a FRAG as the first splay (after any |
| * attributes). |
| */ |
| |
| final CopyContext copySplayContents ( Root r ) |
| { |
| assert !isFinish() && !isXmlns(); |
| |
| if (isContainer()) |
| ensureContentValid(); |
| else if (isNormalAttr()) |
| ensureValueValid(); |
| |
| CopyContext copyContext = new CopyContext(); |
| |
| if (isContainer()) |
| { |
| int cchAttrs = 0; |
| |
| Splay t = nextSplay(), s = t; |
| |
| for ( ; s.isAttr() ; s = s.nextSplay() ) |
| { |
| copyContext.copySplay( s, false ); |
| cchAttrs += s.getCch(); |
| } |
| |
| int cchValue = isLeaf() ? getCchValue() : getCchAfter(); |
| |
| if (cchValue > 0) |
| copyContext.copyFragment( cchValue ); |
| |
| int cchContents = 0; |
| |
| if (!isLeaf()) |
| { |
| Splay finish = getFinishSplay(); |
| |
| for ( Splay u = s ; u != finish ; u = u.nextSplay() ) |
| { |
| copyContext.copySplay( u, true ); |
| cchContents += u.getCch(); |
| } |
| } |
| |
| int cchAll = cchAttrs + cchValue + cchContents; |
| |
| if (cchAll > 0) |
| { |
| char[] text = new char [ cchAll ]; |
| |
| int cp = r.getCp( this ); |
| int cch = getCch(); |
| |
| if (cchAttrs > 0) |
| r._text.fetch( text, 0, cp + cch, cchAttrs ); |
| |
| if (cchValue > 0) |
| r._text.fetch( text, cchAttrs, cp, cchValue ); |
| |
| if (cchContents > 0) |
| { |
| r._text.fetch( |
| text, cchAttrs + cchValue, |
| cp + cch + cchAttrs, cchContents ); |
| } |
| |
| copyContext.copyText( text ); |
| } |
| } |
| else |
| { |
| assert isNormalAttr() || isComment() || isProcinst(); |
| |
| int cchValue = getCchValue(); |
| |
| if (cchValue > 0) |
| { |
| copyContext.copyFragment( cchValue ); |
| |
| char[] text = new char [ cchValue ]; |
| |
| r._text.fetch( text, 0, r.getCp( this ), cchValue ); |
| |
| copyContext.copyText( text ); |
| } |
| } |
| |
| return copyContext; |
| } |
| |
| /** |
| * Copies this splay (deeply). Also copies attributes after a begin |
| * This will update any invalid content. |
| */ |
| |
| final Splay copySplay ( ) |
| { |
| assert !isDoc() && !isFinish(); |
| |
| // Compute splay to stop at (does not get copied). Also compute |
| // a splay, to be copied, which should not have it's text after copied. |
| |
| Splay stopHere, noTextAfter; |
| |
| if (isBegin()) |
| { |
| noTextAfter = getFinishSplay(); |
| stopHere = noTextAfter.nextNonAttrSplay(); |
| } |
| else |
| { |
| noTextAfter = null; |
| stopHere = nextSplay(); |
| } |
| |
| // Make the copy |
| |
| CopyContext copyContext = new CopyContext(); |
| |
| for ( Splay next, s = this ; s != stopHere ; s = next ) |
| { |
| next = s.nextSplay(); |
| |
| copyContext.copySplay( s, s != noTextAfter ); |
| } |
| |
| return copyContext.getTree(); |
| } |
| |
| final void removeAttributes ( Root r ) |
| { |
| assert isContainer(); |
| |
| for ( ; ; ) |
| { |
| Splay s = nextSplay(); |
| |
| if (!s.isAttr()) |
| break; |
| |
| s.remove( r, true ); |
| } |
| } |
| |
| /** |
| * Removes all content from splay. |
| * Begin will be a leaf upon exit. |
| */ |
| |
| final void removeContent ( Root r, boolean removeAttrs ) |
| { |
| assert Root.dv > 0 || getRootSlow() == r; |
| |
| r.startChange(); |
| |
| if (isInvalid()) |
| { |
| toggleIsInvalid(); |
| |
| r.invalidateVersion(); |
| |
| if (isContainer() && removeAttrs) |
| removeAttributes( r ); |
| |
| return; |
| } |
| |
| switch ( getKind() ) |
| { |
| case DOC : |
| break; |
| |
| case BEGIN : |
| { |
| if (isLeaf()) |
| { |
| removeChars( r, 1, getCchValue() ); |
| |
| if (removeAttrs) |
| removeAttributes( r ); |
| |
| return; |
| } |
| |
| break; |
| } |
| |
| case ATTR : |
| { |
| if (isXmlns()) |
| { |
| assert false: "Unexpected kind for removeContent"; |
| } |
| |
| // Fall through |
| } |
| |
| case COMMENT : |
| case PROCINST : |
| { |
| int cchValue = getCchValue(); |
| |
| if (cchValue == 0) |
| return; |
| |
| r._text.remove( r.getCp( this ), cchValue ); |
| |
| r.updateCch( this, - cchValue ); |
| |
| if (getKind() == ATTR) |
| { |
| invalidateText(); |
| |
| if (isXsiType()) |
| getContainer().disconnectTypes( r ); |
| } |
| |
| r.invalidateVersion(); |
| |
| return; |
| } |
| |
| default : |
| assert false: "Unexpected kind for removeContent"; |
| } |
| |
| assert isDoc() || (isBegin() && !isLeaf()); |
| |
| Splay s = nextNonAttrSplay(); |
| |
| if (s.isRoot()) |
| { |
| assert isDoc(); |
| } |
| else |
| { |
| // |
| // Remove all the splays. Inhibit invalidation on each remove to |
| // prevent a flood of invalidations to the parent type (if there). |
| // |
| |
| Splay next; |
| for ( s = nextNonAttrSplay() ; !s.isFinish() ; s = next ) |
| { |
| assert !s.isAttr(); |
| |
| if (s.isLeaf()) |
| next = s.nextNonAttrSplay(); |
| else if (s.isBegin()) |
| next = s.getFinishSplay().nextSplay(); |
| else |
| next = s.nextSplay(); |
| |
| s.remove( r, false ); |
| } |
| |
| // |
| // Have to remove the text after this here because the above |
| // removes could have suuffled text to after this. |
| // |
| |
| assert !isLeaf(); |
| |
| if (getCchAfter() > 0) |
| removeTextAfter( r ); |
| |
| // |
| // Tricky code. Merge the END and the BEGIN to form a leaf. |
| // |
| |
| if (isBegin()) |
| foliate( r ); |
| |
| invalidateText(); |
| |
| r.invalidateVersion(); |
| } |
| |
| // TODO - if removing text from xsi:type or nil must do stuff |
| // TODO - if removing text from xsi:type or nil must do stuff |
| // TODO - if removing text from xsi:type or nil must do stuff |
| // TODO - if removing text from xsi:type or nil must do stuff |
| // TODO - if removing text from xsi:type or nil must do stuff |
| |
| |
| // TODO - if removing attribute xsi:type or xsi:nil, must do stuiff |
| // TODO - if removing attribute xsi:type or xsi:nil, must do stuiff |
| // TODO - if removing attribute xsi:type or xsi:nil, must do stuiff |
| // TODO - if removing attribute xsi:type or xsi:nil, must do stuiff |
| // TODO - if removing attribute xsi:type or xsi:nil, must do stuiff |
| |
| if (removeAttrs) |
| removeAttributes( r ); |
| |
| // If the tree became empty, reset _leftOnly |
| |
| if (r._leftSplay == r._doc && r._doc._rightSplay == null) |
| r._leftOnly = true; |
| |
| assert validate(); |
| } |
| |
| final int copyChars ( |
| Root r, int p, int cch, Root rDst, Splay sDst, int pDst ) |
| { |
| assert pDst > 0 && pDst <= sDst.getEndPos(); |
| |
| int postCch = getPostCch( p ); |
| |
| if (p == 0 || cch == 0 || postCch == 0) |
| return 0; |
| |
| if (cch < 0 || cch > postCch) |
| cch = postCch; |
| |
| assert cch > 0; |
| |
| // TODO - uses string to avoid problems with source and dest from same buffer |
| // rewrite to not create string object |
| |
| char[] chars = new char [ cch ]; |
| |
| r._text.fetch( chars, 0, getCpForPos( r, p ), cch ); |
| |
| sDst.insertChars( pDst, rDst, chars, 0, cch ); |
| |
| return cch; |
| } |
| |
| /** |
| * Move cch chars from pos p on this splay to pos pDst on sDst. |
| * Source and destination can be different documents. Normally cursors |
| * stay put when their char is moved, but moveCursors can cause cursors |
| * tomove with the chars (used internally). |
| */ |
| |
| final int moveChars ( |
| Root r, int p, int cch, Root rDst, Splay sDst, int pDst, |
| boolean moveCursors ) |
| { |
| if (p == 0) |
| return 0; |
| |
| int postCch = getPostCch( p ); |
| |
| if (cch == 0 || postCch == 0) |
| return 0; |
| |
| if (cch < 0 || cch > postCch) |
| cch = postCch; |
| |
| r.startChange(); |
| rDst.startChange(); |
| |
| assert cch > 0; |
| |
| // I don't have to check the invalidity of the source because the |
| // only invalid text I would have to deal with is that in a leaf, and |
| // If the source is in a leaf here, then it must be positioned at the |
| // end (1) which is not sensitive to the invalidity. |
| // |
| // However, I much check the destination for validity because I may |
| // be appending text to an existing value. |
| |
| if (sDst.isContainer() && pDst == 1) |
| { |
| int cchValid = sDst.ensureContentValid(); |
| |
| pDst += cchValid; |
| |
| if (sDst == this && p == 1) |
| p += cchValid; |
| } |
| |
| // |
| |
| if (pDst == 0) |
| { |
| sDst = sDst.prevNonAttrSplay(); |
| pDst = sDst.getEndPos(); |
| } |
| |
| // If the destination if in the range of the source, then this is |
| // effectively a no-op. |
| |
| // TODO - Probably should disconnect Types in the range |
| |
| if (sDst == this && pDst >= p && pDst <= p + cch) |
| return cch; |
| |
| assert pDst > 0; |
| |
| Container cDst = sDst.getContainer( pDst ); |
| Container c = getContainer( p ); |
| |
| // Move the text |
| |
| rDst._text.move( |
| sDst.getCpForPos( rDst, pDst ), r._text, getCpForPos( r, p ), cch ); |
| |
| // Perform the "insertion" first |
| |
| if (!sDst.isLeaf() || pDst > sDst.getPosLeafEnd()) |
| sDst.adjustCchAfter( cch ); |
| |
| rDst.updateCch( sDst, cch ); |
| |
| for ( Goober g = sDst.firstGoober() ; g != null ; |
| g = sDst.nextGoober( g ) ) |
| { |
| int gp = g.getPos(); |
| |
| if (gp >= pDst) |
| g.set( gp + cch ); |
| } |
| |
| assert sDst != this || pDst < p || pDst > p + cch; |
| |
| if (sDst == this) |
| { |
| int pDstSave = pDst; |
| |
| if (pDst > p + cch) |
| pDst -= cch; |
| |
| if (p >= pDstSave) |
| p += cch; |
| } |
| |
| // Then perform the "removal" |
| |
| if (!isLeaf() || p > getPosLeafEnd()) |
| adjustCchAfter( - cch ); |
| |
| r.updateCch( this, - cch ); |
| |
| Goober nextGoober; |
| for ( Goober g = firstGoober() ; g != null ; g = nextGoober ) |
| { |
| nextGoober = nextGoober( g ); |
| |
| int gp = g.getPos(); |
| |
| if (gp >= p + cch) |
| { |
| g.set( gp - cch ); |
| } |
| else if (gp >= p) |
| { |
| if (!moveCursors && g.getKind() == CURSOR) |
| { |
| if (p == getEndPos()) |
| g.set( nextSplay(), 0 ); |
| else |
| g.set( p ); |
| } |
| else |
| g.set( rDst, sDst, pDst + gp - p ); |
| } |
| } |
| |
| cDst.invalidateText(); |
| c.invalidateText(); |
| |
| r.invalidateVersion(); |
| rDst.invalidateVersion(); |
| |
| return cch; |
| } |
| |
| /** |
| * Remove cch chars starting at position p on this splay. |
| */ |
| |
| final int removeChars ( Root r, int p, int cch ) |
| { |
| int maxPos = getMaxPos(); |
| |
| assert p > 0 && p <= maxPos; |
| |
| if (p == 0) |
| return 0; |
| |
| r.startChange(); |
| |
| // TODO - merge the two following chunks of code?????? |
| |
| if (isLeaf()) |
| { |
| int ple = getPosLeafEnd(); |
| |
| if (p <= ple) |
| { |
| if (cch < 0 || ple - p < cch) |
| cch = ple - p; |
| |
| if (cch == 0) |
| return 0; |
| |
| r._text.remove( r.getCp( this ) + p - 1, cch ); |
| |
| r.updateCch( this, - cch ); |
| |
| Goober nextGoober; |
| |
| for ( Goober g = firstGoober() ; g != null ; g = nextGoober ) |
| { |
| nextGoober = nextGoober( g ); |
| |
| int k = g.getKind(); |
| int gp = g.getPos(); |
| |
| if (gp >= p + cch) |
| g.set( gp - cch ); |
| else if (gp >= p) |
| { |
| if (k == CURSOR) |
| g.set( p ); |
| else |
| { |
| assert k == ANNOTATION; |
| g.disconnect( r ); |
| } |
| } |
| } |
| |
| invalidateText(); |
| |
| r.invalidateVersion(); |
| |
| return cch; |
| } |
| } |
| |
| int posAfter = getPosAfter(); |
| |
| assert p >= posAfter; |
| |
| int maxCch = maxPos - p + 1; |
| |
| assert maxCch >= 0; |
| |
| if (cch < 0 || cch > maxCch) |
| cch = maxCch; |
| |
| if (cch <= 0) |
| return 0; |
| |
| Container c = getContainer( p ); |
| |
| r._text.remove( getCpForPos( r, p ), cch ); |
| |
| adjustCchAfter( - cch ); |
| |
| r.updateCch( this, - cch ); |
| |
| Goober nextGoober; |
| for ( Goober g = firstGoober() ; g != null ; g = nextGoober ) |
| { |
| nextGoober = nextGoober( g ); |
| |
| int k = g.getKind(); |
| int gp = g.getPos(); |
| |
| if (gp >= p) |
| { |
| if (k == CURSOR) |
| g.set( nextNonAttrSplay(), 0 ); |
| else |
| g.disconnect( r ); |
| } |
| } |
| |
| c.invalidateText(); |
| |
| r.invalidateVersion(); |
| |
| assert validate(); |
| |
| return cch; |
| } |
| |
| /** |
| * Insert text a position p in this splay |
| */ |
| |
| final void insertChars ( int p, Root r, Object txt, int off, int cch ) |
| { |
| assert p > 0 && p <= getEndPos(); |
| |
| if (cch == 0) |
| return; |
| |
| r.startChange(); |
| |
| Container container = getContainer( p ); |
| |
| if (isContainer() && p == 1) |
| p += ensureContentValid(); |
| |
| r._text.insert( getCpForPos( r, p ), txt, off, cch ); |
| |
| if (p >= getPosAfter()) |
| adjustCchAfter( cch ); |
| |
| r.updateCch( this, cch ); |
| |
| for ( Goober g = firstGoober() ; g != null ; g = nextGoober( g ) ) |
| { |
| int gp = g.getPos(); |
| |
| if (gp >= p) |
| g.set( gp + cch ); |
| } |
| |
| container.invalidateText(); |
| |
| r.invalidateVersion(); |
| |
| assert validate(); |
| } |
| |
| private static final int START_STATE = 0; |
| private static final int SPACE_SEEN_STATE = 1; |
| private static final int NOSPACE_STATE = 2; |
| |
| public final int scrubText( |
| Text text, int ws, int cp, int cch, StringBuffer sb, int state ) |
| { |
| assert text != null; |
| |
| if (text._buf == null) |
| { |
| assert cch == 0; |
| assert cp == 0; |
| return state; |
| } |
| |
| if (cch == 0) |
| return state; |
| |
| boolean replace = false; |
| boolean collapse = false; |
| |
| switch ( ws ) |
| { |
| case TypeStore.WS_UNSPECIFIED : break; |
| case TypeStore.WS_PRESERVE : break; |
| case TypeStore.WS_REPLACE : replace = true; break; |
| case TypeStore.WS_COLLAPSE : collapse = replace = true; break; |
| |
| default : assert false: "Unknown white space rule " +ws; |
| } |
| |
| if (!replace && !collapse) |
| { |
| text.fetch(sb, cp, cch); |
| return state; |
| } |
| |
| int off = text.unObscure( cp, cch ); |
| int startpt = 0; |
| |
| for ( int i = 0 ; i < cch ; i++ ) |
| { |
| char ch = text._buf[ off + i ]; |
| |
| if (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t') |
| { |
| sb.append(text._buf, off + startpt, i - startpt); |
| startpt = i + 1; |
| |
| if (collapse) |
| { |
| if (state == NOSPACE_STATE) |
| state = SPACE_SEEN_STATE; |
| } |
| else |
| sb.append(' '); |
| } |
| else |
| { |
| if (state == SPACE_SEEN_STATE) |
| sb.append( ' ' ); |
| |
| state = NOSPACE_STATE; |
| } |
| } |
| |
| sb.append( text._buf, off + startpt, cch - startpt ); |
| |
| return state; |
| } |
| |
| final String getText ( Root r ) |
| { |
| return getText( r, TypeStore.WS_PRESERVE ); |
| } |
| |
| // final int getText ( Root r, char[] buf, int off, int len ) |
| // { |
| // if (len <= 0) |
| // return 0; |
| // |
| // if (isInvalid()) |
| // { |
| // if (isNormalAttr()) |
| // ensureValueValid(); |
| // else if (isLeaf()) |
| // ensureContentValid(); |
| // } |
| // |
| // int cp = r.getCp( this ); |
| // |
| // if (isNormalAttr() || isComment() || isProcinst() || isLeaf()) |
| // { |
| // int cch = getCchValue(); |
| // |
| // if (cch == 0) |
| // return 0; |
| // |
| // if (cch > len) |
| // cch = len; |
| // |
| // r._text.fetch( buf, off, pos, cch ); |
| // |
| // return cch; |
| // } |
| // |
| // if (!isContainer()) |
| // return 0; |
| // |
| // int originalLen = len; |
| // Splay last = getFinishSplay(); |
| // |
| // for ( Splay s = this ; len > 0 && s != last ; s = s.nextSplay() ) |
| // { |
| // int srcCp, srcCch; |
| // |
| // if (s.isBegin()) |
| // { |
| // if (s.isInvalid()) |
| // s.ensureContentValid(); |
| // |
| // srcCp = cp; |
| // srcCch = s.getCch(); |
| // } |
| // else |
| // { |
| // // It's ok for an attr to be invalid here, gets text after |
| // |
| // srcCp = cp + s.getCchValue(); |
| // srcCch = s.getCchAfter(); |
| // } |
| // |
| // if (srcCch > 0) |
| // { |
| // if (srcCch > len) |
| // srcCch = len; |
| // |
| // int srcOff = r._text.unObscure( srcCp, srcCch ); |
| // |
| // System.arrayCopy( r._text._buf, srcOff, buf, off, srcCch ); |
| // |
| // len -= srcCch; |
| // off += srcCch; |
| // } |
| // |
| // cp += s.getCch(); |
| // } |
| // |
| // return sb.toString(); |
| // } |
| |
| final String getText ( Root r, int ws ) |
| { |
| if (isInvalid()) |
| { |
| if (isNormalAttr()) |
| ensureValueValid(); |
| else if (isLeaf()) |
| ensureContentValid(); |
| } |
| |
| int cp = r.getCp( this ); |
| |
| if (isNormalAttr() || isComment() || isProcinst() || isLeaf()) |
| { |
| int cch = getCchValue(); |
| |
| if (cch == 0) |
| return ""; |
| |
| if (ws == TypeStore.WS_PRESERVE || ws == TypeStore.WS_UNSPECIFIED) |
| return r._text.fetch( cp, cch ); |
| |
| StringBuffer sb = new StringBuffer(); |
| |
| scrubText( r._text, ws, cp, getCchValue(), sb, START_STATE ); |
| |
| return sb.toString(); |
| } |
| |
| if (!isContainer()) |
| return null; |
| |
| Splay last = getFinishSplay(); |
| |
| int scrubState = START_STATE; |
| |
| StringBuffer sb = new StringBuffer(); |
| |
| for ( Splay s = this ; s != last ; s = s.nextSplay() ) |
| { |
| if (s.isBegin()) |
| { |
| if (s.isInvalid()) |
| s.ensureContentValid(); |
| |
| scrubState = |
| scrubText( |
| r._text, ws, cp, s.getCch(), sb, scrubState ); |
| } |
| else |
| { |
| // It's ok for an attr to be invalid here, gets text after |
| |
| int cchAfter = s.getCchAfter(); |
| |
| if (cchAfter > 0) |
| { |
| scrubState = |
| scrubText( |
| r._text, ws, cp + s.getCchValue(), |
| cchAfter, sb, scrubState ); |
| } |
| } |
| |
| cp += s.getCch(); |
| } |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Gets an attr splay for a given container, null otherwise. |
| */ |
| |
| Splay getAttr ( QName name ) |
| { |
| assert name != null; |
| |
| if (!isContainer()) |
| return null; |
| |
| for ( Splay s = nextSplay() ; s.isAttr() ; s = s.nextSplay() ) |
| { |
| if (s.isNormalAttr() && s.getName().equals( name )) |
| return s; |
| } |
| |
| return null; |
| } |
| |
| void setXsiNil ( Root r, boolean nil ) |
| { |
| assert isContainer(); |
| |
| if (getXsiNil( r ) == nil) |
| return; |
| |
| setAttr( r, _xsiNil, "true" ); |
| } |
| |
| boolean getXsiNil ( Root r ) |
| { |
| assert isContainer(); |
| |
| Splay s; |
| |
| for ( s = nextSplay() ; s.isAttr() ; s = s.nextSplay() ) |
| { |
| if (s.isXsiNil()) |
| break; |
| } |
| |
| if (!s.isAttr()) |
| return false; |
| |
| String value = s.getText( r, TypeStore.WS_COLLAPSE ); |
| |
| return value.equals( "true" ) ||value.equals( "1" ); |
| } |
| |
| QName getXsiTypeName ( Root r ) |
| { |
| assert isContainer(); |
| |
| Splay s; |
| |
| for ( s = nextSplay() ; s.isAttr() ; s = s.nextSplay() ) |
| { |
| if (s.isXsiType()) |
| break; |
| } |
| |
| if (!s.isAttr()) |
| return null; |
| |
| assert s.isXsiType(); |
| |
| String value = s.getText( r, TypeStore.WS_COLLAPSE ); |
| |
| // TODO - unobscure the underlying text and use it directly |
| // TODO - should I make sure the prefix is wqell formed? ie. just text |
| |
| String prefix, localname; |
| |
| int firstcolon = value.indexOf( ':' ); |
| |
| if (firstcolon >= 0) |
| { |
| prefix = value.substring( 0, firstcolon ); |
| localname = value.substring( firstcolon + 1 ); |
| } |
| else |
| { |
| prefix = ""; |
| localname = value; |
| } |
| |
| String uri = namespaceForPrefix( prefix, true ); |
| |
| if (uri == null) |
| return null; // no prefix definition found - that's illegal |
| |
| return new QName( uri, localname ); |
| } |
| |
| void setXsiType ( Root r, QName typeName ) |
| { |
| assert isContainer(); |
| |
| if (typeName == null) |
| { |
| removeAttr( r, _xsiType ); |
| return; |
| } |
| |
| String value = typeName.getLocalPart(); |
| |
| String ns = typeName.getNamespaceURI(); |
| |
| String prefix = prefixForNamespace( r, ns, null, true); |
| |
| assert prefix != null : "Cannot establish prefix for " + ns; |
| |
| if (prefix.length() > 0) |
| value = prefix + ":" + value; |
| |
| boolean set = false; |
| |
| setAttr( r, _xsiType, value ); |
| } |
| |
| void removeAttr ( Root r, QName attrName ) |
| { |
| Splay next; |
| for ( Splay s = nextSplay() ; s.isAttr() ; s = next ) |
| { |
| next = s.nextSplay(); |
| |
| if (s.getName().equals( attrName )) |
| s.remove( r, true ); |
| } |
| } |
| |
| void setAttr ( Root r, QName attrName, String value ) |
| { |
| assert isContainer(); |
| assert attrName != null; |
| assert value != null; |
| |
| boolean set = false; |
| |
| Splay next; |
| for ( Splay s = nextSplay() ; s.isAttr() ; s = next ) |
| { |
| next = s.nextSplay(); |
| |
| if (s.getName().equals( attrName )) |
| { |
| if (set) |
| s.remove( r, true ); |
| else |
| { |
| s.setText( r, value, 0, value.length() ); |
| set = true; |
| } |
| } |
| } |
| |
| if (!set) |
| { |
| Splay sInsert = this; |
| int pInsert = 1; |
| |
| if (!isLeaf() && getCch() == 0) |
| { |
| sInsert = nextSplay(); |
| pInsert = 0; |
| } |
| |
| int cchValue = value.length(); |
| |
| Attr attr = new Attr( attrName ); |
| attr.adjustCch( cchValue ); |
| |
| sInsert.insert( r, pInsert, attr, value, 0, cchValue, false ); |
| } |
| } |
| |
| |
| /** |
| * Sets the content/value of this splay to the given text. |
| */ |
| |
| final void setText ( Root r, Object txt, int off, int cch ) |
| { |
| removeContent( r, false ); |
| |
| if (txt == null || cch == 0) |
| return; |
| |
| switch ( getKind() ) |
| { |
| case DOC : |
| case BEGIN : |
| { |
| assert !isBegin() || isLeaf(); |
| insertChars( 1, r, txt, off, cch ); |
| break; |
| } |
| case ATTR : |
| { |
| if (isXmlns()) |
| { |
| assert false: "Unexpected kind for setText"; |
| } |
| // TODO - if setting text of xsi:type or xsi:nil must do stuff |
| // TODO - if setting text of xsi:type or xsi:nil must do stuff |
| // TODO - if setting text of xsi:type or xsi:nil must do stuff |
| // TODO - if setting text of xsi:type or xsi:nil must do stuff |
| |
| // Fall through |
| } |
| case COMMENT : |
| case PROCINST : |
| { |
| r.startChange(); |
| |
| assert getCchValue() == 0; |
| |
| r._text.insert( r.getCp( this ), txt, off, cch ); |
| r.updateCch( this, cch ); |
| |
| if (getKind() == ATTR) |
| { |
| invalidateText(); |
| |
| if (isXsiType()) |
| getContainer().disconnectTypes( r ); |
| } |
| |
| r.invalidateVersion(); |
| |
| break; |
| } |
| default : |
| assert false: "Unexpected kind for setText"; |
| } |
| |
| assert validate(); |
| } |
| |
| final Map lookupAllPrefixMappings ( ) |
| { |
| Map mappings = null; |
| |
| Splay container = this.isContainer() ? this : getContainer(); |
| |
| for ( ; container != null ; container = container.getContainer() ) |
| { |
| for ( Splay s = container.nextSplay() ; s.isAttr() ; |
| s = s.nextSplay() ) |
| { |
| if (s.isXmlns()) |
| { |
| if (mappings == null) |
| mappings = new HashMap(); |
| else if (mappings.containsKey( s.getLocal() )) |
| continue; |
| |
| mappings.put( s.getLocal(), s.getUri() ); |
| } |
| } |
| } |
| |
| return mappings; |
| } |
| |
| final void applyNamespaces ( Root r, Map namespaces ) |
| { |
| if (namespaces == null) |
| return; |
| |
| Splay container = this.isContainer() ? this : getContainer(); |
| |
| namespace_loop: |
| for ( Iterator i = namespaces.keySet().iterator() ; i.hasNext() ; ) |
| { |
| String prefix = (String) i.next(); |
| |
| // Usually, this is the predefined xml namespace |
| if (prefix.toLowerCase().startsWith( "xml" )) |
| continue; |
| |
| String namespace = (String) namespaces.get( prefix ); |
| |
| Splay candidate = container; |
| |
| candidate_loop: |
| for ( Splay c = container.getContainer() ; c != null && !c.isDoc() ; |
| c = c.getContainer() ) |
| { |
| for ( Splay s = c.nextSplay() ; s.isAttr() ; s = s.nextSplay() ) |
| { |
| if (s.isXmlns() && prefix.equals( s.getLocal() )) |
| { |
| if (namespace.equals( s.getUri())) |
| continue namespace_loop; |
| |
| break candidate_loop; |
| } |
| } |
| |
| candidate = c; |
| } |
| |
| // Insert the new namespace at the end of the attr list after |
| // makeing sure all previous mappings are gone |
| QName qname = new QName( namespace, prefix ); |
| |
| removeAttr( r, qname ); |
| |
| candidate.nextNonAttrSplay().insert( |
| r, 0, new Xmlns( qname ), null, 0, 0, false ); |
| } |
| } |
| |
| /** |
| * Returns the prefix associated with this attr/element. |
| */ |
| final String getPrefix ( Root r ) |
| { |
| assert isBegin() || isAttr(); |
| |
| if (isXmlns()) |
| return "xmlns"; |
| |
| // TODO - have a prefix goober to override the mapping on the frontier. |
| // Parser will add this when the natural prefix is not the real prefix. |
| |
| Splay c = isBegin() ? this : getContainer(); |
| |
| // special case |
| String ns = getUri(); |
| |
| // last argument is false, which means, don't modify the tree |
| return prefixForNamespace( r, ns, null, false ); |
| } |
| |
| /** |
| * Given a prefix, returns the namespace corresponding to |
| * the prefix at this location, or null if there is no mapping |
| * for this prefix. |
| * <p> |
| * prefix="" indicates the absence of a prefix. A return value |
| * of "" indicates the no-namespace, and should not be confused |
| * with a return value of null, which indicates an illegal |
| * state, where there is no mapping for the given prefix. |
| * <p> |
| * If the the default namespace is not explicitly mapped in the xml, |
| * the xml spec says that it should be mapped to the no-namespace. |
| * When the 'defaultAlwaysMapped' parameter is true, the default namepsace |
| * will return the no-namespace even if it is not explicity |
| * mapped, otherwise the default namespace will return null. |
| * <p> |
| * This function intercepts the built-in prefixes "xml" and |
| * "xmlns" and returns their well-known namespace URIs. |
| * |
| * @param prefix The prefix to look up. |
| * @param mapDefault If true, return the no-namespace for the default namespace if not set. |
| * @return The mapped namespace URI ("" if no-namespace), or null if no mapping. |
| */ |
| final String namespaceForPrefix ( String prefix, boolean defaultAlwaysMapped ) |
| { |
| // null same as "", means look up the default namespace |
| if (prefix == null) |
| prefix = ""; |
| |
| // handle built-in prefixes |
| if ("xml".equals(prefix)) |
| return _xml1998Uri; |
| if ("xmlns".equals(prefix)) |
| return _xmlnsUri; |
| |
| assert isContainer(); |
| |
| // find an xmlns decl |
| for ( Container c = (Container) this ; c != null ; c = c.getContainer()) |
| { |
| for ( Splay s = c.nextSplay() ; s.isAttr() ; s = s.nextSplay() ) |
| { |
| if (s.isXmlns() && prefix.equals( s.getLocal() )) |
| return s.getUri(); |
| } |
| } |
| |
| // CR135193: if defaultAlwaysMapped, return no-namespace when no default namespace is found |
| // otherwise, return null to indicate no default namespace was found. |
| if (defaultAlwaysMapped && prefix.length() == 0) { |
| return ""; |
| } |
| |
| // no namespace defn found: return null |
| return null; |
| } |
| |
| |
| /** |
| * Returns the prefix to use for a given namespace URI. Can either |
| * be allowed to modify the tree to ensure such a mapping (if r |
| * is non-null), or can be told to return null if there is no |
| * existing mapping (if r is null). |
| * <p> |
| * For the case where the tree may be modified, a suggested prefix |
| * can be supplied. The suggestion may be ignored for any reason, |
| * for example, if the prefix is already in-use. |
| * <p> |
| * Returns "xml" and "xmlns" for the well-known namespace URIs |
| * corresponding to those built-in prefixes. |
| * |
| * @param r should be null if you don't want to modify the tree. |
| * if a prefix can't be found, null will be returned |
| * rather than introducing an xmlns. |
| * @param ns the namespace to look for or introduce a mapping for. |
| * careful with "", because it may have nonlocal effects |
| * if somebody has overridden the default namespace. |
| * @param suggestion a suggested prefix to try first; may be null. |
| * |
| * @param createIfMissing true (normally it's true) if you want to create |
| * an xmlns declaration when one doesn't already exist for |
| * your requested URI. If you don't want to modify the tree, |
| * pass false. |
| */ |
| final String prefixForNamespace ( Root r, String ns, String suggestion, |
| boolean createIfMissing ) |
| { |
| // null same as empty string, means no-namespace |
| if (ns == null) |
| ns = ""; |
| |
| // special cases |
| if (_xml1998Uri.equals( ns )) |
| return "xml"; |
| if (_xmlnsUri.equals( ns )) |
| return "xmlns"; |
| |
| assert isContainer(); |
| |
| Container c; |
| Splay s; |
| |
| // |
| // Special handling is ns is not specified (no namespace) |
| // |
| |
| if (ns.length() == 0) |
| { |
| loop: |
| for ( c = (Container) this ; c != null ; c = c.getContainer() ) |
| { |
| for ( s = c.nextSplay() ; s.isAttr() ; s = s.nextSplay() ) |
| { |
| if (s.isXmlns() && s.getLocal().length() == 0) |
| { |
| if (s.getUri().length() == 0) |
| return s.getLocal(); |
| |
| break loop; |
| } |
| } |
| } |
| |
| // |
| // If there is no overridden default namespace in scope, |
| // return "" - the default default namespace is the no-namepsace. |
| // |
| |
| if ( c == null ) |
| return ""; |
| |
| // |
| // If we found a problematic default namespace but cannot modify |
| // the tree, return null - no prefix available. |
| // |
| |
| if ( !createIfMissing ) |
| return null; |
| |
| // |
| // There is a default namespace which maps to a 'real' namespace, |
| // create a default namespace on this container which is "". This |
| // can screw attributes and such, but, oh well. |
| // |
| |
| for ( s = nextSplay() ; s.isAttr() ; s = s.nextSplay() ) |
| { |
| if (s.isXmlns() && s.getLocal().length() == 0) |
| { |
| s.setName( r, new QName( "", "" ) ); |
| return s.getLocal(); |
| } |
| } |
| |
| // this is an xmlns="" declaration |
| |
| Attr a = new Xmlns( new QName( "", "" ) ); |
| |
| r.startChange(); |
| |
| r.insertSplay( a, this ); |
| |
| r.invalidateVersion(); |
| |
| return a.getLocal(); |
| } |
| |
| // |
| // the ordinary not-a-no-namespace case |
| // |
| |
| assert ns != null && ns.length() > 0; |
| |
| // |
| // look for an existing prefix for the requested URI |
| // |
| |
| for ( c = (Container) this ; c != null ; c = c.getContainer() ) |
| { |
| findxmlns: for ( s = c.nextSplay() ; s.isAttr() ; s = s.nextSplay() ) |
| { |
| if (s.isXmlns() && s.getUri().equals( ns )) |
| { |
| String result = s.getLocal(); |
| |
| // now check to verify that the prefix isn't masked |
| for ( Container c2 = (Container) this ; c2 != c ; c2 = c2.getContainer() ) |
| { |
| for ( Splay s2 = c2.nextSplay() ; s2.isAttr() ; s2 = s2.nextSplay() ) |
| { |
| if (s2.isXmlns() && s2.getLocal().equals(result)) |
| continue findxmlns; |
| } |
| } |
| |
| // not masked: OK. |
| return result; |
| } |
| } |
| } |
| |
| // |
| // We could not find an existing prefix in-scope that works. |
| // If !createIfMissing, don't modify the tree - return null instead. |
| // |
| |
| if (!createIfMissing) |
| return null; |
| |
| // |
| // We must add an xmlns:something="uri", so we must pick a prefix. |
| // We will use the suggestion if it's not already in scope and if |
| // it's not the empty string or an illegal prefix. |
| // |
| |
| if (suggestion != null) |
| { |
| if (suggestion.length() == 0 || suggestion.toLowerCase().startsWith( "xml" )) |
| suggestion = null; // bad suggestion |
| else |
| { |
| for ( c = (Container) this ; c != null ; c = c.getContainer() ) |
| { |
| for ( s = c.nextSplay() ; s.isAttr() ; s = s.nextSplay() ) |
| { |
| if (s.isXmlns() && |
| s.getLocal().equals( suggestion )) |
| { |
| suggestion = null; |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| // |
| // If no suggestion, come up with a safe prefix to use |
| // |
| |
| if (suggestion == null) |
| { |
| String base = QNameHelper.suggestPrefix(ns); |
| suggestion = base; |
| for ( int i = 1 ; ; i++ ) |
| { |
| loop: |
| for ( c = (Container) this ; c != null ; c = c.getContainer() ) |
| { |
| for ( s = c.nextSplay() ; s.isAttr() ; s = s.nextSplay() ) |
| { |
| if (s.isXmlns() && |
| s.getLocal().equals( suggestion )) |
| { |
| suggestion = null; |
| break loop; |
| } |
| } |
| } |
| |
| if (suggestion != null) |
| break; |
| |
| suggestion = base + i; |
| } |
| } |
| |
| Container target = null; |
| Container lastContainer = null; |
| |
| for ( c = (Container) this ; c != null ; c = c.getContainer() ) |
| { |
| lastContainer = c; |
| |
| if (c.isBegin()) |
| target = c; |
| } |
| |
| if (target == null) |
| target = lastContainer; |
| |
| Attr a = new Xmlns( new QName( ns, suggestion ) ); |
| |
| r.startChange(); |
| |
| r.insertSplay( a, target ); |
| |
| r.invalidateVersion(); |
| |
| return a.getLocal(); |
| } |
| |
| /** |
| * Gets the container associated with the content at pos in this |
| * splay. |
| */ |
| |
| final Container getContainer ( int p ) |
| { |
| assert p >= 0 && p <= getEndPos(); |
| assert p > 0 || !isDoc(); |
| |
| if (p == 0) |
| return getContainer(); |
| |
| if (isLeaf()) |
| return p < getPosAfter() ? (Container) this : getContainer(); |
| |
| if (isContainer()) |
| return (Container) this; |
| |
| if (isFinish()) |
| return getContainer().getContainer(); |
| |
| return getContainer(); |
| } |
| |
| /** |
| * Gets the splay which contains this splay. |
| */ |
| |
| abstract Container getContainer ( ); |
| |
| /** |
| * Helper fcn to locate the container this splay is container in. |
| * Should only be called by implementations of getContainer in |
| * cases where the container is not obviously computable. |
| */ |
| |
| Container findContainer ( ) |
| { |
| assert !isContainer(); |
| assert !isFinish(); |
| |
| Splay s = nextSplay(); |
| |
| while ( !s.isFinish() && !s.isBegin() ) |
| s = s.nextSplay(); |
| |
| return s.getContainer(); |
| } |
| |
| final void invalidateText ( ) |
| { |
| assert isValid(); |
| |
| Type t = peekType(); |
| |
| if (t != null) |
| { |
| assert isTypeable(); |
| |
| t.invalidateText(); |
| } |
| } |
| |
| final void invalidateNil ( ) |
| { |
| assert isTypeable(); |
| |
| if (!isAttr()) |
| { |
| Type t = peekType(); |
| |
| if (t != null) |
| t.invalidateNil(); |
| } |
| } |
| |
| protected final void disconnectTypes ( Root r ) |
| { |
| disconnectTypes( r, false ); |
| } |
| |
| protected final void disconnectTypes ( Root r, boolean disconnectDoc ) |
| { |
| assert isTypeable(); |
| |
| Splay last = isAttr() ? this : getFinishSplay(); |
| |
| Splay s = this; |
| |
| for ( ; ; ) |
| { |
| Type t = null; |
| |
| if (s.isNormalAttr() || s.isLeaf()) |
| { |
| t = s.peekType(); |
| } |
| else if (s.isFinish()) |
| { |
| Container c = s.getContainer(); |
| |
| if (!c.isDoc() || disconnectDoc) |
| t = c.peekType(); |
| } |
| |
| if (t != null) |
| { |
| Splay u = t.getSplay(); |
| |
| if (u.isInvalid()) |
| { |
| if (u.isAttr()) |
| t.validateValue( u, t ); |
| else |
| { |
| // The finish splay might get whacked |
| Splay f = u.getFinishSplay(); |
| |
| t.validateContent( u, t ); |
| |
| if (f == last) |
| last = f; |
| } |
| } |
| |
| t.disconnect( r ); |
| } |
| |
| if (s == last) |
| break; |
| |
| if (s.isContainer() && !s.isLeaf() && s.peekType() == null) |
| s = s.getFinishSplay(); |
| else |
| s = s.nextSplay(); |
| } |
| } |
| |
| /** |
| * Sets the type of of this |
| */ |
| |
| final void setType ( Root r, SchemaType sType ) |
| { |
| setType( r, sType, true ); |
| } |
| |
| final void setType ( Root r, SchemaType sType, boolean complain ) |
| { |
| assert isTypeable(); |
| assert sType != null; |
| |
| // |
| // Is the current type already this type? |
| // |
| |
| Type t = peekType(); |
| |
| if (t != null && t.get_schema_type() == sType) |
| return; |
| |
| // |
| // Can always set an arbitrary type at the doc level |
| // |
| |
| if (isDoc()) |
| { |
| disconnectTypes( r, true ); |
| |
| assert peekType() == null; |
| |
| new Type( r, sType, this ); |
| |
| assert validate(); |
| |
| return; |
| } |
| |
| // |
| // Gotta get the parent type to do anything further. If it |
| // can't be found, then barf |
| // |
| |
| Type parentType = getContainer().getType( r ); |
| |
| assert parentType != null; |
| |
| // |
| // You can set the attribute type, as long as it is the natural |
| // type. Attributes cannot have xsi:type |
| // |
| |
| if (isAttr()) |
| { |
| if (parentType.get_attribute_type( getName() ) != sType && complain) |
| { |
| throw new IllegalArgumentException( |
| "Can't set type of attribute to " + sType.toString() ); |
| } |
| |
| return; |
| } |
| |
| assert isBegin(); |
| |
| // |
| // Now, for interior types, I have to deal with xsi:type and |
| // whether or not the type is allowed at this point. |
| // |
| |
| // First check to see if this type can be here sans xsi:type. |
| // If so, make sure there is no xsi:type |
| |
| if (parentType.get_element_type( getName(), null ) == sType) |
| { |
| setXsiType( r, null ); |
| |
| disconnectTypes( r ); |
| |
| assert validate(); |
| |
| return; |
| } |
| |
| // If the desired type has no name, then it cannot be |
| // referenced via xsi:type |
| |
| QName typeName = sType.getName(); |
| |
| if (typeName == null) |
| { |
| if (complain) |
| { |
| throw new IllegalArgumentException( |
| "Can't set type of element, type is un-named" ); |
| } |
| |
| return; |
| } |
| |
| if (parentType.get_element_type( getName(), typeName ) != sType) |
| { |
| if (complain) |
| { |
| throw new IllegalArgumentException( |
| "Can't set type of element, invalid type" ); |
| } |
| |
| return; |
| } |
| |
| setXsiType( r, typeName ); |
| |
| disconnectTypes( r ); |
| |
| assert validate(); |
| } |
| |
| /** |
| * Get the Type associated with this splay. It does not attempt to create |
| * one and will return null is non exists. |
| */ |
| |
| final Type peekType ( ) |
| { |
| assert isTypeable(); |
| |
| Type type = null; |
| |
| if (_goobers != null) |
| { |
| for ( Goober g = firstGoober() ; g != null ; g = nextGoober( g ) ) |
| if (g.getKind() == TYPE) |
| return (Type) g; |
| } |
| |
| assert type != null || isValid(); |
| |
| return type; |
| } |
| |
| /** |
| * Get the Type associated with this splay. If non exists, it attempts |
| * to create one. Returns null if one could not be created. |
| */ |
| |
| final Type getType ( Root r ) |
| { |
| assert isTypeable(); |
| |
| Type type = peekType(); |
| |
| if (!isDoc() && type == null) |
| { |
| Type parentType = getContainer().getType( r ); |
| |
| assert parentType != null; |
| |
| // Defensive |
| if (parentType == null) |
| return null; |
| |
| TypeStoreUser newUser; |
| |
| if (isBegin()) |
| { |
| newUser = |
| parentType.create_element_user( |
| getName(), getXsiTypeName( r ) ); |
| } |
| else |
| { |
| assert isNormalAttr(); |
| |
| newUser = parentType.create_attribute_user( getName() ); |
| } |
| |
| assert newUser != null; |
| |
| // Defensive |
| if (newUser == null) |
| return null; |
| |
| type = new Type( r, newUser, this ); |
| } |
| |
| assert type != null; |
| |
| return type; |
| } |
| |
| /** |
| * Is this inside of the content of sRange? Does an inclusive check. |
| */ |
| |
| final boolean between ( Root r, int pThis, Splay sRange ) |
| { |
| assert Root.dv > 0 || getRootSlow() == r; |
| assert Root.dv > 0 || getRootSlow() == sRange.getRootSlow(); |
| |
| if (sRange.isDoc()) |
| return true; |
| |
| if (compare( r, pThis, sRange, 0 ) < 0) |
| return false; |
| |
| Splay sEnd; |
| int pEnd; |
| |
| assert !sRange.isDoc(); |
| |
| if (sRange.isLeaf()) |
| { |
| sEnd = sRange; |
| pEnd = sRange.getPosLeafEnd() + 1; |
| } |
| else if (sRange.isBegin()) |
| { |
| sEnd = sRange.getFinishSplay(); |
| pEnd = 1; |
| } |
| else |
| { |
| sEnd = sRange.nextSplay(); |
| pEnd = 0; |
| } |
| |
| return compare( r, pThis, sEnd, pEnd ) <= 0; |
| } |
| |
| /** |
| * Compare two positions. Attributes are a bitch. |
| */ |
| |
| final int compare ( Root r, int pThis, Splay sThat, int pThat ) |
| { |
| Splay sThis = this; |
| |
| assert Root.dv > 0 || sThis.getRootSlow() == r; |
| assert Root.dv > 0 || sThat.getRootSlow() == r; |
| assert pThis >= 0 && pThis <= sThis.getEndPos(); |
| assert pThat >= 0 && pThat <= sThat.getEndPos(); |
| |
| // Normalize positions |
| |
| if (pThis == sThis.getEndPos()) |
| { |
| if (sThis.isAttr()) |
| { |
| if ((sThis = sThis.nextSplay()).isAttr()) |
| pThis = 0; |
| else |
| { |
| sThis = sThis.prevNonAttrSplay(); |
| pThis = 1; |
| } |
| } |
| else |
| { |
| sThis = sThis.nextNonAttrSplay(); |
| pThis = 0; |
| } |
| } |
| |
| if (pThat == sThat.getEndPos()) |
| { |
| if (sThat.isAttr()) |
| { |
| if ((sThat = sThat.nextSplay()).isAttr()) |
| pThat = 0; |
| else |
| { |
| sThat = sThat.prevNonAttrSplay(); |
| pThat = 1; |
| } |
| } |
| else |
| { |
| sThat = sThat.nextNonAttrSplay(); |
| pThat = 0; |
| } |
| } |
| |
| assert pThis < sThis.getEndPos(); |
| assert pThat < sThat.getEndPos(); |
| |
| if (sThis == sThat) |
| return pThis < pThat ? -1 : pThis > pThat ? 1 : 0; |
| |
| // |
| |
| if (sThis.isAttr()) |
| { |
| if (sThat.isAttr()) |
| return compare( r, sThat ); |
| |
| if (sThis.prevNonAttrSplay() != sThat) |
| return compare( r, sThat ); |
| |
| return pThat == 0 ? 1 : -1; |
| } |
| else if (sThat.isAttr()) |
| { |
| assert !sThis.isAttr(); |
| |
| if (sThat.prevNonAttrSplay() != sThis) |
| return compare( r, sThat ); |
| |
| return pThis == 0 ? -1 : 1; |
| } |
| else |
| { |
| return compare( r, sThat ); |
| } |
| } |
| |
| private int compare ( Root r, Splay sThat ) |
| { |
| Splay sThis = this; |
| |
| if (sThis == sThat) |
| return 0; |
| |
| if (r.isLeftOnly()) |
| { |
| if (sThis.getCchLeft() < sThat.getCchLeft()) |
| return -1; |
| |
| if (sThis.getCchLeft() > sThat.getCchLeft()) |
| return 1; |
| |
| if (sThis.getCdocBeginLeft() < sThat.getCdocBeginLeft()) |
| return -1; |
| |
| if (sThis.getCdocBeginLeft() > sThat.getCdocBeginLeft()) |
| return 1; |
| |
| while ( sThis != r ) |
| { |
| sThis = sThis.nextSplay(); |
| |
| if (sThis == sThat) |
| return -1; |
| |
| if (sThis.getCch() > 0 || sThis.getCdocBegin() > 0) |
| break; |
| } |
| |
| return 1; |
| } |
| |
| if (sThat.isRoot()) |
| return -1; |
| |
| if (sThis.isRoot()) |
| return 1; |
| |
| sThis.splay( r, r ); |
| sThat.splay( r, sThis ); |
| |
| assert sThis._leftSplay == sThat || sThis._rightSplay == sThat; |
| |
| return sThis._leftSplay == sThat ? 1 : -1; |
| } |
| |
| final Splay nextNonAttrSplay ( ) |
| { |
| Splay s = nextSplay(); |
| |
| while ( s != null && s.isAttr() ) |
| s = s.nextSplay(); |
| |
| return s; |
| } |
| |
| final Splay prevNonAttrSplay ( ) |
| { |
| Splay s = prevSplay(); |
| |
| while ( s != null && s.isAttr() ) |
| s = s.prevSplay(); |
| |
| return s; |
| } |
| |
| final Splay nextSplay ( ) |
| { |
| Splay s = this; |
| Splay r = s._rightSplay; |
| |
| if (r != null) |
| { |
| for ( Splay l = s = r ; (l = l._leftSplay) != null ; ) |
| s = l; |
| |
| return s; |
| } |
| |
| for ( Splay p = s._parentSplay ; ; p = (s = p)._parentSplay ) |
| if (p == null || p._leftSplay == s) |
| return p; |
| } |
| |
| final Splay prevSplay ( ) |
| { |
| Splay s = this; |
| Splay l = s._leftSplay; |
| |
| if (l != null) |
| { |
| for ( Splay r = s = l ; (r = r._rightSplay) != null ; ) |
| s = r; |
| |
| return s; |
| } |
| |
| for ( Splay p = s._parentSplay ; ; p = (s = p)._parentSplay ) |
| if (p == null || p._rightSplay == s) |
| return p; |
| } |
| |
| final void rotateRight ( ) |
| { |
| assert _parentSplay._leftSplay == this; |
| |
| Splay p = _parentSplay; |
| Splay g = p._parentSplay; |
| |
| assert p != null; |
| |
| p._leftSplay = _rightSplay; |
| |
| if (_rightSplay != null) |
| _rightSplay._parentSplay = p; |
| |
| _rightSplay = p; |
| p._parentSplay = this; |
| _parentSplay = g; |
| |
| if (g != null) |
| { |
| if (g._leftSplay == p) |
| g._leftSplay = this; |
| else |
| g._rightSplay = this; |
| } |
| |
| p.adjustCchLeft( - getCchLeft() - getCch() ); |
| p.adjustCdocBeginLeft( - getCdocBeginLeft() - getCdocBegin() ); |
| } |
| |
| final void rotateLeft ( ) |
| { |
| assert _parentSplay._rightSplay == this; |
| |
| Splay p = _parentSplay; |
| Splay g = p._parentSplay; |
| |
| assert p != null; |
| |
| p._rightSplay = _leftSplay; |
| |
| if (_leftSplay != null) |
| _leftSplay._parentSplay = p; |
| |
| _leftSplay = p; |
| p._parentSplay = this; |
| _parentSplay = g; |
| |
| if (g != null) |
| { |
| if (g._leftSplay == p) |
| g._leftSplay = this; |
| else |
| g._rightSplay = this; |
| } |
| |
| adjustCchLeft( p.getCchLeft() + p.getCch() ); |
| adjustCdocBeginLeft( p.getCdocBeginLeft() + p.getCdocBegin() ); |
| } |
| |
| // |
| // Splay this so that it's parent is pStop. |
| // |
| // This version of splay is capable of splaying this to the top. There |
| // are two cases. One is there r is this and this is splayed to be |
| // the left child of r. The other case is where r is null. In this case |
| // the tree this is in is detached from the main document, and this will be |
| // splayed to the top of that subtree such that it's parent will be null. |
| // |
| |
| final void splay ( Root r, Splay pStop ) |
| { |
| assert pStop != null; |
| assert this != pStop; |
| assert Root.dv > 0 || r == null || getRootSlow() == r; |
| assert Root.dv > 0 || r == null || pStop.getRootSlow() == r; |
| assert r == null || r.validateSplayTree(); |
| assert !isRoot(); |
| |
| boolean rotated = false; |
| |
| for ( ; ; ) |
| { |
| assert _parentSplay != null; |
| |
| Splay p = _parentSplay; |
| |
| assert p != null; |
| |
| // If this is a child of the stopper, then we are done |
| |
| if (p == pStop) |
| break; |
| |
| Splay g = p._parentSplay; |
| |
| // If this is child of root, then simple rotate |
| |
| rotated = true; |
| |
| if (g == pStop) |
| { |
| if (p._leftSplay == this) |
| rotateRight(); |
| else |
| rotateLeft(); |
| |
| break; |
| } |
| |
| // Fancy splays |
| |
| if (g._leftSplay == p) |
| { |
| if (p._leftSplay == this) |
| p.rotateRight(); |
| else |
| rotateLeft(); |
| |
| rotateRight(); |
| } |
| else |
| { |
| if (p._rightSplay == this) |
| p.rotateLeft(); |
| else |
| rotateRight(); |
| |
| rotateLeft(); |
| } |
| } |
| |
| if (rotated && r != null) |
| r._leftOnly = false; |
| |
| assert r == null || r.validateSplayTree(); |
| } |
| |
| /** |
| * Remove this splay where root is the splay root of the tree this splay. |
| * This may be a ROOT splay |
| */ |
| |
| final Splay removeSplay ( Splay r ) |
| { |
| Root root = r.isRoot() ? (Root) r : null; |
| |
| assert r._parentSplay == null; |
| assert root == null || root.validateSplayTree(); |
| assert !isRoot(); |
| assert !isDoc(); |
| assert Root.dv > 0 || getSplayRootSlow() == r; |
| |
| // If the splay to be removed has no cch or cbegin, then when the |
| // tree is left only children, we need not splay. |
| |
| int cch = getCch(); |
| int cbegin = getCdocBegin(); |
| |
| if (root != null && root._leftOnly && cch == 0 && cbegin == 0) |
| { |
| assert _rightSplay == null; |
| assert _parentSplay != null; |
| |
| if ((_parentSplay._leftSplay = _leftSplay) != null) |
| _leftSplay._parentSplay = _parentSplay; |
| } |
| else |
| { |
| splay( root, r ); |
| |
| if (_leftSplay == null) |
| { |
| r.adjustCchLeft( - cch ); |
| r.adjustCdocBeginLeft( - cbegin ); |
| |
| if ((r._leftSplay = _rightSplay) != null) |
| _rightSplay._parentSplay = r; |
| } |
| else |
| { |
| Splay p = prevSplay(); |
| |
| p.splay( root, this ); |
| |
| assert p._rightSplay == null; |
| |
| r._leftSplay = p; |
| p._parentSplay = r; |
| r.adjustCchLeft( - cch ); |
| r.adjustCdocBeginLeft( - cbegin ); |
| p._rightSplay = _rightSplay; |
| |
| if (p._rightSplay != null) |
| p._rightSplay._parentSplay = p; |
| } |
| } |
| |
| _leftSplay = _rightSplay = _parentSplay = null; |
| adjustCchLeft( - getCchLeft() ); |
| adjustCdocBeginLeft( - getCdocBeginLeft() ); |
| |
| assert root == null || root.validateSplayTree(); |
| |
| return this; |
| } |
| |
| // |
| // The Zoo (more like a petting zoo now) |
| // |
| |
| static abstract class QNameSplay extends Splay |
| { |
| QNameSplay ( int kind, boolean is, QName name ) |
| { |
| super( kind, is ); |
| _name = name; |
| } |
| |
| final QName getName ( ) { return _name; } |
| final void changeName ( QName name ) { _name = name; } |
| |
| final String getUri ( ) { return _name.getNamespaceURI(); } |
| final String getLocal ( ) { return _name.getLocalPart(); } |
| |
| private QName _name; |
| } |
| |
| static abstract class Container extends QNameSplay |
| { |
| Container ( int kind, boolean is, QName name ) |
| { super( kind, is, name ); } |
| |
| abstract Finish getFinish ( ); |
| } |
| |
| static final class Doc extends Container |
| { |
| Doc ( Root r, QName name ) |
| { super( DOC, false, name ); _root = r; } |
| |
| Container getContainer ( ) { return null; } |
| Finish getFinish ( ) { return _root; } |
| |
| Root _root; |
| } |
| |
| static class Begin extends Container |
| { |
| Begin ( QName name, Container container ) |
| { super( BEGIN, false, name ); _container = container; } |
| |
| final Container getContainer ( ) { return _container; } |
| final Finish getFinish ( ) { return _end; } |
| |
| End _end; |
| Container _container; |
| } |
| |
| static abstract class Finish extends Splay |
| { |
| Finish ( int kind ) { super( kind, false ); } |
| } |
| |
| static final class End extends Finish |
| { |
| End ( Begin begin ) { super( END ); _begin = begin; } |
| |
| Container getContainer ( ) { return _begin; } |
| |
| final Begin _begin; |
| } |
| |
| static final class Comment extends Splay |
| { |
| Comment ( ) { super( COMMENT, false ); } |
| |
| Container getContainer ( ) { return findContainer(); } |
| } |
| |
| static final class Fragment extends Splay |
| { |
| Fragment ( ) { super( COMMENT, true ); } |
| |
| Container getContainer ( ) { return null; } |
| } |
| |
| static class Attr extends QNameSplay |
| { |
| Attr ( QName name ) { super( ATTR, false, name ); } |
| |
| Attr ( QName name, boolean isXmlns ) |
| { |
| super( ATTR, true, name ); |
| assert isXmlns; |
| } |
| |
| Container getContainer ( ) |
| { |
| for ( Splay s = prevSplay() ; ; s = s.prevSplay() ) |
| if (!s.isAttr()) |
| return (Container) s; |
| } |
| } |
| |
| static final class Xmlns extends Attr |
| { |
| Xmlns ( QName name ) { super( name, true ); } |
| } |
| |
| static final class Procinst extends QNameSplay |
| { |
| Procinst ( QName target ) |
| { |
| super( PROCINST, false, target ); |
| } |
| |
| Procinst ( String target ) |
| { |
| super( PROCINST, false, new QName( "", target ) ); |
| } |
| |
| Container getContainer ( ) { return findContainer(); } |
| } |
| |
| /** |
| * Every splay points to a collection of goobers. Goobers can be |
| * aggregated into groups which share the same state (kind and position). |
| */ |
| |
| static final int CURSOR = 0; |
| static final int TYPE = 1; |
| static final int ANNOTATION = 2; |
| static final int AGGREGATE = 3; |
| |
| static abstract class Goober extends Goobers |
| { |
| Goober ( Root r, int kind ) |
| { |
| _root = r; |
| _state = kind; |
| } |
| |
| final int getKind ( ) { return _state & 7; } |
| final Root getRoot ( ) { return _root; } |
| final Splay getSplay ( ) { return _splay; } |
| final int getPos ( ) { return _state >> 3; } |
| final int getState ( ) { return _state; } |
| |
| final boolean isAnnotation ( ) { return getKind() == ANNOTATION; } |
| |
| final void set ( Root r, Splay s, int p ) |
| { |
| assert s != null; |
| doSet( r, s, p ); |
| } |
| |
| private final void doSet ( Root r, Splay s, int p ) |
| { |
| assert s != null || p == 0; |
| assert Root.dv > 0 || s == null || r == s.getRootSlow(); |
| assert Root.dv > 0 || _splay == null || _root == _splay.getRootSlow(); |
| // assert Root.dv > 0 || _root == r || _splay.getRootSlow() != s.getRootSlow(); |
| |
| _root = r; |
| |
| if (_splay != s) |
| { |
| if (_splay != null) |
| remove(); |
| |
| if (s != null) |
| append( s ); |
| } |
| |
| assert p >= 0; |
| assert _splay == null || p <= _splay.getMaxPos(); |
| _state = p * 8 + (_state & 7); |
| } |
| |
| final void set ( Root r ) |
| { |
| assert r != null; |
| _root = r; |
| } |
| |
| final void set ( Splay s, int p ) { doSet( getRoot(), s, p ); } |
| final void set ( int p ) { doSet( getRoot(), getSplay(), p ); } |
| |
| final void set ( Goober g ) |
| { doSet( g.getRoot(), g.getSplay(), g.getPos() ); } |
| |
| void disconnect ( Root r ) |
| { |
| doSet( r, null, 0 ); |
| } |
| |
| void release ( ) |
| { |
| set( null, 0); |
| _root = null; |
| } |
| |
| private final void append ( Splay s ) |
| { |
| assert s != null; |
| assert Root.dv > 0 || s.getRootSlow() == _root; |
| assert _splay == null; |
| assert _parent == null; |
| assert _next == null && _prev == null; |
| |
| if (s._goobers == null) |
| { |
| s._goobers = this; |
| _next = _prev = this; |
| } |
| else |
| { |
| _next = s._goobers; |
| _prev = _next._prev; |
| _next._prev = this; |
| _prev._next = this; |
| } |
| |
| _splay = s; |
| _parent = s; |
| } |
| |
| private final void remove ( ) |
| { |
| assert _splay != null; |
| assert _goobers == null; |
| |
| if (_next == this) |
| { |
| assert _parent._goobers == this; |
| |
| _parent._goobers = null; |
| |
| // If parent is an aggregate goober, remove it too |
| |
| if (_parent != _splay) |
| { |
| Goober g = (Goober) _parent; |
| assert g.getKind() == AGGREGATE; |
| g.remove(); |
| } |
| } |
| else |
| { |
| _prev._next = _next; |
| _next._prev = _prev; |
| |
| if (_parent._goobers == this) |
| _parent._goobers = _next; |
| } |
| |
| _next = _prev = null; |
| |
| _parent = null; |
| _splay = null; |
| } |
| |
| XmlBookmark getBookmark ( ) |
| { |
| return |
| getKind() == ANNOTATION |
| ? ((Annotation) this).getXmlBookmark() |
| : null; |
| } |
| |
| String getKindName ( ) |
| { |
| switch ( getKind() ) |
| { |
| case CURSOR : return "CURSOR"; |
| case TYPE : return "TYPE"; |
| case ANNOTATION : return "ANNOTATION"; |
| default : return "<unknow goober kind>"; |
| } |
| } |
| |
| private Root _root; |
| |
| private Splay _splay; |
| |
| private int _state; // pos and kind |
| |
| Goober _next; |
| Goober _prev; |
| Goobers _parent; |
| } |
| |
| static final class CursorGoober extends Goober |
| { |
| CursorGoober ( Root r ) |
| { |
| super( r, CURSOR ); |
| } |
| |
| private static final HashMap createDebugIdMap ( ) |
| { |
| // Creepy way to discover is assert is enabled. |
| HashMap map = null; |
| assert (map = new HashMap()) != null; |
| return map; |
| } |
| |
| public int getDebugId ( ) |
| { |
| if (_debugIds == null) |
| return -1; |
| |
| synchronized ( _debugIds ) |
| { |
| if (!_debugIds.containsKey( this )) |
| _debugIds.put( this, new Integer( _nextDebugId++ ) ); |
| |
| return ((Integer) _debugIds.get( this )).intValue(); |
| } |
| } |
| |
| private static final HashMap _debugIds = createDebugIdMap(); |
| private static int _nextDebugId = 1; |
| } |
| |
| static class Annotation extends Goober implements XmlMark |
| { |
| public Object monitor() |
| { |
| return getRoot(); |
| } |
| |
| Annotation ( Root r ) |
| { |
| super( r, ANNOTATION ); |
| } |
| |
| Annotation ( Root r, XmlBookmark a ) |
| { |
| this( r ); |
| |
| if (a._ref == null) |
| _annotation = a; |
| else |
| _ref = a._ref; |
| |
| _key = a.getKey(); |
| } |
| |
| void setKey ( Object key ) |
| { |
| _key = key; |
| } |
| |
| // called via bookmark (public via XmlMark) |
| public final XmlCursor createCursor ( ) |
| { |
| synchronized (monitor()) |
| { |
| if (getSplay() == null) |
| { |
| throw new IllegalStateException( |
| "Attempting to create a cursor on a bookmark that " + |
| "has been cleared or replaced."); |
| } |
| |
| return new Cursor( getRoot(), getSplay(), getPos() ); |
| } |
| } |
| |
| public XmlBookmark getXmlBookmark ( ) |
| { |
| if (_annotation != null) |
| return _annotation; |
| |
| if (_ref != null) |
| return (XmlBookmark) _ref.get(); |
| |
| return null; |
| } |
| |
| void disconnect ( Root r ) |
| { |
| super.disconnect( r ); |
| |
| XmlBookmark xa = getXmlBookmark(); |
| |
| if (xa != null) |
| xa._currentMark = null; |
| } |
| |
| XmlBookmark _annotation; |
| Reference _ref; |
| |
| Object _key; |
| } |
| |
| final Goober firstGoober ( ) |
| { |
| if (_goobers == null || _goobers.getKind() != AGGREGATE) |
| return _goobers; |
| |
| assert _goobers._goobers != null; |
| assert _goobers._goobers.getKind() != AGGREGATE; |
| |
| return _goobers._goobers; |
| } |
| |
| final Goober nextGoober ( Goober g ) |
| { |
| assert g != null; |
| |
| if ((g = g._next) != g._parent._goobers) |
| return g; |
| |
| return g._parent == g._splay ? null : nextGoober( (Goober) g._parent ); |
| } |
| |
| static final boolean _assertEnabled = getAssertEnabled(); |
| |
| private static boolean getAssertEnabled ( ) |
| { |
| // Creepy way to discover is assert is enabled. |
| boolean enabled = false; |
| assert (enabled = true) || true; |
| return enabled; |
| } |
| |
| static void assertAssertEnabled ( ) |
| { |
| if (!_assertEnabled) |
| { |
| throw |
| new RuntimeException( |
| "Assert needs to be enabled for this operation" ); |
| } |
| } |
| |
| // Associate id's with splays when asserts are enabled |
| |
| private static final HashMap _debugIds = |
| _assertEnabled ? new HashMap() : null; |
| |
| private static int _nextDebugId = 1; |
| |
| public int getDebugId ( ) |
| { |
| if (_debugIds == null) |
| return -1; |
| |
| synchronized ( _debugIds ) |
| { |
| if (!_debugIds.containsKey( this )) |
| _debugIds.put( this, new Integer( _nextDebugId++ ) ); |
| |
| return ((Integer) _debugIds.get( this )).intValue(); |
| } |
| } |
| |
| void dump ( ) { getRootSlow().dump(); } |
| void dump ( boolean verbose ) { getRootSlow().dump( verbose ); } |
| |
| public Splay getSplayRootSlow ( ) |
| { |
| assertAssertEnabled(); |
| |
| Splay s = this; |
| |
| while ( s._parentSplay != null ) |
| s = s._parentSplay; |
| |
| return s; |
| } |
| |
| public Root getRootSlow ( ) |
| { |
| assertAssertEnabled(); |
| |
| Splay s = getSplayRootSlow(); |
| |
| return s.isRoot() ? (Root) s : null; |
| } |
| |
| int getCpSlow ( ) |
| { |
| assertAssertEnabled(); |
| |
| int cch = 0; |
| |
| for ( Splay s = this ; (s = s.prevSplay()) != null ; ) |
| cch += s.getCch(); |
| |
| return cch; |
| } |
| |
| int compareSlow ( Splay sThat ) |
| { |
| assertAssertEnabled(); |
| |
| assert Root.dv > 0 || getRootSlow() == sThat.getRootSlow(); |
| |
| if (this == sThat) |
| return 0; |
| |
| for ( Splay s = this ; s != null ; s = s.nextSplay() ) |
| if (s == sThat) |
| return -1; |
| |
| for ( Splay s = this ; s != null ; s = s.prevSplay() ) |
| if (s == sThat) |
| return 1; |
| |
| assert false: "Yikes!"; |
| |
| return 0; |
| } |
| |
| boolean validate ( ) |
| { |
| assertAssertEnabled(); |
| |
| return getRootSlow().validate(); |
| } |
| |
| // |
| // Document splay tree members |
| // |
| |
| Splay _leftSplay; |
| Splay _rightSplay; |
| Splay _parentSplay; |
| |
| private int _bits; // 27: cDocbeginLeft, 1: invalid, 1: multi, 3: kind |
| private int _cch; |
| private int _cchAfter; |
| private int _cchLeft; |
| } |