/*   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.XmlDocumentProperties;
import org.apache.xmlbeans.XmlLineNumber;
import org.apache.xmlbeans.XmlOptions;
import org.w3c.dom.Node;

import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;

public class Jsr173 {
    public static Node nodeFromStream(XMLStreamReader xs) {
        if (!(xs instanceof Jsr173GateWay)) {
            return null;
        }

        Jsr173GateWay gw = (Jsr173GateWay) xs;

        Locale l = gw._l;

        if (l.noSync()) {
            l.enter();
            try {
                return nodeFromStreamImpl(gw);
            } finally {
                l.exit();
            }
        } else {
            synchronized (l) {
                l.enter();
                try {
                    return nodeFromStreamImpl(gw);
                } finally {
                    l.exit();
                }
            }
        }

    }

    public static Node nodeFromStreamImpl(Jsr173GateWay gw) {
        Cur c = gw._xs.getStreamCur();

        return c.isNode() ? (Node) c.getDom() : (Node) null;
    }

    public static XMLStreamReader newXmlStreamReader(Cur c, Object src, int off, int cch) {
        XMLStreamReaderBase xs = new XMLStreamReaderForString(c, src, off, cch);

        if (c._locale.noSync()) {
            return new UnsyncedJsr173(c._locale, xs);
        } else {
            return new SyncedJsr173(c._locale, xs);
        }
    }

    public static XMLStreamReader newXmlStreamReader(Cur c, XmlOptions options) {
        options = XmlOptions.maskNull(options);

        boolean inner = options.isSaveInner() && !options.isSaveOuter();

        XMLStreamReaderBase xs;

        int k = c.kind();

        if (k == Cur.TEXT || k < 0) {
            xs = new XMLStreamReaderForString(c, c.getChars(-1), c._offSrc, c._cchSrc);
        } else if (inner) {
            if (!c.hasAttrs() && !c.hasChildren()) {
                xs = new XMLStreamReaderForString(c, c.getFirstChars(), c._offSrc, c._cchSrc);
            } else {
                assert c.isContainer();
                xs = new XMLStreamReaderForNode(c, true);
            }
        } else {
            xs = new XMLStreamReaderForNode(c, false);
        }

        if (c._locale.noSync()) {
            return new UnsyncedJsr173(c._locale, xs);
        } else {
            return new SyncedJsr173(c._locale, xs);
        }
    }

    //
    //
    //

    private static final class XMLStreamReaderForNode extends XMLStreamReaderBase {
        public XMLStreamReaderForNode(Cur c, boolean inner) {
            super(c);

            assert c.isContainer() || c.isComment() || c.isProcinst() || c.isAttr();

            // Iterate over everything *between* _cur and _end.  Do
            // not iterate the thing to the right of _end

            if (inner) {
                assert c.isContainer();

                _cur = c.weakCur(this);

                if (!_cur.toFirstAttr()) {
                    _cur.next();
                }

                _end = c.weakCur(this);
                _end.toEnd();
            } else {
                _cur = c.weakCur(this);

                if (c.isRoot()) {
                    _wholeDoc = true;
                } else {
                    _end = c.weakCur(this);

                    if (c.isAttr()) {
                        if (!_end.toNextAttr()) {
                            _end.toParent();
                            _end.next();
                        }
                    } else {
                        _end.skip();
                    }
                }
            }

            if (!_wholeDoc) {
                // Set the _done bit properly

                _cur.push();

                try {
                    next();
                } catch (XMLStreamException e) {
                    throw new RuntimeException(e.getMessage(), e);
                }

                _cur.pop();
            }

            assert _wholeDoc || !_cur.isSamePos(_end);
        }

        protected Cur getStreamCur() {
            return _cur;
        }

        //
        //
        //

        public boolean hasNext() throws XMLStreamException {
            checkChanged();

            return !_done;
        }

        public int getEventType() {
            switch (_cur.kind()) {
                case Cur.ROOT:
                    return START_DOCUMENT;
                case -Cur.ROOT:
                    return END_DOCUMENT;
                case Cur.ELEM:
                    return START_ELEMENT;
                case -Cur.ELEM:
                    return END_ELEMENT;
                case Cur.ATTR:
                    return _cur.isXmlns() ? NAMESPACE : ATTRIBUTE;
                case Cur.TEXT:
                    return CHARACTERS;
                case Cur.COMMENT:
                    return COMMENT;
                case Cur.PROCINST:
                    return PROCESSING_INSTRUCTION;
                default:
                    throw new IllegalStateException();
            }
        }

        public int next() throws XMLStreamException {
            checkChanged();

            if (!hasNext()) {
                throw new IllegalStateException("No next event in stream");
            }

            int kind = _cur.kind();

            if (kind == -Cur.ROOT) {
                assert _wholeDoc;
                _done = true;
            } else {
                if (kind == Cur.ATTR) {
                    if (!_cur.toNextAttr()) {
                        _cur.toParent();
                        _cur.next();
                    }
                } else if (kind == Cur.COMMENT || kind == Cur.PROCINST) {
                    _cur.skip();
                } else if (kind == Cur.ROOT) {
                    if (!_cur.toFirstAttr()) {
                        _cur.next();
                    }
                } else {
                    _cur.next();
                }

                assert _wholeDoc || _end != null;

                _done = _wholeDoc ? _cur.kind() == -Cur.ROOT : _cur.isSamePos(_end);
            }

            _textFetched = false;
            _srcFetched = false;

            return getEventType();
        }

        public String getText() {
            checkChanged();

            int k = _cur.kind();

            if (k == Cur.COMMENT) {
                return _cur.getValueAsString();
            }

            if (k == Cur.TEXT) {
                return _cur.getCharsAsString();
            }

            throw new IllegalStateException();
        }

        public boolean isStartElement() {
            return getEventType() == START_ELEMENT;
        }

        public boolean isEndElement() {
            return getEventType() == END_ELEMENT;
        }

        public boolean isCharacters() {
            return getEventType() == CHARACTERS;
        }

        public String getElementText() throws XMLStreamException {
            checkChanged();

            if (!isStartElement()) {
                throw new IllegalStateException();
            }

            StringBuilder sb = new StringBuilder();

            for (; ; ) {
                if (!hasNext()) {
                    throw new XMLStreamException();
                }

                int e = next();

                if (e == END_ELEMENT) {
                    break;
                } else if (e == START_ELEMENT) {
                    throw new XMLStreamException();
                } else if (e != COMMENT && e != PROCESSING_INSTRUCTION) {
                    sb.append(getText());
                }
            }

            return sb.toString();
        }

        public int nextTag() throws XMLStreamException {
            checkChanged();

            for (; ; ) {
                if (isStartElement() || isEndElement()) {
                    return getEventType();
                }

                if (!isWhiteSpace()) {
                    throw new XMLStreamException();
                }

                if (!hasNext()) {
                    throw new XMLStreamException();
                }

                next();
            }
        }

        private static boolean matchAttr(Cur c, String uri, String local) {
            assert c.isNormalAttr();

            QName name = c.getName();

            return
                name.getLocalPart().equals(local) &&
                (uri == null || name.getNamespaceURI().equals(uri));
        }

        private static Cur toAttr(Cur c, String uri, String local) {
            if (uri == null || local == null || local.length() == 0) {
                throw new IllegalArgumentException();
            }

            Cur ca = c.tempCur();
            boolean match = false;

            if (c.isElem()) {
                if (ca.toFirstAttr()) {
                    do {
                        if (ca.isNormalAttr() && matchAttr(ca, uri, local)) {
                            match = true;
                            break;
                        }
                    }
                    while (ca.toNextSibling());
                }
            } else if (c.isNormalAttr()) {
                match = matchAttr(c, uri, local);
            } else {
                throw new IllegalStateException();
            }

            if (!match) {
                ca.release();
                ca = null;
            }

            return ca;
        }

        public String getAttributeValue(String uri, String local) {
            Cur ca = toAttr(_cur, uri, local);

            String value = null;

            if (ca != null) {
                value = ca.getValueAsString();
                ca.release();
            }

            return value;
        }

        private static Cur toAttr(Cur c, int i) {
            if (i < 0) {
                throw new IndexOutOfBoundsException("Attribute index is negative");
            }

            Cur ca = c.tempCur();
            boolean match = false;

            if (c.isElem()) {
                if (ca.toFirstAttr()) {
                    do {
                        if (ca.isNormalAttr() && i-- == 0) {
                            match = true;
                            break;
                        }
                    }
                    while (ca.toNextSibling());
                }
            } else if (c.isNormalAttr()) {
                match = i == 0;
            } else {
                throw new IllegalStateException();
            }

            if (!match) {
                ca.release();
                throw new IndexOutOfBoundsException("Attribute index is too large");
            }

            return ca;
        }

        public int getAttributeCount() {
            int n = 0;

            if (_cur.isElem()) {
                Cur ca = _cur.tempCur();

                if (ca.toFirstAttr()) {
                    do {
                        if (ca.isNormalAttr()) {
                            n++;
                        }
                    }
                    while (ca.toNextSibling());
                }

                ca.release();
            } else if (_cur.isNormalAttr()) {
                n++;
            } else {
                throw new IllegalStateException();
            }

            return n;
        }

        public QName getAttributeName(int index) {
            Cur ca = toAttr(_cur, index);
            QName name = ca.getName();
            ca.release();
            return name;
        }

        public String getAttributeNamespace(int index) {
            return getAttributeName(index).getNamespaceURI();
        }

        public String getAttributeLocalName(int index) {
            return getAttributeName(index).getLocalPart();
        }

        public String getAttributePrefix(int index) {
            return getAttributeName(index).getPrefix();
        }

        public String getAttributeType(int index) {
            toAttr(_cur, index).release();
            return "CDATA";
        }

        public String getAttributeValue(int index) {
            Cur ca = toAttr(_cur, index);

            String value = null;

            if (ca != null) {
                value = ca.getValueAsString();
                ca.release();
            }

            return value;
        }

        public boolean isAttributeSpecified(int index) {
            // Go to attr to force index check
            Cur ca = toAttr(_cur, index);
            ca.release();

            return false;
        }

        public int getNamespaceCount() {
            int n = 0;

            if (_cur.isElem() || _cur.kind() == -Cur.ELEM) {
                Cur ca = _cur.tempCur();

                if (_cur.kind() == -Cur.ELEM) {
                    ca.toParent();
                }

                if (ca.toFirstAttr()) {
                    do {
                        if (ca.isXmlns()) {
                            n++;
                        }
                    }
                    while (ca.toNextSibling());
                }

                ca.release();
            } else if (_cur.isXmlns()) {
                n++;
            } else {
                throw new IllegalStateException();
            }

            return n;
        }

        private static Cur toXmlns(Cur c, int i) {
            if (i < 0) {
                throw new IndexOutOfBoundsException("Namespace index is negative");
            }

            Cur ca = c.tempCur();
            boolean match = false;

            if (c.isElem() || c.kind() == -Cur.ELEM) {
                if (c.kind() == -Cur.ELEM) {
                    ca.toParent();
                }

                if (ca.toFirstAttr()) {
                    do {
                        if (ca.isXmlns() && i-- == 0) {
                            match = true;
                            break;
                        }
                    }
                    while (ca.toNextSibling());
                }
            } else if (c.isXmlns()) {
                match = i == 0;
            } else {
                throw new IllegalStateException();
            }

            if (!match) {
                ca.release();
                throw new IndexOutOfBoundsException("Namespace index is too large");
            }

            return ca;
        }

        public String getNamespacePrefix(int index) {
            Cur ca = toXmlns(_cur, index);
            String prefix = ca.getXmlnsPrefix();
            ca.release();
            return prefix;
        }

        public String getNamespaceURI(int index) {
            Cur ca = toXmlns(_cur, index);
            String uri = ca.getXmlnsUri();
            ca.release();
            return uri;
        }

        private void fetchChars() {
            if (!_textFetched) {
                int k = _cur.kind();

                Cur cText = null;

                if (k == Cur.COMMENT) {
                    cText = _cur.tempCur();
                    cText.next();
                } else if (k == Cur.TEXT) {
                    cText = _cur;
                } else {
                    throw new IllegalStateException();
                }

                Object src = cText.getChars(-1);

                ensureCharBufLen(cText._cchSrc);

                CharUtil.getChars(
                    _chars, _offChars = 0, src, cText._offSrc, _cchChars = cText._cchSrc);

                if (cText != _cur) {
                    cText.release();
                }

                _textFetched = true;
            }
        }

        private void ensureCharBufLen(int cch) {
            if (_chars == null || _chars.length < cch) {
                int l = 256;

                while (l < cch) {
                    l *= 2;
                }

                _chars = new char[l];
            }
        }

        public char[] getTextCharacters() {
            checkChanged();

            fetchChars();

            return _chars;
        }

        public int getTextStart() {
            checkChanged();

            fetchChars();

            return _offChars;
        }

        public int getTextLength() {
            checkChanged();

            fetchChars();

            return _cchChars;
        }

        public int getTextCharacters(
            int sourceStart, char[] target, int targetStart, int length)
            throws XMLStreamException {
            if (length < 0) {
                throw new IndexOutOfBoundsException();
            }

            if (targetStart < 0 || targetStart >= target.length) {
                throw new IndexOutOfBoundsException();
            }

            if (targetStart + length > target.length) {
                throw new IndexOutOfBoundsException();
            }

            if (!_srcFetched) {
                int k = _cur.kind();

                Cur cText = null;

                if (k == Cur.COMMENT) {
                    cText = _cur.tempCur();
                    cText.next();
                } else if (k == Cur.TEXT) {
                    cText = _cur;
                } else {
                    throw new IllegalStateException();
                }

                _src = cText.getChars(-1);
                _offSrc = cText._offSrc;
                _cchSrc = cText._cchSrc;

                if (cText != _cur) {
                    cText.release();
                }

                _srcFetched = true;
            }

            if (sourceStart > _cchSrc) {
                throw new IndexOutOfBoundsException();
            }

            if (sourceStart + length > _cchSrc) {
                length = _cchSrc - sourceStart;
            }

            CharUtil.getChars(target, targetStart, _src, _offSrc, length);

            return length;
        }

        public boolean hasText() {
            int k = _cur.kind();

            return k == Cur.COMMENT || k == Cur.TEXT;
        }

        public boolean hasName() {
            int k = _cur.kind();
            return k == Cur.ELEM || k == -Cur.ELEM;
        }

        public QName getName() {
            if (!hasName()) {
                throw new IllegalStateException();
            }

            return _cur.getName();
        }

        public String getNamespaceURI() {
            return getName().getNamespaceURI();
        }

        public String getLocalName() {
            return getName().getLocalPart();
        }

        public String getPrefix() {
            return getName().getPrefix();
        }

        public String getPITarget() {
            return _cur.kind() == Cur.PROCINST ? _cur.getName().getLocalPart() : null;
        }

        public String getPIData() {
            return _cur.kind() == Cur.PROCINST ? _cur.getValueAsString() : null;
        }

        //
        //
        //

        private boolean _wholeDoc;
        private boolean _done;

        private Cur _cur;
        private Cur _end;

        private boolean _srcFetched;
        private Object _src;
        private int _offSrc;
        private int _cchSrc;

        private boolean _textFetched;
        private char[] _chars;
        private int _offChars;
        private int _cchChars;
    }

    //
    //
    //

    private static abstract class XMLStreamReaderBase
        implements XMLStreamReader, NamespaceContext, Location {
        XMLStreamReaderBase(Cur c) {
            _locale = c._locale;
            _version = _locale.version();
        }

        protected final void checkChanged() {
            if (_version != _locale.version()) {
                throw new ConcurrentModificationException("Document changed while streaming");
            }
        }

        //
        // XMLStreamReader methods
        //

        public void close() throws XMLStreamException {
            checkChanged();
        }

        public boolean isWhiteSpace() {
            checkChanged();

            // TODO - avoid creating a string here
            String s = getText();

            return _locale.getCharUtil().isWhiteSpace(s, 0, s.length());
        }

        public Location getLocation() {
            checkChanged();

            Cur c = getStreamCur();

            XmlLineNumber ln = (XmlLineNumber) c.getBookmark(XmlLineNumber.class);

            // BUGBUG - put source name here
            _uri = null;

            if (ln != null) {
                _line = ln.getLine();
                _column = ln.getColumn();
                _offset = ln.getOffset();
            } else {
                _line = -1;
                _column = -1;
                _offset = -1;
            }

            return this;
        }


        public Object getProperty(String name) {
            checkChanged();

            if (name == null) {
                throw new IllegalArgumentException("Property name is null");
            }

            // BUGBUG - I should implement some perperties here

            return null;
        }

        public String getCharacterEncodingScheme() {
            checkChanged();

            XmlDocumentProperties props = _locale.getDocProps(getStreamCur(), false);

            return props == null ? null : props.getEncoding();
        }

        public String getEncoding() {
            // BUGBUG - this should probably return the actual decoding used on the document
            return null;
        }

        public String getVersion() {
            checkChanged();

            XmlDocumentProperties props = _locale.getDocProps(getStreamCur(), false);

            return props == null ? null : props.getVersion();
        }

        public boolean isStandalone() {
            checkChanged();

            XmlDocumentProperties props = _locale.getDocProps(getStreamCur(), false);

            return props == null ? false : props.getStandalone();
        }

        public boolean standaloneSet() {
            checkChanged();

            return false;
        }

        public void require(int type, String namespaceURI, String localName)
            throws XMLStreamException {
            checkChanged();

            if (type != getEventType()) {
                throw new XMLStreamException();
            }

            if (namespaceURI != null && !getNamespaceURI().equals(namespaceURI)) {
                throw new XMLStreamException();
            }

            if (localName != null && !getLocalName().equals(localName)) {
                throw new XMLStreamException();
            }
        }

        //
        // Location methods do not need a gatway
        //

        public int getCharacterOffset() {
            return _offset;
        }

        public int getColumnNumber() {
            return _column;
        }

        public int getLineNumber() {
            return _line;
        }

        public String getLocationURI() {
            return _uri;
        }

        public String getPublicId() {
            return null;
        }

        public String getSystemId() {
            return null;
        }

        public NamespaceContext getNamespaceContext() {
            throw new RuntimeException("This version of getNamespaceContext should not be called");
        }

        public String getNamespaceURI(String prefix) {
            checkChanged();

            Cur c = getStreamCur();

            c.push();

            if (!c.isContainer()) {
                c.toParent();
            }

            String ns = c.namespaceForPrefix(prefix, true);

            c.pop();

            return ns;
        }

        public String getPrefix(String namespaceURI) {
            checkChanged();

            Cur c = getStreamCur();

            c.push();

            if (!c.isContainer()) {
                c.toParent();
            }

            String prefix = c.prefixForNamespace(namespaceURI, null, false);

            c.pop();

            return prefix;
        }

        public Iterator<String> getPrefixes(String namespaceURI) {
            checkChanged();

            // BUGBUG - get only one for now ...

            HashMap<String, String> map = new HashMap<>();

            map.put(namespaceURI, getPrefix(namespaceURI));

            return map.values().iterator();
        }

        //
        //
        //

        protected abstract Cur getStreamCur();

        //
        //
        //

        private Locale _locale;
        private long _version;

        String _uri;

        int _line = -1;
        int _column = -1;
        int _offset = -1;
    }

    //
    //
    //

    private static final class XMLStreamReaderForString extends XMLStreamReaderBase {
        XMLStreamReaderForString(Cur c, Object src, int off, int cch) {
            super(c);

            _src = src;
            _off = off;
            _cch = cch;

            _cur = c;
        }

        protected Cur getStreamCur() {
            return _cur;
        }

        //
        // Legal stream methods
        //

        public String getText() {
            checkChanged();

            return CharUtil.getString(_src, _off, _cch);
        }

        public char[] getTextCharacters() {
            checkChanged();

            char[] chars = new char[_cch];

            CharUtil.getChars(chars, 0, _src, _off, _cch);

            return chars;
        }

        public int getTextStart() {
            checkChanged();

            return _off;
        }

        public int getTextLength() {
            checkChanged();

            return _cch;
        }

        public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) {
            checkChanged();

            if (length < 0) {
                throw new IndexOutOfBoundsException();
            }

            if (sourceStart > _cch) {
                throw new IndexOutOfBoundsException();
            }

            if (sourceStart + length > _cch) {
                length = _cch - sourceStart;
            }

            CharUtil.getChars(target, targetStart, _src, _off + sourceStart, length);

            return length;
        }

        public int getEventType() {
            checkChanged();
            return CHARACTERS;
        }

        public boolean hasName() {
            checkChanged();
            return false;
        }

        public boolean hasNext() {
            checkChanged();
            return false;
        }

        public boolean hasText() {
            checkChanged();
            return true;
        }

        public boolean isCharacters() {
            checkChanged();
            return true;
        }

        public boolean isEndElement() {
            checkChanged();
            return false;
        }

        public boolean isStartElement() {
            checkChanged();
            return false;
        }

        //
        // Illegal stream methods
        //

        public int getAttributeCount() {
            throw new IllegalStateException();
        }

        public String getAttributeLocalName(int index) {
            throw new IllegalStateException();
        }

        public QName getAttributeName(int index) {
            throw new IllegalStateException();
        }

        public String getAttributeNamespace(int index) {
            throw new IllegalStateException();
        }

        public String getAttributePrefix(int index) {
            throw new IllegalStateException();
        }

        public String getAttributeType(int index) {
            throw new IllegalStateException();
        }

        public String getAttributeValue(int index) {
            throw new IllegalStateException();
        }

        public String getAttributeValue(String namespaceURI, String localName) {
            throw new IllegalStateException();
        }

        public String getElementText() {
            throw new IllegalStateException();
        }

        public String getLocalName() {
            throw new IllegalStateException();
        }

        public QName getName() {
            throw new IllegalStateException();
        }

        public int getNamespaceCount() {
            throw new IllegalStateException();
        }

        public String getNamespacePrefix(int index) {
            throw new IllegalStateException();
        }

        public String getNamespaceURI(int index) {
            throw new IllegalStateException();
        }

        public String getNamespaceURI() {
            throw new IllegalStateException();
        }

        public String getPIData() {
            throw new IllegalStateException();
        }

        public String getPITarget() {
            throw new IllegalStateException();
        }

        public String getPrefix() {
            throw new IllegalStateException();
        }

        public boolean isAttributeSpecified(int index) {
            throw new IllegalStateException();
        }

        public int next() {
            throw new IllegalStateException();
        }

        public int nextTag() {
            throw new IllegalStateException();
        }

        public String getPublicId() {
            throw new IllegalStateException();
        }

        public String getSystemId() {
            throw new IllegalStateException();
        }

        private Cur _cur;
        private Object _src;
        private int _off;
        private int _cch;
    }

    //
    //
    //

    private static abstract class Jsr173GateWay {
        public Jsr173GateWay(Locale l, XMLStreamReaderBase xs) {
            _l = l;
            _xs = xs;
        }

        Locale _l;
        XMLStreamReaderBase _xs;
    }

    private static final class SyncedJsr173 extends Jsr173GateWay implements XMLStreamReader, Location, NamespaceContext {
        public SyncedJsr173(Locale l, XMLStreamReaderBase xs) {
            super(l, xs);
        }

        public Object getProperty(java.lang.String name) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getProperty(name);
                } finally {
                    _l.exit();
                }
            }
        }

        public int next() throws XMLStreamException {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.next();
                } finally {
                    _l.exit();
                }
            }
        }

        public void require(int type, String namespaceURI, String localName) throws XMLStreamException {
            synchronized (_l) {
                _l.enter();
                try {
                    _xs.require(type, namespaceURI, localName);
                } finally {
                    _l.exit();
                }
            }
        }

        public String getElementText() throws XMLStreamException {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getElementText();
                } finally {
                    _l.exit();
                }
            }
        }

        public int nextTag() throws XMLStreamException {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.nextTag();
                } finally {
                    _l.exit();
                }
            }
        }

        public boolean hasNext() throws XMLStreamException {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.hasNext();
                } finally {
                    _l.exit();
                }
            }
        }

        public void close() throws XMLStreamException {
            synchronized (_l) {
                _l.enter();
                try {
                    _xs.close();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getNamespaceURI(String prefix) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getNamespaceURI(prefix);
                } finally {
                    _l.exit();
                }
            }
        }

        public boolean isStartElement() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.isStartElement();
                } finally {
                    _l.exit();
                }
            }
        }

        public boolean isEndElement() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.isEndElement();
                } finally {
                    _l.exit();
                }
            }
        }

        public boolean isCharacters() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.isCharacters();
                } finally {
                    _l.exit();
                }
            }
        }

        public boolean isWhiteSpace() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.isWhiteSpace();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getAttributeValue(String namespaceURI, String localName) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getAttributeValue(namespaceURI, localName);
                } finally {
                    _l.exit();
                }
            }
        }

        public int getAttributeCount() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getAttributeCount();
                } finally {
                    _l.exit();
                }
            }
        }

        public QName getAttributeName(int index) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getAttributeName(index);
                } finally {
                    _l.exit();
                }
            }
        }

        public String getAttributeNamespace(int index) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getAttributeNamespace(index);
                } finally {
                    _l.exit();
                }
            }
        }

        public String getAttributeLocalName(int index) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getAttributeLocalName(index);
                } finally {
                    _l.exit();
                }
            }
        }

        public String getAttributePrefix(int index) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getAttributePrefix(index);
                } finally {
                    _l.exit();
                }
            }
        }

        public String getAttributeType(int index) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getAttributeType(index);
                } finally {
                    _l.exit();
                }
            }
        }

        public String getAttributeValue(int index) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getAttributeValue(index);
                } finally {
                    _l.exit();
                }
            }
        }

        public boolean isAttributeSpecified(int index) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.isAttributeSpecified(index);
                } finally {
                    _l.exit();
                }
            }
        }

        public int getNamespaceCount() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getNamespaceCount();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getNamespacePrefix(int index) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getNamespacePrefix(index);
                } finally {
                    _l.exit();
                }
            }
        }

        public String getNamespaceURI(int index) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getNamespaceURI(index);
                } finally {
                    _l.exit();
                }
            }
        }

        public NamespaceContext getNamespaceContext() {
            return this;
        }

        public int getEventType() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getEventType();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getText() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getText();
                } finally {
                    _l.exit();
                }
            }
        }

        public char[] getTextCharacters() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getTextCharacters();
                } finally {
                    _l.exit();
                }
            }
        }

        public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) throws XMLStreamException {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getTextCharacters(sourceStart, target, targetStart, length);
                } finally {
                    _l.exit();
                }
            }
        }

        public int getTextStart() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getTextStart();
                } finally {
                    _l.exit();
                }
            }
        }

        public int getTextLength() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getTextLength();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getEncoding() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getEncoding();
                } finally {
                    _l.exit();
                }
            }
        }

        public boolean hasText() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.hasText();
                } finally {
                    _l.exit();
                }
            }
        }

        public Location getLocation() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getLocation();
                } finally {
                    _l.exit();
                }
            }
        }

        public QName getName() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getName();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getLocalName() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getLocalName();
                } finally {
                    _l.exit();
                }
            }
        }

        public boolean hasName() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.hasName();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getNamespaceURI() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getNamespaceURI();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getPrefix() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getPrefix();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getVersion() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getVersion();
                } finally {
                    _l.exit();
                }
            }
        }

        public boolean isStandalone() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.isStandalone();
                } finally {
                    _l.exit();
                }
            }
        }

        public boolean standaloneSet() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.standaloneSet();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getCharacterEncodingScheme() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getCharacterEncodingScheme();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getPITarget() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getPITarget();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getPIData() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getPIData();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getPrefix(String namespaceURI) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getPrefix(namespaceURI);
                } finally {
                    _l.exit();
                }
            }
        }

        public Iterator<String> getPrefixes(String namespaceURI) {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getPrefixes(namespaceURI);
                } finally {
                    _l.exit();
                }
            }
        }

        public int getCharacterOffset() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getCharacterOffset();
                } finally {
                    _l.exit();
                }
            }
        }

        public int getColumnNumber() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getColumnNumber();
                } finally {
                    _l.exit();
                }
            }
        }

        public int getLineNumber() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getLineNumber();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getLocationURI() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getLocationURI();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getPublicId() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getPublicId();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getSystemId() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getSystemId();
                } finally {
                    _l.exit();
                }
            }
        }
    }

    private static final class UnsyncedJsr173 extends Jsr173GateWay implements XMLStreamReader, Location, NamespaceContext {
        public UnsyncedJsr173(Locale l, XMLStreamReaderBase xs) {
            super(l, xs);
        }

        public Object getProperty(java.lang.String name) {
            try {
                _l.enter();
                return _xs.getProperty(name);
            } finally {
                _l.exit();
            }
        }

        public int next() throws XMLStreamException {
            try {
                _l.enter();
                return _xs.next();
            } finally {
                _l.exit();
            }
        }

        public void require(int type, String namespaceURI, String localName) throws XMLStreamException {
            try {
                _l.enter();
                _xs.require(type, namespaceURI, localName);
            } finally {
                _l.exit();
            }
        }

        public String getElementText() throws XMLStreamException {
            try {
                _l.enter();
                return _xs.getElementText();
            } finally {
                _l.exit();
            }
        }

        public int nextTag() throws XMLStreamException {
            try {
                _l.enter();
                return _xs.nextTag();
            } finally {
                _l.exit();
            }
        }

        public boolean hasNext() throws XMLStreamException {
            try {
                _l.enter();
                return _xs.hasNext();
            } finally {
                _l.exit();
            }
        }

        public void close() throws XMLStreamException {
            try {
                _l.enter();
                _xs.close();
            } finally {
                _l.exit();
            }
        }

        public String getNamespaceURI(String prefix) {
            try {
                _l.enter();
                return _xs.getNamespaceURI(prefix);
            } finally {
                _l.exit();
            }
        }

        public boolean isStartElement() {
            try {
                _l.enter();
                return _xs.isStartElement();
            } finally {
                _l.exit();
            }
        }

        public boolean isEndElement() {
            try {
                _l.enter();
                return _xs.isEndElement();
            } finally {
                _l.exit();
            }
        }

        public boolean isCharacters() {
            try {
                _l.enter();
                return _xs.isCharacters();
            } finally {
                _l.exit();
            }
        }

        public boolean isWhiteSpace() {
            try {
                _l.enter();
                return _xs.isWhiteSpace();
            } finally {
                _l.exit();
            }
        }

        public String getAttributeValue(String namespaceURI, String localName) {
            try {
                _l.enter();
                return _xs.getAttributeValue(namespaceURI, localName);
            } finally {
                _l.exit();
            }
        }

        public int getAttributeCount() {
            try {
                _l.enter();
                return _xs.getAttributeCount();
            } finally {
                _l.exit();
            }
        }

        public QName getAttributeName(int index) {
            try {
                _l.enter();
                return _xs.getAttributeName(index);
            } finally {
                _l.exit();
            }
        }

        public String getAttributeNamespace(int index) {
            try {
                _l.enter();
                return _xs.getAttributeNamespace(index);
            } finally {
                _l.exit();
            }
        }

        public String getAttributeLocalName(int index) {
            try {
                _l.enter();
                return _xs.getAttributeLocalName(index);
            } finally {
                _l.exit();
            }
        }

        public String getAttributePrefix(int index) {
            try {
                _l.enter();
                return _xs.getAttributePrefix(index);
            } finally {
                _l.exit();
            }
        }

        public String getAttributeType(int index) {
            try {
                _l.enter();
                return _xs.getAttributeType(index);
            } finally {
                _l.exit();
            }
        }

        public String getAttributeValue(int index) {
            try {
                _l.enter();
                return _xs.getAttributeValue(index);
            } finally {
                _l.exit();
            }
        }

        public boolean isAttributeSpecified(int index) {
            try {
                _l.enter();
                return _xs.isAttributeSpecified(index);
            } finally {
                _l.exit();
            }
        }

        public int getNamespaceCount() {
            try {
                _l.enter();
                return _xs.getNamespaceCount();
            } finally {
                _l.exit();
            }
        }

        public String getNamespacePrefix(int index) {
            try {
                _l.enter();
                return _xs.getNamespacePrefix(index);
            } finally {
                _l.exit();
            }
        }

        public String getNamespaceURI(int index) {
            try {
                _l.enter();
                return _xs.getNamespaceURI(index);
            } finally {
                _l.exit();
            }
        }

        public NamespaceContext getNamespaceContext() {
            return this;
        }

        public int getEventType() {
            try {
                _l.enter();
                return _xs.getEventType();
            } finally {
                _l.exit();
            }
        }

        public String getText() {
            try {
                _l.enter();
                return _xs.getText();
            } finally {
                _l.exit();
            }
        }

        public char[] getTextCharacters() {
            try {
                _l.enter();
                return _xs.getTextCharacters();
            } finally {
                _l.exit();
            }
        }

        public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) throws XMLStreamException {
            try {
                _l.enter();
                return _xs.getTextCharacters(sourceStart, target, targetStart, length);
            } finally {
                _l.exit();
            }
        }

        public int getTextStart() {
            try {
                _l.enter();
                return _xs.getTextStart();
            } finally {
                _l.exit();
            }
        }

        public int getTextLength() {
            try {
                _l.enter();
                return _xs.getTextLength();
            } finally {
                _l.exit();
            }
        }

        public String getEncoding() {
            try {
                _l.enter();
                return _xs.getEncoding();
            } finally {
                _l.exit();
            }
        }

        public boolean hasText() {
            try {
                _l.enter();
                return _xs.hasText();
            } finally {
                _l.exit();
            }
        }

        public Location getLocation() {
            try {
                _l.enter();
                return _xs.getLocation();
            } finally {
                _l.exit();
            }
        }

        public QName getName() {
            try {
                _l.enter();
                return _xs.getName();
            } finally {
                _l.exit();
            }
        }

        public String getLocalName() {
            try {
                _l.enter();
                return _xs.getLocalName();
            } finally {
                _l.exit();
            }
        }

        public boolean hasName() {
            try {
                _l.enter();
                return _xs.hasName();
            } finally {
                _l.exit();
            }
        }

        public String getNamespaceURI() {
            try {
                _l.enter();
                return _xs.getNamespaceURI();
            } finally {
                _l.exit();
            }
        }

        public String getPrefix() {
            try {
                _l.enter();
                return _xs.getPrefix();
            } finally {
                _l.exit();
            }
        }

        public String getVersion() {
            try {
                _l.enter();
                return _xs.getVersion();
            } finally {
                _l.exit();
            }
        }

        public boolean isStandalone() {
            try {
                _l.enter();
                return _xs.isStandalone();
            } finally {
                _l.exit();
            }
        }

        public boolean standaloneSet() {
            try {
                _l.enter();
                return _xs.standaloneSet();
            } finally {
                _l.exit();
            }
        }

        public String getCharacterEncodingScheme() {
            try {
                _l.enter();
                return _xs.getCharacterEncodingScheme();
            } finally {
                _l.exit();
            }
        }

        public String getPITarget() {
            try {
                _l.enter();
                return _xs.getPITarget();
            } finally {
                _l.exit();
            }
        }

        public String getPIData() {
            try {
                _l.enter();
                return _xs.getPIData();
            } finally {
                _l.exit();
            }
        }

        public String getPrefix(String namespaceURI) {
            try {
                _l.enter();
                return _xs.getPrefix(namespaceURI);
            } finally {
                _l.exit();
            }
        }

        public Iterator<String> getPrefixes(String namespaceURI) {
            try {
                _l.enter();
                return _xs.getPrefixes(namespaceURI);
            } finally {
                _l.exit();
            }
        }

        public int getCharacterOffset() {
            try {
                _l.enter();
                return _xs.getCharacterOffset();
            } finally {
                _l.exit();
            }
        }

        public int getColumnNumber() {
            try {
                _l.enter();
                return _xs.getColumnNumber();
            } finally {
                _l.exit();
            }
        }

        public int getLineNumber() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getLineNumber();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getLocationURI() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getLocationURI();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getPublicId() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getPublicId();
                } finally {
                    _l.exit();
                }
            }
        }

        public String getSystemId() {
            synchronized (_l) {
                _l.enter();
                try {
                    return _xs.getSystemId();
                } finally {
                    _l.exit();
                }
            }
        }
    }
}

