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