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 */ 017package org.apache.commons.io.input; 018 019import static java.lang.Math.min; 020 021import java.io.ByteArrayInputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.util.Objects; 025 026import org.apache.commons.io.build.AbstractOrigin; 027import org.apache.commons.io.build.AbstractStreamBuilder; 028 029/** 030 * This is an alternative to {@link java.io.ByteArrayInputStream} which removes the synchronization overhead for non-concurrent access; as such this class is 031 * not thread-safe. 032 * <p> 033 * To build an instance, see {@link Builder}. 034 * </p> 035 * 036 * @see ByteArrayInputStream 037 * @since 2.7 038 */ 039//@NotThreadSafe 040public class UnsynchronizedByteArrayInputStream extends InputStream { 041 042 /** 043 * Builds a new {@link UnsynchronizedByteArrayInputStream} instance. 044 * <p> 045 * Using a Byte Array: 046 * </p> 047 * 048 * <pre>{@code 049 * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder() 050 * .setByteArray(byteArray) 051 * .setOffset(0) 052 * .setLength(byteArray.length) 053 * .get();} 054 * </pre> 055 * <p> 056 * Using File IO: 057 * </p> 058 * 059 * <pre>{@code 060 * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder() 061 * .setFile(file) 062 * .setOffset(0) 063 * .setLength(byteArray.length) 064 * .get();} 065 * </pre> 066 * <p> 067 * Using NIO Path: 068 * </p> 069 * 070 * <pre>{@code 071 * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder() 072 * .setPath(path) 073 * .setOffset(0) 074 * .setLength(byteArray.length) 075 * .get();} 076 * </pre> 077 */ 078 public static class Builder extends AbstractStreamBuilder<UnsynchronizedByteArrayInputStream, Builder> { 079 080 private int offset; 081 private int length; 082 083 /** 084 * Constructs a new instance. 085 * <p> 086 * This builder use the aspects byte[], offset and length. 087 * </p> 088 * <p> 089 * You must provide an origin that can be converted to a byte[] by this builder, otherwise, this call will throw an 090 * {@link UnsupportedOperationException}. 091 * </p> 092 * 093 * @return a new instance. 094 * @throws UnsupportedOperationException if the origin cannot provide a byte[]. 095 * @throws IllegalStateException if the {@code origin} is {@code null}. 096 * @see AbstractOrigin#getByteArray() 097 */ 098 @Override 099 public UnsynchronizedByteArrayInputStream get() throws IOException { 100 return new UnsynchronizedByteArrayInputStream(checkOrigin().getByteArray(), offset, length); 101 } 102 103 @Override 104 public Builder setByteArray(final byte[] origin) { 105 length = Objects.requireNonNull(origin, "origin").length; 106 return super.setByteArray(origin); 107 } 108 109 /** 110 * Sets the length. 111 * 112 * @param length Must be greater or equal to 0. 113 * @return this. 114 */ 115 public Builder setLength(final int length) { 116 if (length < 0) { 117 throw new IllegalArgumentException("length cannot be negative"); 118 } 119 this.length = length; 120 return this; 121 } 122 123 /** 124 * Sets the offset. 125 * 126 * @param offset Must be greater or equal to 0. 127 * @return this. 128 */ 129 public Builder setOffset(final int offset) { 130 if (offset < 0) { 131 throw new IllegalArgumentException("offset cannot be negative"); 132 } 133 this.offset = offset; 134 return this; 135 } 136 137 } 138 139 /** 140 * The end of stream marker. 141 */ 142 public static final int END_OF_STREAM = -1; 143 144 /** 145 * Constructs a new {@link Builder}. 146 * 147 * @return a new {@link Builder}. 148 */ 149 public static Builder builder() { 150 return new Builder(); 151 } 152 153 /** 154 * The underlying data buffer. 155 */ 156 private final byte[] data; 157 158 /** 159 * End Of Data. 160 * 161 * Similar to data.length, i.e. the last readable offset + 1. 162 */ 163 private final int eod; 164 165 /** 166 * Current offset in the data buffer. 167 */ 168 private int offset; 169 170 /** 171 * The current mark (if any). 172 */ 173 private int markedOffset; 174 175 /** 176 * Constructs a new byte array input stream. 177 * 178 * @param data the buffer 179 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. 180 */ 181 @Deprecated 182 public UnsynchronizedByteArrayInputStream(final byte[] data) { 183 this.data = Objects.requireNonNull(data, "data"); 184 this.offset = 0; 185 this.eod = data.length; 186 this.markedOffset = this.offset; 187 } 188 189 /** 190 * Constructs a new byte array input stream. 191 * 192 * @param data the buffer 193 * @param offset the offset into the buffer 194 * 195 * @throws IllegalArgumentException if the offset is less than zero 196 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. 197 */ 198 @Deprecated 199 public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset) { 200 Objects.requireNonNull(data, "data"); 201 if (offset < 0) { 202 throw new IllegalArgumentException("offset cannot be negative"); 203 } 204 this.data = data; 205 this.offset = min(offset, data.length > 0 ? data.length : offset); 206 this.eod = data.length; 207 this.markedOffset = this.offset; 208 } 209 210 /** 211 * Constructs a new byte array input stream. 212 * 213 * @param data the buffer 214 * @param offset the offset into the buffer 215 * @param length the length of the buffer 216 * 217 * @throws IllegalArgumentException if the offset or length less than zero 218 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. 219 */ 220 @Deprecated 221 public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length) { 222 if (offset < 0) { 223 throw new IllegalArgumentException("offset cannot be negative"); 224 } 225 if (length < 0) { 226 throw new IllegalArgumentException("length cannot be negative"); 227 } 228 this.data = Objects.requireNonNull(data, "data"); 229 this.offset = min(offset, data.length > 0 ? data.length : offset); 230 this.eod = min(this.offset + length, data.length); 231 this.markedOffset = this.offset; 232 } 233 234 @Override 235 public int available() { 236 return offset < eod ? eod - offset : 0; 237 } 238 239 @SuppressWarnings("sync-override") 240 @Override 241 public void mark(final int readlimit) { 242 this.markedOffset = this.offset; 243 } 244 245 @Override 246 public boolean markSupported() { 247 return true; 248 } 249 250 @Override 251 public int read() { 252 return offset < eod ? data[offset++] & 0xff : END_OF_STREAM; 253 } 254 255 @Override 256 public int read(final byte[] dest) { 257 Objects.requireNonNull(dest, "dest"); 258 return read(dest, 0, dest.length); 259 } 260 261 @Override 262 public int read(final byte[] dest, final int off, final int len) { 263 Objects.requireNonNull(dest, "dest"); 264 if (off < 0 || len < 0 || off + len > dest.length) { 265 throw new IndexOutOfBoundsException(); 266 } 267 268 if (offset >= eod) { 269 return END_OF_STREAM; 270 } 271 272 int actualLen = eod - offset; 273 if (len < actualLen) { 274 actualLen = len; 275 } 276 if (actualLen <= 0) { 277 return 0; 278 } 279 System.arraycopy(data, offset, dest, off, actualLen); 280 offset += actualLen; 281 return actualLen; 282 } 283 284 @SuppressWarnings("sync-override") 285 @Override 286 public void reset() { 287 this.offset = this.markedOffset; 288 } 289 290 @Override 291 public long skip(final long n) { 292 if (n < 0) { 293 throw new IllegalArgumentException("Skipping backward is not supported"); 294 } 295 296 long actualSkip = eod - offset; 297 if (n < actualSkip) { 298 actualSkip = n; 299 } 300 301 offset = Math.addExact(offset, Math.toIntExact(n)); 302 return actualSkip; 303 } 304}