    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 *      https://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 static org.apache.commons.io.IOUtils.CR;
    020import static org.apache.commons.io.IOUtils.EOF;
    021import static org.apache.commons.io.IOUtils.LF;
    022
    023import java.io.IOException;
    024import java.io.InputStream;
    025
    026/**
    027 * A filtering input stream that ensures the content will have Windows line endings, CRLF.
    028 *
    029 * @since 2.5
    030 */
    031public class WindowsLineEndingInputStream extends InputStream {
    032
    033    private boolean atEos;
    034
    035    private boolean atSlashCr;
    036
    037    private boolean atSlashLf;
    038
    039    private final InputStream in;
    040
    041    private boolean injectSlashLf;
    042
    043    private final boolean lineFeedAtEos;
    044
    045    /**
    046     * Constructs an input stream that filters another stream.
    047     *
    048     * @param in                        The input stream to wrap.
    049     * @param lineFeedAtEos true to ensure that the stream ends with CRLF.
    050     */
    051    public WindowsLineEndingInputStream(final InputStream in, final boolean lineFeedAtEos) {
    052        this.in = in;
    053        this.lineFeedAtEos = lineFeedAtEos;
    054    }
    055
    056    /**
    057     * Closes the stream. Also closes the underlying stream.
    058     *
    059     * @throws IOException upon error
    060     */
    061    @Override
    062    public void close() throws IOException {
    063        super.close();
    064        in.close();
    065    }
    066
    067    /**
    068     * Handles the end of stream condition.
    069     *
    070     * @return The next char to output to the stream.
    071     */
    072    private int handleEos() {
    073        if (!lineFeedAtEos) {
    074            return EOF;
    075        }
    076        if (!atSlashLf && !atSlashCr) {
    077            atSlashCr = true;
    078            return CR;
    079        }
    080        if (!atSlashLf) {
    081            atSlashCr = false;
    082            atSlashLf = true;
    083            return LF;
    084        }
    085        return EOF;
    086    }
    087
    088    /**
    089     * {@inheritDoc}
    090     */
    091    @Override
    092    public synchronized void mark(final int readLimit) {
    093        throw UnsupportedOperationExceptions.mark();
    094    }
    095
    096    /**
    097     * {@inheritDoc}
    098     */
    099    @Override
    100    public synchronized int read() throws IOException {
    101        if (atEos) {
    102            return handleEos();
    103        }
    104        if (injectSlashLf) {
    105            injectSlashLf = false;
    106            return LF;
    107        }
    108        final boolean prevWasSlashR = atSlashCr;
    109        final int target = in.read();
    110        atEos = target == EOF;
    111        if (!atEos) {
    112            atSlashCr = target == CR;
    113            atSlashLf = target == LF;
    114        }
    115        if (atEos) {
    116            return handleEos();
    117        }
    118        if (target == LF && !prevWasSlashR) {
    119            injectSlashLf = true;
    120            return CR;
    121        }
    122        return target;
    123    }
    124}