| /* |
| * Copyright (c) 2008, 2010-2012, 2015 Eike Stepper (Berlin, Germany) and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Eike Stepper - initial API and implementation |
| * Andre Dietisheim - Bug 262875: java.nio.BufferUnderFlowException https://bugs.eclipse.org/bugs/show_bug.cgi?id=262875 |
| */ |
| package org.eclipse.net4j.buffer; |
| |
| import org.eclipse.net4j.util.HexUtil; |
| import org.eclipse.net4j.util.IErrorHandler; |
| import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump; |
| import org.eclipse.net4j.util.io.IORuntimeException; |
| import org.eclipse.net4j.util.lifecycle.LifecycleUtil; |
| import org.eclipse.net4j.util.om.trace.ContextTracer; |
| |
| import org.eclipse.internal.net4j.bundle.OM; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.nio.ByteBuffer; |
| |
| /** |
| * An {@link OutputStream output stream} that fragments the written byte sequence into fixed-sized {@link IBuffer |
| * buffers} and passes them to configured {@link IBufferHandler buffer handler}. |
| * |
| * @author Eike Stepper |
| */ |
| public class BufferOutputStream extends OutputStream |
| { |
| public static final boolean DEFAULT_PROPAGATE_CLOSE = false; |
| |
| private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_BUFFER_STREAM, BufferOutputStream.class); |
| |
| private final boolean tracerEnabled; |
| |
| private IBufferProvider bufferProvider; |
| |
| private IBufferHandler bufferHandler; |
| |
| private IBuffer currentBuffer; |
| |
| private short channelID; |
| |
| private Throwable error; |
| |
| @ExcludeFromDump |
| private transient IErrorHandler errorHandler = new IErrorHandler() |
| { |
| public void handleError(Throwable t) |
| { |
| setError(t); |
| } |
| }; |
| |
| public BufferOutputStream(IBufferHandler bufferHandler, IBufferProvider bufferProvider, short channelID) |
| { |
| if (bufferHandler == null) |
| { |
| throw new IllegalArgumentException("bufferHandler == null"); //$NON-NLS-1$ |
| } |
| |
| if (bufferProvider == null) |
| { |
| throw new IllegalArgumentException("bufferProvider == null"); //$NON-NLS-1$ |
| } |
| |
| this.bufferHandler = bufferHandler; |
| this.bufferProvider = bufferProvider; |
| this.channelID = channelID; |
| tracerEnabled = TRACER.isEnabled(); |
| } |
| |
| public BufferOutputStream(IBufferHandler bufferHandler, short channelID) |
| { |
| this(bufferHandler, extractBufferProvider(bufferHandler), channelID); |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| public Throwable getError() |
| { |
| return error; |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| public void setError(Throwable error) |
| { |
| this.error = error; |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Override |
| public void write(int b) throws IOException |
| { |
| throwExceptionOnError(); |
| flushIfFilled(); |
| ensureBufferPrivate(); |
| |
| // If this was called with a primitive byte with a negative value, |
| // the implicit conversion prepended 24 leading 1's. We'll undo those. |
| b = b & 0xFF; |
| |
| if (tracerEnabled) |
| { |
| TRACER.trace("--> " + HexUtil.formatByte(b) //$NON-NLS-1$ |
| + (b >= 32 ? " " + Character.toString((char)b) : "")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| ByteBuffer buffer = currentBuffer.getByteBuffer(); |
| buffer.put((byte)b); |
| } |
| |
| /** |
| * Flushes the current buffer, it's handled over to the buffer handler. |
| * |
| * @throws IOException |
| * Signals that an I/O exception has occurred. |
| * @see #currentBuffer |
| * @see IBufferHandler#handleBuffer(IBuffer) |
| */ |
| @Override |
| public void flush() throws IOException |
| { |
| flushPrivate(); |
| } |
| |
| /** |
| * Flushes the current buffer if it has no remaining space. |
| * |
| * @throws IOException |
| * Signals that an I/O exception has occurred. |
| */ |
| private void flushIfFilled() throws IOException |
| { |
| if (currentBuffer != null && !currentBuffer.getByteBuffer().hasRemaining()) |
| { |
| flushPrivate(); |
| } |
| } |
| |
| private void flushPrivate() |
| { |
| if (currentBuffer != null) |
| { |
| bufferHandler.handleBuffer(currentBuffer); |
| currentBuffer = null; |
| } |
| } |
| |
| public void flushWithEOS() throws IOException |
| { |
| flushWithEOS(false); |
| } |
| |
| /** |
| * @since 4.4 |
| */ |
| public void flushWithEOS(boolean ccam) throws IOException |
| { |
| throwExceptionOnError(); |
| ensureBufferPrivate(); |
| currentBuffer.setEOS(true); |
| currentBuffer.setCCAM(ccam); |
| flushPrivate(); |
| } |
| |
| @Override |
| public void close() throws IOException |
| { |
| try |
| { |
| if (isPropagateClose()) |
| { |
| LifecycleUtil.deactivate(bufferHandler); |
| } |
| } |
| finally |
| { |
| bufferHandler = null; |
| bufferProvider = null; |
| currentBuffer = null; |
| super.close(); |
| } |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "BufferOutputStream"; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Ensures that this BufferOutputStream has a buffer. If the current buffer was flushed a new one is fetched from the |
| * buffer provider. |
| * |
| * @throws IOException |
| * Signals that an I/O exception has occurred. |
| * @see #flush() |
| * @see IBufferProvider#provideBuffer() |
| */ |
| protected void ensureBuffer() throws IOException |
| { |
| ensureBufferPrivate(); |
| } |
| |
| private void ensureBufferPrivate() |
| { |
| if (currentBuffer == null) |
| { |
| currentBuffer = bufferProvider.provideBuffer(); |
| currentBuffer.setErrorHandler(errorHandler); |
| currentBuffer.startPutting(channelID); |
| } |
| } |
| |
| /** |
| * Throws an exception if there's an error. |
| * |
| * @throws IOException |
| * Signals that an I/O exception has occurred. |
| * @see #error |
| */ |
| private void throwExceptionOnError() throws IOException |
| { |
| if (error != null) |
| { |
| if (error instanceof IOException) |
| { |
| throw (IOException)error; |
| } |
| |
| if (error instanceof RuntimeException) |
| { |
| throw (RuntimeException)error; |
| } |
| |
| throw new IORuntimeException(error); |
| } |
| } |
| |
| protected boolean isPropagateClose() |
| { |
| return DEFAULT_PROPAGATE_CLOSE; |
| } |
| |
| private static IBufferProvider extractBufferProvider(IBufferHandler bufferHandler) |
| { |
| if (bufferHandler instanceof IBufferProvider) |
| { |
| return (IBufferProvider)bufferHandler; |
| } |
| |
| throw new IllegalArgumentException("Buffer handler unable to provide buffers"); //$NON-NLS-1$ |
| } |
| } |