1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package org.apache.commons.compress.utils; 20 21 import java.io.IOException; 22 import java.nio.ByteBuffer; 23 import java.nio.channels.ClosedChannelException; 24 import java.nio.channels.SeekableByteChannel; 25 import java.util.Arrays; 26 import java.util.concurrent.atomic.AtomicBoolean; 27 28 /** 29 * A {@link SeekableByteChannel} implementation that wraps a byte[]. 30 * 31 * <p>When this channel is used for writing an internal buffer grows to accommodate 32 * incoming data. A natural size limit is the value of {@link Integer#MAX_VALUE}. 33 * Internal buffer can be accessed via {@link SeekableInMemoryByteChannel#array()}.</p> 34 * 35 * @since 1.13 36 * @NotThreadSafe 37 */ 38 public class SeekableInMemoryByteChannel implements SeekableByteChannel { 39 40 private static final int NAIVE_RESIZE_LIMIT = Integer.MAX_VALUE >> 1; 41 42 private byte[] data; 43 private final AtomicBoolean closed = new AtomicBoolean(); 44 private int position, size; 45 46 /** 47 * Constructor taking a byte array. 48 * 49 * <p>This constructor is intended to be used with pre-allocated buffer or when 50 * reading from a given byte array.</p> 51 * 52 * @param data input data or pre-allocated array. 53 */ SeekableInMemoryByteChannel(byte[] data)54 public SeekableInMemoryByteChannel(byte[] data) { 55 this.data = data; 56 size = data.length; 57 } 58 59 /** 60 * Parameterless constructor - allocates internal buffer by itself. 61 */ SeekableInMemoryByteChannel()62 public SeekableInMemoryByteChannel() { 63 this(new byte[0]); 64 } 65 66 /** 67 * Constructor taking a size of storage to be allocated. 68 * 69 * <p>Creates a channel and allocates internal storage of a given size.</p> 70 * 71 * @param size size of internal buffer to allocate, in bytes. 72 */ SeekableInMemoryByteChannel(int size)73 public SeekableInMemoryByteChannel(int size) { 74 this(new byte[size]); 75 } 76 77 @Override position()78 public long position() { 79 return position; 80 } 81 82 @Override position(long newPosition)83 public SeekableByteChannel position(long newPosition) throws IOException { 84 ensureOpen(); 85 if (newPosition < 0L || newPosition > Integer.MAX_VALUE) { 86 throw new IllegalArgumentException("Position has to be in range 0.. " + Integer.MAX_VALUE); 87 } 88 position = (int) newPosition; 89 return this; 90 } 91 92 @Override size()93 public long size() { 94 return size; 95 } 96 97 @Override truncate(long newSize)98 public SeekableByteChannel truncate(long newSize) { 99 if (size > newSize) { 100 size = (int) newSize; 101 } 102 repositionIfNecessary(); 103 return this; 104 } 105 106 @Override read(ByteBuffer buf)107 public int read(ByteBuffer buf) throws IOException { 108 ensureOpen(); 109 repositionIfNecessary(); 110 int wanted = buf.remaining(); 111 int possible = size - position; 112 if (possible <= 0) { 113 return -1; 114 } 115 if (wanted > possible) { 116 wanted = possible; 117 } 118 buf.put(data, position, wanted); 119 position += wanted; 120 return wanted; 121 } 122 123 @Override close()124 public void close() { 125 closed.set(true); 126 } 127 128 @Override isOpen()129 public boolean isOpen() { 130 return !closed.get(); 131 } 132 133 @Override write(ByteBuffer b)134 public int write(ByteBuffer b) throws IOException { 135 ensureOpen(); 136 int wanted = b.remaining(); 137 int possibleWithoutResize = size - position; 138 if (wanted > possibleWithoutResize) { 139 int newSize = position + wanted; 140 if (newSize < 0) { // overflow 141 resize(Integer.MAX_VALUE); 142 wanted = Integer.MAX_VALUE - position; 143 } else { 144 resize(newSize); 145 } 146 } 147 b.get(data, position, wanted); 148 position += wanted; 149 if (size < position) { 150 size = position; 151 } 152 return wanted; 153 } 154 155 /** 156 * Obtains the array backing this channel. 157 * 158 * <p>NOTE: 159 * The returned buffer is not aligned with containing data, use 160 * {@link #size()} to obtain the size of data stored in the buffer.</p> 161 * 162 * @return internal byte array. 163 */ array()164 public byte[] array() { 165 return data; 166 } 167 resize(int newLength)168 private void resize(int newLength) { 169 int len = data.length; 170 if (len <= 0) { 171 len = 1; 172 } 173 if (newLength < NAIVE_RESIZE_LIMIT) { 174 while (len < newLength) { 175 len <<= 1; 176 } 177 } else { // avoid overflow 178 len = newLength; 179 } 180 data = Arrays.copyOf(data, len); 181 } 182 ensureOpen()183 private void ensureOpen() throws ClosedChannelException { 184 if (!isOpen()) { 185 throw new ClosedChannelException(); 186 } 187 } 188 repositionIfNecessary()189 private void repositionIfNecessary() { 190 if (position > size) { 191 position = size; 192 } 193 } 194 195 } 196