CircularInputStream.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.io.input;

  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.util.Objects;

  21. import org.apache.commons.io.IOUtils;

  22. /**
  23.  * An {@link InputStream} that repeats provided bytes for given target byte count.
  24.  * <p>
  25.  * Closing this input stream has no effect. The methods in this class can be called after the stream has been closed
  26.  * without generating an {@link IOException}.
  27.  * </p>
  28.  *
  29.  * @see InfiniteCircularInputStream
  30.  * @since 2.8.0
  31.  */
  32. public class CircularInputStream extends AbstractInputStream {

  33.     /**
  34.      * Throws an {@link IllegalArgumentException} if the input contains -1.
  35.      *
  36.      * @param repeatContent input to validate.
  37.      * @return the input.
  38.      */
  39.     private static byte[] validate(final byte[] repeatContent) {
  40.         Objects.requireNonNull(repeatContent, "repeatContent");
  41.         for (final byte b : repeatContent) {
  42.             if (b == IOUtils.EOF) {
  43.                 throw new IllegalArgumentException("repeatContent contains the end-of-stream marker " + IOUtils.EOF);
  44.             }
  45.         }
  46.         return repeatContent;
  47.     }

  48.     private long byteCount;
  49.     private int position = IOUtils.EOF;
  50.     private final byte[] repeatedContent;
  51.     private final long targetByteCount;

  52.     /**
  53.      * Constructs an instance from the specified array of bytes.
  54.      *
  55.      * @param repeatContent Input buffer to be repeated this buffer is not copied.
  56.      * @param targetByteCount How many bytes the read. A negative number means an infinite target count.
  57.      */
  58.     public CircularInputStream(final byte[] repeatContent, final long targetByteCount) {
  59.         this.repeatedContent = validate(repeatContent);
  60.         if (repeatContent.length == 0) {
  61.             throw new IllegalArgumentException("repeatContent is empty.");
  62.         }
  63.         this.targetByteCount = targetByteCount;
  64.     }

  65.     @Override
  66.     public int available() throws IOException {
  67.         // A negative targetByteCount means an infinite target count.
  68.         return isClosed() ? 0 : targetByteCount <= Integer.MAX_VALUE ? Math.max(Integer.MAX_VALUE, (int) targetByteCount) : Integer.MAX_VALUE;
  69.     }

  70.     @Override
  71.     public void close() throws IOException {
  72.         super.close();
  73.         byteCount = targetByteCount;
  74.     }

  75.     @Override
  76.     public int read() {
  77.         if (targetByteCount >= 0 || isClosed()) {
  78.             if (byteCount == targetByteCount) {
  79.                 return IOUtils.EOF;
  80.             }
  81.             byteCount++;
  82.         }
  83.         position = (position + 1) % repeatedContent.length;
  84.         return repeatedContent[position] & 0xff;
  85.     }

  86. }