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.IOException; 020import java.io.InputStream; 021import java.util.Objects; 022 023import org.apache.commons.io.IOUtils; 024 025/** 026 * An {@link InputStream} that repeats provided bytes for given target byte count. 027 * <p> 028 * Closing this input stream has no effect. The methods in this class can be called after the stream has been closed 029 * without generating an {@link IOException}. 030 * </p> 031 * 032 * @see InfiniteCircularInputStream 033 * @since 2.8.0 034 */ 035public class CircularInputStream extends AbstractInputStream { 036 037 /** 038 * Throws an {@link IllegalArgumentException} if the input contains -1. 039 * 040 * @param repeatContent input to validate. 041 * @return the input. 042 */ 043 private static byte[] validate(final byte[] repeatContent) { 044 Objects.requireNonNull(repeatContent, "repeatContent"); 045 for (final byte b : repeatContent) { 046 if (b == IOUtils.EOF) { 047 throw new IllegalArgumentException("repeatContent contains the end-of-stream marker " + IOUtils.EOF); 048 } 049 } 050 return repeatContent; 051 } 052 053 private long byteCount; 054 private int position = IOUtils.EOF; 055 private final byte[] repeatedContent; 056 private final long targetByteCount; 057 058 /** 059 * Constructs an instance from the specified array of bytes. 060 * 061 * @param repeatContent Input buffer to be repeated this buffer is not copied. 062 * @param targetByteCount How many bytes the read. A negative number means an infinite target count. 063 */ 064 public CircularInputStream(final byte[] repeatContent, final long targetByteCount) { 065 this.repeatedContent = validate(repeatContent); 066 if (repeatContent.length == 0) { 067 throw new IllegalArgumentException("repeatContent is empty."); 068 } 069 this.targetByteCount = targetByteCount; 070 } 071 072 @Override 073 public int available() throws IOException { 074 // A negative targetByteCount means an infinite target count. 075 return isClosed() ? 0 : targetByteCount <= Integer.MAX_VALUE ? Math.max(Integer.MAX_VALUE, (int) targetByteCount) : Integer.MAX_VALUE; 076 } 077 078 @Override 079 public void close() throws IOException { 080 super.close(); 081 byteCount = targetByteCount; 082 } 083 084 @Override 085 public int read() { 086 if (targetByteCount >= 0 || isClosed()) { 087 if (byteCount == targetByteCount) { 088 return IOUtils.EOF; 089 } 090 byteCount++; 091 } 092 position = (position + 1) % repeatedContent.length; 093 return repeatedContent[position] & 0xff; 094 } 095 096}