SquirrelJME/SquirrelJME

View on GitHub
modules/cldc-compact/src/main/java/cc/squirreljme/runtime/cldc/util/StreamUtils.java

Summary

Maintainability
A
0 mins
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 cc.squirreljme.runtime.cldc.util;

import cc.squirreljme.jvm.mle.RuntimeShelf;
import cc.squirreljme.jvm.mle.constants.MemoryProfileType;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * General utilities for streams.
 *
 * @since 2021/09/06
 */
public final class StreamUtils
{
    /**
     * Not used.
     * 
     * @since 2021/09/06
     */
    private StreamUtils()
    {
    }
    
    /**
     * Determines the best available buffer size.
     * 
     * @param __in The stream to read from, may be {@code null}.
     * @return The recommended buffer.
     * @throws IOException On read errors.
     * @throws NullPointerException On null arguments.
     * @since 2021/09/06
     */
    public static byte[] buffer(InputStream __in)
        throws IOException, NullPointerException
    {
        return new byte[StreamUtils.bufferSize(__in)];
    }
    
    /**
     * Determines the best available buffer size.
     * 
     * @param __in The stream to read from, may be {@code null}.
     * @return The recommended buffer size.
     * @throws IOException On read errors.
     * @throws NullPointerException On null arguments.
     * @since 2021/12/05
     */
    public static int bufferSize(InputStream __in)
        throws IOException, NullPointerException
    {
        // Determine the initial buffer size
        int initCap;
        switch (RuntimeShelf.memoryProfile())
        {
            case MemoryProfileType.MINIMAL:
                initCap = 4096;
                break;
            
            case MemoryProfileType.NORMAL:
            default:
                initCap = 16384;
                break;
        }
        
        // Number of bytes already available can be used to determine a more
        // optimal buffer size
        int avail = -1;
        if (__in != null)
            avail = Math.max(-1, __in.available());
        
        // Determine the allocation size to use, use the initial cap if
        // the available bytes are unknown or if the buffer size is very
        // small otherwise.
        int allocSize;
        if (avail <= 512)
            allocSize = initCap;
        
        // Otherwise use an allocation size that fits with this, but make sure
        // it is a power of two
        else
            allocSize = Math.min(initCap, Integer.highestOneBit(avail + 1));
        
        // Use this size
        return allocSize;
    }
    
    /**
     * Copies the given byte array to the given output stream, the stream
     * is not closed by this method.
     * 
     * @param __in The input byte array.
     * @param __out The output stream.
     * @throws IOException On read/write errors.
     * @throws NullPointerException On null arguments.
     * @since 2023/10/14
     */
    public static void copy(byte[] __in, OutputStream __out)
        throws IOException, NullPointerException
    {
        if (__in == null || __out == null)
            throw new NullPointerException("NARG");
        
        try (ByteArrayInputStream in = new ByteArrayInputStream(__in))
        {
            StreamUtils.copy(in, __out, StreamUtils.buffer(null));
        }
    }
    
    /**
     * Copies the given input stream to the given output stream, the streams
     * are not closed by this method.
     * 
     * @param __in The input stream.
     * @param __out The output stream.
     * @throws IOException On read/write errors.
     * @throws NullPointerException On null arguments.
     * @since 2021/09/06
     */
    public static void copy(InputStream __in, OutputStream __out)
        throws IOException, NullPointerException
    {
        if (__in == null || __out == null)
            throw new NullPointerException("NARG");
        
        StreamUtils.copy(__in, __out, StreamUtils.buffer(__in));
    }
    
    /**
     * Copies the given input stream to the given output stream using the
     * given buffer as the temporary storage area, the streams
     * are not closed by this method.
     * 
     * @param __in The input stream.
     * @param __out The output stream.
     * @param __tempBuf The temporary storage buffer.
     * @throws IOException On read/write errors.
     * @throws NullPointerException On null arguments.
     * @since 2021/09/06
     */
    public static void copy(InputStream __in, OutputStream __out,
        byte[] __tempBuf)
        throws IOException, NullPointerException
    {
        if (__in == null || __out == null || __tempBuf == null)
            throw new NullPointerException("NARG");
        
        // Perform the copy
        int chunk = __tempBuf.length;
        for (;;)
        {
            int rc = __in.read(__tempBuf);
            
            // EOF?
            if (rc < 0)
                break;
            
            __out.write(__tempBuf, 0, rc);
        }
    }
    
    /**
     * Reads every byte within the input stream.
     * 
     * @param __in The stream to read from.
     * @return A byte array containing all of the stream bytes.
     * @throws IOException On read errors.
     * @throws NullPointerException On null arguments.
     * @since 2021/12/05
     */
    public static byte[] readAll(InputStream __in)
        throws IOException, NullPointerException
    {
        if (__in == null)
            throw new NullPointerException();
        
        // Setup buffers for temporary copy
        int bufLen = StreamUtils.bufferSize(__in);
        byte[] buf = new byte[bufLen];
        
        // Write into our target buffer
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream(bufLen))
        {
            for (;;)
            {
                int rc = __in.read(buf, 0, bufLen);
                
                // EOF?
                if (rc < 0)
                    break;
                
                // Store within
                baos.write(buf, 0, rc);
            }
        
            // All done!
            return baos.toByteArray();
        }
    }
    
    /**
     * Reads every line within the input stream.
     * 
     * @param __in The stream to read from.
     * @param __charset The character set to use.
     * @return A list of all the lines.
     * @throws IOException On read errors.
     * @throws NullPointerException On null arguments.
     * @since 2023/07/25
     */
    public static List<String> readAllLines(InputStream __in, String __charset)
        throws IOException, NullPointerException
    {
        if (__in == null || __charset == null)
            throw new NullPointerException("NARG");
        
        BufferedReader in = new BufferedReader(
            new InputStreamReader(__in, __charset));
        
        // Read all lines
        List<String> result = new ArrayList<>();
        for (;;)
        {
            // Stop on EOF
            String ln = in.readLine();
            if (ln == null)
                break;
            
            // Ignore blank lines
            ln = ln.trim();
            if (ln.isEmpty())
                continue;
            
            // Store as read
            result.add(ln);
        }
        
        return result;
    }
    
    /**
     * Similarly to {@link DataInputStream#readFully(byte[], int, int)} this
     * will read all of the bytes within the stream however this will return
     * a lower return value if EOF was reached.
     * 
     * @param __in The stream to read from.
     * @param __b The output buffer.
     * @return The number of bytes read or {@code -1} on EOF.
     * @throws IOException On read errors.
     * @throws NullPointerException On null arguments.
     * @since 2021/12/05
     */
    public static int readMostly(InputStream __in, byte[] __b)
        throws IOException, NullPointerException
    {
        if (__in == null || __b == null)
            throw new NullPointerException("NARG");
        
        return StreamUtils.readMostly(__in, __b, 0, __b.length);
    }
    
    /**
     * Similarly to {@link DataInputStream#readFully(byte[], int, int)} this
     * will read all of the bytes within the stream however this will return
     * a lower return value if EOF was reached.
     * 
     * @param __in The stream to read from.
     * @param __b The output buffer.
     * @param __o The offset into the buffer.
     * @param __l The number of bytes to read.
     * @return The number of bytes read or {@code -1} on EOF.
     * @throws IndexOutOfBoundsException If the offset and/or length are
     * negative or exceed the array bounds.
     * @throws IOException On read errors.
     * @throws NullPointerException On null arguments.
     * @since 2021/12/05
     */
    public static int readMostly(InputStream __in, byte[] __b, int __o,
        int __l)
        throws IndexOutOfBoundsException, IOException, NullPointerException
    {
        if (__in == null || __b == null)
            throw new NullPointerException("NARG");
        if (__o < 0 || __l < 0 || (__o + __l) < 0 || (__o + __l) > __b.length)
            throw new IndexOutOfBoundsException("IOOB");
        
        // Constantly read in as much as possible
        int rv = 0;
        while (rv < __l)
        {
            // Read entire chunk
            int rc = __in.read(__b, __o + rv, __l - rv);
            
            // Reached EOF
            if (rc < 0)
            {
                if (rv == 0)
                    return -1;
                break;
            }
            
            // These many bytes were read, we might try again
            rv += rc;
        }
        
        return rv;
    }
}