/*********************************************************************************************************************** * Copyright (C) 2010-2013 by the Stratosphere project (http://stratosphere.eu) * * 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 eu.stratosphere.nephele.util; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.util.ArrayDeque; import java.util.Queue; /** * This class is a special test implementation of a {@link ReadableByteChannel} and {@link WritableByteChannel}. Data is * first written into main memory through the {@link WritableByteChannel} interface. Afterwards, the data can be read * again through the {@link ReadableByteChannel} abstraction. The implementation is capable of simulating interruptions * in the byte stream. * <p> * This class is not thread-safe. * */ public class InterruptibleByteChannel implements ReadableByteChannel, WritableByteChannel { /** * The initial size of the internal memory buffer in bytes. */ private static final int INITIAL_BUFFER_SIZE = 8192; /** * Stores the requested interruptions of the byte stream during write operations that still have to be processed. */ private final Queue<Integer> writeInterruptPositions = new ArrayDeque<Integer>(); /** * Stores the requested interruptions of the byte stream during read operations that still have to be processed. */ private final Queue<Integer> readInterruptPositions = new ArrayDeque<Integer>(); /** * The internal memory buffer used to hold the written data. */ private ByteBuffer buffer = ByteBuffer.allocate(INITIAL_BUFFER_SIZE); /** * Stores if the channel is still open. */ private boolean isOpen = true; /** * Stores if the channel is still in write phase. */ private boolean isInWritePhase = true; /** * Constructs a new interruptible byte channel. * * @param writeInterruptPositions * the positions to interrupt the byte stream during write operations or <code>null</code> if no * interruptions are requested * @param readInterruptPositions * the positions to interrupt the byte stream during read operations or <code>null</code> if no interruptions * are requested */ public InterruptibleByteChannel(final int[] writeInterruptPositions, final int[] readInterruptPositions) { if (writeInterruptPositions != null) { for (int i = 0; i < writeInterruptPositions.length - 1; ++i) { if (writeInterruptPositions[i] > writeInterruptPositions[i + 1]) { throw new IllegalArgumentException("Write interrupt positions must be ordered ascendingly"); } this.writeInterruptPositions.add(Integer.valueOf(writeInterruptPositions[i])); } this.writeInterruptPositions.add(Integer .valueOf(writeInterruptPositions[writeInterruptPositions.length - 1])); } if (readInterruptPositions != null) { for (int i = 0; i < readInterruptPositions.length - 1; ++i) { if (readInterruptPositions[i] > readInterruptPositions[i + 1]) { throw new IllegalArgumentException("Read interrupt positions must be ordered ascendingly"); } this.readInterruptPositions.add(Integer.valueOf(readInterruptPositions[i])); } this.readInterruptPositions.add(Integer .valueOf(readInterruptPositions[readInterruptPositions.length - 1])); } } @Override public boolean isOpen() { return this.isOpen; } @Override public void close() throws IOException { this.isOpen = false; } @Override public int write(final ByteBuffer src) throws IOException { if (!this.isOpen) { throw new ClosedChannelException(); } if (!this.isInWritePhase) { throw new IllegalStateException("Channel is not in write phase anymore"); } if (src.remaining() > this.buffer.remaining()) { increaseBufferSize(); } int numberOfBytesToAccept = src.remaining(); if (!this.writeInterruptPositions.isEmpty() && (this.buffer.position() + numberOfBytesToAccept < this.writeInterruptPositions.peek().intValue())) { numberOfBytesToAccept = this.writeInterruptPositions.poll().intValue() - this.buffer.position(); this.buffer.limit(this.buffer.position() + numberOfBytesToAccept); this.buffer.put(src); this.buffer.limit(this.buffer.capacity()); return numberOfBytesToAccept; } this.buffer.put(src); return numberOfBytesToAccept; } @Override public int read(final ByteBuffer dst) throws IOException { if (!this.isOpen) { throw new ClosedChannelException(); } if (this.isInWritePhase) { throw new IllegalStateException("Channel is still in write phase"); } if (!this.buffer.hasRemaining()) { return -1; } int numberOfBytesToRetrieve = Math.min(this.buffer.remaining(), dst.remaining()); if (!this.readInterruptPositions.isEmpty() && (this.buffer.position() + numberOfBytesToRetrieve > this.readInterruptPositions.peek().intValue())) { numberOfBytesToRetrieve = this.readInterruptPositions.poll().intValue() - this.buffer.position(); } final int oldLimit = this.buffer.limit(); this.buffer.limit(this.buffer.position() + numberOfBytesToRetrieve); dst.put(this.buffer); this.buffer.limit(oldLimit); return numberOfBytesToRetrieve; } /** * Switches the channel to read phase. */ public void switchToReadPhase() { if (!this.isInWritePhase) { throw new IllegalStateException("Channel is already in read phase"); } this.isInWritePhase = false; this.buffer.flip(); } /** * Doubles the capacity of the internal byte buffer while preserving its content. */ private void increaseBufferSize() { final ByteBuffer newBuf = ByteBuffer.allocate(this.buffer.capacity() * 2); this.buffer.flip(); newBuf.put(this.buffer); this.buffer = newBuf; } }