All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.commons.base64.Base64OutputStream Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014-2024 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed 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.helger.commons.base64;

import java.io.IOException;
import java.io.OutputStream;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.collection.ArrayHelper;
import com.helger.commons.io.stream.WrappedOutputStream;

/**
 * A {@link Base64OutputStream} will write data to another
 * OutputStream , given in the constructor, and encode/decode
 * to/from Base64 notation on the fly.
 *
 * @see Base64
 * @since 1.3
 */
@NotThreadSafe
public class Base64OutputStream extends WrappedOutputStream
{
  private final boolean m_bEncode;
  private int m_nPosition;
  private byte [] m_aBuffer;
  private final int m_nBufferLength;
  private int m_nLineLength;
  private final boolean m_bBreakLines;
  // Scratch used in a few places
  private final byte [] m_aB4;
  private boolean m_bSuspendEncoding;
  // Record for later
  private final int m_nOptions;
  // Local copies to avoid extra method calls
  private final byte [] m_aDecodabet;
  private byte [] m_aNewLineBytes = new byte [] { Base64.NEW_LINE };

  /**
   * Constructs a {@link Base64OutputStream} in ENCODE mode.
   *
   * @param aOS
   *        the OutputStream to which data will be written.
   * @since 1.3
   */
  public Base64OutputStream (@Nonnull final OutputStream aOS)
  {
    this (aOS, Base64.ENCODE);
  }

  /**
   * Constructs a {@link Base64OutputStream} in either ENCODE or DECODE mode.
   * 

* Valid options: * *

   *   ENCODE or DECODE: Encode or Decode as data is read.
   *   DO_BREAK_LINES: don't break lines at 76 characters
   *     (only meaningful when encoding)
   * 
*

* Example: new Base64.OutputStream( out, Base64.ENCODE ) * * @param aOS * the OutputStream to which data will be written. * @param nOptions * Specified options. * @see Base64#ENCODE * @see Base64#DECODE * @see Base64#DO_BREAK_LINES * @since 1.3 */ public Base64OutputStream (@Nonnull final OutputStream aOS, final int nOptions) { super (aOS); m_bBreakLines = (nOptions & Base64.DO_BREAK_LINES) != 0; m_bEncode = (nOptions & Base64.ENCODE) != 0; m_nBufferLength = m_bEncode ? 3 : 4; m_aBuffer = new byte [m_nBufferLength]; m_nPosition = 0; m_nLineLength = 0; m_bSuspendEncoding = false; m_aB4 = new byte [4]; m_nOptions = nOptions; m_aDecodabet = Base64._getDecodabet (nOptions); } /** * Set the newline bytes to be used, so that "\r\n" can be used instead of the * default "\n" * * @param aNewLineBytes * The newline bytes to be used. May neither be null nor * empty. * @since 9.3.4 */ public void setNewLineBytes (@Nonnull @Nonempty final byte [] aNewLineBytes) { ValueEnforcer.notEmpty (aNewLineBytes, "NewLineBytes"); m_aNewLineBytes = ArrayHelper.getCopy (aNewLineBytes); } /** * Writes the byte to the output stream after converting to/from Base64 * notation. When encoding, bytes are buffered three at a time before the * output stream actually gets a write() call. When decoding, bytes are * buffered four at a time. * * @param theByte * the byte to write * @since 1.3 */ @Override public void write (final int theByte) throws IOException { // Encoding suspended? if (m_bSuspendEncoding) { out.write (theByte); return; } // Encode? if (m_bEncode) { m_aBuffer[m_nPosition++] = (byte) theByte; if (m_nPosition >= m_nBufferLength) { // Enough to encode. out.write (Base64._encode3to4 (m_aB4, m_aBuffer, m_nBufferLength, m_nOptions)); m_nLineLength += 4; if (m_bBreakLines && m_nLineLength >= Base64.MAX_LINE_LENGTH) { out.write (m_aNewLineBytes); m_nLineLength = 0; } m_nPosition = 0; } } else { // Else, Decoding // Meaningful Base64 character? if (m_aDecodabet[theByte & 0x7f] > Base64.WHITE_SPACE_ENC) { m_aBuffer[m_nPosition++] = (byte) theByte; if (m_nPosition >= m_nBufferLength) { // Enough to output. final int len = Base64._decode4to3 (m_aBuffer, 0, m_aB4, 0, m_nOptions); out.write (m_aB4, 0, len); m_nPosition = 0; } } else if (m_aDecodabet[theByte & 0x7f] != Base64.WHITE_SPACE_ENC) { throw new IOException ("Invalid character in Base64 data."); } } } /** * Calls {@link #write(int)} repeatedly until len bytes are * written. * * @param aBytes * array from which to read bytes * @param nOfs * offset for array * @param nLen * max number of bytes to read into array * @since 1.3 */ @Override public void write (@Nonnull final byte [] aBytes, @Nonnegative final int nOfs, @Nonnegative final int nLen) throws IOException { // Encoding suspended? if (m_bSuspendEncoding) { out.write (aBytes, nOfs, nLen); return; } for (int i = 0; i < nLen; i++) write (aBytes[nOfs + i]); } /** * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer without * closing the stream. * * @throws IOException * if there's an error. */ public void flushBase64 () throws IOException { if (m_nPosition > 0) { if (m_bEncode) { out.write (Base64._encode3to4 (m_aB4, m_aBuffer, m_nPosition, m_nOptions)); m_nPosition = 0; } else { throw new IOException ("Base64 input not properly padded."); } } } /** * Flushes and closes (I think, in the superclass) the stream. * * @since 1.3 */ @Override public void close () throws IOException { // 1. Ensure that pending characters are written flushBase64 (); // 2. Actually close the stream // Base class both flushes and closes. if (out != null) super.close (); m_aBuffer = null; out = null; } /** * Suspends encoding of the stream. May be helpful if you need to embed a * piece of base64-encoded data in a stream. * * @throws IOException * if there's an error flushing * @since 1.5.1 */ public void suspendEncoding () throws IOException { flushBase64 (); m_bSuspendEncoding = true; } /** * Resumes encoding of the stream. May be helpful if you need to embed a piece * of base64-encoded data in a stream. * * @since 1.5.1 */ public void resumeEncoding () { m_bSuspendEncoding = false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy