/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

//@@3RD PARTY CODE@@

// ===========================================================================
// Copyright (c) 1996 Mort Bay Consulting Pty. Ltd. All rights reserved.
// Id: LineInput.java,v 1.9 2003/01/19 21:39:29 gregwilkins Exp
// ---------------------------------------------------------------------------

package com.sun.xml.bind.webapp.multipart;

import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;


/* ------------------------------------------------------------ */
/** Fast LineInput InputStream.
 * This buffered InputStream provides methods for reading lines
 * of bytes. The lines can be converted to String or character
 * arrays either using the default encoding or a user supplied
 * encoding.
 *
 * Buffering and data copying are highly optimized, making this
 * an ideal class for protocols that mix character encoding lines
 * with arbitrary byte data (eg HTTP).
 *
 * The buffer size is also the maximum line length in bytes and/or
 * characters. If the byte length of a line is less than the max,
 * but the character length is greater, than then trailing characters
 * are lost.
 *
 * Line termination is forgiving and accepts CR, LF, CRLF or EOF.
 * Line input uses the mark/reset mechanism, so any marks set
 * prior to a readLine call are lost.
 *
 * @version Id: LineInput.java,v 1.9 2003/01/19 21:39:29 gregwilkins Exp
 * @author Greg Wilkins (gregw)
 */
public class LineInput extends FilterInputStream                           
{
    /* ------------------------------------------------------------ */
    private byte _buf[];
    private ByteBuffer _byteBuffer;
    private InputStreamReader _reader;
    private int _mark=-1;  // reset marker
    private int _pos;      // Start marker
    private int _avail;    // Available back marker, may be byte limited
    private int _contents; // Absolute back marker of buffer
    private int _byteLimit=-1;
    private boolean _newByteLimit;
    private LineBuffer _lineBuffer;
    private String _encoding;
    private boolean _eof=false;
    private boolean _lastCr=false;
    private boolean _seenCrLf=false;
    
    private final static int LF=10;
    private final static int CR=13;

    
    /* ------------------------------------------------------------ */
    /** Constructor.
     * Default buffer and maximum line size is 2048.
     * @param in The underlying input stream.
     */
    public LineInput(InputStream in)
    {
        this(in,0);
    }
    
    /* ------------------------------------------------------------ */
    /** Constructor. 
     * @param in The underlying input stream.
     * @param bufferSize The buffer size and maximum line length.
     */
    public LineInput(InputStream in, int bufferSize)
    {
        super(in);
        _mark=-1;
        if (bufferSize==0)
            bufferSize=2048;
        _buf=new byte[bufferSize];
        _byteBuffer=new ByteBuffer(_buf);
        _lineBuffer=new LineBuffer(bufferSize);
        _reader=new InputStreamReader(_byteBuffer);
    }
    
    /* ------------------------------------------------------------ */
    /** Constructor. 
     * @param in The underlying input stream.
     * @param bufferSize The buffer size and maximum line length.
     * @param encoding the character encoding to use for readLine methods.
     * @exception UnsupportedEncodingException 
     */
    public LineInput(InputStream in, int bufferSize, String encoding)
        throws UnsupportedEncodingException
    {
        super(in);
        _mark=-1;
        if (bufferSize==0)
            bufferSize=2048;
        _buf=new byte[bufferSize];
        _byteBuffer=new ByteBuffer(_buf);
        _lineBuffer=new LineBuffer(bufferSize);
        _reader=new InputStreamReader(_byteBuffer,encoding);
        _encoding=encoding;
    }
    
    /* ------------------------------------------------------------ */
    public InputStream getInputStream()
    {
        return in;
    }
    
    /* ------------------------------------------------------------ */
    /** Set the byte limit.
     * If set, only this number of bytes are read before EOF.
     * @param bytes Limit number of bytes, or -1 for no limit.
     */
    public void setByteLimit(int bytes)
    {
        _byteLimit=bytes;
        
        if (bytes>=0)
        {
            _newByteLimit=true;
            _byteLimit-=_contents-_pos;
            if (_byteLimit<0)
            {
                _avail+=_byteLimit;
                _byteLimit=0;
            }
        }
        else
        {
            _newByteLimit=false;
            _avail=_contents;
            _eof=false;
        }
    }
    
    
    /* ------------------------------------------------------------ */
    /** Get the byte limit.
     * @return Number of bytes until EOF is returned or -1 for no limit.
     */
    public int getByteLimit()
    {
        if (_byteLimit<0)
            return _byteLimit;
        
        return _byteLimit+_avail-_pos;
    }
    
    /* ------------------------------------------------------------ */
    /** Read a line ended by CR, LF or CRLF.
     * The default or supplied encoding is used to convert bytes to
     * characters.
     * @return The line as a String or null for EOF.
     * @exception IOException 
     */
    public synchronized String readLine()
        throws IOException
    {
        int len=fillLine(_buf.length);
        
        if (len<0)
            return null;

        String s=null;
        if (_encoding==null)
            s=new String(_buf,_mark,len);
        else
        {
            try
            {
                s=new String(_buf,_mark,len,_encoding);
            }
            catch(UnsupportedEncodingException e)
            {
                System.err.println(e);
            }
        }
        _mark=-1;

        return s;
    }
    
    /* ------------------------------------------------------------ */
    /** Read a line ended by CR, LF or CRLF.
     * The default or supplied encoding is used to convert bytes to
     * characters.
     * @param c Character buffer to place the line into.
     * @param off Offset into the buffer.
     * @param len Maximum length of line.
     * @return The length of the line or -1 for EOF.
     * @exception IOException 
     */
    public int readLine(char[] c,int off,int len)
        throws IOException
    {
        int blen=fillLine(len);

        if (blen<0)
            return -1;
        if (blen==0)
            return 0;
        
        _byteBuffer.setStream(_mark,blen);
        len=_reader.read(c,off,len);
        _mark=-1;

        return len;
    }
    
    /* ------------------------------------------------------------ */
    /** Read a line ended by CR, LF or CRLF.
     * @param b Byte array to place the line into.
     * @param off Offset into the buffer.
     * @param len Maximum length of line.
     * @return The length of the line or -1 for EOF.
     * @exception IOException 
     */
    public int readLine(byte[] b,int off,int len)
        throws IOException
    {
        len=fillLine(len);

        if (len<0)
            return -1;
        if (len==0)
            return 0;
        
        System.arraycopy(_buf,_mark, b, off, len);
        _mark=-1;

        return len;
    }

    
    /* ------------------------------------------------------------ */
    /** Read a Line ended by CR, LF or CRLF.
     * Read a line into a shared LineBuffer instance.  The LineBuffer is
     * resused between calls and should not be held by the caller.
     * The default or supplied encoding is used to convert bytes to
     * characters.
     * @return LineBuffer instance or null for EOF.
     * @exception IOException 
     */
    public LineBuffer readLineBuffer()
        throws IOException
    {
        return readLineBuffer(_buf.length);
    }
    
    /* ------------------------------------------------------------ */
    /** Read a Line ended by CR, LF or CRLF.
     * Read a line into a shared LineBuffer instance.  The LineBuffer is
     * resused between calls and should not be held by the caller.
     * The default or supplied encoding is used to convert bytes to
     * characters.
     * @param len Maximum length of a line, or 0 for default
     * @return LineBuffer instance or null for EOF.
     * @exception IOException 
     */
    public LineBuffer readLineBuffer(int len)
        throws IOException
    {
        len=fillLine(len>0?len:_buf.length);

        if (len<0)
            return null;
        
        if (len==0)
        {
            _lineBuffer.size=0;
            return _lineBuffer;
        }

        _byteBuffer.setStream(_mark,len);
        _lineBuffer.size=
            _reader.read(_lineBuffer.buffer,0,_lineBuffer.buffer.length);
        _mark=-1;

        return _lineBuffer;
    }
    
    /* ------------------------------------------------------------ */
    public synchronized int read() throws IOException
    {
        int b;
        if (_pos >=_avail)
            fill();
        if (_pos >=_avail)
            b=-1;
        else
            b=_buf[_pos++]&255;
        
        return b;
    }
 
 
    /* ------------------------------------------------------------ */
    public synchronized int read(byte b[], int off, int len) throws IOException
    {
        int avail=_avail-_pos;
        if (avail <=0)
        {
            fill();
            avail=_avail-_pos;
        }

        if (avail <=0)
            len=-1;
        else
        {
            len=(avail < len) ? avail : len;
            System.arraycopy(_buf,_pos,b,off,len);
            _pos +=len;
        }
        
        return len;
    }
    
    /* ------------------------------------------------------------ */
    public long skip(long n) throws IOException
    {
        int avail=_avail-_pos;
        if (avail <=0)
        {
            fill();
            avail=_avail-_pos;
        }

        if (avail <=0)
            n=0;
        else
        {
            n=(avail < n) ? avail : n;
            _pos +=n;
        }
        
        return n;
    }


    /* ------------------------------------------------------------ */
    public synchronized int available()
        throws IOException
    {
        int in_stream=in.available();
        if (_byteLimit>=0 && in_stream>_byteLimit)
            in_stream=_byteLimit;
        
        return _avail - _pos + in_stream;
    }

    /* ------------------------------------------------------------ */
    public synchronized void mark(int limit)
        throws IllegalArgumentException
    {
        if (limit>_buf.length)
        {
            byte[] new_buf=new byte[limit];
            System.arraycopy(_buf,_pos,new_buf,_pos,_avail-_pos);
            _buf=new_buf;
            if (_byteBuffer!=null)
                _byteBuffer.setBuffer(_buf);
        }
        _mark=_pos;
    }

    /* ------------------------------------------------------------ */
    public synchronized void reset()
        throws IOException
    {
        if (_mark < 0)
            throw new IOException("Resetting to invalid mark");
        if (_byteLimit>=0)
            _byteLimit+=_pos-_mark;
        _pos=_mark;
        _mark=-1;
    }

    /* ------------------------------------------------------------ */
    public boolean markSupported()
    {
        return true;
    }
    
    /* ------------------------------------------------------------ */
    private void fill()
        throws IOException
    {
        // if the mark is in the middle of the buffer
        if (_mark > 0)
        {
            // moved saved bytes to start of buffer
            int saved=_contents - _mark;
            System.arraycopy(_buf, _mark, _buf, 0, saved);
            _pos-=_mark;
            _avail-=_mark;
            _contents=saved;
            _mark=0;
        }
        else if (_mark<0 && _pos>0)
        {
            // move remaining bytes to start of buffer
            int saved=_contents-_pos;
            System.arraycopy(_buf,_pos, _buf, 0, saved);
            _avail-=_pos;
            _contents=saved;
            _pos=0;
        }
        else if (_mark==0 && _pos>0 && _contents==_buf.length)
        {
            // Discard the mark as we need the space.
            _mark=-1;
            fill();
            return;
        }

        // Get ready to top up the buffer
        int n=0;
        _eof=false;

        // Handle byte limited EOF
        if (_byteLimit==0)
            _eof=true;
        // else loop until something is read.
        else while (!_eof && n==0 && _buf.length>_contents)
        {
            // try to read as much as will fit.
            int space=_buf.length-_contents;

            n=in.read(_buf,_contents,space);

            if (n<=0)
            {
                // If no bytes - we could be NBIO, so we want to avoid
                // a busy loop.
                if (n==0)
                {
                    // Yield to give a chance for some bytes to turn up
                    Thread.yield();

                    // Do a byte read as that is blocking
                    int b = in.read();
                    if (b>=0)
                    {
                        n=1;
                        _buf[_contents++]=(byte)b;
                    }
                    else
                        _eof=true;
                }
                else
                    _eof=true;
            }
            else
                _contents+=n;
            _avail=_contents;

            // If we have a byte limit
            if (_byteLimit>0)
            {
                // adjust the bytes available
                if (_contents-_pos >=_byteLimit)
                    _avail=_byteLimit+_pos;
                
                if (n>_byteLimit)
                    _byteLimit=0;
                else if (n>=0)
                    _byteLimit-=n;
                else if (n==-1)
                    throw new IOException("Premature EOF");
            }
        }
        
        // If we have some characters and the last read was a CR and
        // the first char is a LF, skip it
        if (_avail-_pos>0 && _lastCr && _buf[_pos]==LF)
        {
            _seenCrLf=true;
            _pos++;
            if (_mark>=0)
                _mark++;
            _lastCr=false;

            // If the byte limit has just been imposed, dont count
            // LF as content.
            if(_byteLimit>=0 && _newByteLimit)
            {
                if (_avail<_contents)
                    _avail++;
                else
                    _byteLimit++;
            }
            // If we ate all that ws filled, fill some more
            if (_pos==_avail)
                fill();
        }
        _newByteLimit=false;
    }

    
    /* ------------------------------------------------------------ */
    private int fillLine(int maxLen)
        throws IOException
    {
        _mark=_pos;
        
        if (_pos>=_avail)
            fill();
        if (_pos>=_avail)
            return -1;
        
        byte b;  
        boolean cr=_lastCr;
        boolean lf=false;
        _lastCr=false;
        int len=0;
        
    LineLoop:
        while (_pos<=_avail)
        {
            // if we have gone past the end of the buffer
            while (_pos==_avail)
            {
                // If EOF or no more space in the buffer,
                // return a line.
                if (_eof || (_mark==0 && _contents==_buf.length))
                {
                    _lastCr=!_eof && _buf[_avail-1]==CR;
                    
                    cr=true;
                    lf=true;
                    break LineLoop;
                }
                
                // If we have a CR and no more characters are available
                if (cr && in.available()==0 && !_seenCrLf)
                {
                    _lastCr=true;
                    cr=true;
                    lf=true;
                    break LineLoop;
                }
                else
                {
                    // Else just wait for more...
                    _pos=_mark;
                    fill();
                    _pos=len;
                    cr=false;
                }
            }

            // Get the byte
            b=_buf[_pos++];
            
            switch(b)
            {
              case LF:
                  if (cr) _seenCrLf=true;
                  lf=true;
                  break LineLoop;
                
              case CR: 
                  if (cr)
                  {
                      // Double CR
                      if (_pos>1)
                      {
                          _pos--;
                          break LineLoop;
                      }
                  }
                  cr=true;
                  break;
                
              default:
                  if(cr)
                  {
                      if (_pos==1)
                          cr=false;
                      else
                      {
                          _pos--;
                          break LineLoop;
                      }
                  }
                  
                  len++;
                  if (len==maxLen)
                  {
                      // look for EOL
                      if (_mark!=0 && _pos+2>=_avail && _avail<_buf.length)
                          fill();
                          
                      if (_pos<_avail && _buf[_pos]==CR)
                      {
                          cr=true;
                          _pos++;
                      }
                      if (_pos<_avail && _buf[_pos]==LF)
                      {
                          lf=true;
                          _pos++;
                      }
                      
                      if (!cr && !lf)
                      {
                          // fake EOL
                          lf=true;
                          cr=true;
                      }
                      break LineLoop;
                  }
                  
                  break;
            }
        }
        
        if (!cr && !lf && len==0)
            len=-1;
        
        return len;
    }

    /* ------------------------------------------------------------ */
    private static class ByteBuffer extends ByteArrayInputStream
    {
        ByteBuffer(byte[] buffer)
        {
            super(buffer);
        }
        
        void setBuffer(byte[] buffer)
        {
            buf=buffer;
        }
        
        void setStream(int offset,int length)
        {
            pos=offset;
            count=offset+length;
            mark=-1;
        }        
    }
    
    /* ------------------------------------------------------------ */
    /** Reusable LineBuffer.
     * Externalized LineBuffer for fast line parsing.
     */
    public static class LineBuffer
    {
        public char[] buffer;
        public int size;
        public LineBuffer(int maxLineLength)
        {buffer=new char[maxLineLength];}

        public String toString(){return new String(buffer,0,size);}
    }

    /* ------------------------------------------------------------ */
    public void destroy()
    {
        _byteBuffer=null;
        _reader=null;
        _lineBuffer=null;
        _encoding=null;
    }
}


