/* * 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.nifi.controller.repository.io; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ArrayManagedOutputStream extends OutputStream { private final MemoryManager memoryManager; private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); private final Lock readLock = rwLock.readLock(); private final Lock writeLock = rwLock.writeLock(); private final List<byte[]> blocks = new ArrayList<>(); private int currentIndex; private byte[] currentBlock; private long curSize; public ArrayManagedOutputStream(final MemoryManager memoryManager) { this.memoryManager = memoryManager; } @Override public void write(final byte[] b, int off, final int len) throws IOException { writeLock.lock(); try { final int bytesFreeThisBlock = currentBlock == null ? 0 : currentBlock.length - currentIndex; if (bytesFreeThisBlock >= len) { System.arraycopy(b, off, currentBlock, currentIndex, len); currentIndex += len; curSize += len; return; } // Try to get all of the blocks needed final long bytesNeeded = len - bytesFreeThisBlock; int blocksNeeded = (int) (bytesNeeded / memoryManager.getBlockSize()); if (blocksNeeded * memoryManager.getBlockSize() < bytesNeeded) { blocksNeeded++; } // get all of the blocks that we need final List<byte[]> newBlocks = new ArrayList<>(blocksNeeded); for (int i = 0; i < blocksNeeded; i++) { final byte[] newBlock = memoryManager.checkOut(); if (newBlock == null) { memoryManager.checkIn(newBlocks); throw new IOException("No space left in Content Repository"); } newBlocks.add(newBlock); } // we've successfully obtained the blocks needed. Copy the data. // first copy what we can to the current block long bytesCopied = 0; final int bytesForCur = currentBlock == null ? 0 : currentBlock.length - currentIndex; if (bytesForCur > 0) { System.arraycopy(b, off, currentBlock, currentIndex, bytesForCur); off += bytesForCur; bytesCopied += bytesForCur; currentBlock = null; currentIndex = 0; } // then copy to all new blocks for (final byte[] block : newBlocks) { final int bytesToCopy = (int) Math.min(len - bytesCopied, block.length); System.arraycopy(b, off, block, 0, bytesToCopy); currentIndex = bytesToCopy; currentBlock = block; off += bytesToCopy; bytesCopied += bytesToCopy; } curSize += len; blocks.addAll(newBlocks); } finally { writeLock.unlock(); } } @Override public void write(final int b) throws IOException { final byte[] bytes = new byte[1]; bytes[0] = (byte) (b & 0xFF); write(bytes); } public void destroy() { writeLock.lock(); try { memoryManager.checkIn(blocks); blocks.clear(); currentBlock = null; currentIndex = 0; curSize = 0L; } finally { writeLock.unlock(); } } public long size() { readLock.lock(); try { return curSize; } finally { readLock.unlock(); } } public void reset() { destroy(); } public void writeTo(final OutputStream out) throws IOException { readLock.lock(); try { int i = 0; for (final byte[] block : blocks) { if (++i == blocks.size()) { if (currentIndex > 0) { out.write(block, 0, currentIndex); } } else { out.write(block); } } } finally { readLock.unlock(); } } public int getBufferLength() { readLock.lock(); try { if (currentBlock == null) { return 0; } // all blocks are same size return blocks.size() * currentBlock.length; } finally { readLock.unlock(); } } public InputStream newInputStream() { final int blockSize; final long totalSize; readLock.lock(); try { if (blocks.isEmpty()) { return new ByteArrayInputStream(new byte[0]); } blockSize = memoryManager.getBlockSize(); totalSize = curSize; } finally { readLock.unlock(); } return new InputStream() { int blockIndex = 0; int byteIndex = 0; long bytesRead = 0L; @Override public int read() throws IOException { readLock.lock(); try { if (bytesRead >= totalSize) { return -1; } if (byteIndex >= blockSize) { blockIndex++; byteIndex = 0; } final byte[] buffer = blocks.get(blockIndex); final int b = buffer[byteIndex++] & 0xFF; bytesRead++; return b; } finally { readLock.unlock(); } } @Override public int read(final byte[] b, final int off, final int len) throws IOException { readLock.lock(); try { if (bytesRead >= totalSize) { return -1; } if (byteIndex >= blockSize) { blockIndex++; byteIndex = 0; } final byte[] buffer = blocks.get(blockIndex); final long bytesUnread = totalSize - bytesRead; final int bytesToCopy = (int) Math.min(bytesUnread, Math.min(len, buffer.length - byteIndex)); System.arraycopy(buffer, byteIndex, b, off, bytesToCopy); byteIndex += bytesToCopy; bytesRead += bytesToCopy; return bytesToCopy; } finally { readLock.unlock(); } } }; } }