/*   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.richParser;

import org.apache.xmlbeans.GDate;
import org.apache.xmlbeans.GDateBuilder;
import org.apache.xmlbeans.GDuration;
import org.apache.xmlbeans.XmlCalendar;
import org.apache.xmlbeans.impl.common.InvalidLexicalValueException;
import org.apache.xmlbeans.impl.common.XMLChar;
import org.apache.xmlbeans.impl.common.XmlWhitespace;
import org.apache.xmlbeans.impl.util.HexBin;
import org.apache.xmlbeans.impl.util.XsTypeConverter;

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.io.ByteArrayInputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;

/**
 * Author: Cezar Andrei (cezar.andrei at bea.com)
 * Date: Nov 17, 2003
 */
public class XMLStreamReaderExtImpl
    implements XMLStreamReaderExt {
    private final XMLStreamReader _xmlStream;
    private final CharSeqTrimWS _charSeq;
    private String _defaultValue;

    public XMLStreamReaderExtImpl(XMLStreamReader xmlStream) {
        if (xmlStream == null) {
            throw new IllegalArgumentException();
        }

        _xmlStream = xmlStream;
        _charSeq = new CharSeqTrimWS(this);
    }

    public XMLStreamReader getUnderlyingXmlStream() {
        return _xmlStream;
    }

    // XMLStreamReaderExt methods
    public String getStringValue()
        throws XMLStreamException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_PRESERVE);
        return _charSeq.toString();
    }

    public String getStringValue(int wsStyle)
        throws XMLStreamException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_PRESERVE);
        //REVIEW zieg 2004-01-11 - we should write a collapse method
        //that takes a CharSequence to void creating this extra String object
        return XmlWhitespace.collapse(_charSeq.toString(), wsStyle);
    }

    public boolean getBooleanValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexBoolean(_charSeq);
        } catch (InvalidLexicalValueException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public byte getByteValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexByte(_charSeq);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public short getShortValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexShort(_charSeq);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public int getIntValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexInt(_charSeq);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public long getLongValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexLong(_charSeq);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public BigInteger getBigIntegerValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexInteger(_charSeq);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public BigDecimal getBigDecimalValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexDecimal(_charSeq);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public float getFloatValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexFloat(_charSeq);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public double getDoubleValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexDouble(_charSeq);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public InputStream getHexBinaryValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        String text = _charSeq.toString();
        byte[] buf = HexBin.decode(text.getBytes(StandardCharsets.ISO_8859_1));
        if (buf != null) {
            return new ByteArrayInputStream(buf);
        } else {
            throw new InvalidLexicalValueException("invalid hexBinary value", _charSeq.getLocation());
        }
    }

    public InputStream getBase64Value()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        String text = _charSeq.toString();
        byte[] buf = Base64.getMimeDecoder().decode(text.getBytes(StandardCharsets.ISO_8859_1));
        if (buf != null) {
            return new ByteArrayInputStream(buf);
        } else {
            throw new InvalidLexicalValueException("invalid base64Binary value", _charSeq.getLocation());
        }
    }

    public XmlCalendar getCalendarValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return new GDateBuilder(_charSeq).getCalendar();
        } catch (IllegalArgumentException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public Date getDateValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return new GDateBuilder(_charSeq).getDate();
        } catch (IllegalArgumentException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public GDate getGDateValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexGDate(_charSeq);
        } catch (IllegalArgumentException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public GDuration getGDurationValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return new GDuration(_charSeq);
        } catch (IllegalArgumentException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public QName getQNameValue()
        throws XMLStreamException, InvalidLexicalValueException {
        _charSeq.reload(CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexQName(_charSeq, _xmlStream.getNamespaceContext());
        } catch (InvalidLexicalValueException e) {
            throw new InvalidLexicalValueException(e.getMessage(), _charSeq.getLocation());
        }
    }

    public String getAttributeStringValue(int index) throws XMLStreamException {
        return _xmlStream.getAttributeValue(index);
    }

    public String getAttributeStringValue(int index, int wsStyle) throws XMLStreamException {
        return XmlWhitespace.collapse(_xmlStream.getAttributeValue(index), wsStyle);
    }

    public boolean getAttributeBooleanValue(int index) throws XMLStreamException {
        try {
            return XsTypeConverter.lexBoolean(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM));
        } catch (InvalidLexicalValueException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public byte getAttributeByteValue(int index) throws XMLStreamException {
        try {
            return XsTypeConverter.lexByte(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM));
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public short getAttributeShortValue(int index) throws XMLStreamException {
        try {
            return XsTypeConverter.lexShort(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM));
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public int getAttributeIntValue(int index) throws XMLStreamException {
        try {
            return XsTypeConverter.lexInt(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM));
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public long getAttributeLongValue(int index) throws XMLStreamException {
        try {
            return XsTypeConverter.lexLong(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM));
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public BigInteger getAttributeBigIntegerValue(int index) throws XMLStreamException {
        try {
            return XsTypeConverter.lexInteger(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM));
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public BigDecimal getAttributeBigDecimalValue(int index) throws XMLStreamException {
        try {
            return XsTypeConverter.lexDecimal(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM));
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public float getAttributeFloatValue(int index) throws XMLStreamException {
        try {
            return XsTypeConverter.lexFloat(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM));
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public double getAttributeDoubleValue(int index) throws XMLStreamException {
        try {
            return XsTypeConverter.lexDouble(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM));
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public InputStream getAttributeHexBinaryValue(int index) throws XMLStreamException {
        String text = _charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM).toString();
        byte[] buf = HexBin.decode(text.getBytes(StandardCharsets.ISO_8859_1));
        if (buf != null) {
            return new ByteArrayInputStream(buf);
        } else {
            throw new InvalidLexicalValueException("invalid hexBinary value", _charSeq.getLocation());
        }
    }

    public InputStream getAttributeBase64Value(int index) throws XMLStreamException {
        String text = _charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM).toString();
        byte[] buf = Base64.getMimeDecoder().decode(text.getBytes(StandardCharsets.ISO_8859_1));
        if (buf != null) {
            return new ByteArrayInputStream(buf);
        } else {
            throw new InvalidLexicalValueException("invalid base64Binary value", _charSeq.getLocation());
        }
    }

    public XmlCalendar getAttributeCalendarValue(int index) throws XMLStreamException {
        try {
            return new GDateBuilder(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM)).
                getCalendar();
        } catch (IllegalArgumentException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public Date getAttributeDateValue(int index) throws XMLStreamException {
        try {
            return new GDateBuilder(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM))
                .getDate();
        } catch (IllegalArgumentException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public GDate getAttributeGDateValue(int index) throws XMLStreamException {
        try {
            return new GDate(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM));
        } catch (IllegalArgumentException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public GDuration getAttributeGDurationValue(int index) throws XMLStreamException {
        try {
            return new GDuration(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM));
        } catch (IllegalArgumentException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public QName getAttributeQNameValue(int index) throws XMLStreamException {
        try {
            return XsTypeConverter.lexQName(_charSeq.reloadAtt(index, CharSeqTrimWS.XMLWHITESPACE_TRIM),
                _xmlStream.getNamespaceContext());
        } catch (InvalidLexicalValueException e) {
            throw new InvalidLexicalValueException(e.getMessage(), _charSeq.getLocation());
        }
    }

    public String getAttributeStringValue(String uri, String local) throws XMLStreamException {
        return _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_PRESERVE).toString();
    }

    public String getAttributeStringValue(String uri, String local, int wsStyle) throws XMLStreamException {
        return XmlWhitespace.collapse(_xmlStream.getAttributeValue(uri, local), wsStyle);
    }

    public boolean getAttributeBooleanValue(String uri, String local) throws XMLStreamException {
        CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexBoolean(cs);
        } catch (InvalidLexicalValueException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public byte getAttributeByteValue(String uri, String local) throws XMLStreamException {
        CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexByte(cs);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public short getAttributeShortValue(String uri, String local) throws XMLStreamException {
        CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexShort(cs);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public int getAttributeIntValue(String uri, String local) throws XMLStreamException {
        CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexInt(cs);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public long getAttributeLongValue(String uri, String local) throws XMLStreamException {
        CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexLong(cs);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public BigInteger getAttributeBigIntegerValue(String uri, String local) throws XMLStreamException {
        CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexInteger(cs);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public BigDecimal getAttributeBigDecimalValue(String uri, String local) throws XMLStreamException {
        CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexDecimal(cs);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public float getAttributeFloatValue(String uri, String local) throws XMLStreamException {
        CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexFloat(cs);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public double getAttributeDoubleValue(String uri, String local) throws XMLStreamException {
        CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexDouble(cs);
        } catch (NumberFormatException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public InputStream getAttributeHexBinaryValue(String uri, String local) throws XMLStreamException {
        CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
        String text = cs.toString();
        byte[] buf = HexBin.decode(text.getBytes(StandardCharsets.ISO_8859_1));
        if (buf != null) {
            return new ByteArrayInputStream(buf);
        } else {
            throw new InvalidLexicalValueException("invalid hexBinary value", _charSeq.getLocation());
        }
    }

    public InputStream getAttributeBase64Value(String uri, String local) throws XMLStreamException {
        CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
        String text = cs.toString();
        byte[] buf = Base64.getMimeDecoder().decode(text.getBytes(StandardCharsets.ISO_8859_1));
        if (buf != null) {
            return new ByteArrayInputStream(buf);
        } else {
            throw new InvalidLexicalValueException("invalid base64Binary value", _charSeq.getLocation());
        }
    }

    public XmlCalendar getAttributeCalendarValue(String uri, String local) throws XMLStreamException {
        CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return new GDateBuilder(cs).getCalendar();
        } catch (IllegalArgumentException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public Date getAttributeDateValue(String uri, String local) throws XMLStreamException {
        try {
            CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
            return new GDateBuilder(cs).getDate();
        } catch (IllegalArgumentException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public GDate getAttributeGDateValue(String uri, String local) throws XMLStreamException {
        try {
            CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
            return new GDate(cs);
        } catch (IllegalArgumentException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public GDuration getAttributeGDurationValue(String uri, String local) throws XMLStreamException {
        try {
            return new GDuration(_charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM));
        } catch (IllegalArgumentException e) {
            throw new InvalidLexicalValueException(e, _charSeq.getLocation());
        }
    }

    public QName getAttributeQNameValue(String uri, String local) throws XMLStreamException {
        CharSequence cs = _charSeq.reloadAtt(uri, local, CharSeqTrimWS.XMLWHITESPACE_TRIM);
        try {
            return XsTypeConverter.lexQName(cs, _xmlStream.getNamespaceContext());
        } catch (InvalidLexicalValueException e) {
            throw new InvalidLexicalValueException(e.getMessage(), _charSeq.getLocation());
        }
    }

    public void setDefaultValue(String defaultValue) throws XMLStreamException {
        _defaultValue = defaultValue;
    }

    /**
     * Only trims the XML whitespace at edges, it should not be used for WS collapse
     * Used for int, short, byte
     */
    private static class CharSeqTrimWS
        implements CharSequence {
        final static int XMLWHITESPACE_PRESERVE = 1;
        final static int XMLWHITESPACE_TRIM = 2;

        private static int INITIAL_SIZE = 100;
        private char[] _buf = new char[INITIAL_SIZE];
        private int _start, _length = 0;
        private int _nonWSStart = 0;
        private int _nonWSEnd = 0;
        private String _toStringValue;
        private XMLStreamReaderExtImpl _xmlSteam;
        //private boolean _supportForGetTextCharacters = true;
        private final ExtLocation _location;
        private boolean _hasText;

        CharSeqTrimWS(XMLStreamReaderExtImpl xmlSteam) {
            _xmlSteam = xmlSteam;
            _location = new ExtLocation();
        }

        void reload(int style)
            throws XMLStreamException {
            _toStringValue = null;
            _location.reset();
            _hasText = false;

            fillBuffer();

            if (style == XMLWHITESPACE_PRESERVE) {
                _nonWSStart = 0;
                _nonWSEnd = _length;

                // takes defaultValue only if there wasn't any text
                if (!_hasText && _xmlSteam._defaultValue != null) {
                    _length = 0;
                    fillBufferFromString(_xmlSteam._defaultValue);
                }
            } else if (style == XMLWHITESPACE_TRIM) {
                for (_nonWSStart = 0; _nonWSStart < _length; _nonWSStart++) {
                    if (!XMLChar.isSpace(_buf[_nonWSStart])) {
                        break;
                    }
                }
                for (_nonWSEnd = _length; _nonWSEnd > _nonWSStart; _nonWSEnd--) {
                    if (!XMLChar.isSpace(_buf[_nonWSEnd - 1])) {
                        break;
                    }
                }

                // takes defaultValue if length after triming is 0
                if (length() == 0 && _xmlSteam._defaultValue != null) {
                    _length = 0;
                    fillBufferFromString(_xmlSteam._defaultValue);

                    //apply whispace rule on the default value
                    for (_nonWSStart = 0; _nonWSStart < _length; _nonWSStart++) {
                        if (!XMLChar.isSpace(_buf[_nonWSStart])) {
                            break;
                        }
                    }
                    for (_nonWSEnd = _length; _nonWSEnd > _nonWSStart; _nonWSEnd--) {
                        if (!XMLChar.isSpace(_buf[_nonWSEnd - 1])) {
                            break;
                        }
                    }
                }
            }
            _xmlSteam._defaultValue = null;
        }

        private void fillBuffer()
            throws XMLStreamException {
            _length = 0;

            if (_xmlSteam.getEventType() == XMLStreamReader.START_DOCUMENT) {
                _xmlSteam.next();
            }
            if (_xmlSteam.isStartElement()) {
                _xmlSteam.next();
            }

            int depth = 0;
            String error = null;
            int eventType = _xmlSteam.getEventType();

            loop:
            while (true) {
                switch (eventType) {
                    case XMLStreamReader.CDATA:
                    case XMLStreamReader.CHARACTERS:
                    case XMLStreamReader.SPACE:
                        _location.set(_xmlSteam.getLocation());

                        if (depth == 0) {
                            addTextToBuffer();
                        }

                        break;

                    case XMLStreamReader.ATTRIBUTE:
                    case XMLStreamReader.COMMENT:
                    case XMLStreamReader.DTD:
                    case XMLStreamReader.ENTITY_DECLARATION:
                    case XMLStreamReader.NAMESPACE:
                    case XMLStreamReader.NOTATION_DECLARATION:
                    case XMLStreamReader.PROCESSING_INSTRUCTION:
                    case XMLStreamReader.START_DOCUMENT:
                        // ignore
                        break;

                    case XMLStreamReader.END_DOCUMENT:
                        _location.set(_xmlSteam.getLocation());

                        break loop;

                    case XMLStreamReader.END_ELEMENT:
                        _location.set(_xmlSteam.getLocation());
                        depth--;
                        if (depth < 0) {
                            break loop;
                        }
                        break;

                    case XMLStreamReader.ENTITY_REFERENCE:
                        _location.set(_xmlSteam.getLocation());

                        addEntityToBuffer();
                        break;

                    case XMLStreamReader.START_ELEMENT:
                        depth++;
                        error = "Unexpected element '" + _xmlSteam.getName() + "' in text content.";
                        _location.set(_xmlSteam.getLocation());

                        break;
                }
                eventType = _xmlSteam.next();
            }
            if (error != null) {
                throw new XMLStreamException(error);
            }
        }

        private void ensureBufferLength(int lengthToAdd) {
            if (_length + lengthToAdd > _buf.length) {
                char[] newBuf = new char[_length + lengthToAdd];
                if (_length > 0) {
                    System.arraycopy(_buf, 0, newBuf, 0, _length);
                }
                _buf = newBuf;
            }
        }

        private void fillBufferFromString(CharSequence value) {
            int textLength = value.length();
            ensureBufferLength(textLength);

            for (int i = 0; i < textLength; i++) {
                _buf[i] = value.charAt(i);
            }
            _length = textLength;
        }

        private void addTextToBuffer() {
            _hasText = true;
            int textLength = _xmlSteam.getTextLength();
            ensureBufferLength(textLength);

            /*
            Commented out as part of the receipt of the more up to date
            jsr173_1.0_ri.jar. getTextCharacters(int, char[], int, int)
            used to throw UnsupportedOperationException always. Now it no longer
            does, but getTextCharacters(int, char[], int, int) does not return what
            we expect. So reverting to always calling getTextCharacters() until
            we can work out whether it's us that's wrong or them.

            if (_supportForGetTextCharacters)
                try
                {
                    _length = _xmlSteam.getTextCharacters(0, _buf, _length, textLength);
                }
                catch(Exception e)
                {
                    _supportForGetTextCharacters = false;
                }
            */

            // if(!_supportForGetTextCharacters)
            //{
            System.arraycopy(_xmlSteam.getTextCharacters(), _xmlSteam.getTextStart(), _buf, _length, textLength);
            _length = _length + textLength;
            //}
        }

        private void addEntityToBuffer() {
            String text = _xmlSteam.getText();

            int textLength = text.length();
            ensureBufferLength(textLength);

            text.getChars(0, text.length(), _buf, _length);
            _length = _length + text.length();
        }

        CharSequence reloadAtt(int index, int style)
            throws XMLStreamException {
            _location.reset();
            _location.set(_xmlSteam.getLocation());
            String value = _xmlSteam.getAttributeValue(index);

            if (value == null && _xmlSteam._defaultValue != null) {
                value = _xmlSteam._defaultValue;
            }

            _xmlSteam._defaultValue = null;

            int length = value.length();

            if (style == XMLWHITESPACE_PRESERVE) {
                return value;
            } else if (style == XMLWHITESPACE_TRIM) {
                int nonWSStart, nonWSEnd;
                for (nonWSStart = 0; nonWSStart < length; nonWSStart++) {
                    if (!XMLChar.isSpace(value.charAt(nonWSStart))) {
                        break;
                    }
                }
                for (nonWSEnd = length; nonWSEnd > nonWSStart; nonWSEnd--) {
                    if (!XMLChar.isSpace(value.charAt(nonWSEnd - 1))) {
                        break;
                    }
                }
                if (nonWSStart == 0 && nonWSEnd == length) {
                    return value;
                } else {
                    return value.subSequence(nonWSStart, nonWSEnd);
                }
            }

            throw new IllegalStateException("unknown style");
        }

        CharSequence reloadAtt(String uri, String local, int style)
            throws XMLStreamException {
            _location.reset();
            _location.set(_xmlSteam.getLocation());
            String value = _xmlSteam.getAttributeValue(uri, local);

            if (value == null && _xmlSteam._defaultValue != null) {
                value = _xmlSteam._defaultValue;
            }

            _xmlSteam._defaultValue = null;

            int length = value.length();

            if (style == XMLWHITESPACE_PRESERVE) {
                return value;
            } else if (style == XMLWHITESPACE_TRIM) {
                for (_nonWSStart = 0; _nonWSStart < length; _nonWSStart++) {
                    if (!XMLChar.isSpace(value.charAt(_nonWSStart))) {
                        break;
                    }
                }
                for (_nonWSEnd = length; _nonWSEnd > _nonWSStart; _nonWSEnd--) {
                    if (!XMLChar.isSpace(value.charAt(_nonWSEnd - 1))) {
                        break;
                    }
                }
                if (_nonWSStart == 0 && _nonWSEnd == length) {
                    return value;
                } else {
                    return value.subSequence(_nonWSStart, _nonWSEnd);
                }
            }
            throw new IllegalStateException("unknown style");
        }

        Location getLocation() {
            ExtLocation loc = new ExtLocation();
            loc.set(_location);
            return loc;
        }

        public int length() {
            return _nonWSEnd - _nonWSStart;
        }

        public char charAt(int index) {
            // for each char, this has to be fast, using assert instead of if throw
            assert (index < _nonWSEnd - _nonWSStart && -1 < index) :
                "Index " + index + " must be >-1 and <" + (_nonWSEnd - _nonWSStart);

            return _buf[_nonWSStart + index];
        }

        public CharSequence subSequence(int start, int end) {
            return new String(_buf, _nonWSStart + start, end - start);
        }

        public String toString() {
            if (_toStringValue != null) {
                return _toStringValue;
            }

            _toStringValue = new String(_buf, _nonWSStart, _nonWSEnd - _nonWSStart);
            return _toStringValue;
        }

        private static class ExtLocation implements Location {
            private int _line;
            private int _col;
            private int _off;
            private String _pid;
            private String _sid;
            private boolean _isSet;

            ExtLocation() {
                _isSet = false;
            }

            public int getLineNumber() {
                if (_isSet) {
                    return _line;
                } else {
                    throw new IllegalStateException();
                }
            }

            public int getColumnNumber() {
                if (_isSet) {
                    return _col;
                } else {
                    throw new IllegalStateException();
                }
            }

            public int getCharacterOffset() {
                if (_isSet) {
                    return _off;
                } else {
                    throw new IllegalStateException();
                }
            }

            public String getPublicId() {
                if (_isSet) {
                    return _pid;
                } else {
                    throw new IllegalStateException();
                }
            }

            public String getSystemId() {
                if (_isSet) {
                    return _sid;
                } else {
                    throw new IllegalStateException();
                }
            }

            void set(Location loc) {
                if (_isSet) {
                    return;
                }

                _isSet = true;
                _line = loc.getLineNumber();
                _col = loc.getColumnNumber();
                _off = loc.getCharacterOffset();
                _pid = loc.getPublicId();
                _sid = loc.getSystemId();
            }

            void reset() {
                _isSet = false;
            }
        }
    }

    // XMLStreamReader methods
    public Object getProperty(String s)
        throws IllegalArgumentException {
        return _xmlStream.getProperty(s);
    }

    public int next()
        throws XMLStreamException {
        return _xmlStream.next();
    }

    public void require(int i, String s, String s1)
        throws XMLStreamException {
        _xmlStream.require(i, s, s1);
    }

    public String getElementText() throws XMLStreamException {
        return _xmlStream.getElementText();
    }

    public int nextTag() throws XMLStreamException {
        return _xmlStream.nextTag();
    }

    public boolean hasNext() throws XMLStreamException {
        return _xmlStream.hasNext();
    }

    public void close() throws XMLStreamException {
        _xmlStream.close();
    }

    public String getNamespaceURI(String s) {
        return _xmlStream.getNamespaceURI(s);
    }

    public boolean isStartElement() {
        return _xmlStream.isStartElement();
    }

    public boolean isEndElement() {
        return _xmlStream.isEndElement();
    }

    public boolean isCharacters() {
        return _xmlStream.isCharacters();
    }

    public boolean isWhiteSpace() {
        return _xmlStream.isWhiteSpace();
    }

    public String getAttributeValue(String s, String s1) {
        return _xmlStream.getAttributeValue(s, s1);
    }

    public int getAttributeCount() {
        return _xmlStream.getAttributeCount();
    }

    public QName getAttributeName(int i) {
        return _xmlStream.getAttributeName(i);
    }

    public String getAttributeNamespace(int i) {
        return _xmlStream.getAttributeNamespace(i);
    }

    public String getAttributeLocalName(int i) {
        return _xmlStream.getAttributeLocalName(i);
    }

    public String getAttributePrefix(int i) {
        return _xmlStream.getAttributePrefix(i);
    }

    public String getAttributeType(int i) {
        return _xmlStream.getAttributeType(i);
    }

    public String getAttributeValue(int i) {
        return _xmlStream.getAttributeValue(i);
    }

    public boolean isAttributeSpecified(int i) {
        return _xmlStream.isAttributeSpecified(i);
    }

    public int getNamespaceCount() {
        return _xmlStream.getNamespaceCount();
    }

    public String getNamespacePrefix(int i) {
        return _xmlStream.getNamespacePrefix(i);
    }

    public String getNamespaceURI(int i) {
        return _xmlStream.getNamespaceURI(i);
    }

    public NamespaceContext getNamespaceContext() {
        return _xmlStream.getNamespaceContext();
    }

    public int getEventType() {
        return _xmlStream.getEventType();
    }

    public String getText() {
        return _xmlStream.getText();
    }

    public char[] getTextCharacters() {
        return _xmlStream.getTextCharacters();
    }

    public int getTextCharacters(int i, char[] chars, int i1, int i2)
        throws XMLStreamException {
        return _xmlStream.getTextCharacters(i, chars, i1, i2);
    }

    public int getTextStart() {
        return _xmlStream.getTextStart();
    }

    public int getTextLength() {
        return _xmlStream.getTextLength();
    }

    public String getEncoding() {
        return _xmlStream.getEncoding();
    }

    public boolean hasText() {
        return _xmlStream.hasText();
    }

    public Location getLocation() {
        return _xmlStream.getLocation();
    }

    public QName getName() {
        return _xmlStream.getName();
    }

    public String getLocalName() {
        return _xmlStream.getLocalName();
    }

    public boolean hasName() {
        return _xmlStream.hasName();
    }

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

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

    public String getVersion() {
        return _xmlStream.getVersion();
    }

    public boolean isStandalone() {
        return _xmlStream.isStandalone();
    }

    public boolean standaloneSet() {
        return _xmlStream.standaloneSet();
    }

    public String getCharacterEncodingScheme() {
        return _xmlStream.getCharacterEncodingScheme();
    }

    public String getPITarget() {
        return _xmlStream.getPITarget();
    }

    public String getPIData() {
        return _xmlStream.getPIData();
    }
}
