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