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