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