SquirrelJME/SquirrelJME

View on GitHub
modules/cldc-compact/src/main/java/java/io/BufferedReader.java

Summary

Maintainability
B
4 hrs
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
//     Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------

package java.io;

import cc.squirreljme.runtime.cldc.annotation.Api;
import cc.squirreljme.runtime.cldc.annotation.ImplementationNote;
import cc.squirreljme.runtime.cldc.annotation.ProgrammerTip;
import cc.squirreljme.runtime.cldc.debug.Debugging;

/**
 * This is a reader which is backed by a buffer which can read a larger block
 * of characters at once which should increase the efficiency of operations,
 * it additionally allows for marking.
 *
 * It is recommended to wrap these around {@link InputStreamReader} for
 * example due to that class not being efficient due to character conversions.
 *
 * @since 2018/11/22
 */
@ImplementationNote("This implementation uses a simple flat buffer with a " +
    "length and size.")
@Api
public class BufferedReader
    extends Reader
{
    /** Default buffer size. */
    private static final int _DEFAULT_SIZE =
        128;
    
    /** The buffer to source from. */
    private final Reader _in;
    
    /** The buffer, this is a linear buffer, null for close(). */
    private char[] _buf;
    
    /** The buffer limit. */
    private int _limit;
    
    /** The read position of the buffer (which one to read next). */
    private int _rp;
    
    /** The write position of the buffer (the valid characters). */
    private int _wp;
    
    /**
     * Initializes the reader.
     *
     * @param __r The reader to source from.
     * @param __bs The size of the internal buffer.
     * @throws IllegalArgumentException If the buffer size is zero or
     * negative.
     * @throws NullPointerException On null arguments.
     * @since 2018/11/22
     */
    @Api
    public BufferedReader(Reader __r, int __bs)
        throws IllegalArgumentException, NullPointerException
    {
        if (__r == null)
            throw new NullPointerException("NARG");
        
        /* {@squirreljme.error ZZ0g Cannot have a zero or negative buffer
        size.} */
        if (__bs <= 0)
            throw new IllegalArgumentException("ZZ0g");
        
        this._in = __r;
        this._buf = new char[__bs];
        this._limit = __bs;
    }
    
    /**
     * Initializes the buffer using a default buffer size.
     *
     * @param __r The reader to source from.
     * @throws NullPointerException On null arguments.
     * @since 2018/11/22
     */
    @Api
    public BufferedReader(Reader __r)
        throws NullPointerException
    {
        this(__r, BufferedReader._DEFAULT_SIZE);
    }
    
    /**
     * {@inheritDoc}
     * @since 2018/11/22
     */
    @Override
    public void close()
        throws IOException
    {
        // The buffer not existing indicates this is closed
        char[] buf = this._buf;
        if (buf != null)
            this._buf = null;
        
        // Close the underlying stream
        this._in.close();
    }
    
    @Override
    @ProgrammerTip("If the mark length is greater than the length of the " +
     "internal buffer, it will be re-allocated to fit. Care must be taken " +
     "depending on how large of a buffer is needed to be stored.")
    @Api
    public void mark(int __l)
        throws IOException
    {
        // Has been closed?
        char[] buf = this._buf;
        if (buf == null)
            throw new IOException("CLSD");
        
        if (false)
            throw new IOException();
        throw Debugging.todo();
    }
    
    /**
     * {@inheritDoc}
     * @since 2018/11/22
     */
    @Override
    public boolean markSupported()
    {
        return true;
    }
    
    /**
     * {@inheritDoc}
     * @since 2018/11/22
     */
    @Override
    public int read()
        throws IOException
    {
        // Has this been closed?
        char[] buf = this._buf;
        if (buf == null)
            throw new IOException("CLSD");
        
        // There are no characters in the buffer? Fill it
        int rp = this._rp;
        if (rp == this._wp)
        {
            // Read up to the limit
            int rc = this._in.read(buf, 0, this._limit);
            
            // EOF reached
            if (rc < 0)
                return -1;
            
            // Set new properties
            rp = 0;
            this._wp = rc;
        }
        
        // Read and return the next character
        int rv = buf[rp++];
        this._rp = rp;
        return rv;
    }
    
    /**
     * {@inheritDoc}
     * @since 2018/11/22
     */
    @Override
    public int read(char[] __c)
        throws IOException, NullPointerException
    {
        return this.read(__c, 0, __c.length);
    }
    
    /**
     * {@inheritDoc}
     * @since 2018/11/22
     */
    @Override
    public int read(char[] __c, int __o, int __l)
        throws IndexOutOfBoundsException, IOException, NullPointerException
    {
        if (__c == null)
            throw new NullPointerException("NARG");
        if (__o < 0 || __l < 0 || (__o + __l) < 0 || (__o + __l) > __c.length)
            throw new IndexOutOfBoundsException("IOOB");
        
        // Has been closed?
        char[] buf = this._buf;
        if (buf == null)
            throw new IOException("CLOS");
        
        // Number of characters read
        int rv = 0;
        int rp = this._rp,
            wp = this._wp;
        
        // Drain what remains of the buffer
        int left = wp - rp;
        if (left > 0)
        {
            int lim = (Math.min(left, __l));
            
            for (; rv < lim; rv++)
                __c[__o++] = buf[rp++];
            
            // Invalidate the buffer
            if (rp == wp)
            {
                this._rp = 0;
                this._wp = 0;
            }
        }
        
        // Nothing was in the buffer, so read directly from the source
        // stream
        Reader in = this._in;
        while (rv < __l)
        {
            int rc = in.read();
            
            // EOF
            if (rc < 0)
                return (rv == 0 ? -1 : rv);
            
            // Add character
            __c[__o++] = (char)rc;
            rv++;
        }
        
        // Return the read count
        return rv;
    }
    
    /**
     * Reads a line from the input and returns it.
     *
     * @return The line which was read, or {@code null} on EOF.
     * @throws IOException On read errors.
     * @since 2018/11/22
     */
    @Api
    public String readLine()
        throws IOException
    {
        // Has this been closed?
        char[] buf = this._buf;
        if (buf == null)
            throw new IOException("CLSD");
        
        // Read/write positions
        int rp = this._rp,
            wp = this._wp;
        
        // The line is potentially what is left in the buffer perhaps
        // But do not make a super tiny string builder, make a guess as to
        // what the average line length is.
        int diff = wp - rp;
        StringBuilder sb = new StringBuilder((Math.max(diff, 64)));
        
        // Continually read data
        Reader in = this._in;
        boolean wasinbuf = false;
        for (;;)
        {
            // Was newline read? Did we stop on a CR?
            boolean readnl = false,
                stoppedoncr = false,
                readeof = false;
            
            // Scan
            int ln = rp;
            if (ln < wp)
            {
                // Was something in the buffer?
                wasinbuf = true;
                
                while (ln < wp)
                {
                    // Stop on end of line characters
                    char c = buf[ln];
                    if ((stoppedoncr = (c == '\r')) || c == '\n')
                    {
                        readnl = true;
                        break;
                    }
                    
                    // Keep going
                    ln++;
                }
                
                // Append all the characters in this buffer
                sb.append(buf, rp, ln - rp);
                
                // Discard everything which was appended
                this._rp = (rp = ln);
            }
            
            // Ran out of characters to use?
            if (rp == wp)
            {
                // Read in new characters to the buffer
                int rc = in.read(buf, 0, this._limit);
                
                // EOF was reached, if the buffer was empty then nothing was
                // read anyway
                if (rc < 0)
                {
                    // True EOF
                    if (!wasinbuf && sb.length() == 0)
                        return null;
                    
                    readeof = true;
                }
                
                // Set new properties
                this._rp = (rp = ln = 0);
                this._wp = (wp = (Math.max(rc, 0)));
            }
            
            // Eat newline?
            if (readnl)
            {
                // We stopped on a CR, need to check if the following character
                // is a newline. However since the CRLF pair can end on a
                // buffer read barrier, it must be checked to make sure
                // there is absolutely no connection still.
                if (stoppedoncr)
                {
                    // There are characters left in the buffer
                    int gap = ln + 1;
                    if (gap < wp)
                    {
                        // Is a newline, so skip it
                        if (buf[gap] == '\n')
                            rp = ln + 2;
                        
                        // Otherwise do not
                        else
                            rp = ln + 1;
                    }
                    
                    // Need to actually read a character, to see what it
                    // is
                    else
                    {
                        int rx = in.read();
                        
                        // If the character is not a newline and is not EOF
                        // we will just place it in the buffer as if it
                        // were a fresh buffer
                        if (rx >= 0 && rx != '\n')
                        {
                            // Store character state
                            buf[wp++] = (char)rx;
                            this._rp = rp;
                            this._wp = wp;
                            
                            // Do not do any more of our loop stuff
                            break;
                        }
                        
                        // We will be skipping a character in the buffer
                        // anyway so ln == wp, so always just skip one
                        rp = ln + 1;
                    }
                }
                
                // Skip single character
                else
                    rp = ln + 1;
                
                // Store the new read position
                this._rp = rp;
                
                // Stop
                break;
            }
            
            // Nothing else to do on EOF
            if (readeof)
                break;
        }
        
        // Use this line
        return sb.toString();
    }
    
    /**
     * {@inheritDoc}
     * @since 2018/11/22
     */
    @Override
    public boolean ready()
        throws IOException
    {
        // Has been closed?
        char[] buf = this._buf;
        if (buf == null)
            throw new IOException("CLSD");
        
        // There are characters in the buffer or the stream itself is
        // ready
        return (this._rp < this._wp) || this._in.ready();
    }
    
    /**
     * {@inheritDoc}
     * @since 2018/11/22
     */
    @Override
    public void reset()
        throws IOException
    {
        // Has been closed?
        char[] buf = this._buf;
        if (buf == null)
            throw new IOException("CLOS");
        
        if (false)
            throw new IOException();
        throw Debugging.todo();
    }
}