/** * 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 com.huawei.streaming.operator.inputstream; import java.io.IOException; import java.io.InputStream; /** An InputStream that reads data from a byte array * and optionally fills the byte array from another OutputStream as needed. * Utility methods are provided for efficiently reading primitive types and strings. */ public class Input extends InputStream { private byte[] buffer; private int capacity, position, limit, total; private InputStream inputStream; /** Creates a new Input for reading from a byte array. * @param bufferSize The size of the buffer. An exception is thrown if more bytes than this are read. */ public Input(int bufferSize) { this.capacity = bufferSize; buffer = new byte[bufferSize]; } /** Creates a new Input for reading from a byte array. * @param buffer An exception is thrown if more bytes than this are read. */ public Input(byte[] buffer) { setBuffer(buffer, 0, buffer.length); } /** Creates a new Input for reading from a byte array. * @param buffer An exception is thrown if more bytes than this are read. */ public Input(byte[] buffer, int offset, int count) { setBuffer(buffer, offset, count); } /** Creates a new Input for reading from an InputStream with a buffer size of 4096. */ public Input(InputStream input) { this(4096); if (input == null) throw new IllegalArgumentException("inputStream cannot be null."); this.inputStream = input; } /** Sets a new buffer. The position and total are reset, discarding any buffered bytes. */ public void setBuffer(byte[] bytes) { setBuffer(bytes, 0, bytes.length); } /** Sets a new buffer. The position and total are reset, discarding any buffered bytes. */ public void setBuffer(byte[] bytes, int offset, int count) { if (bytes == null) throw new IllegalArgumentException("bytes cannot be null."); buffer = bytes; position = offset; limit = count; capacity = bytes.length; total = 0; inputStream = null; } /** Returns the number of bytes read. */ public int getTotal() { return total + position; } /** Returns the current position in the buffer. */ public int getPosition() { return position; } /** Sets the current position in the buffer. */ public void setPosition(int position) { this.position = position; } /** Returns the limit for the buffer. */ public int getLimit() { return limit; } /** Sets the limit in the buffer. */ public void setLimit(int limit) { this.limit = limit; } /** Sets the position and total to zero. */ public void rewind() { position = 0; total = 0; } /** Discards the specified number of bytes. */ public void skip(int count) throws IOException { int skipCount = Math.min(limit - position, count); while (true) { position += skipCount; count -= skipCount; if (count == 0) break; skipCount = Math.min(count, capacity); require(skipCount); } } /** Fills the buffer with more bytes. Can be overridden to fill the bytes from a source other than the InputStream. */ protected int fill(byte[] buffer, int offset, int count) throws IOException { if (inputStream == null) return -1; try { return inputStream.read(buffer, offset, count); } catch (IOException ex) { throw new IOException(ex); } } /** @param required Must be > 0. The buffer is filled until it has at least this many bytes. * @return the number of bytes remaining. * @throws IOException if EOS is reached before required bytes are read (buffer underflow). */ private int require(int required) throws IOException { int remaining = limit - position; if (remaining >= required) return remaining; if (required > capacity) throw new IOException("Buffer too small: capacity: " + capacity + ", required: " + required); // Compact. System.arraycopy(buffer, position, buffer, 0, remaining); total += position; position = 0; while (true) { int count = fill(buffer, remaining, capacity - remaining); if (count == -1) { if (remaining >= required) break; throw new IOException("Buffer underflow."); } remaining += count; if (remaining >= required) break; // Enough has been read. } limit = remaining; return remaining; } /** @param optional Try to fill the buffer with this many bytes. * @return the number of bytes remaining, but not more than optional, or -1 if the EOS was reached and the buffer is empty. */ private int optional(int optional) throws IOException { int remaining = limit - position; if (remaining >= optional) return optional; optional = Math.min(optional, capacity); // Compact. System.arraycopy(buffer, position, buffer, 0, remaining); total += position; position = 0; while (true) { int count = fill(buffer, remaining, capacity - remaining); if (count == -1) break; remaining += count; if (remaining >= optional) break; // Enough has been read. } limit = remaining; return remaining == 0 ? -1 : Math.min(remaining, optional); } // InputStream /** Reads a single byte as an int from 0 to 255, or -1 if there are no more bytes are available. */ public int read() throws IOException { if (optional(1) == 0) return -1; return buffer[position++] & 0xFF; } /** Reads bytes.length bytes or less and writes them to the specified byte[], starting at 0, and returns the number of bytes * read. */ public int read(byte[] bytes) throws IOException { return read(bytes, 0, bytes.length); } /** Reads count bytes or less and writes them to the specified byte[], starting at offset, and returns the number of bytes read * or -1 if no more bytes are available. */ public int read(byte[] bytes, int offset, int count) throws IOException { if (bytes == null) throw new IllegalArgumentException("bytes cannot be null."); int startingCount = count; int copyCount = Math.min(limit - position, count); while (true) { System.arraycopy(buffer, position, bytes, offset, copyCount); position += copyCount; count -= copyCount; if (count == 0) break; offset += copyCount; copyCount = optional(count); if (copyCount == -1) { // End of data. if (startingCount == count) return -1; break; } if (position == limit) break; } return startingCount - count; } /** Discards the specified number of bytes. */ public long skip(long count) throws IOException { long remaining = count; while (remaining > 0) { int skip = Math.max(Integer.MAX_VALUE, (int)remaining); skip(skip); remaining -= skip; } return count; } // byte /** Reads a single byte. */ public byte readByte() throws IOException { require(1); return buffer[position++]; } /** Reads a byte as an int from 0 to 255. */ public int readByteUnsigned() throws IOException { require(1); return buffer[position++] & 0xFF; } /** Reads the specified number of bytes into a new byte[]. */ public byte[] readBytes(int length) throws IOException { byte[] bytes = new byte[length]; readBytes(bytes, 0, length); return bytes; } /** Reads bytes.length bytes and writes them to the specified byte[], starting at index 0. */ public void readBytes(byte[] bytes) throws IOException { readBytes(bytes, 0, bytes.length); } /** Reads count bytes and writes them to the specified byte[], starting at offset. */ public void readBytes(byte[] bytes, int offset, int count) throws IOException { if (bytes == null) throw new IllegalArgumentException("bytes cannot be null."); int copyCount = Math.min(limit - position, count); while (true) { System.arraycopy(buffer, position, bytes, offset, copyCount); position += copyCount; count -= copyCount; if (count == 0) break; offset += copyCount; copyCount = Math.min(count, capacity); require(copyCount); } } }