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