blob: e38b4e989557f4aa73e5c27cab619c52cdc2c8a6 [file] [log] [blame]
/* 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 org.apache.xmlbeans.XmlBeans;
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 ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(isBegin());
_bits ^= 0x8;
}
final boolean isValid ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(((_bits & 0x10) == 0) || isTypeable());
return (_bits & 0x10) == 0;
}
final boolean isInvalid ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(((_bits & 0x10) == 0) || isTypeable());
return (_bits & 0x10) != 0;
}
final void toggleIsInvalid ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(isTypeable());
_bits ^= 0x10;
}
final void adjustCch ( int delta )
{
_cch += delta;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(_cch >= 0);
}
final void adjustCchAfter ( int delta )
{
_cchAfter += delta;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(_cchAfter >= 0);
}
final void adjustCchLeft ( int delta )
{
_cchLeft += delta;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(_cchLeft >= 0);
}
final void adjustCdocBeginLeft ( int d )
{
_bits += d * 32;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(getCdocBeginLeft() >= 0);
}
final Splay getFinishSplay ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(isContainer());
return isLeaf() ? this : ((Container) this).getFinish();
}
final int getPosAfter ( )
{
return getEndPos() - getCchAfter();
}
final int getPosLeafEnd ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 :
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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())
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 :
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(isBegin());
XmlBeans.assertTrue(!isLeaf());
}
Begin b = (Begin) this;
Splay e = b.getFinish();
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(b.nextNonAttrSplay() == e);
XmlBeans.assertTrue(b._end == e);
XmlBeans.assertTrue(e.isEnd());
XmlBeans.assertTrue(!b.isLeaf());
// Caller must initiate change
XmlBeans.assertTrue(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 );
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue( isLeaf() );
// Caller must initiate change
XmlBeans.assertTrue( r.validateChangeStarted() );
// Caller must ensure leaf is valid
XmlBeans.assertTrue( 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 );
}
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue( validate() );
}
/**
* Remove the text after *this* splay.
* No validation is done, caller must do that
*/
private final void removeTextAfter ( Root r )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue( r != null );
XmlBeans.assertTrue( Root.dv > 0 || getRootSlow() == r );
XmlBeans.assertTrue( !isRoot() && !isAttr() );
// Caller must initiate change
XmlBeans.assertTrue( 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 );
}
}
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(r != null);
XmlBeans.assertTrue(Root.dv > 0 || getRootSlow() == r);
// Caller must initiate change
XmlBeans.assertTrue(r.validateChangeStarted());
}
Splay endText = this;
if (isBegin() && !isLeaf())
endText = getFinishSplay();
int cchEndText = endText.getCchAfter();
if (cchEndText == 0)
return 0;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(!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 );
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 );
}
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(r != null);
XmlBeans.assertTrue(Root.dv > 0 || getRootSlow() == r);
XmlBeans.assertTrue(!isDoc() && !isFinish());
XmlBeans.assertTrue(rDst == null || (r != rDst || !sDst.between( r, pDst, this ) ));
XmlBeans.assertTrue(rDst == null || sDst != null);
XmlBeans.assertTrue(rDst == null || Root.dv > 0 || sDst.getRootSlow() == rDst);
XmlBeans.assertTrue(rDst == null || pDst >= 0 && pDst < sDst.getEndPos());
XmlBeans.assertTrue(
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()));
}
}
}
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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())
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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();
}
}
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(r.validate());
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(p >= 0);
XmlBeans.assertTrue(p < getEndPos());
XmlBeans.assertTrue(pDst >= 0);
XmlBeans.assertTrue(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
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(p < getEndPos());
XmlBeans.assertTrue(cchTxt >= 0);
XmlBeans.assertTrue(rootInsert != null);
XmlBeans.assertTrue(rootInsert._parentSplay == null);
XmlBeans.assertTrue(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.
XmlBeans.assertTrue(
rootInsert._leftSplay == null ||
rootInsert._leftSplay._leftSplay == null);
}
r.startChange();
Splay sInsert =
rootInsert._leftSplay == null ? rootInsert : rootInsert._leftSplay;
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(!sInsert.isDoc() && !sInsert.isFinish());
XmlBeans.assertTrue(Root.dv > 0 || getRootSlow() == r);
// When this member is called, we better be able to do the insert.
XmlBeans.assertTrue(true || sInsert.checkInsertionValidity( 0, this, p, false ));
// If there is a fragment, there better be text in it
XmlBeans.assertTrue(!sInsert.isFragment() || sInsert.getCch() > 0);
}
//
Splay s = this;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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())
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(ple == 1);
XmlBeans.assertTrue(p == 1);
}
int cchValid = s.ensureContentValid();
p += cchValid;
ple += cchValid;
}
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(s.getFinishSplay() == s);
defoliate( r );
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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.
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(!sInsert.isAttr() || (p == 1 && s.isContainer()));
sOrig = s;
pOrig = p;
cchBypassed = getEndPos() - p;
s = s.nextNonAttrSplay();
p = 0;
}
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue( p == 0);
XmlBeans.assertTrue(!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;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(b._container == null);
b._container = c;
t = t.getFinishSplay();
}
else if (t.isFragment())
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 );
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(cchMoved > 0);
XmlBeans.assertTrue(cchMoved == cchBypassed);
}
}
// build a leaf if possible
if (s.isEnd() && s.getContainer().nextNonAttrSplay() == s)
s.getContainer().foliate( r );
r.invalidateVersion();
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(!isFinish() && !sSrc.isFinish());
XmlBeans.assertTrue(Root.dv > 0 || getRootSlow() == r);
XmlBeans.assertTrue(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
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 );
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(isLeaf() || getFinishSplay() == nextSplay());
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(!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();
}
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(cch > 0);
Splay s = new Fragment();
s.adjustCchAfter( cch );
s.adjustCch( cch );
copy( s );
}
Splay getTree ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(_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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(!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
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(!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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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())
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 :
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(false, "Unexpected kind for removeContent");
}
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(isDoc() || (isBegin() && !isLeaf()));
Splay s = nextNonAttrSplay();
if (s.isRoot())
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(!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.
//
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(!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;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(validate());
}
final int copyChars (
Root r, int p, int cch, Root rDst, Splay sDst, int pDst )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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();
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 );
}
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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();
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(k == ANNOTATION);
g.disconnect( r );
}
}
}
invalidateText();
r.invalidateVersion();
return cch;
}
}
int posAfter = getPosAfter();
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(p >= posAfter);
int maxCch = maxPos - p + 1;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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();
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(validate());
return cch;
}
/**
* Insert text a position p in this splay
*/
final void insertChars ( int p, Root r, Object txt, int off, int cch )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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();
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(text != null);
if (text._buf == null)
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(cch == 0);
XmlBeans.assertTrue(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 : if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(isContainer());
if (getXsiNil( r ) == nil)
return;
setAttr( r, _xsiNil, "true" );
}
boolean getXsiNil ( Root r )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(isContainer());
Splay s;
for ( s = nextSplay() ; s.isAttr() ; s = s.nextSplay() )
{
if (s.isXsiType())
break;
}
if (!s.isAttr())
return null;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(isContainer());
if (typeName == null)
{
removeAttr( r, _xsiType );
return;
}
String value = typeName.getLocalPart();
String ns = typeName.getNamespaceURI();
String prefix = prefixForNamespace( r, ns, null, true);
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(prefix != null, "Cannot establish prefix for ".concat(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 )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(isContainer());
XmlBeans.assertTrue(attrName != null);
XmlBeans.assertTrue(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 :
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(!isBegin() || isLeaf());
insertChars( 1, r, txt, off, cch );
break;
}
case ATTR :
{
if (isXmlns())
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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();
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 :
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(false, "Unexpected kind for setText");
}
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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";
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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
//
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(p >= 0 && p <= getEndPos());
XmlBeans.assertTrue(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 ( )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(!isContainer());
XmlBeans.assertTrue(!isFinish());
}
Splay s = nextSplay();
while ( !s.isFinish() && !s.isBegin() )
s = s.nextSplay();
return s.getContainer();
}
final void invalidateText ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(isValid());
Type t = peekType();
if (t != null)
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(isTypeable());
t.invalidateText();
}
}
final void invalidateNil ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(isTypeable());
XmlBeans.assertTrue(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 );
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(peekType() == null);
new Type( r, sType, this );
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(validate());
return;
}
//
// Gotta get the parent type to do anything further. If it
// can't be found, then barf
//
Type parentType = getContainer().getType( r );
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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;
}
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 );
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 );
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(isTypeable());
Type type = null;
if (_goobers != null)
{
for ( Goober g = firstGoober() ; g != null ; g = nextGoober( g ) )
if (g.getKind() == TYPE)
return (Type) g;
}
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(isTypeable());
Type type = peekType();
if (!isDoc() && type == null)
{
Type parentType = getContainer().getType( r );
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(parentType != null);
// Defensive
if (parentType == null)
return null;
TypeStoreUser newUser;
if (isBegin())
{
newUser =
parentType.create_element_user(
getName(), getXsiTypeName( r ) );
}
else
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(isNormalAttr());
newUser = parentType.create_attribute_user( getName() );
}
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(newUser != null);
// Defensive
if (newUser == null)
return null;
type = new Type( r, newUser, this );
}
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(Root.dv > 0 || getRootSlow() == r);
XmlBeans.assertTrue(Root.dv > 0 || getRootSlow() == sRange.getRootSlow());
}
if (sRange.isDoc())
return true;
if (compare( r, pThis, sRange, 0 ) < 0)
return false;
Splay sEnd;
int pEnd;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(!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;
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(Root.dv > 0 || sThis.getRootSlow() == r);
XmlBeans.assertTrue(Root.dv > 0 || sThat.getRootSlow() == r);
XmlBeans.assertTrue(pThis >= 0 && pThis <= sThis.getEndPos());
XmlBeans.assertTrue(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;
}
}
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(pThis < sThis.getEndPos());
XmlBeans.assertTrue(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())
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(!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 );
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(_parentSplay._leftSplay == this);
Splay p = _parentSplay;
Splay g = p._parentSplay;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 ( )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(_parentSplay._rightSplay == this);
Splay p = _parentSplay;
Splay g = p._parentSplay;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(pStop != null);
XmlBeans.assertTrue(this != pStop);
XmlBeans.assertTrue(Root.dv > 0 || r == null || getRootSlow() == r);
XmlBeans.assertTrue(Root.dv > 0 || r == null || pStop.getRootSlow() == r);
XmlBeans.assertTrue(r == null || r.validateSplayTree());
XmlBeans.assertTrue(!isRoot());
}
boolean rotated = false;
for ( ; ; )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(_parentSplay != null);
Splay p = _parentSplay;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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;
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(r._parentSplay == null);
XmlBeans.assertTrue(root == null || root.validateSplayTree());
XmlBeans.assertTrue(!isRoot());
XmlBeans.assertTrue(!isDoc());
XmlBeans.assertTrue(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)
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(_rightSplay == null);
XmlBeans.assertTrue(_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 );
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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() );
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 );
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(s != null);
doSet( r, s, p );
}
private final void doSet ( Root r, Splay s, int p )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(s != null || p == 0);
XmlBeans.assertTrue(Root.dv > 0 || s == null || r == s.getRootSlow());
XmlBeans.assertTrue(Root.dv > 0 || _splay == null || _root == _splay.getRootSlow());
// XmlBeans.assertTrue(Root.dv > 0 || _root == r || _splay.getRootSlow() != s.getRootSlow());
}
_root = r;
if (_splay != s)
{
if (_splay != null)
remove();
if (s != null)
append( s );
}
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(p >= 0);
XmlBeans.assertTrue(_splay == null || p <= _splay.getMaxPos());
}
_state = p * 8 + (_state & 7);
}
final void set ( Root r )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(s != null);
XmlBeans.assertTrue(Root.dv > 0 || s.getRootSlow() == _root);
XmlBeans.assertTrue(_splay == null);
XmlBeans.assertTrue(_parent == null);
XmlBeans.assertTrue(_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 ( )
{
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(_splay != null);
XmlBeans.assertTrue(_goobers == null);
}
if (_next == this)
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(_parent._goobers == this);
_parent._goobers = null;
// If parent is an aggregate goober, remove it too
if (_parent != _splay)
{
Goober g = (Goober) _parent;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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;
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue((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;
if (XmlBeans.ASSERTS)
{
XmlBeans.assertTrue(_goobers._goobers != null);
XmlBeans.assertTrue(_goobers._goobers.getKind() != AGGREGATE);
}
return _goobers._goobers;
}
final Goober nextGoober ( Goober g )
{
if (XmlBeans.ASSERTS)
XmlBeans.assertTrue(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 ( )
{
return XmlBeans.ASSERTS;
}
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();
XmlBeans.assertTrue(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;
XmlBeans.assertTrue(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;
}