1/*
2 * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.xml.internal.stream.buffer.stax;
27
28import com.sun.xml.internal.stream.buffer.AbstractProcessor;
29import com.sun.xml.internal.stream.buffer.AttributesHolder;
30import com.sun.xml.internal.stream.buffer.XMLStreamBuffer;
31import com.sun.xml.internal.stream.buffer.XMLStreamBufferMark;
32import com.sun.xml.internal.org.jvnet.staxex.NamespaceContextEx;
33import com.sun.xml.internal.org.jvnet.staxex.XMLStreamReaderEx;
34
35import javax.xml.XMLConstants;
36import javax.xml.namespace.QName;
37import javax.xml.stream.Location;
38import javax.xml.stream.XMLStreamException;
39import javax.xml.stream.XMLStreamReader;
40import java.util.*;
41
42/**
43 * A processor of a {@link XMLStreamBuffer} that reads the XML infoset as
44 * {@link XMLStreamReader}.
45 *
46 * <p>
47 * Because of {@link XMLStreamReader} design, this processor always produce
48 * a full document infoset, even if the buffer just contains a fragment.
49 *
50 * <p>
51 * When {@link XMLStreamBuffer} contains a multiple tree (AKA "forest"),
52 * {@link XMLStreamReader} will behave as if there are multiple root elements
53 * (so you'll see {@link #START_ELEMENT} event where you'd normally expect
54 * {@link #END_DOCUMENT}.)
55 *
56 * @author Paul.Sandoz@Sun.Com
57 * @author K.Venugopal@sun.com
58 */
59public class StreamReaderBufferProcessor extends AbstractProcessor implements XMLStreamReaderEx {
60    private static final int CACHE_SIZE = 16;
61
62    // Stack to hold element and namespace declaration information
63    protected ElementStackEntry[] _stack = new ElementStackEntry[CACHE_SIZE];
64    /** The top-most active entry of the {@link #_stack}. */
65    protected ElementStackEntry _stackTop;
66    /** The element depth that we are in. Used to determine when we are done with a tree. */
67    protected int _depth;
68
69    // Arrays to hold all namespace declarations
70    /**
71     * Namespace prefixes. Can be empty but not null.
72     */
73    protected String[] _namespaceAIIsPrefix = new String[CACHE_SIZE];
74    protected String[] _namespaceAIIsNamespaceName = new String[CACHE_SIZE];
75    protected int _namespaceAIIsEnd;
76
77    // Internal namespace context implementation
78    protected InternalNamespaceContext _nsCtx = new InternalNamespaceContext();
79
80    // The current event type
81    protected int _eventType;
82
83    /**
84     * Holder of the attributes.
85     *
86     * Be careful that this follows the SAX convention of using "" instead of null.
87     */
88    protected AttributesHolder _attributeCache;
89
90    // Characters as a CharSequence
91    protected CharSequence _charSequence;
92
93    // Characters as a char array with offset and length
94    protected char[] _characters;
95    protected int _textOffset;
96    protected int _textLen;
97
98    protected String _piTarget;
99    protected String _piData;
100
101    //
102    // Represents the parser state wrt the end of parsing.
103    //
104    /**
105     * The parser is in the middle of parsing a document,
106     * with no end in sight.
107     */
108    private static final int PARSING = 1;
109    /**
110     * The parser has already reported the {@link #END_ELEMENT},
111     * and we are parsing a fragment. We'll report {@link #END_DOCUMENT}
112     * next and be done.
113     */
114    private static final int PENDING_END_DOCUMENT = 2;
115    /**
116     * The parser has reported the {@link #END_DOCUMENT} event,
117     * so we are really done parsing.
118     */
119    private static final int COMPLETED = 3;
120
121    /**
122     * True if processing is complete.
123     */
124    private int _completionState;
125
126    public StreamReaderBufferProcessor() {
127        for (int i=0; i < _stack.length; i++){
128            _stack[i] = new ElementStackEntry();
129        }
130
131        _attributeCache = new AttributesHolder();
132    }
133
134    public StreamReaderBufferProcessor(XMLStreamBuffer buffer) throws XMLStreamException {
135        this();
136        setXMLStreamBuffer(buffer);
137    }
138
139    public void setXMLStreamBuffer(XMLStreamBuffer buffer) throws XMLStreamException {
140        setBuffer(buffer,buffer.isFragment());
141
142        _completionState = PARSING;
143        _namespaceAIIsEnd = 0;
144        _characters = null;
145        _charSequence = null;
146        _eventType = START_DOCUMENT;
147    }
148
149    /**
150     * Does {@link #nextTag()} and if the parser moved to a new start tag,
151     * returns a {@link XMLStreamBufferMark} that captures the infoset starting
152     * from the newly discovered element.
153     *
154     * <p>
155     * (Ideally we should have a method that works against the current position,
156     * but the way the data structure is read makes this somewhat difficult.)
157     *
158     * This creates a new {@link XMLStreamBufferMark} that shares the underlying
159     * data storage, thus it's fairly efficient.
160     */
161    public XMLStreamBuffer nextTagAndMark() throws XMLStreamException {
162        while (true) {
163            int s = peekStructure();
164            if((s &TYPE_MASK)==T_ELEMENT) {
165                // next is start element.
166                Map<String,String> inscope = new HashMap<String, String>(_namespaceAIIsEnd);
167
168                for (int i=0 ; i<_namespaceAIIsEnd; i++)
169                    inscope.put(_namespaceAIIsPrefix[i],_namespaceAIIsNamespaceName[i]);
170
171                XMLStreamBufferMark mark = new XMLStreamBufferMark(inscope, this);
172                next();
173                return mark;
174            } else if((s &TYPE_MASK)==T_DOCUMENT) {
175                //move the pointer to next structure.
176                readStructure();
177                //mark the next start element
178                XMLStreamBufferMark mark = new XMLStreamBufferMark(new HashMap<String, String>(_namespaceAIIsEnd), this);
179                next();
180                return mark;
181            }
182
183            if(next()==END_ELEMENT)
184                return null;
185        }
186    }
187
188    public Object getProperty(String name) {
189        return null;
190    }
191
192    public int next() throws XMLStreamException {
193        switch(_completionState) {
194            case COMPLETED:
195                throw new XMLStreamException("Invalid State");
196            case PENDING_END_DOCUMENT:
197                _namespaceAIIsEnd = 0;
198                _completionState = COMPLETED;
199                return _eventType = END_DOCUMENT;
200        }
201
202        // Pop the stack of elements
203        // This is a post-processing operation
204        // The stack of the element should be poppoed after
205        // the END_ELEMENT event is returned so that the correct element name
206        // and namespace scope is returned
207        switch(_eventType) {
208            case END_ELEMENT:
209                if (_depth > 1) {
210                    _depth--;
211                    // _depth index is always set to the next free stack entry
212                    // to push
213                    popElementStack(_depth);
214                } else if (_depth == 1) {
215                    _depth--;
216                }
217        }
218
219        _characters = null;
220        _charSequence = null;
221        while(true) {// loop only if we read STATE_DOCUMENT
222            int eiiState = readEiiState();
223            switch(eiiState) {
224                case STATE_DOCUMENT:
225                    // we'll always produce a full document, and we've already report START_DOCUMENT event.
226                    // so simply skil this
227                    continue;
228                case STATE_ELEMENT_U_LN_QN: {
229                    final String uri = readStructureString();
230                    final String localName = readStructureString();
231                    final String prefix = getPrefixFromQName(readStructureString());
232
233                    processElement(prefix, uri, localName, isInscope(_depth));
234                    return _eventType = START_ELEMENT;
235                }
236                case STATE_ELEMENT_P_U_LN:
237                    processElement(readStructureString(), readStructureString(), readStructureString(),isInscope(_depth));
238                    return _eventType = START_ELEMENT;
239                case STATE_ELEMENT_U_LN:
240                    processElement(null, readStructureString(), readStructureString(),isInscope(_depth));
241                    return _eventType = START_ELEMENT;
242                case STATE_ELEMENT_LN:
243                    processElement(null, null, readStructureString(),isInscope(_depth));
244                    return _eventType = START_ELEMENT;
245                case STATE_TEXT_AS_CHAR_ARRAY_SMALL:
246                    _textLen = readStructure();
247                    _textOffset = readContentCharactersBuffer(_textLen);
248                    _characters = _contentCharactersBuffer;
249
250                    return _eventType = CHARACTERS;
251                case STATE_TEXT_AS_CHAR_ARRAY_MEDIUM:
252                    _textLen = readStructure16();
253                    _textOffset = readContentCharactersBuffer(_textLen);
254                    _characters = _contentCharactersBuffer;
255
256                    return _eventType = CHARACTERS;
257                case STATE_TEXT_AS_CHAR_ARRAY_COPY:
258                    _characters = readContentCharactersCopy();
259                    _textLen = _characters.length;
260                    _textOffset = 0;
261
262                    return _eventType = CHARACTERS;
263                case STATE_TEXT_AS_STRING:
264                    _eventType = CHARACTERS;
265                    _charSequence = readContentString();
266
267                    return _eventType = CHARACTERS;
268                case STATE_TEXT_AS_OBJECT:
269                    _eventType = CHARACTERS;
270                    _charSequence = (CharSequence)readContentObject();
271
272                    return _eventType = CHARACTERS;
273                case STATE_COMMENT_AS_CHAR_ARRAY_SMALL:
274                    _textLen = readStructure();
275                    _textOffset = readContentCharactersBuffer(_textLen);
276                    _characters = _contentCharactersBuffer;
277
278                    return _eventType = COMMENT;
279                case STATE_COMMENT_AS_CHAR_ARRAY_MEDIUM:
280                    _textLen = readStructure16();
281                    _textOffset = readContentCharactersBuffer(_textLen);
282                    _characters = _contentCharactersBuffer;
283
284                    return _eventType = COMMENT;
285                case STATE_COMMENT_AS_CHAR_ARRAY_COPY:
286                    _characters = readContentCharactersCopy();
287                    _textLen = _characters.length;
288                    _textOffset = 0;
289
290                    return _eventType = COMMENT;
291                case STATE_COMMENT_AS_STRING:
292                    _charSequence = readContentString();
293
294                    return _eventType = COMMENT;
295                case STATE_PROCESSING_INSTRUCTION:
296                    _piTarget = readStructureString();
297                    _piData = readStructureString();
298
299                    return _eventType = PROCESSING_INSTRUCTION;
300                case STATE_END:
301                    if (_depth > 1) {
302                        // normal case
303                        return _eventType = END_ELEMENT;
304                    } else if (_depth == 1) {
305                        // this is the last end element for the current tree.
306                        if (_fragmentMode) {
307                            if(--_treeCount==0) // is this the last tree in the forest?
308                                _completionState = PENDING_END_DOCUMENT;
309                        }
310                        return _eventType = END_ELEMENT;
311                    } else {
312                        // this only happens when we are processing a full document
313                        // and we hit the "end of document" marker
314                        _namespaceAIIsEnd = 0;
315                        _completionState = COMPLETED;
316                        return _eventType = END_DOCUMENT;
317                    }
318                default:
319                    throw new XMLStreamException("Internal XSB error: Invalid State="+eiiState);
320            }
321            // this should be unreachable
322        }
323    }
324
325    public final void require(int type, String namespaceURI, String localName) throws XMLStreamException {
326        if( type != _eventType) {
327            throw new XMLStreamException("");
328        }
329        if( namespaceURI != null && !namespaceURI.equals(getNamespaceURI())) {
330            throw new XMLStreamException("");
331        }
332        if(localName != null && !localName.equals(getLocalName())) {
333            throw new XMLStreamException("");
334        }
335    }
336
337    public final String getElementTextTrim() throws XMLStreamException {
338        // TODO getElementText* methods more efficiently
339        return getElementText().trim();
340    }
341
342    public final String getElementText() throws XMLStreamException {
343        if(_eventType != START_ELEMENT) {
344            throw new XMLStreamException("");
345        }
346
347        next();
348        return getElementText(true);
349    }
350
351    public final String getElementText(boolean startElementRead) throws XMLStreamException {
352        if (!startElementRead) {
353            throw new XMLStreamException("");
354        }
355
356        int eventType = getEventType();
357        StringBuilder content = new StringBuilder();
358        while(eventType != END_ELEMENT ) {
359            if(eventType == CHARACTERS
360                    || eventType == CDATA
361                    || eventType == SPACE
362                    || eventType == ENTITY_REFERENCE) {
363                content.append(getText());
364            } else if(eventType == PROCESSING_INSTRUCTION
365                    || eventType == COMMENT) {
366                // skipping
367            } else if(eventType == END_DOCUMENT) {
368                throw new XMLStreamException("");
369            } else if(eventType == START_ELEMENT) {
370                throw new XMLStreamException("");
371            } else {
372                throw new XMLStreamException("");
373            }
374            eventType = next();
375        }
376        return content.toString();
377    }
378
379    public final int nextTag() throws XMLStreamException {
380        next();
381        return nextTag(true);
382    }
383
384    public final int nextTag(boolean currentTagRead) throws XMLStreamException {
385        int eventType = getEventType();
386        if (!currentTagRead) {
387            eventType = next();
388        }
389        while((eventType == CHARACTERS && isWhiteSpace()) // skip whitespace
390        || (eventType == CDATA && isWhiteSpace())
391        || eventType == SPACE
392        || eventType == PROCESSING_INSTRUCTION
393        || eventType == COMMENT) {
394            eventType = next();
395        }
396        if (eventType != START_ELEMENT && eventType != END_ELEMENT) {
397            throw new XMLStreamException("");
398        }
399        return eventType;
400    }
401
402    public final boolean hasNext() {
403        return (_eventType != END_DOCUMENT);
404    }
405
406    public void close() throws XMLStreamException {
407    }
408
409    public final boolean isStartElement() {
410        return (_eventType == START_ELEMENT);
411    }
412
413    public final boolean isEndElement() {
414        return (_eventType == END_ELEMENT);
415    }
416
417    public final boolean isCharacters() {
418        return (_eventType == CHARACTERS);
419    }
420
421    public final boolean isWhiteSpace() {
422        if(isCharacters() || (_eventType == CDATA)){
423            char [] ch = this.getTextCharacters();
424            int start = this.getTextStart();
425            int length = this.getTextLength();
426            for (int i = start; i < length; i++){
427                final char c = ch[i];
428                if (!(c == 0x20 || c == 0x9 || c == 0xD || c == 0xA))
429                    return false;
430            }
431            return true;
432        }
433        return false;
434    }
435
436    public final String getAttributeValue(String namespaceURI, String localName) {
437        if (_eventType != START_ELEMENT) {
438            throw new IllegalStateException("");
439        }
440
441        if (namespaceURI == null) {
442            // Set to the empty string to be compatible with the
443            // org.xml.sax.Attributes interface
444            namespaceURI = "";
445        }
446
447        return _attributeCache.getValue(namespaceURI, localName);
448    }
449
450    public final int getAttributeCount() {
451        if (_eventType != START_ELEMENT) {
452            throw new IllegalStateException("");
453        }
454
455        return _attributeCache.getLength();
456    }
457
458    public final javax.xml.namespace.QName getAttributeName(int index) {
459        if (_eventType != START_ELEMENT) {
460            throw new IllegalStateException("");
461        }
462
463        final String prefix = _attributeCache.getPrefix(index);
464        final String localName = _attributeCache.getLocalName(index);
465        final String uri = _attributeCache.getURI(index);
466        return new QName(uri,localName,prefix);
467    }
468
469
470    public final String getAttributeNamespace(int index) {
471        if (_eventType != START_ELEMENT) {
472            throw new IllegalStateException("");
473        }
474        return fixEmptyString(_attributeCache.getURI(index));
475    }
476
477    public final String getAttributeLocalName(int index) {
478        if (_eventType != START_ELEMENT) {
479            throw new IllegalStateException("");
480        }
481        return _attributeCache.getLocalName(index);
482    }
483
484    public final String getAttributePrefix(int index) {
485        if (_eventType != START_ELEMENT) {
486            throw new IllegalStateException("");
487        }
488        return fixEmptyString(_attributeCache.getPrefix(index));
489    }
490
491    public final String getAttributeType(int index) {
492        if (_eventType != START_ELEMENT) {
493            throw new IllegalStateException("");
494        }
495        return _attributeCache.getType(index);
496    }
497
498    public final String getAttributeValue(int index) {
499        if (_eventType != START_ELEMENT) {
500            throw new IllegalStateException("");
501        }
502
503        return _attributeCache.getValue(index);
504    }
505
506    public final boolean isAttributeSpecified(int index) {
507        return false;
508    }
509
510    public final int getNamespaceCount() {
511        if (_eventType == START_ELEMENT || _eventType == END_ELEMENT) {
512            return _stackTop.namespaceAIIsEnd - _stackTop.namespaceAIIsStart;
513        }
514
515        throw new IllegalStateException("");
516    }
517
518    public final String getNamespacePrefix(int index) {
519        if (_eventType == START_ELEMENT || _eventType == END_ELEMENT) {
520            return _namespaceAIIsPrefix[_stackTop.namespaceAIIsStart + index];
521        }
522
523        throw new IllegalStateException("");
524    }
525
526    public final String getNamespaceURI(int index) {
527        if (_eventType == START_ELEMENT || _eventType == END_ELEMENT) {
528            return _namespaceAIIsNamespaceName[_stackTop.namespaceAIIsStart + index];
529        }
530
531        throw new IllegalStateException("");
532    }
533
534    public final String getNamespaceURI(String prefix) {
535        return _nsCtx.getNamespaceURI(prefix);
536    }
537
538    public final NamespaceContextEx getNamespaceContext() {
539        return _nsCtx;
540    }
541
542    public final int getEventType() {
543        return _eventType;
544    }
545
546    public final String getText() {
547        if (_characters != null) {
548            String s = new String(_characters, _textOffset, _textLen);
549            _charSequence = s;
550            return s;
551        } else if (_charSequence != null) {
552            return _charSequence.toString();
553        } else {
554            throw new IllegalStateException();
555        }
556    }
557
558    public final char[] getTextCharacters() {
559        if (_characters != null) {
560            return _characters;
561        } else if (_charSequence != null) {
562            // TODO try to avoid creation of a temporary String for some
563            // CharSequence implementations
564            _characters = _charSequence.toString().toCharArray();
565            _textLen = _characters.length;
566            _textOffset = 0;
567            return _characters;
568        } else {
569            throw new IllegalStateException();
570        }
571    }
572
573    public final int getTextStart() {
574        if (_characters != null) {
575            return _textOffset;
576        } else if (_charSequence != null) {
577            return 0;
578        } else {
579            throw new IllegalStateException();
580        }
581    }
582
583    public final int getTextLength() {
584        if (_characters != null) {
585            return _textLen;
586        } else if (_charSequence != null) {
587            return _charSequence.length();
588        } else {
589            throw new IllegalStateException();
590        }
591    }
592
593    public final int getTextCharacters(int sourceStart, char[] target,
594                                       int targetStart, int length) throws XMLStreamException {
595        if (_characters != null) {
596        } else if (_charSequence != null) {
597            _characters = _charSequence.toString().toCharArray();
598            _textLen = _characters.length;
599            _textOffset = 0;
600        } else {
601            throw new IllegalStateException("");
602        }
603
604        try {
605            int remaining = _textLen - sourceStart;
606            int len = remaining > length ? length : remaining;
607            sourceStart += _textOffset;
608            System.arraycopy(_characters, sourceStart, target, targetStart, len);
609            return len;
610        } catch (IndexOutOfBoundsException e) {
611            throw new XMLStreamException(e);
612        }
613    }
614
615    private class CharSequenceImpl implements CharSequence {
616        private final int _offset;
617        private final int _length;
618
619        CharSequenceImpl(int offset, int length) {
620            _offset = offset;
621            _length = length;
622        }
623
624        public int length() {
625            return _length;
626        }
627
628        public char charAt(int index) {
629            if (index >= 0 && index < _textLen) {
630                return _characters[_textOffset + index];
631            } else {
632                throw new IndexOutOfBoundsException();
633            }
634        }
635
636        public CharSequence subSequence(int start, int end) {
637            final int length = end - start;
638            if (end < 0 || start < 0 || end > length || start > end) {
639                throw new IndexOutOfBoundsException();
640            }
641
642            return new CharSequenceImpl(_offset + start, length);
643        }
644
645        @Override
646        public String toString() {
647            return new String(_characters, _offset, _length);
648        }
649    }
650
651    public final CharSequence getPCDATA() {
652        if (_characters != null) {
653            return new CharSequenceImpl(_textOffset, _textLen);
654        } else if (_charSequence != null) {
655            return _charSequence;
656        } else {
657            throw new IllegalStateException();
658        }
659    }
660
661    public final String getEncoding() {
662        return "UTF-8";
663    }
664
665    public final boolean hasText() {
666        return (_characters != null || _charSequence != null);
667    }
668
669    public final Location getLocation() {
670        return new DummyLocation();
671    }
672
673    public final boolean hasName() {
674        return (_eventType == START_ELEMENT || _eventType == END_ELEMENT);
675    }
676
677    public final QName getName() {
678        return _stackTop.getQName();
679    }
680
681    public final String getLocalName() {
682        return _stackTop.localName;
683    }
684
685    public final String getNamespaceURI() {
686        return _stackTop.uri;
687    }
688
689    public final String getPrefix() {
690        return _stackTop.prefix;
691
692    }
693
694    public final String getVersion() {
695        return "1.0";
696    }
697
698    public final boolean isStandalone() {
699        return false;
700    }
701
702    public final boolean standaloneSet() {
703        return false;
704    }
705
706    public final String getCharacterEncodingScheme() {
707        return "UTF-8";
708    }
709
710    public final String getPITarget() {
711        if (_eventType == PROCESSING_INSTRUCTION) {
712            return _piTarget;
713        }
714        throw new IllegalStateException("");
715    }
716
717    public final String getPIData() {
718        if (_eventType == PROCESSING_INSTRUCTION) {
719            return _piData;
720        }
721        throw new IllegalStateException("");
722    }
723
724    protected void processElement(String prefix, String uri, String localName, boolean inscope) {
725        pushElementStack();
726        _stackTop.set(prefix, uri, localName);
727
728        _attributeCache.clear();
729
730        int item = peekStructure();
731        if ((item & TYPE_MASK) == T_NAMESPACE_ATTRIBUTE || inscope) {
732            // Skip the namespace declarations on the element
733            // they will have been added already
734            item = processNamespaceAttributes(item, inscope);
735        }
736        if ((item & TYPE_MASK) == T_ATTRIBUTE) {
737            processAttributes(item);
738        }
739    }
740
741    private boolean isInscope(int depth) {
742        return _buffer.getInscopeNamespaces().size() > 0 && depth ==0;
743    }
744
745    private void resizeNamespaceAttributes() {
746        final String[] namespaceAIIsPrefix = new String[_namespaceAIIsEnd * 2];
747        System.arraycopy(_namespaceAIIsPrefix, 0, namespaceAIIsPrefix, 0, _namespaceAIIsEnd);
748        _namespaceAIIsPrefix = namespaceAIIsPrefix;
749
750        final String[] namespaceAIIsNamespaceName = new String[_namespaceAIIsEnd * 2];
751        System.arraycopy(_namespaceAIIsNamespaceName, 0, namespaceAIIsNamespaceName, 0, _namespaceAIIsEnd);
752        _namespaceAIIsNamespaceName = namespaceAIIsNamespaceName;
753    }
754
755    private int processNamespaceAttributes(int item, boolean inscope){
756        _stackTop.namespaceAIIsStart = _namespaceAIIsEnd;
757        Set<String> prefixSet = inscope ? new HashSet<String>() : Collections.<String>emptySet();
758
759        while((item & TYPE_MASK) == T_NAMESPACE_ATTRIBUTE) {
760            if (_namespaceAIIsEnd == _namespaceAIIsPrefix.length) {
761                resizeNamespaceAttributes();
762            }
763
764            switch(getNIIState(item)){
765                case STATE_NAMESPACE_ATTRIBUTE:
766                    // Undeclaration of default namespace
767                    _namespaceAIIsPrefix[_namespaceAIIsEnd] =
768                    _namespaceAIIsNamespaceName[_namespaceAIIsEnd++] = "";
769                    if (inscope) {
770                        prefixSet.add("");
771                    }
772                    break;
773                case STATE_NAMESPACE_ATTRIBUTE_P:
774                    // Undeclaration of namespace
775                    _namespaceAIIsPrefix[_namespaceAIIsEnd] = readStructureString();
776                    if (inscope) {
777                        prefixSet.add(_namespaceAIIsPrefix[_namespaceAIIsEnd]);
778                    }
779                    _namespaceAIIsNamespaceName[_namespaceAIIsEnd++] = "";
780                    break;
781                case STATE_NAMESPACE_ATTRIBUTE_P_U:
782                    // Declaration with prefix
783                    _namespaceAIIsPrefix[_namespaceAIIsEnd] = readStructureString();
784                    if (inscope) {
785                        prefixSet.add(_namespaceAIIsPrefix[_namespaceAIIsEnd]);
786                    }
787                    _namespaceAIIsNamespaceName[_namespaceAIIsEnd++] = readStructureString();
788                    break;
789                case STATE_NAMESPACE_ATTRIBUTE_U:
790                    // Default declaration
791                    _namespaceAIIsPrefix[_namespaceAIIsEnd] = "";
792                    if (inscope) {
793                        prefixSet.add("");
794                    }
795                    _namespaceAIIsNamespaceName[_namespaceAIIsEnd++] = readStructureString();
796                    break;
797            }
798            readStructure();
799
800            item = peekStructure();
801        }
802
803        if (inscope) {
804            for (Map.Entry<String, String> e : _buffer.getInscopeNamespaces().entrySet()) {
805                String key = fixNull(e.getKey());
806                // If the prefix is already written, do not write the prefix
807                if (!prefixSet.contains(key)) {
808                    if (_namespaceAIIsEnd == _namespaceAIIsPrefix.length) {
809                        resizeNamespaceAttributes();
810                    }
811                    _namespaceAIIsPrefix[_namespaceAIIsEnd] = key;
812                    _namespaceAIIsNamespaceName[_namespaceAIIsEnd++] = e.getValue();
813                }
814            }
815        }
816        _stackTop.namespaceAIIsEnd = _namespaceAIIsEnd;
817
818        return item;
819    }
820
821    private static String fixNull(String s) {
822        if (s == null) return "";
823        else return s;
824    }
825
826    private void processAttributes(int item){
827        do {
828            switch(getAIIState(item)){
829                case STATE_ATTRIBUTE_U_LN_QN: {
830                    final String uri = readStructureString();
831                    final String localName = readStructureString();
832                    final String prefix = getPrefixFromQName(readStructureString());
833                    _attributeCache.addAttributeWithPrefix(prefix, uri, localName, readStructureString(), readContentString());
834                    break;
835                }
836                case STATE_ATTRIBUTE_P_U_LN:
837                    _attributeCache.addAttributeWithPrefix(readStructureString(), readStructureString(), readStructureString(), readStructureString(), readContentString());
838                    break;
839                case STATE_ATTRIBUTE_U_LN:
840                    // _attributeCache follows SAX convention
841                    _attributeCache.addAttributeWithPrefix("", readStructureString(), readStructureString(), readStructureString(), readContentString());
842                    break;
843                case STATE_ATTRIBUTE_LN: {
844                    _attributeCache.addAttributeWithPrefix("", "", readStructureString(), readStructureString(), readContentString());
845                    break;
846                }
847                default :
848                    assert false : "Internal XSB Error: wrong attribute state, Item="+item;
849            }
850            readStructure();
851
852            item = peekStructure();
853        } while((item & TYPE_MASK) == T_ATTRIBUTE);
854    }
855
856    private void pushElementStack() {
857        if (_depth == _stack.length) {
858            // resize stack
859            ElementStackEntry [] tmp = _stack;
860            _stack = new ElementStackEntry[_stack.length * 3 /2 + 1];
861            System.arraycopy(tmp, 0, _stack, 0, tmp.length);
862            for (int i = tmp.length; i < _stack.length; i++){
863                _stack[i] = new ElementStackEntry();
864            }
865        }
866
867        _stackTop = _stack[_depth++];
868    }
869
870    private void popElementStack(int depth) {
871        // _depth is checked outside this method
872        _stackTop = _stack[depth - 1];
873        // Move back the position of the namespace index
874        _namespaceAIIsEnd = _stack[depth].namespaceAIIsStart;
875    }
876
877    private final class ElementStackEntry {
878        /**
879         * Prefix.
880         * Just like everywhere else in StAX, this can be null but can't be empty.
881         */
882        String prefix;
883        /**
884         * Namespace URI.
885         * Just like everywhere else in StAX, this can be null but can't be empty.
886         */
887        String uri;
888        String localName;
889        QName qname;
890
891        // Start and end of namespace declarations
892        // in namespace declaration arrays
893        int namespaceAIIsStart;
894        int namespaceAIIsEnd;
895
896        public void set(String prefix, String uri, String localName) {
897            this.prefix = prefix;
898            this.uri = uri;
899            this.localName = localName;
900            this.qname = null;
901
902            this.namespaceAIIsStart = this.namespaceAIIsEnd = StreamReaderBufferProcessor.this._namespaceAIIsEnd;
903        }
904
905        public QName getQName() {
906            if (qname == null) {
907                qname = new QName(fixNull(uri), localName, fixNull(prefix));
908            }
909            return qname;
910        }
911
912        private String fixNull(String s) {
913            return (s == null) ? "" : s;
914        }
915    }
916
917    private final class InternalNamespaceContext implements NamespaceContextEx {
918        @SuppressWarnings({"StringEquality"})
919        public String getNamespaceURI(String prefix) {
920            if (prefix == null) {
921                throw new IllegalArgumentException("Prefix cannot be null");
922            }
923
924            /*
925             * If the buffer was created using string interning
926             * intern the prefix and check for reference equality
927             * rather than using String.equals();
928             */
929            if (_stringInterningFeature) {
930                prefix = prefix.intern();
931
932                // Find the most recently declared prefix
933                for (int i = _namespaceAIIsEnd - 1; i >=0; i--) {
934                    if (prefix == _namespaceAIIsPrefix[i]) {
935                        return _namespaceAIIsNamespaceName[i];
936                    }
937                }
938            } else {
939                // Find the most recently declared prefix
940                for (int i = _namespaceAIIsEnd - 1; i >=0; i--) {
941                    if (prefix.equals(_namespaceAIIsPrefix[i])) {
942                        return _namespaceAIIsNamespaceName[i];
943                    }
944                }
945            }
946
947            // Check for XML-based prefixes
948            if (prefix.equals(XMLConstants.XML_NS_PREFIX)) {
949                return XMLConstants.XML_NS_URI;
950            } else if (prefix.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
951                return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
952            }
953
954            return null;
955        }
956
957        public String getPrefix(String namespaceURI) {
958            final Iterator i = getPrefixes(namespaceURI);
959            if (i.hasNext()) {
960                return (String)i.next();
961            } else {
962                return null;
963            }
964        }
965
966        public Iterator getPrefixes(final String namespaceURI) {
967            if (namespaceURI == null){
968                throw new IllegalArgumentException("NamespaceURI cannot be null");
969            }
970
971            if (namespaceURI.equals(XMLConstants.XML_NS_URI)) {
972                return Collections.singletonList(XMLConstants.XML_NS_PREFIX).iterator();
973            } else if (namespaceURI.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) {
974                return Collections.singletonList(XMLConstants.XMLNS_ATTRIBUTE).iterator();
975            }
976
977            return new Iterator() {
978                private int i = _namespaceAIIsEnd - 1;
979                private boolean requireFindNext = true;
980                private String p;
981
982                private String findNext() {
983                    while(i >= 0) {
984                        // Find the most recently declared namespace
985                        if (namespaceURI.equals(_namespaceAIIsNamespaceName[i])) {
986                            // Find the most recently declared prefix of the namespace
987                            // and check if the prefix is in scope with that namespace
988                            if (getNamespaceURI(_namespaceAIIsPrefix[i]).equals(
989                                    _namespaceAIIsNamespaceName[i])) {
990                                return p = _namespaceAIIsPrefix[i];
991                            }
992                        }
993                        i--;
994                    }
995                    return p = null;
996                }
997
998                public boolean hasNext() {
999                    if (requireFindNext) {
1000                        findNext();
1001                        requireFindNext = false;
1002                    }
1003                    return (p != null);
1004                }
1005
1006                public Object next() {
1007                    if (requireFindNext) {
1008                        findNext();
1009                    }
1010                    requireFindNext = true;
1011
1012                    if (p == null) {
1013                        throw new NoSuchElementException();
1014                    }
1015
1016                    return p;
1017                }
1018
1019                public void remove() {
1020                    throw new UnsupportedOperationException();
1021                }
1022            };
1023        }
1024
1025        private class BindingImpl implements NamespaceContextEx.Binding {
1026            final String _prefix;
1027            final String _namespaceURI;
1028
1029            BindingImpl(String prefix, String namespaceURI) {
1030                _prefix = prefix;
1031                _namespaceURI = namespaceURI;
1032            }
1033
1034            public String getPrefix() {
1035                return _prefix;
1036            }
1037
1038            public String getNamespaceURI() {
1039                return _namespaceURI;
1040            }
1041        }
1042
1043        public Iterator<NamespaceContextEx.Binding> iterator() {
1044            return new Iterator<NamespaceContextEx.Binding>() {
1045                private final int end = _namespaceAIIsEnd - 1;
1046                private int current = end;
1047                private boolean requireFindNext = true;
1048                private NamespaceContextEx.Binding namespace;
1049
1050                private NamespaceContextEx.Binding findNext() {
1051                    while(current >= 0) {
1052                        final String prefix = _namespaceAIIsPrefix[current];
1053
1054                        // Find if the current prefix occurs more recently
1055                        // If so then it is not in scope
1056                        int i = end;
1057                        for (;i > current; i--) {
1058                            if (prefix.equals(_namespaceAIIsPrefix[i])) {
1059                                break;
1060                            }
1061                        }
1062                        if (i == current--) {
1063                            // The current prefix is in-scope
1064                            return namespace = new BindingImpl(prefix, _namespaceAIIsNamespaceName[current]);
1065                        }
1066                    }
1067                    return namespace = null;
1068                }
1069
1070                public boolean hasNext() {
1071                    if (requireFindNext) {
1072                        findNext();
1073                        requireFindNext = false;
1074                    }
1075                    return (namespace != null);
1076                }
1077
1078                public NamespaceContextEx.Binding next() {
1079                    if (requireFindNext) {
1080                        findNext();
1081                    }
1082                    requireFindNext = true;
1083
1084                    if (namespace == null) {
1085                        throw new NoSuchElementException();
1086                    }
1087
1088                    return namespace;
1089                }
1090
1091                public void remove() {
1092                    throw new UnsupportedOperationException();
1093                }
1094            };
1095        }
1096    }
1097
1098    private class DummyLocation  implements Location {
1099        public int getLineNumber() {
1100            return -1;
1101        }
1102
1103        public int getColumnNumber() {
1104            return -1;
1105        }
1106
1107        public int getCharacterOffset() {
1108            return -1;
1109        }
1110
1111        public String getPublicId() {
1112            return null;
1113        }
1114
1115        public String getSystemId() {
1116            return _buffer.getSystemId();
1117        }
1118    }
1119
1120    private static String fixEmptyString(String s) {
1121        // s must not be null, so no need to check for that. that would be bug.
1122        if(s.length()==0)   return null;
1123        else                return s;
1124    }
1125
1126}
1127