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