UnixLineEndingInputStream.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 static org.apache.commons.io.IOUtils.CR;
  19. import static org.apache.commons.io.IOUtils.EOF;
  20. import static org.apache.commons.io.IOUtils.LF;

  21. import java.io.IOException;
  22. import java.io.InputStream;

  23. /**
  24.  * A filtering input stream that ensures the content will have UNIX-style line endings, LF.
  25.  *
  26.  * @since 2.5
  27.  */
  28. public class UnixLineEndingInputStream extends InputStream {

  29.     private boolean atEos;

  30.     private boolean atSlashCr;

  31.     private boolean atSlashLf;

  32.     private final InputStream in;

  33.     private final boolean lineFeedAtEndOfFile;

  34.     /**
  35.      * Constructs an input stream that filters another stream
  36.      *
  37.      * @param inputStream                        The input stream to wrap
  38.      * @param ensureLineFeedAtEndOfFile true to ensure that the file ends with LF
  39.      */
  40.     public UnixLineEndingInputStream(final InputStream inputStream, final boolean ensureLineFeedAtEndOfFile) {
  41.         this.in = inputStream;
  42.         this.lineFeedAtEndOfFile = ensureLineFeedAtEndOfFile;
  43.     }

  44.     /**
  45.      * Closes the stream. Also closes the underlying stream.
  46.      * @throws IOException upon error
  47.      */
  48.     @Override
  49.     public void close() throws IOException {
  50.         super.close();
  51.         in.close();
  52.     }

  53.     /**
  54.      * Handles the end of stream condition.
  55.      *
  56.      * @param previousWasSlashCr Indicates if the last seen was a {@code \r}.
  57.      * @return The next char to output to the stream.
  58.      */
  59.     private int handleEos(final boolean previousWasSlashCr) {
  60.         if (previousWasSlashCr || !lineFeedAtEndOfFile) {
  61.             return EOF;
  62.         }
  63.         if (!atSlashLf) {
  64.             atSlashLf = true;
  65.             return LF;
  66.         }
  67.         return EOF;
  68.     }

  69.     /**
  70.      * {@inheritDoc}
  71.      */
  72.     @Override
  73.     public synchronized void mark(final int readLimit) {
  74.         throw UnsupportedOperationExceptions.mark();
  75.     }

  76.     /**
  77.      * {@inheritDoc}
  78.      */
  79.     @Override
  80.     public int read() throws IOException {
  81.         final boolean previousWasSlashR = atSlashCr;
  82.         if (atEos) {
  83.             return handleEos(previousWasSlashR);
  84.         }
  85.         final int target = readWithUpdate();
  86.         if (atEos) {
  87.             return handleEos(previousWasSlashR);
  88.         }
  89.         if (atSlashCr) {
  90.             return LF;
  91.         }

  92.         if (previousWasSlashR && atSlashLf) {
  93.             return read();
  94.         }

  95.         return target;
  96.     }

  97.     /**
  98.      * Reads the next item from the target, updating internal flags in the process
  99.      * @return the next int read from the target stream
  100.      * @throws IOException upon error
  101.      */
  102.     private int readWithUpdate() throws IOException {
  103.         final int target = this.in.read();
  104.         atEos = target == EOF;
  105.         if (atEos) {
  106.             return target;
  107.         }
  108.         atSlashCr = target == CR;
  109.         atSlashLf = target == LF;
  110.         return target;
  111.     }
  112. }