    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;
    021
    022/**
    023 * A filtering input stream that ensures the content will have windows line endings, CRLF.
    024 *
    025 * @since 2.5
    026 */
    027public class WindowsLineEndingInputStream  extends InputStream {
    028
    029    private boolean slashRSeen = false;
    030
    031    private boolean slashNSeen = false;
    032
    033    private boolean injectSlashN = false;
    034
    035    private boolean eofSeen = false;
    036
    037    private final InputStream target;
    038
    039    private final boolean ensureLineFeedAtEndOfFile;
    040
    041    /**
    042     * Create an input stream that filters another stream
    043     *
    044     * @param in                        The input stream to wrap
    045     * @param ensureLineFeedAtEndOfFile true to ensure that the file ends with CRLF
    046     */
    047    public WindowsLineEndingInputStream( final InputStream in, final boolean ensureLineFeedAtEndOfFile ) {
    048        this.target = in;
    049        this.ensureLineFeedAtEndOfFile = ensureLineFeedAtEndOfFile;
    050    }
    051
    052    /**
    053     * Reads the next item from the target, updating internal flags in the process
    054     * @return the next int read from the target stream
    055     * @throws IOException upon error
    056     */
    057    private int readWithUpdate() throws IOException {
    058        final int target = this.target.read();
    059        eofSeen = target == -1;
    060        if ( eofSeen ) {
    061            return target;
    062        }
    063        slashRSeen = target == '\r';
    064        slashNSeen = target == '\n';
    065        return target;
    066    }
    067
    068    /**
    069     * {@inheritDoc}
    070     */
    071    @Override
    072    public int read() throws IOException {
    073        if ( eofSeen ) {
    074            return eofGame();
    075        } else if ( injectSlashN ) {
    076            injectSlashN = false;
    077            return '\n';
    078        } else {
    079            final boolean prevWasSlashR = slashRSeen;
    080            final int target = readWithUpdate();
    081            if ( eofSeen ) {
    082                return eofGame();
    083            }
    084            if ( target == '\n' ) {
    085                if ( !prevWasSlashR )
    086                {
    087                    injectSlashN = true;
    088                    return '\r';
    089                }
    090            }
    091            return target;
    092        }
    093    }
    094
    095    /**
    096     * Handles the eof-handling at the end of the stream
    097     * @return The next char to output to the stream
    098     */
    099
    100    private int eofGame() {
    101        if ( !ensureLineFeedAtEndOfFile ) {
    102            return -1;
    103        }
    104        if ( !slashNSeen && !slashRSeen ) {
    105            slashRSeen = true;
    106            return '\r';
    107        }
    108        if ( !slashNSeen ) {
    109            slashRSeen = false;
    110            slashNSeen = true;
    111            return '\n';
    112        }
    113        return -1;
    114    }
    115
    116    /**
    117     * Closes the stream. Also closes the underlying stream.
    118     * @throws IOException upon error
    119     */
    120    @Override
    121    public void close() throws IOException {
    122        super.close();
    123        target.close();
    124    }
    125
    126    /**
    127     * {@inheritDoc}
    128     */
    129    @Override
    130    public synchronized void mark( final int readlimit ) {
    131        throw new UnsupportedOperationException( "Mark not supported" );
    132    }
    133}