WindowsLineEndingInputStream.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 Windows line endings, CRLF.
  25.  *
  26.  * @since 2.5
  27.  */
  28. public class WindowsLineEndingInputStream extends InputStream {

  29.     private boolean atEos;

  30.     private boolean atSlashCr;

  31.     private boolean atSlashLf;

  32.     private final InputStream in;

  33.     private boolean injectSlashLf;

  34.     private final boolean lineFeedAtEos;

  35.     /**
  36.      * Constructs an input stream that filters another stream.
  37.      *
  38.      * @param in                        The input stream to wrap.
  39.      * @param lineFeedAtEos true to ensure that the stream ends with CRLF.
  40.      */
  41.     public WindowsLineEndingInputStream(final InputStream in, final boolean lineFeedAtEos) {
  42.         this.in = in;
  43.         this.lineFeedAtEos = lineFeedAtEos;
  44.     }

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

  55.     /**
  56.      * Handles the end of stream condition.
  57.      *
  58.      * @return The next char to output to the stream.
  59.      */
  60.     private int handleEos() {
  61.         if (!lineFeedAtEos) {
  62.             return EOF;
  63.         }
  64.         if (!atSlashLf && !atSlashCr) {
  65.             atSlashCr = true;
  66.             return CR;
  67.         }
  68.         if (!atSlashLf) {
  69.             atSlashCr = false;
  70.             atSlashLf = true;
  71.             return LF;
  72.         }
  73.         return EOF;
  74.     }

  75.     /**
  76.      * {@inheritDoc}
  77.      */
  78.     @Override
  79.     public synchronized void mark(final int readLimit) {
  80.         throw UnsupportedOperationExceptions.mark();
  81.     }

  82.     /**
  83.      * {@inheritDoc}
  84.      */
  85.     @Override
  86.     public int read() throws IOException {
  87.         if (atEos) {
  88.             return handleEos();
  89.         }
  90.         if (injectSlashLf) {
  91.             injectSlashLf = false;
  92.             return LF;
  93.         }
  94.         final boolean prevWasSlashR = atSlashCr;
  95.         final int target = in.read();
  96.         atEos = target == EOF;
  97.         if (!atEos) {
  98.             atSlashCr = target == CR;
  99.             atSlashLf = target == LF;
  100.         }
  101.         if (atEos) {
  102.             return handleEos();
  103.         }
  104.         if (target == LF && !prevWasSlashR) {
  105.             injectSlashLf = true;
  106.             return CR;
  107.         }
  108.         return target;
  109.     }
  110. }