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 /** 096 * Builds a new {@link UnsynchronizedByteArrayInputStream}. 097 * <p> 098 * You must set an aspect that supports {@code byte[]} on this builder, otherwise, this method throws an exception. 099 * </p> 100 * <p> 101 * This builder uses the following aspects: 102 * </p> 103 * <ul> 104 * <li>{@code byte[]}</li> 105 * <li>offset</li> 106 * <li>length</li> 107 * </ul> 108 * 109 * @return a new instance. 110 * @throws UnsupportedOperationException if the origin cannot provide a {@code byte[]}. 111 * @throws IllegalStateException if the {@code origin} is {@code null}. 112 * @throws IOException if an I/O error occurs converting to an {@code byte[]} using {@link AbstractOrigin#getByteArray()}. 113 * @see AbstractOrigin#getByteArray() 114 * @see #getUnchecked() 115 */ 116 @Override 117 public UnsynchronizedByteArrayInputStream get() throws IOException { 118 return new UnsynchronizedByteArrayInputStream(this); 119 } 120 121 @Override 122 public Builder setByteArray(final byte[] origin) { 123 length = Objects.requireNonNull(origin, "origin").length; 124 return super.setByteArray(origin); 125 } 126 127 /** 128 * Sets the length. 129 * 130 * @param length Must be greater or equal to 0. 131 * @return {@code this} instance. 132 */ 133 public Builder setLength(final int length) { 134 if (length < 0) { 135 throw new IllegalArgumentException("length cannot be negative"); 136 } 137 this.length = length; 138 return this; 139 } 140 141 /** 142 * Sets the offset. 143 * 144 * @param offset Must be greater or equal to 0. 145 * @return {@code this} instance. 146 */ 147 public Builder setOffset(final int offset) { 148 if (offset < 0) { 149 throw new IllegalArgumentException("offset cannot be negative"); 150 } 151 this.offset = offset; 152 return this; 153 } 154 155 } 156 157 /** 158 * The end of stream marker. 159 */ 160 public static final int END_OF_STREAM = -1; 161 162 /** 163 * Constructs a new {@link Builder}. 164 * 165 * @return a new {@link Builder}. 166 */ 167 public static Builder builder() { 168 return new Builder(); 169 } 170 171 private static int minPosLen(final byte[] data, final int defaultValue) { 172 requireNonNegative(defaultValue, "defaultValue"); 173 return Math.min(defaultValue, data.length > 0 ? data.length : defaultValue); 174 } 175 176 private static int requireNonNegative(final int value, final String name) { 177 if (value < 0) { 178 throw new IllegalArgumentException(name + " cannot be negative"); 179 } 180 return value; 181 } 182 183 /** 184 * The underlying data buffer. 185 */ 186 private final byte[] data; 187 188 /** 189 * End Of Data. 190 * 191 * Similar to data.length, which is the last readable offset + 1. 192 */ 193 private final int eod; 194 195 /** 196 * Current offset in the data buffer. 197 */ 198 private int offset; 199 200 /** 201 * The current mark (if any). 202 */ 203 private int markedOffset; 204 205 private UnsynchronizedByteArrayInputStream(final Builder builder) throws IOException { 206 this(builder.getByteArray(), builder.offset, builder.length); 207 } 208 209 /** 210 * Constructs a new byte array input stream. 211 * 212 * @param data the buffer. 213 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. 214 */ 215 @Deprecated 216 public UnsynchronizedByteArrayInputStream(final byte[] data) { 217 this(data, data.length, 0, 0); 218 } 219 220 /** 221 * Constructs a new byte array input stream. 222 * 223 * @param data the buffer. 224 * @param offset the offset into the buffer. 225 * @throws IllegalArgumentException if the offset is less than zero. 226 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. 227 */ 228 @Deprecated 229 public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset) { 230 this(data, data.length, Math.min(requireNonNegative(offset, "offset"), minPosLen(data, offset)), minPosLen(data, offset)); 231 } 232 233 /** 234 * Constructs a new byte array input stream. 235 * 236 * @param data the buffer. 237 * @param offset the offset into the buffer. 238 * @param length the length of the buffer. 239 * @throws IllegalArgumentException if the offset or length less than zero. 240 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. 241 */ 242 @Deprecated 243 public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length) { 244 requireNonNegative(offset, "offset"); 245 requireNonNegative(length, "length"); 246 this.data = Objects.requireNonNull(data, "data"); 247 this.eod = Math.min(minPosLen(data, offset) + length, data.length); 248 this.offset = minPosLen(data, offset); 249 this.markedOffset = minPosLen(data, offset); 250 } 251 252 private UnsynchronizedByteArrayInputStream(final byte[] data, final int eod, final int offset, final int markedOffset) { 253 this.data = Objects.requireNonNull(data, "data"); 254 this.eod = eod; 255 this.offset = offset; 256 this.markedOffset = markedOffset; 257 } 258 259 @Override 260 public int available() { 261 return offset < eod ? eod - offset : 0; 262 } 263 264 @SuppressWarnings("sync-override") 265 @Override 266 public void mark(final int readLimit) { 267 this.markedOffset = this.offset; 268 } 269 270 @Override 271 public boolean markSupported() { 272 return true; 273 } 274 275 @Override 276 public int read() { 277 return offset < eod ? data[offset++] & 0xff : END_OF_STREAM; 278 } 279 280 @Override 281 public int read(final byte[] dest) { 282 Objects.requireNonNull(dest, "dest"); 283 return read(dest, 0, dest.length); 284 } 285 286 @Override 287 public int read(final byte[] dest, final int off, final int len) { 288 IOUtils.checkFromIndexSize(dest, off, len); 289 if (len == 0) { 290 return 0; 291 } 292 293 if (offset >= eod) { 294 return END_OF_STREAM; 295 } 296 297 int actualLen = eod - offset; 298 if (len < actualLen) { 299 actualLen = len; 300 } 301 if (actualLen <= 0) { 302 return 0; 303 } 304 System.arraycopy(data, offset, dest, off, actualLen); 305 offset += actualLen; 306 return actualLen; 307 } 308 309 @SuppressWarnings("sync-override") 310 @Override 311 public void reset() { 312 this.offset = this.markedOffset; 313 } 314 315 @Override 316 public long skip(final long n) { 317 if (n < 0) { 318 throw new IllegalArgumentException("Skipping backward is not supported"); 319 } 320 321 long actualSkip = eod - offset; 322 if (n < actualSkip) { 323 actualSkip = n; 324 } 325 326 offset = Math.addExact(offset, Math.toIntExact(n)); 327 return actualSkip; 328 } 329}