blob: c8d43620b7c64bc19dd4d22b61769d3fd9690c0c [file] [log] [blame]
/*
* 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.axiom.util.base64;
import java.io.IOException;
import java.io.OutputStream;
/**
* Base class for {@link OutputStream} implementations that encode data in base64.
*/
public abstract class AbstractBase64EncodingOutputStream extends OutputStream {
private final boolean ignoreFlush;
private final byte[] in = new byte[3];
private final byte[] out = new byte[4];
private int rest; // Number of bytes remaining in the inBuffer
private boolean completed;
/**
* Constructor.
*
* @param ignoreFlush
* Specifies if calls to {@link #flush()} should be ignored. Setting this to
* <code>true</code> is particular useful in conjunction with
* {@code DataHandler.writeTo(OutputStream)}: that method may call {@link #flush()}
* after writing the data, but the call to {@code DataHandler.writeTo(OutputStream)}
* must be followed by a call to {@link #close()} or {@link #complete()} which would
* then output a single chunk with a few bytes. In some cases this may be
* inconvenient.
*/
public AbstractBase64EncodingOutputStream(boolean ignoreFlush) {
this.ignoreFlush = ignoreFlush;
}
/**
* Default constructor. This constructor does the same as
* {@link #AbstractBase64EncodingOutputStream(boolean)} with <code>ignoreFlush</code> set to
* <code>false</code>.
*/
public AbstractBase64EncodingOutputStream() {
this(false);
}
@Override
public final void write(byte[] b, int off, int len) throws IOException {
if (completed) {
throw new IOException("Attempt to write data after base64 encoding has been completed");
}
if (rest > 0) {
while (len > 0 && rest < 3) {
in[rest++] = b[off++];
len--;
}
if (rest == 3) {
encode(in, 0, 3);
rest = 0;
}
}
while (len >= 3) {
encode(b, off, 3);
off += 3;
len -= 3;
}
while (len > 0) {
in[rest++] = b[off++];
len--;
}
}
@Override
public final void write(int b) throws IOException {
in[rest++] = (byte)b;
if (rest == 3) {
encode(in, 0, 3);
rest = 0;
}
}
/**
* Write out any pending data, including padding if necessary.
*
* @throws IOException if an I/O error occurs
*/
public final void complete() throws IOException {
if (!completed) {
if (rest > 0) {
encode(in, 0, rest);
}
flushBuffer();
completed = true;
}
}
private void encode(byte[] data, int off, int len) throws IOException {
if (len == 1) {
int i = data[off] & 0xff;
out[0] = Base64Constants.S_BASE64CHAR[i >> 2];
out[1] = Base64Constants.S_BASE64CHAR[(i << 4) & 0x3f];
out[2] = Base64Constants.S_BASE64PAD;
out[3] = Base64Constants.S_BASE64PAD;
} else if (len == 2) {
int i = ((data[off] & 0xff) << 8) + (data[off + 1] & 0xff);
out[0] = Base64Constants.S_BASE64CHAR[i >> 10];
out[1] = Base64Constants.S_BASE64CHAR[(i >> 4) & 0x3f];
out[2] = Base64Constants.S_BASE64CHAR[(i << 2) & 0x3f];
out[3] = Base64Constants.S_BASE64PAD;
} else {
int i = ((data[off] & 0xff) << 16)
+ ((data[off + 1] & 0xff) << 8)
+ (data[off + 2] & 0xff);
out[0] = Base64Constants.S_BASE64CHAR[i >> 18];
out[1] = Base64Constants.S_BASE64CHAR[(i >> 12) & 0x3f];
out[2] = Base64Constants.S_BASE64CHAR[(i >> 6) & 0x3f];
out[3] = Base64Constants.S_BASE64CHAR[i & 0x3f];
}
doWrite(out);
}
@Override
public final void flush() throws IOException {
if (!ignoreFlush) {
flushBuffer();
doFlush();
}
}
@Override
public final void close() throws IOException {
complete();
doClose();
}
/**
* Write base64 encoded data. If necessary, the implementation should accumulate
* the data in a buffer before writing it to the underlying stream.
*
* @param b a byte array of length 4
* @throws IOException if an I/O error occurs
*/
protected abstract void doWrite(byte[] b) throws IOException;
/**
* Write any pending data to the underlying stream, if applicable.
* Note that implementations should not flush the underlying stream.
*
* @throws IOException if an I/O error occurs
*/
protected abstract void flushBuffer() throws IOException;
/**
* Flush the underlying stream, if applicable.
*
* @throws IOException if an I/O error occurs
*/
protected abstract void doFlush() throws IOException;
/**
* Close the underlying stream, if applicable.
*
* @throws IOException if an I/O error occurs
*/
protected abstract void doClose() throws IOException;
}