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