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