001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.commons.compress.utils; 021 022import java.io.IOException; 023import java.nio.ByteBuffer; 024import java.nio.channels.ClosedChannelException; 025import java.nio.channels.SeekableByteChannel; 026import java.util.Arrays; 027import java.util.concurrent.atomic.AtomicBoolean; 028 029/** 030 * A {@link SeekableByteChannel} implementation that wraps a byte[]. 031 * <p> 032 * When this channel is used for writing an internal buffer grows to accommodate incoming data. The natural size limit is the value of {@link Integer#MAX_VALUE} 033 * and it is not possible to {@link #position(long) set the position} or {@link #truncate truncate} to a value bigger than that. Internal buffer can be accessed 034 * via {@link SeekableInMemoryByteChannel#array()}. 035 * </p> 036 * 037 * @since 1.13 038 * @NotThreadSafe 039 */ 040public class SeekableInMemoryByteChannel implements SeekableByteChannel { 041 042 private static final int NAIVE_RESIZE_LIMIT = Integer.MAX_VALUE >> 1; 043 044 private byte[] data; 045 private final AtomicBoolean closed = new AtomicBoolean(); 046 private int position; 047 private int size; 048 049 /** 050 * Constructs a new instance using a default empty buffer. 051 */ 052 public SeekableInMemoryByteChannel() { 053 this(ByteUtils.EMPTY_BYTE_ARRAY); 054 } 055 056 /** 057 * Constructs a new instance from a byte array. 058 * <p> 059 * This constructor is intended to be used with pre-allocated buffer or when reading from a given byte array. 060 * </p> 061 * 062 * @param data input data or pre-allocated array. 063 */ 064 public SeekableInMemoryByteChannel(final byte[] data) { 065 this.data = data; 066 this.size = data.length; 067 } 068 069 /** 070 * Constructs a new instance from a size of storage to be allocated. 071 * <p> 072 * Creates a channel and allocates internal storage of a given size. 073 * </p> 074 * 075 * @param size size of internal buffer to allocate, in bytes. 076 */ 077 public SeekableInMemoryByteChannel(final int size) { 078 this(new byte[size]); 079 } 080 081 /** 082 * Obtains the array backing this channel. 083 * <p> 084 * NOTE: The returned buffer is not aligned with containing data, use {@link #size()} to obtain the size of data stored in the buffer. 085 * </p> 086 * 087 * @return internal byte array. 088 */ 089 public byte[] array() { 090 return data; 091 } 092 093 @Override 094 public void close() { 095 closed.set(true); 096 } 097 098 private void ensureOpen() throws ClosedChannelException { 099 if (!isOpen()) { 100 throw new ClosedChannelException(); 101 } 102 } 103 104 @Override 105 public boolean isOpen() { 106 return !closed.get(); 107 } 108 109 /** 110 * Returns this channel's position. 111 * <p> 112 * This method violates the contract of {@link SeekableByteChannel#position()} as it will not throw any exception when invoked on a closed channel. Instead 113 * it will return the position the channel had when close has been called. 114 * </p> 115 */ 116 @Override 117 public long position() { 118 return position; 119 } 120 121 @Override 122 public SeekableByteChannel position(final long newPosition) throws IOException { 123 ensureOpen(); 124 if (newPosition < 0L || newPosition > Integer.MAX_VALUE) { 125 throw new IOException("Position must be in range [0.." + Integer.MAX_VALUE + "]"); 126 } 127 position = (int) newPosition; 128 return this; 129 } 130 131 @Override 132 public int read(final ByteBuffer buf) throws IOException { 133 ensureOpen(); 134 int wanted = buf.remaining(); 135 final int possible = size - position; 136 if (possible <= 0) { 137 return -1; 138 } 139 if (wanted > possible) { 140 wanted = possible; 141 } 142 buf.put(data, position, wanted); 143 position += wanted; 144 return wanted; 145 } 146 147 private void resize(final int newLength) { 148 int len = data.length; 149 if (len <= 0) { 150 len = 1; 151 } 152 if (newLength < NAIVE_RESIZE_LIMIT) { 153 while (len < newLength) { 154 len <<= 1; 155 } 156 } else { // avoid overflow 157 len = newLength; 158 } 159 data = Arrays.copyOf(data, len); 160 } 161 162 /** 163 * Returns the current size of entity to which this channel is connected. 164 * <p> 165 * This method violates the contract of {@link SeekableByteChannel#size} as it will not throw any exception when invoked on a closed channel. Instead it 166 * will return the size the channel had when close has been called. 167 * </p> 168 */ 169 @Override 170 public long size() { 171 return size; 172 } 173 174 /** 175 * Truncates the entity, to which this channel is connected, to the given size. 176 * <p> 177 * This method violates the contract of {@link SeekableByteChannel#truncate} as it will not throw any exception when invoked on a closed channel. 178 * </p> 179 * 180 * @throws IllegalArgumentException if size is negative or bigger than the maximum of a Java integer 181 */ 182 @Override 183 public SeekableByteChannel truncate(final long newSize) { 184 if (newSize < 0L || newSize > Integer.MAX_VALUE) { 185 throw new IllegalArgumentException("Size must be range [0.." + Integer.MAX_VALUE + "]"); 186 } 187 if (size > newSize) { 188 size = (int) newSize; 189 } 190 if (position > newSize) { 191 position = (int) newSize; 192 } 193 return this; 194 } 195 196 @Override 197 public int write(final ByteBuffer b) throws IOException { 198 ensureOpen(); 199 int wanted = b.remaining(); 200 final int possibleWithoutResize = size - position; 201 if (wanted > possibleWithoutResize) { 202 final int newSize = position + wanted; 203 if (newSize < 0) { // overflow 204 resize(Integer.MAX_VALUE); 205 wanted = Integer.MAX_VALUE - position; 206 } else { 207 resize(newSize); 208 } 209 } 210 b.get(data, position, wanted); 211 position += wanted; 212 if (size < position) { 213 size = position; 214 } 215 return wanted; 216 } 217 218}