// Copyright 2003, FreeHEP.
package org.freehep.util.io;

import java.io.IOException;
import java.io.InputStream;
import java.util.NoSuchElementException;

/**
 * The XMLSequence allows parsing by an XML Parser of concatenated XML segments.
 * Each segment needs to start with "<?xml". If the underlying stream contains
 * just one segment "<xml?" may be left out. Methods hasNext() and next()
 * should be called to retrieve the inputstream for the XMLParser.
 * 
 * Typical usage:
 * 
 * <PRE>
 * 
 * XMLSequence sequence = new XMLSequence(new FileInputStream("file.xml"));
 * 
 * SAXParserFactory factory = SAXParserFactory.newInstance(); XMLReader
 * xmlReader = factory.newSAXParser().getXMLReader();
 * 
 * while (sequence.hasNext()) { InputStream input = sequence.next(); InputSource
 * source = new InputSource(input); xmlReader.parse(source); input.close(); }
 * sequence.close();
 * 
 * </PRE>
 * 
 * IMPORTANT: inherits from InputStream rather than FilterInputStream so that
 * the correct read(byte[], int, int) method is used.
 * 
 * @author Mark Donszelmann
 * @version $Id: XMLSequence.java 8584 2006-08-10 23:06:37Z duns $
 */
public class XMLSequence extends InputStream {

    // xml declaration (lowercase!)
    private int[] xml = new int[] { '<', '?', 'x', 'm', 'l' };

    private int xmlIndex;

    // look ahead buffer
    private int buffer[] = new int[xml.length];

    private int index;

    private boolean bufferEmpty;

    // underlying stream
    private InputStream in;

    private boolean closed;

    private boolean eof;

    /**
     * Create a XML Sequence.
     * 
     * @param input stream to read from
     */
    public XMLSequence(InputStream input) {
        super();
        in = input;
        closed = false;
        eof = false;
        xmlIndex = 0;
        index = 0;
        bufferEmpty = true;
    }

    /**
     * Is another XML segment available.
     * @return true if another XML segment can be read
     */
    public boolean hasNext() {
        if (closed)
            return false;

        try {
            return readUntilXMLDeclaration();
        } catch (IOException e) {
            return false;
        }
    }

    /**
     * Returns the next XML segment.
     * @return stream to read next XML segment from.
     * @throws IOException if read fails
     */
    public InputStream next() throws IOException {
        if (eof)
            throw new NoSuchElementException(getClass() + ": at EOF.");
        if (closed)
            throw new NoSuchElementException(getClass() + ": already closed.");

        if (!readUntilXMLDeclaration())
            throw new NoSuchElementException(getClass()
                    + ": No more sequences.");

        xmlIndex = 0;
        return new NoCloseInputStream(this);
    }

    public int read() throws IOException {
        if (xmlIndex == xml.length)
            return -1;
        if (closed)
            return -1;

        int a = -1;
        if (!eof) {

            // fill buffer (at start of stream)
            if (bufferEmpty) {
                index = 0;
                for (int i = 0; i < xml.length; i++) {
                    int c = in.read();
                    buffer[i] = c;
                    if (buffer[i] == xml[xmlIndex]) {
                        // we are reading <?xml or <
                        xmlIndex++;
                    } else if (xmlIndex > 0) {
                        // we are reading <..., which is also fine.
                        xmlIndex++;
                    } else {
                        xmlIndex = 0;
                    }
                }
                bufferEmpty = false;

                if (xmlIndex == xml.length)
                    return -1;
            }

            // read next character
            a = in.read();
            if (a != -1) {
                // check if seq
                if (a == xml[xmlIndex]) {
                    xmlIndex++;
                } else {
                    xmlIndex = 0;
                }
            } else {
                // underlying end-of-file
                eof = true;
            }
        }

        // returned all...
        if (eof && (buffer[index] == -1))
            return -1;

        // replace and return next character
        int b = buffer[index];
        buffer[index] = a;
        index = (index + 1) % buffer.length;
        return b;
    }

    public void mark(int readLimit) {
        in.mark(readLimit);
    }

    public boolean markSupported() {
        return in.markSupported();
    }

    public void reset() throws IOException {
        if (closed)
            throw new IOException(getClass() + ": already closed.");

        if (!in.markSupported())
            throw new IOException(getClass() + ": does not support reset().");

        in.reset();
        xmlIndex = 0;
        index = 0;
        bufferEmpty = true;
        eof = false;
    }

    public void close() throws IOException {
        if (!closed) {
            in.close();
            closed = true;
        }
    }

    private boolean readUntilXMLDeclaration() throws IOException {
        while (read() >= 0)
            ;
        return (xmlIndex == xml.length);
    }
}
