blob: 4cd3c3d4243eb871015529e2789e87bb6f9d247f [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE.txt 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.oodt.commons.io;
import java.io.FilterInputStream;
import java.io.InputStream;
import java.io.IOException;
import org.apache.oodt.commons.util.Base64;
/** An input stream that decodes its data from the RFC-1512 base 64 format.
*
* Wrap this input stream around another input stream, and all the bytes will be converted
* from their base-64 format when you read from it.
*
* @author Kelly
*/
public class Base64DecodingInputStream extends FilterInputStream {
/** Construct a base-64 decoding input stream.
*
* @param inputStream The input stream to decode.
*/
public Base64DecodingInputStream(InputStream inputStream) {
super(inputStream);
}
/** Read the next byte.
*
* Decode more base-64 data and return the next decoded byte.
*
* @return The byte, or -1 on end of stream.
* @throws IOException If an I/O error occurs.
*/
public int read() throws IOException {
if (in == null) throw new IOException("Can't read from a closed stream");
// If we've used up the decoded data buffer, read 4 more bytes and decode 'em.
if (buffer == null || index == buffer.length) {
byte[] streamBuf = new byte[4];
int toRead = 4;
int atIndex = 0;
int actuallyGot;
boolean firstRead = true;
while (toRead > 0) {
actuallyGot = in.read(streamBuf, atIndex, toRead);
if (actuallyGot == -1) {
if (firstRead) return -1;
else break;
}
firstRead = false;
atIndex += actuallyGot;
toRead -= actuallyGot;
}
buffer = Base64.decode(streamBuf);
if (buffer.length == 0) {
buffer = null;
return -1;
}
index = 0;
}
return buffer[index++] & 0xff;
}
/** Read a bunch of bytes.
*
* This decodes base-64 data from the underlying stream and puts the result into
* the given array.
*
* @param b The buffer to fill with decoded base-64 data.
* @param offset Where in the buffer to start filling.
* @param length How many bytes to fill.
* @return The actual number of decoded bytes.
* @throws IOException If an I/O error occurs.
*/
public int read(byte[] b, int offset, int length) throws IOException {
if (b == null) throw new IllegalArgumentException("Can't read data into a null array");
if (offset < 0 || offset >= b.length)
throw new IndexOutOfBoundsException("Can't read data into an array with indexes 0.." + (b.length-1)
+ " at index " + offset);
if (length < 0) throw new IllegalArgumentException("Can't read a negative amount of data");
if (offset + length > b.length)
throw new IndexOutOfBoundsException("Can't read data past the right edge of an array");
if (in == null) throw new IOException("Can't read from a closed stream");
int c = read();
if (c == -1) return -1;
b[offset] = (byte) c;
int i = 1;
try {
for (; i < length; ++i) {
c = read();
if (c == -1) break;
b[offset + i] = (byte) c;
}
} catch (IOException ignore) {}
return i;
}
/** Skip bytes.
*
* This method skips and discards <var>n</var> decoded bytes on the input stream.
*
* @param n Number of bytes to skip.
* @return Actual number of bytes skipped.
* @throws IOException If an I/O error occurs.
*/
public long skip(long n) throws IOException {
if (in == null) throw new IOException("Can't skip past data on a closed stream");
int actuallySkipped = 0;
while (n > 0) {
if (read() == -1) return actuallySkipped;
--n;
++actuallySkipped;
}
return actuallySkipped;
}
/** Return bytes available for reading or skipping without blocking.
*
* @return The number of bytes that can be read from this stream or skipped over
* on the stream without blocking.
* @throws IOException If an I/O error occurs.
*/
public int available() throws IOException {
if (in == null) throw new IOException("Can't see how many bytes are available on a closed stream");
if (buffer != null && index < buffer.length)
return buffer.length - index;
return in.available() >= 4? 1 : 0;
}
/** Close this stream.
*
* @throws IOException If an I/O error occurs.
*/
public void close() throws IOException {
if (in == null) throw new IOException("Can't close a closed stream");
in.close();
in = null;
buffer = null;
}
/** Buffer for decoded data.
*/
private byte[] buffer;
/** Where we'll next read out of the buffer.
*
* Since we always read 4 bytes at a time (a base-64 block), we can decode that
* into as many as 3 bytes, so start out the index in an invalid location.
*/
private int index = 3;
}