Skip to content

Commit 19b55af

Browse files
committed
[IO-459] Add WindowsLineEndingInputStream and UnixLineEndingInputStream
Submitted by Kristian Rosenvold git-svn-id: https://svn.apache.org/repos/asf/commons/proper/io/trunk@1634738 13f79535-47bb-0310-9956-ffa450edef68
1 parent c9de74f commit 19b55af

4 files changed

Lines changed: 366 additions & 0 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package org.apache.commons.io.input;
2+
3+
/*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
import java.io.IOException;
23+
import java.io.InputStream;
24+
25+
/**
26+
* A filtering input stream that ensures the content will have unix-style line endings, LF.
27+
* @since 2.5
28+
*/
29+
public class UnixLineEndingInputStream
30+
extends InputStream {
31+
32+
private boolean slashNSeen = false;
33+
34+
private boolean eofSeen = false;
35+
36+
private final InputStream target;
37+
38+
private final boolean ensureLineFeedAtEndOfFile;
39+
40+
/**
41+
* Create an input stream that filters another stream
42+
*
43+
* @param in The input stream to wrap
44+
* @param ensureLineFeedAtEndOfFile true to ensure that the file ends with LF
45+
*/
46+
public UnixLineEndingInputStream(InputStream in, boolean ensureLineFeedAtEndOfFile) {
47+
this.target = in;
48+
this.ensureLineFeedAtEndOfFile = ensureLineFeedAtEndOfFile;
49+
}
50+
51+
private int readWithUpdate() throws IOException {
52+
final int target = this.target.read();
53+
eofSeen = target == -1;
54+
if (eofSeen) {
55+
return target;
56+
}
57+
slashNSeen = target == '\n';
58+
return target;
59+
}
60+
61+
/**
62+
* @inheritDoc
63+
*/
64+
65+
@Override public int read()
66+
throws IOException {
67+
if (eofSeen) {
68+
return eofGame();
69+
} else {
70+
int target = readWithUpdate();
71+
if (eofSeen) {
72+
return eofGame();
73+
}
74+
if (target == '\r') {
75+
target = readWithUpdate();
76+
}
77+
return target;
78+
}
79+
}
80+
81+
private int eofGame() {
82+
if (!ensureLineFeedAtEndOfFile) {
83+
return -1;
84+
}
85+
if (!slashNSeen) {
86+
slashNSeen = true;
87+
return '\n';
88+
} else {
89+
return -1;
90+
}
91+
}
92+
93+
/**
94+
* Closes the stream. Also closes the underlying stream.
95+
*/
96+
@Override
97+
public void close()
98+
throws IOException {
99+
super.close();
100+
target.close();
101+
}
102+
103+
/**
104+
* @inheritDoc
105+
*/
106+
@Override
107+
public synchronized void mark(int readlimit) {
108+
throw new UnsupportedOperationException("Mark notsupported");
109+
}
110+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package org.apache.commons.io.input;
2+
3+
/*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
import java.io.IOException;
23+
import java.io.InputStream;
24+
25+
/**
26+
* A filtering input stream that ensures the content will have windows line endings, CRLF.
27+
*/
28+
public class WindowsLineEndingInputStream
29+
extends InputStream {
30+
31+
private boolean slashRSeen = false;
32+
33+
private boolean slashNSeen = false;
34+
35+
private boolean injectSlashN = false;
36+
37+
private boolean eofSeen = false;
38+
39+
private final InputStream target;
40+
41+
private final boolean ensureLineFeedAtEndOfFile;
42+
43+
/**
44+
* Create an input stream that filters another stream
45+
*
46+
* @param in The input stream to wrap
47+
* @param ensureLineFeedAtEndOfFile true to ensure that the file ends with CRLF
48+
*/
49+
public WindowsLineEndingInputStream(InputStream in, boolean ensureLineFeedAtEndOfFile) {
50+
this.target = in;
51+
this.ensureLineFeedAtEndOfFile = ensureLineFeedAtEndOfFile;
52+
}
53+
54+
private int readWithUpdate() throws IOException {
55+
final int target = this.target.read();
56+
eofSeen = target == -1;
57+
if (eofSeen) {
58+
return target;
59+
}
60+
slashRSeen = target == '\r';
61+
slashNSeen = target == '\n';
62+
return target;
63+
}
64+
65+
/**
66+
* @inheritDoc
67+
*/
68+
@Override
69+
public int read() throws IOException {
70+
if (eofSeen) {
71+
return eofGame();
72+
} else if (injectSlashN) {
73+
injectSlashN = false;
74+
return '\n';
75+
} else {
76+
boolean prevWasSlashR = slashRSeen;
77+
int target = readWithUpdate();
78+
if (eofSeen) {
79+
return eofGame();
80+
}
81+
if (target == '\n') {
82+
if (!prevWasSlashR) {
83+
injectSlashN = true;
84+
return '\r';
85+
}
86+
}
87+
return target;
88+
}
89+
}
90+
91+
private int eofGame() {
92+
if (!ensureLineFeedAtEndOfFile) {
93+
return -1;
94+
}
95+
if (!slashNSeen && !slashRSeen) {
96+
slashRSeen = true;
97+
return '\r';
98+
}
99+
if (!slashNSeen) {
100+
slashRSeen = false;
101+
slashNSeen = true;
102+
return '\n';
103+
} else {
104+
return -1;
105+
}
106+
}
107+
108+
/**
109+
* Closes the stream. Also closes the underlying stream.
110+
*/
111+
@Override
112+
public void close() throws IOException {
113+
super.close();
114+
target.close();
115+
}
116+
117+
/**
118+
* @inheritDoc
119+
*/
120+
@Override public synchronized void mark(int readlimit) {
121+
throw new UnsupportedOperationException("Mark not supported");
122+
}
123+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.apache.commons.io.input;
2+
3+
import static org.junit.Assert.*;
4+
5+
import java.io.ByteArrayInputStream;
6+
import java.io.IOException;
7+
8+
import org.junit.Test;
9+
10+
public class UnixLineEndingInputStreamTest {
11+
12+
@Test
13+
public void simpleString() throws Exception {
14+
assertEquals("abc\n", roundtrip("abc"));
15+
}
16+
17+
@Test
18+
public void inTheMiddleOfTheLine() throws Exception {
19+
assertEquals("a\nbc\n", roundtrip("a\r\nbc"));
20+
}
21+
22+
@Test
23+
public void multipleBlankLines() throws Exception {
24+
assertEquals("a\n\nbc\n", roundtrip("a\r\n\r\nbc"));
25+
}
26+
27+
@Test
28+
public void twoLinesAtEnd() throws Exception {
29+
assertEquals("a\n\n", roundtrip("a\r\n\r\n"));
30+
}
31+
32+
@Test
33+
public void malformed() throws Exception {
34+
assertEquals("abc", roundtrip("a\rbc", false));
35+
}
36+
37+
@Test
38+
public void retainLineFeed() throws Exception {
39+
assertEquals("a\n\n", roundtrip("a\r\n\r\n", false));
40+
assertEquals("a", roundtrip("a", false));
41+
}
42+
43+
private String roundtrip(String msg) throws IOException {
44+
return roundtrip(msg, true);
45+
}
46+
47+
private String roundtrip(String msg, boolean ensure) throws IOException {
48+
ByteArrayInputStream baos = new ByteArrayInputStream(msg.getBytes("UTF-8"));
49+
UnixLineEndingInputStream lf = new UnixLineEndingInputStream(baos, ensure);
50+
byte[] buf = new byte[100];
51+
final int read = lf.read(buf);
52+
return new String(buf, 0, read, "UTF-8");
53+
}
54+
55+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package org.apache.commons.io.input;
2+
/*
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
*/
20+
21+
import static org.junit.Assert.assertEquals;
22+
23+
import java.io.ByteArrayInputStream;
24+
import java.io.IOException;
25+
26+
import org.junit.Test;
27+
28+
public class WindowsLineEndingInputStreamTest {
29+
@Test
30+
public void simpleString() throws Exception {
31+
assertEquals("abc\r\n", roundtrip("abc"));
32+
}
33+
34+
@Test
35+
public void inTheMiddleOfTheLine() throws Exception {
36+
assertEquals("a\r\nbc\r\n", roundtrip("a\r\nbc"));
37+
}
38+
39+
@Test
40+
public void multipleBlankLines() throws Exception {
41+
assertEquals("a\r\n\r\nbc\r\n", roundtrip("a\r\n\r\nbc"));
42+
}
43+
44+
@Test
45+
public void twoLinesAtEnd() throws Exception {
46+
assertEquals("a\r\n\r\n", roundtrip("a\r\n\r\n"));
47+
}
48+
49+
@Test
50+
public void linuxLinefeeds() throws Exception {
51+
final String roundtrip = roundtrip("ab\nc", false);
52+
assertEquals("ab\r\nc", roundtrip);
53+
}
54+
55+
56+
@Test
57+
public void malformed() throws Exception {
58+
assertEquals("a\rbc", roundtrip("a\rbc", false));
59+
}
60+
61+
@Test
62+
public void retainLineFeed() throws Exception {
63+
assertEquals("a\r\n\r\n", roundtrip("a\r\n\r\n", false));
64+
assertEquals("a", roundtrip("a", false));
65+
}
66+
67+
private String roundtrip(String msg) throws IOException {
68+
return roundtrip(msg, true);
69+
}
70+
71+
private String roundtrip(String msg, boolean ensure) throws IOException {
72+
ByteArrayInputStream baos = new ByteArrayInputStream(msg.getBytes("UTF-8"));
73+
WindowsLineEndingInputStream lf = new WindowsLineEndingInputStream(baos, ensure);
74+
byte[] buf = new byte[100];
75+
final int read = lf.read(buf);
76+
return new String(buf, 0, read, "UTF-8");
77+
}
78+
}

0 commit comments

Comments
 (0)