com.sun.xml.stream.buffer.stax.StreamReaderBufferProcessor Maven / Gradle / Ivy
Show all versions of streambuffer Show documentation
/*
* Copyright (c) 2005, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package com.sun.xml.stream.buffer.stax;
import com.sun.xml.stream.buffer.AbstractProcessor;
import com.sun.xml.stream.buffer.AttributesHolder;
import com.sun.xml.stream.buffer.XMLStreamBuffer;
import com.sun.xml.stream.buffer.XMLStreamBufferMark;
import org.jvnet.staxex.NamespaceContextEx;
import org.jvnet.staxex.XMLStreamReaderEx;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.util.*;
/**
* A processor of a {@link XMLStreamBuffer} that reads the XML infoset as
* {@link XMLStreamReader}.
*
*
* Because of {@link XMLStreamReader} design, this processor always produce
* a full document infoset, even if the buffer just contains a fragment.
*
*
* When {@link XMLStreamBuffer} contains a multiple tree (AKA "forest"),
* {@link XMLStreamReader} will behave as if there are multiple root elements
* (so you'll see {@link #START_ELEMENT} event where you'd normally expect
* {@link #END_DOCUMENT}.)
*
* @author [email protected]
* @author [email protected]
*/
public class StreamReaderBufferProcessor extends AbstractProcessor implements XMLStreamReaderEx {
private static final int CACHE_SIZE = 16;
// Stack to hold element and namespace declaration information
private ElementStackEntry[] _stack = new ElementStackEntry[CACHE_SIZE];
/** The top-most active entry of the {@link #_stack}. */
private ElementStackEntry _stackTop;
/** The element depth that we are in. Used to determine when we are done with a tree. */
protected int _depth;
// Arrays to hold all namespace declarations
/**
* Namespace prefixes. Can be empty but not null.
*/
protected String[] _namespaceAIIsPrefix = new String[CACHE_SIZE];
protected String[] _namespaceAIIsNamespaceName = new String[CACHE_SIZE];
protected int _namespaceAIIsEnd;
// Internal namespace context implementation
private InternalNamespaceContext _nsCtx = new InternalNamespaceContext();
// The current event type
protected int _eventType;
/**
* Holder of the attributes.
*
* Be careful that this follows the SAX convention of using "" instead of null.
*/
protected AttributesHolder _attributeCache;
// Characters as a CharSequence
protected CharSequence _charSequence;
// Characters as a char array with offset and length
protected char[] _characters;
protected int _textOffset;
protected int _textLen;
protected String _piTarget;
protected String _piData;
//
// Represents the parser state wrt the end of parsing.
//
/**
* The parser is in the middle of parsing a document,
* with no end in sight.
*/
private static final int PARSING = 1;
/**
* The parser has already reported the {@link #END_ELEMENT},
* and we are parsing a fragment. We'll report {@link #END_DOCUMENT}
* next and be done.
*/
private static final int PENDING_END_DOCUMENT = 2;
/**
* The parser has reported the {@link #END_DOCUMENT} event,
* so we are really done parsing.
*/
private static final int COMPLETED = 3;
/**
* True if processing is complete.
*/
private int _completionState;
public StreamReaderBufferProcessor() {
super();
for (int i=0; i < _stack.length; i++){
_stack[i] = new ElementStackEntry();
}
_attributeCache = new AttributesHolder();
}
public StreamReaderBufferProcessor(XMLStreamBuffer buffer) throws XMLStreamException {
this();
setXMLStreamBuffer(buffer);
}
public void setXMLStreamBuffer(XMLStreamBuffer buffer) throws XMLStreamException {
setBuffer(buffer,buffer.isFragment());
_completionState = PARSING;
_namespaceAIIsEnd = 0;
_characters = null;
_charSequence = null;
_eventType = START_DOCUMENT;
}
/**
* Does {@link #nextTag()} and if the parser moved to a new start tag,
* returns a {@link XMLStreamBufferMark} that captures the infoset starting
* from the newly discovered element.
*
*
* (Ideally we should have a method that works against the current position,
* but the way the data structure is read makes this somewhat difficult.)
*
* This creates a new {@link XMLStreamBufferMark} that shares the underlying
* data storage, thus it's fairly efficient.
*/
public XMLStreamBuffer nextTagAndMark() throws XMLStreamException {
while (true) {
int s = peekStructure();
if((s &TYPE_MASK)==T_ELEMENT) {
// next is start element.
Map inscope = new HashMap<>(_namespaceAIIsEnd);
for (int i=0 ; i<_namespaceAIIsEnd; i++)
inscope.put(_namespaceAIIsPrefix[i],_namespaceAIIsNamespaceName[i]);
XMLStreamBufferMark mark = new XMLStreamBufferMark(inscope, this);
next();
return mark;
} else if((s &TYPE_MASK)==T_DOCUMENT) {
//move the pointer to next structure.
readStructure();
//mark the next start element
XMLStreamBufferMark mark = new XMLStreamBufferMark(new HashMap<>(_namespaceAIIsEnd), this);
next();
return mark;
}
if(next()==END_ELEMENT)
return null;
}
}
@Override
public Object getProperty(String name) {
return null;
}
@Override
public int next() throws XMLStreamException {
switch(_completionState) {
case COMPLETED:
throw new XMLStreamException("Invalid State");
case PENDING_END_DOCUMENT:
_namespaceAIIsEnd = 0;
_completionState = COMPLETED;
return _eventType = END_DOCUMENT;
}
// Pop the stack of elements
// This is a post-processing operation
// The stack of the element should be poppoed after
// the END_ELEMENT event is returned so that the correct element name
// and namespace scope is returned
switch(_eventType) {
case END_ELEMENT:
if (_depth > 1) {
_depth--;
// _depth index is always set to the next free stack entry
// to push
popElementStack(_depth);
} else if (_depth == 1) {
_depth--;
}
}
_characters = null;
_charSequence = null;
while(true) {// loop only if we read STATE_DOCUMENT
int eiiState = readEiiState();
switch(eiiState) {
case STATE_DOCUMENT:
// we'll always produce a full document, and we've already report START_DOCUMENT event.
// so simply skil this
continue;
case STATE_ELEMENT_U_LN_QN: {
final String uri = readStructureString();
final String localName = readStructureString();
final String prefix = getPrefixFromQName(readStructureString());
processElement(prefix, uri, localName, isInscope(_depth));
return _eventType = START_ELEMENT;
}
case STATE_ELEMENT_P_U_LN:
processElement(readStructureString(), readStructureString(), readStructureString(),isInscope(_depth));
return _eventType = START_ELEMENT;
case STATE_ELEMENT_U_LN:
processElement(null, readStructureString(), readStructureString(),isInscope(_depth));
return _eventType = START_ELEMENT;
case STATE_ELEMENT_LN:
processElement(null, null, readStructureString(),isInscope(_depth));
return _eventType = START_ELEMENT;
case STATE_TEXT_AS_CHAR_ARRAY_SMALL:
_textLen = readStructure();
_textOffset = readContentCharactersBuffer(_textLen);
_characters = _contentCharactersBuffer;
return _eventType = CHARACTERS;
case STATE_TEXT_AS_CHAR_ARRAY_MEDIUM:
_textLen = readStructure16();
_textOffset = readContentCharactersBuffer(_textLen);
_characters = _contentCharactersBuffer;
return _eventType = CHARACTERS;
case STATE_TEXT_AS_CHAR_ARRAY_COPY:
_characters = readContentCharactersCopy();
_textLen = _characters.length;
_textOffset = 0;
return _eventType = CHARACTERS;
case STATE_TEXT_AS_STRING:
_eventType = CHARACTERS;
_charSequence = readContentString();
return _eventType = CHARACTERS;
case STATE_TEXT_AS_OBJECT:
_eventType = CHARACTERS;
_charSequence = (CharSequence)readContentObject();
return _eventType = CHARACTERS;
case STATE_COMMENT_AS_CHAR_ARRAY_SMALL:
_textLen = readStructure();
_textOffset = readContentCharactersBuffer(_textLen);
_characters = _contentCharactersBuffer;
return _eventType = COMMENT;
case STATE_COMMENT_AS_CHAR_ARRAY_MEDIUM:
_textLen = readStructure16();
_textOffset = readContentCharactersBuffer(_textLen);
_characters = _contentCharactersBuffer;
return _eventType = COMMENT;
case STATE_COMMENT_AS_CHAR_ARRAY_COPY:
_characters = readContentCharactersCopy();
_textLen = _characters.length;
_textOffset = 0;
return _eventType = COMMENT;
case STATE_COMMENT_AS_STRING:
_charSequence = readContentString();
return _eventType = COMMENT;
case STATE_PROCESSING_INSTRUCTION:
_piTarget = readStructureString();
_piData = readStructureString();
return _eventType = PROCESSING_INSTRUCTION;
case STATE_END:
if (_depth > 1) {
// normal case
return _eventType = END_ELEMENT;
} else if (_depth == 1) {
// this is the last end element for the current tree.
if (_fragmentMode) {
if(--_treeCount==0) // is this the last tree in the forest?
_completionState = PENDING_END_DOCUMENT;
}
return _eventType = END_ELEMENT;
} else {
// this only happens when we are processing a full document
// and we hit the "end of document" marker
_namespaceAIIsEnd = 0;
_completionState = COMPLETED;
return _eventType = END_DOCUMENT;
}
default:
throw new XMLStreamException("Internal XSB error: Invalid State="+eiiState);
}
// this should be unreachable
}
}
@Override
public final void require(int type, String namespaceURI, String localName) throws XMLStreamException {
if( type != _eventType) {
throw new XMLStreamException("");
}
if( namespaceURI != null && !namespaceURI.equals(getNamespaceURI())) {
throw new XMLStreamException("");
}
if(localName != null && !localName.equals(getLocalName())) {
throw new XMLStreamException("");
}
}
@Override
public final String getElementTextTrim() throws XMLStreamException {
// TODO getElementText* methods more efficiently
return getElementText().trim();
}
@Override
public final String getElementText() throws XMLStreamException {
if(_eventType != START_ELEMENT) {
throw new XMLStreamException("");
}
next();
return getElementText(true);
}
public final String getElementText(boolean startElementRead) throws XMLStreamException {
if (!startElementRead) {
throw new XMLStreamException("");
}
int eventType = getEventType();
StringBuilder content = new StringBuilder();
while(eventType != END_ELEMENT ) {
if(eventType == CHARACTERS
|| eventType == CDATA
|| eventType == SPACE
|| eventType == ENTITY_REFERENCE) {
content.append(getText());
} else if(eventType == PROCESSING_INSTRUCTION
|| eventType == COMMENT) {
// skipping
} else if(eventType == END_DOCUMENT) {
throw new XMLStreamException("");
} else if(eventType == START_ELEMENT) {
throw new XMLStreamException("");
} else {
throw new XMLStreamException("");
}
eventType = next();
}
return content.toString();
}
@Override
public final int nextTag() throws XMLStreamException {
next();
return nextTag(true);
}
public final int nextTag(boolean currentTagRead) throws XMLStreamException {
int eventType = getEventType();
if (!currentTagRead) {
eventType = next();
}
while((eventType == CHARACTERS && isWhiteSpace()) // skip whitespace
|| (eventType == CDATA && isWhiteSpace())
|| eventType == SPACE
|| eventType == PROCESSING_INSTRUCTION
|| eventType == COMMENT) {
eventType = next();
}
if (eventType != START_ELEMENT && eventType != END_ELEMENT) {
throw new XMLStreamException("");
}
return eventType;
}
@Override
public final boolean hasNext() {
return (_eventType != END_DOCUMENT);
}
@Override
public void close() throws XMLStreamException {
}
@Override
public final boolean isStartElement() {
return (_eventType == START_ELEMENT);
}
@Override
public final boolean isEndElement() {
return (_eventType == END_ELEMENT);
}
@Override
public final boolean isCharacters() {
return (_eventType == CHARACTERS);
}
@Override
public final boolean isWhiteSpace() {
if(isCharacters() || (_eventType == CDATA)){
char [] ch = this.getTextCharacters();
int start = this.getTextStart();
int length = this.getTextLength();
for (int i = start; i < length; i++){
final char c = ch[i];
if (!(c == 0x20 || c == 0x9 || c == 0xD || c == 0xA))
return false;
}
return true;
}
return false;
}
@Override
public final String getAttributeValue(String namespaceURI, String localName) {
if (_eventType != START_ELEMENT) {
throw new IllegalStateException("");
}
if (namespaceURI == null) {
// Set to the empty string to be compatible with the
// org.xml.sax.Attributes interface
namespaceURI = "";
}
return _attributeCache.getValue(namespaceURI, localName);
}
@Override
public final int getAttributeCount() {
if (_eventType != START_ELEMENT) {
throw new IllegalStateException("");
}
return _attributeCache.getLength();
}
@Override
public final javax.xml.namespace.QName getAttributeName(int index) {
if (_eventType != START_ELEMENT) {
throw new IllegalStateException("");
}
final String prefix = _attributeCache.getPrefix(index);
final String localName = _attributeCache.getLocalName(index);
final String uri = _attributeCache.getURI(index);
return new QName(uri,localName,prefix);
}
@Override
public final String getAttributeNamespace(int index) {
if (_eventType != START_ELEMENT) {
throw new IllegalStateException("");
}
return fixEmptyString(_attributeCache.getURI(index));
}
@Override
public final String getAttributeLocalName(int index) {
if (_eventType != START_ELEMENT) {
throw new IllegalStateException("");
}
return _attributeCache.getLocalName(index);
}
@Override
public final String getAttributePrefix(int index) {
if (_eventType != START_ELEMENT) {
throw new IllegalStateException("");
}
return fixEmptyString(_attributeCache.getPrefix(index));
}
@Override
public final String getAttributeType(int index) {
if (_eventType != START_ELEMENT) {
throw new IllegalStateException("");
}
return _attributeCache.getType(index);
}
@Override
public final String getAttributeValue(int index) {
if (_eventType != START_ELEMENT) {
throw new IllegalStateException("");
}
return _attributeCache.getValue(index);
}
@Override
public final boolean isAttributeSpecified(int index) {
return false;
}
@Override
public final int getNamespaceCount() {
if (_eventType == START_ELEMENT || _eventType == END_ELEMENT) {
return _stackTop.namespaceAIIsEnd - _stackTop.namespaceAIIsStart;
}
throw new IllegalStateException("");
}
@Override
public final String getNamespacePrefix(int index) {
if (_eventType == START_ELEMENT || _eventType == END_ELEMENT) {
return _namespaceAIIsPrefix[_stackTop.namespaceAIIsStart + index];
}
throw new IllegalStateException("");
}
@Override
public final String getNamespaceURI(int index) {
if (_eventType == START_ELEMENT || _eventType == END_ELEMENT) {
return _namespaceAIIsNamespaceName[_stackTop.namespaceAIIsStart + index];
}
throw new IllegalStateException("");
}
@Override
public final String getNamespaceURI(String prefix) {
return _nsCtx.getNamespaceURI(prefix);
}
@Override
public final NamespaceContextEx getNamespaceContext() {
return _nsCtx;
}
@Override
public final int getEventType() {
return _eventType;
}
@Override
public final String getText() {
if (_characters != null) {
String s = new String(_characters, _textOffset, _textLen);
_charSequence = s;
return s;
} else if (_charSequence != null) {
return _charSequence.toString();
} else {
throw new IllegalStateException();
}
}
@Override
public final char[] getTextCharacters() {
if (_characters != null) {
return _characters;
} else if (_charSequence != null) {
// TODO try to avoid creation of a temporary String for some
// CharSequence implementations
_characters = _charSequence.toString().toCharArray();
_textLen = _characters.length;
_textOffset = 0;
return _characters;
} else {
throw new IllegalStateException();
}
}
@Override
public final int getTextStart() {
if (_characters != null) {
return _textOffset;
} else if (_charSequence != null) {
return 0;
} else {
throw new IllegalStateException();
}
}
@Override
public final int getTextLength() {
if (_characters != null) {
return _textLen;
} else if (_charSequence != null) {
return _charSequence.length();
} else {
throw new IllegalStateException();
}
}
@Override
public final int getTextCharacters(int sourceStart, char[] target,
int targetStart, int length) throws XMLStreamException {
if (_characters != null) {
} else if (_charSequence != null) {
_characters = _charSequence.toString().toCharArray();
_textLen = _characters.length;
_textOffset = 0;
} else {
throw new IllegalStateException("");
}
try {
int remaining = _textLen - sourceStart;
int len = remaining > length ? length : remaining;
sourceStart += _textOffset;
System.arraycopy(_characters, sourceStart, target, targetStart, len);
return len;
} catch (IndexOutOfBoundsException e) {
throw new XMLStreamException(e);
}
}
private class CharSequenceImpl implements CharSequence {
private final int _offset;
private final int _length;
CharSequenceImpl(int offset, int length) {
_offset = offset;
_length = length;
}
@Override
public int length() {
return _length;
}
@Override
public char charAt(int index) {
if (index >= 0 && index < _textLen) {
return _characters[_textOffset + index];
} else {
throw new IndexOutOfBoundsException();
}
}
@Override
public CharSequence subSequence(int start, int end) {
final int length = end - start;
if (end < 0 || start < 0 || end > length || start > end) {
throw new IndexOutOfBoundsException();
}
return new CharSequenceImpl(_offset + start, length);
}
@Override
public String toString() {
return new String(_characters, _offset, _length);
}
}
@Override
public final CharSequence getPCDATA() {
if (_characters != null) {
return new CharSequenceImpl(_textOffset, _textLen);
} else if (_charSequence != null) {
return _charSequence;
} else {
throw new IllegalStateException();
}
}
@Override
public final String getEncoding() {
return "UTF-8";
}
@Override
public final boolean hasText() {
return (_characters != null || _charSequence != null);
}
@Override
public final Location getLocation() {
return new DummyLocation();
}
@Override
public final boolean hasName() {
return (_eventType == START_ELEMENT || _eventType == END_ELEMENT);
}
@Override
public final QName getName() {
return _stackTop.getQName();
}
@Override
public final String getLocalName() {
return _stackTop.localName;
}
@Override
public final String getNamespaceURI() {
return _stackTop.uri;
}
@Override
public final String getPrefix() {
return _stackTop.prefix;
}
@Override
public final String getVersion() {
return "1.0";
}
@Override
public final boolean isStandalone() {
return false;
}
@Override
public final boolean standaloneSet() {
return false;
}
@Override
public final String getCharacterEncodingScheme() {
return "UTF-8";
}
@Override
public final String getPITarget() {
if (_eventType == PROCESSING_INSTRUCTION) {
return _piTarget;
}
throw new IllegalStateException("");
}
@Override
public final String getPIData() {
if (_eventType == PROCESSING_INSTRUCTION) {
return _piData;
}
throw new IllegalStateException("");
}
protected void processElement(String prefix, String uri, String localName, boolean inscope) {
pushElementStack();
_stackTop.set(prefix, uri, localName);
_attributeCache.clear();
int item = peekStructure();
if ((item & TYPE_MASK) == T_NAMESPACE_ATTRIBUTE || inscope) {
// Skip the namespace declarations on the element
// they will have been added already
item = processNamespaceAttributes(item, inscope);
}
if ((item & TYPE_MASK) == T_ATTRIBUTE) {
processAttributes(item);
}
}
private boolean isInscope(int depth) {
return _buffer.getInscopeNamespaces().size() > 0 && depth ==0;
}
private void resizeNamespaceAttributes() {
final String[] namespaceAIIsPrefix = new String[_namespaceAIIsEnd * 2];
System.arraycopy(_namespaceAIIsPrefix, 0, namespaceAIIsPrefix, 0, _namespaceAIIsEnd);
_namespaceAIIsPrefix = namespaceAIIsPrefix;
final String[] namespaceAIIsNamespaceName = new String[_namespaceAIIsEnd * 2];
System.arraycopy(_namespaceAIIsNamespaceName, 0, namespaceAIIsNamespaceName, 0, _namespaceAIIsEnd);
_namespaceAIIsNamespaceName = namespaceAIIsNamespaceName;
}
private int processNamespaceAttributes(int item, boolean inscope){
_stackTop.namespaceAIIsStart = _namespaceAIIsEnd;
Set prefixSet = inscope ? new HashSet<>() : Collections.emptySet();
while((item & TYPE_MASK) == T_NAMESPACE_ATTRIBUTE) {
if (_namespaceAIIsEnd == _namespaceAIIsPrefix.length) {
resizeNamespaceAttributes();
}
switch(getNIIState(item)){
case STATE_NAMESPACE_ATTRIBUTE:
// Undeclaration of default namespace
_namespaceAIIsPrefix[_namespaceAIIsEnd] =
_namespaceAIIsNamespaceName[_namespaceAIIsEnd++] = "";
if (inscope) {
prefixSet.add("");
}
break;
case STATE_NAMESPACE_ATTRIBUTE_P:
// Undeclaration of namespace
_namespaceAIIsPrefix[_namespaceAIIsEnd] = readStructureString();
if (inscope) {
prefixSet.add(_namespaceAIIsPrefix[_namespaceAIIsEnd]);
}
_namespaceAIIsNamespaceName[_namespaceAIIsEnd++] = "";
break;
case STATE_NAMESPACE_ATTRIBUTE_P_U:
// Declaration with prefix
_namespaceAIIsPrefix[_namespaceAIIsEnd] = readStructureString();
if (inscope) {
prefixSet.add(_namespaceAIIsPrefix[_namespaceAIIsEnd]);
}
_namespaceAIIsNamespaceName[_namespaceAIIsEnd++] = readStructureString();
break;
case STATE_NAMESPACE_ATTRIBUTE_U:
// Default declaration
_namespaceAIIsPrefix[_namespaceAIIsEnd] = "";
if (inscope) {
prefixSet.add("");
}
_namespaceAIIsNamespaceName[_namespaceAIIsEnd++] = readStructureString();
break;
}
readStructure();
item = peekStructure();
}
if (inscope) {
for (Map.Entry e : _buffer.getInscopeNamespaces().entrySet()) {
String key = fixNull(e.getKey());
// If the prefix is already written, do not write the prefix
if (!prefixSet.contains(key)) {
if (_namespaceAIIsEnd == _namespaceAIIsPrefix.length) {
resizeNamespaceAttributes();
}
_namespaceAIIsPrefix[_namespaceAIIsEnd] = key;
_namespaceAIIsNamespaceName[_namespaceAIIsEnd++] = e.getValue();
}
}
}
_stackTop.namespaceAIIsEnd = _namespaceAIIsEnd;
return item;
}
private static String fixNull(String s) {
if (s == null) return "";
else return s;
}
private void processAttributes(int item){
do {
switch(getAIIState(item)){
case STATE_ATTRIBUTE_U_LN_QN: {
final String uri = readStructureString();
final String localName = readStructureString();
final String prefix = getPrefixFromQName(readStructureString());
_attributeCache.addAttributeWithPrefix(prefix, uri, localName, readStructureString(), readContentString());
break;
}
case STATE_ATTRIBUTE_P_U_LN:
_attributeCache.addAttributeWithPrefix(readStructureString(), readStructureString(), readStructureString(), readStructureString(), readContentString());
break;
case STATE_ATTRIBUTE_U_LN:
// _attributeCache follows SAX convention
_attributeCache.addAttributeWithPrefix("", readStructureString(), readStructureString(), readStructureString(), readContentString());
break;
case STATE_ATTRIBUTE_LN: {
_attributeCache.addAttributeWithPrefix("", "", readStructureString(), readStructureString(), readContentString());
break;
}
default :
assert false : "Internal XSB Error: wrong attribute state, Item="+item;
}
readStructure();
item = peekStructure();
} while((item & TYPE_MASK) == T_ATTRIBUTE);
}
private void pushElementStack() {
if (_depth == _stack.length) {
// resize stack
ElementStackEntry [] tmp = _stack;
_stack = new ElementStackEntry[_stack.length * 3 /2 + 1];
System.arraycopy(tmp, 0, _stack, 0, tmp.length);
for (int i = tmp.length; i < _stack.length; i++){
_stack[i] = new ElementStackEntry();
}
}
_stackTop = _stack[_depth++];
}
private void popElementStack(int depth) {
// _depth is checked outside this method
_stackTop = _stack[depth - 1];
// Move back the position of the namespace index
_namespaceAIIsEnd = _stack[depth].namespaceAIIsStart;
}
private final class ElementStackEntry {
/**
* Prefix.
* Just like everywhere else in StAX, this can be null but can't be empty.
*/
String prefix;
/**
* Namespace URI.
* Just like everywhere else in StAX, this can be null but can't be empty.
*/
String uri;
String localName;
QName qname;
// Start and end of namespace declarations
// in namespace declaration arrays
int namespaceAIIsStart;
int namespaceAIIsEnd;
public void set(String prefix, String uri, String localName) {
this.prefix = prefix;
this.uri = uri;
this.localName = localName;
this.qname = null;
this.namespaceAIIsStart = this.namespaceAIIsEnd = StreamReaderBufferProcessor.this._namespaceAIIsEnd;
}
public QName getQName() {
if (qname == null) {
qname = new QName(fixNull(uri), localName, fixNull(prefix));
}
return qname;
}
private String fixNull(String s) {
return (s == null) ? "" : s;
}
}
private final class InternalNamespaceContext implements NamespaceContextEx {
@SuppressWarnings({"StringEquality"})
@Override
public String getNamespaceURI(String prefix) {
if (prefix == null) {
throw new IllegalArgumentException("Prefix cannot be null");
}
/*
* If the buffer was created using string interning
* intern the prefix and check for reference equality
* rather than using String.equals();
*/
if (_stringInterningFeature) {
prefix = prefix.intern();
// Find the most recently declared prefix
for (int i = _namespaceAIIsEnd - 1; i >=0; i--) {
if (prefix == _namespaceAIIsPrefix[i]) {
return _namespaceAIIsNamespaceName[i];
}
}
} else {
// Find the most recently declared prefix
for (int i = _namespaceAIIsEnd - 1; i >=0; i--) {
if (prefix.equals(_namespaceAIIsPrefix[i])) {
return _namespaceAIIsNamespaceName[i];
}
}
}
// Check for XML-based prefixes
if (prefix.equals(XMLConstants.XML_NS_PREFIX)) {
return XMLConstants.XML_NS_URI;
} else if (prefix.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
}
return null;
}
@Override
public String getPrefix(String namespaceURI) {
final Iterator i = getPrefixes(namespaceURI);
if (i.hasNext()) {
return i.next();
} else {
return null;
}
}
@Override
public Iterator getPrefixes(final String namespaceURI) {
if (namespaceURI == null){
throw new IllegalArgumentException("NamespaceURI cannot be null");
}
if (namespaceURI.equals(XMLConstants.XML_NS_URI)) {
return Collections.singletonList(XMLConstants.XML_NS_PREFIX).iterator();
} else if (namespaceURI.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) {
return Collections.singletonList(XMLConstants.XMLNS_ATTRIBUTE).iterator();
}
return new Iterator() {
private int i = _namespaceAIIsEnd - 1;
private boolean requireFindNext = true;
private String p;
private String findNext() {
while(i >= 0) {
// Find the most recently declared namespace
if (namespaceURI.equals(_namespaceAIIsNamespaceName[i])) {
// Find the most recently declared prefix of the namespace
// and check if the prefix is in scope with that namespace
if (getNamespaceURI(_namespaceAIIsPrefix[i]).equals(
_namespaceAIIsNamespaceName[i])) {
return p = _namespaceAIIsPrefix[i];
}
}
i--;
}
return p = null;
}
@Override
public boolean hasNext() {
if (requireFindNext) {
findNext();
requireFindNext = false;
}
return (p != null);
}
@Override
public String next() {
if (requireFindNext) {
findNext();
}
requireFindNext = true;
if (p == null) {
throw new NoSuchElementException();
}
return p;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
private class BindingImpl implements NamespaceContextEx.Binding {
final String _prefix;
final String _namespaceURI;
BindingImpl(String prefix, String namespaceURI) {
_prefix = prefix;
_namespaceURI = namespaceURI;
}
@Override
public String getPrefix() {
return _prefix;
}
@Override
public String getNamespaceURI() {
return _namespaceURI;
}
}
@Override
public Iterator iterator() {
return new Iterator() {
private final int end = _namespaceAIIsEnd - 1;
private int current = end;
private boolean requireFindNext = true;
private NamespaceContextEx.Binding namespace;
private NamespaceContextEx.Binding findNext() {
while(current >= 0) {
final String prefix = _namespaceAIIsPrefix[current];
// Find if the current prefix occurs more recently
// If so then it is not in scope
int i = end;
for (;i > current; i--) {
if (prefix.equals(_namespaceAIIsPrefix[i])) {
break;
}
}
if (i == current--) {
// The current prefix is in-scope
return namespace = new BindingImpl(prefix, _namespaceAIIsNamespaceName[current]);
}
}
return namespace = null;
}
@Override
public boolean hasNext() {
if (requireFindNext) {
findNext();
requireFindNext = false;
}
return (namespace != null);
}
@Override
public NamespaceContextEx.Binding next() {
if (requireFindNext) {
findNext();
}
requireFindNext = true;
if (namespace == null) {
throw new NoSuchElementException();
}
return namespace;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
private class DummyLocation implements Location {
@Override
public int getLineNumber() {
return -1;
}
@Override
public int getColumnNumber() {
return -1;
}
@Override
public int getCharacterOffset() {
return -1;
}
@Override
public String getPublicId() {
return null;
}
@Override
public String getSystemId() {
return _buffer.getSystemId();
}
}
private static String fixEmptyString(String s) {
// s must not be null, so no need to check for that. that would be bug.
if(s.length()==0) return null;
else return s;
}
}