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