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