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}