1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.io.input; 18 19 import static java.lang.Math.min; 20 21 import java.io.ByteArrayInputStream; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.util.Objects; 25 26 import org.apache.commons.io.build.AbstractOrigin; 27 import org.apache.commons.io.build.AbstractStreamBuilder; 28 29 /** 30 * This is an alternative to {@link java.io.ByteArrayInputStream} which removes the synchronization overhead for non-concurrent access; as such this class is 31 * not thread-safe. 32 * <p> 33 * To build an instance, see {@link Builder}. 34 * </p> 35 * 36 * @see ByteArrayInputStream 37 * @since 2.7 38 */ 39 //@NotThreadSafe 40 public class UnsynchronizedByteArrayInputStream extends InputStream { 41 42 /** 43 * Builds a new {@link UnsynchronizedByteArrayInputStream} instance. 44 * <p> 45 * Using a Byte Array: 46 * </p> 47 * 48 * <pre>{@code 49 * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder() 50 * .setByteArray(byteArray) 51 * .setOffset(0) 52 * .setLength(byteArray.length) 53 * .get();} 54 * </pre> 55 * <p> 56 * Using File IO: 57 * </p> 58 * 59 * <pre>{@code 60 * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder() 61 * .setFile(file) 62 * .setOffset(0) 63 * .setLength(byteArray.length) 64 * .get();} 65 * </pre> 66 * <p> 67 * Using NIO Path: 68 * </p> 69 * 70 * <pre>{@code 71 * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder() 72 * .setPath(path) 73 * .setOffset(0) 74 * .setLength(byteArray.length) 75 * .get();} 76 * </pre> 77 */ 78 public static class Builder extends AbstractStreamBuilder<UnsynchronizedByteArrayInputStream, Builder> { 79 80 private int offset; 81 private int length; 82 83 /** 84 * Constructs a new instance. 85 * <p> 86 * This builder use the aspects byte[], offset and length. 87 * </p> 88 * <p> 89 * You must provide an origin that can be converted to a byte[] by this builder, otherwise, this call will throw an 90 * {@link UnsupportedOperationException}. 91 * </p> 92 * 93 * @return a new instance. 94 * @throws UnsupportedOperationException if the origin cannot provide a byte[]. 95 * @throws IllegalStateException if the {@code origin} is {@code null}. 96 * @see AbstractOrigin#getByteArray() 97 */ 98 @Override get()99 public UnsynchronizedByteArrayInputStream get() throws IOException { 100 return new UnsynchronizedByteArrayInputStream(checkOrigin().getByteArray(), offset, length); 101 } 102 103 @Override setByteArray(final byte[] origin)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 */ setLength(final int length)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 */ setOffset(final int offset)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 */ builder()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 UnsynchronizedByteArrayInputStream(final byte[] data)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 UnsynchronizedByteArrayInputStream(final byte[] data, final int offset)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 UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length)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 available()235 public int available() { 236 return offset < eod ? eod - offset : 0; 237 } 238 239 @SuppressWarnings("sync-override") 240 @Override mark(final int readlimit)241 public void mark(final int readlimit) { 242 this.markedOffset = this.offset; 243 } 244 245 @Override markSupported()246 public boolean markSupported() { 247 return true; 248 } 249 250 @Override read()251 public int read() { 252 return offset < eod ? data[offset++] & 0xff : END_OF_STREAM; 253 } 254 255 @Override read(final byte[] dest)256 public int read(final byte[] dest) { 257 Objects.requireNonNull(dest, "dest"); 258 return read(dest, 0, dest.length); 259 } 260 261 @Override read(final byte[] dest, final int off, final int len)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 reset()286 public void reset() { 287 this.offset = this.markedOffset; 288 } 289 290 @Override skip(final long n)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 } 305