ReaderUtil.java

/*
 * Copyright (c) 2021, Stein Eldar Johnsen
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 net.morimekta.strings;

import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;

import static java.util.Objects.requireNonNull;

/**
 * Utilities for reading strings from base readers.
 */
public final class ReaderUtil {
    /**
     * Read all characters from the reader and return the complete string.
     *
     * @param reader The reader to read all from.
     * @return The read string.
     * @throws IOException If unable to read from reader.
     */
    public static String readAll(Reader reader) throws IOException {
        StringBuilder builder = new StringBuilder();
        char[] buffer = new char[1024];
        int n;
        while ((n = reader.read(buffer)) > 0) {
            builder.append(buffer, 0, n);
        }
        return builder.toString();
    }

    /**
     * Read from reader until given character is reached.
     *
     * @param reader     The reader to read string from.
     * @param terminator The terminating character. Will stop reading when encountering this.
     * @return The read string.
     * @throws IOException If unable to read from reader.
     */
    public static String readUntil(Reader reader, char terminator) throws IOException {
        StringBuilder builder = new StringBuilder();
        int c = -1;
        while ((c = reader.read()) >= 0) {
            if (c == terminator) {
                break;
            }
            builder.append((char) c);
        }
        return builder.toString();
    }

    /**
     * Read from reader until given character is reached.
     *
     * @param reader     The reader to read string from.
     * @param terminator The terminating character. Will stop reading when encountering this.
     * @return The read string.
     * @throws IOException If unable to read from reader.
     */
    public static String readUntil(Reader reader, CharSequence terminator) throws IOException {
        requireNonNull(reader, "reader == null");
        requireNonNull(terminator, "terminator == null");
        if (terminator.length() == 0) {
            throw new IllegalArgumentException("Empty terminator string");
        } else if (terminator.length() == 1) {
            return readUntil(reader, terminator.charAt(0));
        } else {
            return readUntilInternal(reader, terminator.toString().toCharArray(), new char[terminator.length()]);
        }
    }

    /**
     * Skip all bytes in stream until (and including) given byte is found.
     *
     * @param in        Input stream to read from.
     * @param separator Byte to skip until.
     * @return True iff the separator was encountered.
     * @throws IOException if unable to read from stream.
     */
    public static boolean skipUntil(Reader in, char separator) throws IOException {
        int r;
        while ((r = in.read()) >= 0) {
            if (((byte) r) == separator) {
                return true;
            }
        }
        return false;
    }

    /**
     * Skip all bytes reader stream until (and including) given separator is found.
     *
     * @param reader     Input stream to read from.
     * @param terminator Separator bytes to skip until.
     * @return True iff the separator was encountered.
     * @throws IOException if unable to read from stream.
     */
    public static boolean skipUntil(Reader reader, CharSequence terminator) throws IOException {
        requireNonNull(reader, "reader == null");
        requireNonNull(terminator, "terminator == null");
        final int len = terminator.length();
        if (len == 0) {
            throw new IllegalArgumentException("Empty terminator string");
        } else if (len == 1) {
            return skipUntil(reader, terminator.charAt(0));
        } else {
            return skipUntilInternal(reader, terminator.toString().toCharArray(), new char[len]);
        }
    }

    // --- PRIVATE ---

    private ReaderUtil() {}

    private static String readUntilInternal(Reader in, char[] separator, char[] buffer) throws IOException {
        StringBuilder builder = new StringBuilder();
        int r;
        int l = 0;
        while ((r = in.read()) >= 0) {
            if (l >= buffer.length) {
                builder.append(buffer[0]);
            }
            System.arraycopy(buffer, 1, buffer, 0, buffer.length - 1);
            buffer[buffer.length - 1] = (char) r;
            ++l;
            if (l >= buffer.length && Arrays.equals(separator, buffer)) {
                return builder.toString();
            }
        }
        builder.append(buffer, 0, Math.min(l, buffer.length));
        return builder.toString();
    }

    private static boolean skipUntilInternal(Reader in, char[] separator, char[] buffer) throws IOException {
        int r;
        int l = 0;
        while ((r = in.read()) >= 0) {
            System.arraycopy(buffer, 1, buffer, 0, buffer.length - 1);
            buffer[buffer.length - 1] = (char) r;
            ++l;
            if (l >= buffer.length && Arrays.equals(separator, buffer)) {
                return true;
            }
        }
        return false;
    }
}