/* * 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 org.apache.pdfbox.io; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; /** * PushBackInputStream for byte arrays. * * The inheritance from PushBackInputStream is only to avoid the * introduction of an interface with all PushBackInputStream * methods. The parent PushBackInputStream is not used in any way and * all methods are overridden. (Thus when adding new methods to PushBackInputStream * override them in this class as well!) * unread() is limited to the number of bytes already read from this stream (i.e. * the current position in the array). This limitation usually poses no problem * to a parser, but allows for some optimization since only one array has to * be dealt with. * * Note: This class is not thread safe. Clients must provide synchronization * if needed. * * Note: Calling unread() after mark() will cause (part of) the unread data to be * read again after reset(). Thus do not call unread() between mark() and reset(). * * @author Andreas Weiss (andreas.weiss@switzerland.org) * @version $Revision: 1.2 $ */ public class ByteArrayPushBackInputStream extends PushBackInputStream { private byte[] data; private int datapos; private int datalen; private int save; // dummy for base class constructor private static final InputStream DUMMY = new ByteArrayInputStream("".getBytes()); /** * Constructor. * @param input Data to read from. Note that calls to unread() will * modify this array! If this is not desired, pass a copy. * * @throws IOException If there is an IO error. */ public ByteArrayPushBackInputStream(byte[] input) throws IOException { super(DUMMY, 1); data = input; datapos = 0; save = datapos; datalen = input != null ? input.length : 0; } /** * This will peek at the next byte. * * @return The next byte on the stream, leaving it as available to read. */ public int peek() { try { // convert negative values to 128..255 return (data[datapos] + 0x100) & 0xff; } catch (ArrayIndexOutOfBoundsException ex) { // could check this before, but this is a rare case // and this method is called sufficiently often to justify this // optimization return -1; } } /** * A simple test to see if we are at the end of the stream. * * @return true if we are at the end of the stream. */ public boolean isEOF() { return datapos >= datalen; } /** * Save the state of this stream. * @param readlimit Has no effect. * @see InputStream#mark(int) */ public void mark(int readlimit) { if (false) { ++readlimit; // avoid unused param warning } save = datapos; } /** * Check if mark is supported. * @return Always true. * @see InputStream#markSupported() */ public boolean markSupported() { return true; } /** * Restore the state of this stream to the last saveState call. * @see InputStream#reset() */ public void reset() { datapos = save; } /** Available bytes. * @see InputStream#available() * @return Available bytes. */ public int available() { int av = datalen - datapos; return av > 0 ? av : 0; } /** Totally available bytes in the underlying array. * @return Available bytes. */ public int size() { return datalen; } /** * Pushes back a byte. * After this method returns, the next byte to be read will have the value (byte)by. * @param by the int value whose low-order byte is to be pushed back. * @throws IOException - If there is not enough room in the buffer for the byte. * @see java.io.PushbackInputStream#unread(int) */ public void unread(int by) throws IOException { if (datapos == 0) { throw new IOException("ByteArrayParserInputStream.unread(int): " + "cannot unread 1 byte at buffer position " + datapos); } --datapos; data[datapos] = (byte)by; } /** * Pushes back a portion of an array of bytes by copying it to the * front of the pushback buffer. After this method returns, the next byte * to be read will have the value b[off], the byte after that will have * the value b[off+1], and so forth. * @param buffer the byte array to push back. * @param off the start offset of the data. * @param len the number of bytes to push back. * @throws IOException If there is not enough room in the pushback buffer * for the specified number of bytes. * @see java.io.PushbackInputStream#unread(byte[], int, int) */ public void unread(byte[] buffer, int off, int len) throws IOException { if (len <= 0 || off >= buffer.length) { return; } if (off < 0) { off = 0; } if (len > buffer.length) { len = buffer.length; } localUnread(buffer, off, len); } /** * Pushes back a portion of an array of bytes by copying it to the * front of the pushback buffer. After this method returns, the next byte * to be read will have the value buffer[0], the byte after that will have * the value buffer[1], and so forth. * @param buffer the byte array to push back. * @throws IOException If there is not enough room in the pushback buffer * for the specified number of bytes. * @see java.io.PushbackInputStream#unread(byte[]) */ public void unread(byte[] buffer) throws IOException { localUnread(buffer, 0, buffer.length); } /** * Pushes back a portion of an array of bytes by copying it to the * front of the pushback buffer. After this method returns, the next byte * to be read will have the value buffer[off], the byte after that will have * the value buffer[off+1], and so forth. * Internal method that assumes off and len to be valid. * @param buffer the byte array to push back. * @param off the start offset of the data. * @param len the number of bytes to push back. * @throws IOException If there is not enough room in the pushback buffer * for the specified number of bytes. * @see java.io.PushbackInputStream#unread(byte[], int, int) */ private void localUnread(byte[] buffer, int off, int len) throws IOException { if (datapos < len) { throw new IOException("ByteArrayParserInputStream.unread(int): " + "cannot unread " + len + " bytes at buffer position " + datapos); } datapos -= len; System.arraycopy(buffer, off, data, datapos, len); } /** * Read a byte. * @see InputStream#read() * @return Byte read or -1 if no more bytes are available. */ public int read() { try { // convert negative values to 128..255 return (data[datapos++] + 0x100) & 0xff; } catch (ArrayIndexOutOfBoundsException ex) { // could check this before, but this is a rare case // and this method is called sufficiently often to justify this // optimization datapos = datalen; return -1; } } /** * Read a number of bytes. * @see InputStream#read(byte[]) * @param buffer the buffer into which the data is read. * @return the total number of bytes read into the buffer, or -1 if there * is no more data because the end of the stream has been reached. */ public int read(byte[] buffer) { return localRead(buffer, 0, buffer.length); } /** * Read a number of bytes. * @see InputStream#read(byte[], int, int) * @param buffer the buffer into which the data is read. * @param off the start offset in array buffer at which the data is written. * @param len the maximum number of bytes to read. * @return the total number of bytes read into the buffer, or -1 if there * is no more data because the end of the stream has been reached. */ public int read(byte[] buffer, int off, int len) { if (len <= 0 || off >= buffer.length) { return 0; } if (off < 0) { off = 0; } if (len > buffer.length) { len = buffer.length; } return localRead(buffer, off, len); } /** * Read a number of bytes. Internal method that assumes off and len to be * valid. * @see InputStream#read(byte[], int, int) * @param buffer the buffer into which the data is read. * @param off the start offset in array buffer at which the data is written. * @param len the maximum number of bytes to read. * @return the total number of bytes read into the buffer, or -1 if there * is no more data because the end of the stream has been reached. */ public int localRead(byte[] buffer, int off, int len) { if (len == 0) { return 0; // must return 0 even if at end! } else if (datapos >= datalen) { return -1; } else { int newpos = datapos + len; if (newpos > datalen) { newpos = datalen; len = newpos - datapos; } System.arraycopy(data, datapos, buffer, off, len); datapos = newpos; return len; } } /** * Skips over and discards n bytes of data from this input stream. * The skip method may, for a variety of reasons, end up skipping over some * smaller number of bytes, possibly 0. This may result from any of a number * of conditions; reaching end of file before n bytes have been skipped is * only one possibility. The actual number of bytes skipped is returned. * If n is negative, no bytes are skipped. * @param num the number of bytes to be skipped. * @return the actual number of bytes skipped. * @see InputStream#skip(long) */ public long skip(long num) { if (num <= 0) { return 0; } else { long newpos = datapos + num; if (newpos >= datalen) { num = datalen - datapos; datapos = datalen; } else { datapos = (int)newpos; } return num; } } /** Position the stream at a given index. Positioning the stream * at position size() will cause the next call to read() to return -1. * * @param newpos Position in the underlying array. A negative value will be * interpreted as 0, a value greater than size() as size(). * @return old position. */ public int seek(int newpos) { if (newpos < 0) { newpos = 0; } else if (newpos > datalen) { newpos = datalen; } int oldpos = pos; pos = newpos; return oldpos; } }