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( InputStream in, 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            boolean prevWasSlashR = slashRSeen;
080            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        } else {
113            return -1;
114        }
115    }
116
117    /**
118     * Closes the stream. Also closes the underlying stream.
119     * @throws IOException upon error
120     */
121    @Override
122    public void close() throws IOException {
123        super.close();
124        target.close();
125    }
126
127    /**
128     * {@inheritDoc}
129     */
130    @Override
131    public synchronized void mark( int readlimit ) {
132        throw new UnsupportedOperationException( "Mark not supported" );
133    }
134}