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 032 * incoming data. A natural size limit is the value of {@link Integer#MAX_VALUE}. 033 * Internal buffer can be accessed via {@link 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 * Constructor taking a byte array. 048 * 049 * <p>This constructor is intended to be used with pre-allocated buffer or when 050 * reading from a given byte array.</p> 051 * 052 * @param data input data or pre-allocated array. 053 */ 054 public SeekableInMemoryByteChannel(byte[] data) { 055 this.data = data; 056 size = data.length; 057 } 058 059 /** 060 * Parameterless constructor - allocates internal buffer by itself. 061 */ 062 public SeekableInMemoryByteChannel() { 063 this(new byte[0]); 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(int size) { 074 this(new byte[size]); 075 } 076 077 @Override 078 public long position() { 079 return position; 080 } 081 082 @Override 083 public SeekableByteChannel position(long newPosition) throws IOException { 084 ensureOpen(); 085 if (newPosition < 0L || newPosition > Integer.MAX_VALUE) { 086 throw new IllegalArgumentException("Position has to be in range 0.. " + Integer.MAX_VALUE); 087 } 088 position = (int) newPosition; 089 return this; 090 } 091 092 @Override 093 public long size() { 094 return size; 095 } 096 097 @Override 098 public SeekableByteChannel truncate(long newSize) { 099 if (size > newSize) { 100 size = (int) newSize; 101 } 102 repositionIfNecessary(); 103 return this; 104 } 105 106 @Override 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 124 public void close() { 125 closed.set(true); 126 } 127 128 @Override 129 public boolean isOpen() { 130 return !closed.get(); 131 } 132 133 @Override 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 */ 164 public byte[] array() { 165 return data; 166 } 167 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 183 private void ensureOpen() throws ClosedChannelException { 184 if (!isOpen()) { 185 throw new ClosedChannelException(); 186 } 187 } 188 189 private void repositionIfNecessary() { 190 if (position > size) { 191 position = size; 192 } 193 } 194 195}