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