Skip to content

Commit 7791a85

Browse files
committed
[IO-577] Add readers to filter out given characters:
CharacterSetFilterReader and CharacterFilterReader.
1 parent bc10af4 commit 7791a85

6 files changed

Lines changed: 383 additions & 0 deletions

File tree

src/changes/changes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ The <action> type attribute can be add,update,fix,remove.
6565
<action issue="IO-572" dev="ggregory" type="update" due-to="Pranet Verma">
6666
Refactor duplicate code in org.apache.commons.io.FileUtils.
6767
</action>
68+
<action issue="IO-577" dev="ggregory" type="add" due-to="Gary Gregory">
69+
Add readers to filter out given characters: CharacterSetFilterReader and CharacterFilterReader.
70+
</action>
6871
</release>
6972

7073
<release version="2.6" date="2017-10-15" description="Java 7 required, Java 9 supported.">
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.io.input;
18+
19+
import java.io.FilterReader;
20+
import java.io.IOException;
21+
import java.io.Reader;
22+
23+
/**
24+
* A filter reader that filters out characters where subclasses decide which characters to filter out.
25+
*/
26+
public abstract class AbstractCharacterFilterReader extends FilterReader {
27+
28+
/**
29+
* Constructs a new reader.
30+
*
31+
* @param reader
32+
* the reader to filter
33+
*/
34+
protected AbstractCharacterFilterReader(final Reader reader) {
35+
super(reader);
36+
}
37+
38+
@Override
39+
public int read() throws IOException {
40+
int ch;
41+
do {
42+
ch = in.read();
43+
} while (filter(ch));
44+
return ch;
45+
}
46+
47+
/**
48+
* Returns true if the given character should be filtered out, false to keep the character.
49+
*
50+
* @param ch
51+
* the character to test.
52+
* @return true if the given character should be filtered out, false to keep the character.
53+
*/
54+
protected abstract boolean filter(int ch);
55+
56+
@Override
57+
public int read(final char[] cbuf, final int off, final int len) throws IOException {
58+
final int read = super.read(cbuf, off, len);
59+
if (read == -1) {
60+
return -1;
61+
}
62+
int pos = off - 1;
63+
for (int readPos = off; readPos < off + read; readPos++) {
64+
if (filter(read)) {
65+
continue;
66+
}
67+
pos++;
68+
if (pos < readPos) {
69+
cbuf[pos] = cbuf[readPos];
70+
}
71+
}
72+
return pos - off + 1;
73+
}
74+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.io.input;
18+
19+
import java.io.Reader;
20+
21+
/**
22+
* A filter reader that filters out a given character represented as an <code>int</code> code point, handy to remove
23+
* known junk characters from CSV files for example. This class is the most efficient way to filter out a single
24+
* character, as opposed to using a {@link CharacterSetFilterReader}. You can also nest {@link CharacterFilterReader}s.
25+
*/
26+
public class CharacterFilterReader extends AbstractCharacterFilterReader {
27+
28+
private final int skip;
29+
30+
/**
31+
* Constructs a new reader.
32+
*
33+
* @param reader
34+
* the reader to filter.
35+
* @param skip
36+
* the character to filter out.
37+
*/
38+
public CharacterFilterReader(final Reader reader, final int skip) {
39+
super(reader);
40+
this.skip = skip;
41+
}
42+
43+
@Override
44+
protected boolean filter(final int ch) {
45+
return ch == skip;
46+
}
47+
48+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.io.input;
18+
19+
import java.io.Reader;
20+
import java.util.Collections;
21+
import java.util.Set;
22+
23+
/**
24+
* A filter reader that removes a given set of characters represented as <code>int</code> code points, handy to remove
25+
* known junk characters from CSV files for example.
26+
* <p>
27+
* This class must convert each <code>int</code> read to an <code>Integer</code>. You can increase the Integer cache
28+
* with a system property, see {@link Integer}.
29+
* </p>
30+
*/
31+
public class CharacterSetFilterReader extends AbstractCharacterFilterReader {
32+
33+
private static final Set<Integer> EMPTY_SET = Collections.emptySet();
34+
private final Set<Integer> skipSet;
35+
36+
/**
37+
* Constructs a new reader.
38+
*
39+
* @param reader
40+
* the reader to filter.
41+
* @param skip
42+
* the set of characters to filter out.
43+
*/
44+
public CharacterSetFilterReader(final Reader reader, final Set<Integer> skip) {
45+
super(reader);
46+
this.skipSet = skip == null ? EMPTY_SET : Collections.unmodifiableSet(skip);
47+
}
48+
49+
@Override
50+
protected boolean filter(final int ch) {
51+
// Note WRT Integer.valueOf(): You can increase the Integer cache with a system property, see {@link Integer}.
52+
return skipSet.contains(Integer.valueOf(ch));
53+
}
54+
55+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.io.input;
18+
19+
import java.io.IOException;
20+
import java.io.StringReader;
21+
import java.util.HashSet;
22+
23+
import org.junit.Assert;
24+
import org.junit.Test;
25+
26+
public class CharacterFilterReaderTest {
27+
28+
@Test
29+
public void testInputSize0FilterSize1() throws IOException {
30+
final StringReader input = new StringReader("");
31+
final HashSet<Integer> codePoints = new HashSet<>();
32+
codePoints.add(Integer.valueOf('a'));
33+
try (CharacterFilterReader reader = new CharacterFilterReader(input, 'A')) {
34+
Assert.assertEquals(-1, reader.read());
35+
}
36+
}
37+
38+
@Test
39+
public void testInputSize1FilterSize1() throws IOException {
40+
try (StringReader input = new StringReader("a");
41+
CharacterFilterReader reader = new CharacterFilterReader(input, 'a')) {
42+
Assert.assertEquals(-1, reader.read());
43+
}
44+
}
45+
46+
@Test
47+
public void testInputSize2FilterSize1FilterAll() throws IOException {
48+
final StringReader input = new StringReader("aa");
49+
try (CharacterFilterReader reader = new CharacterFilterReader(input, 'a')) {
50+
Assert.assertEquals(-1, reader.read());
51+
}
52+
}
53+
54+
@Test
55+
public void testInputSize2FilterSize1FilterFirst() throws IOException {
56+
final StringReader input = new StringReader("ab");
57+
try (CharacterFilterReader reader = new CharacterFilterReader(input, 'a')) {
58+
Assert.assertEquals('b', reader.read());
59+
Assert.assertEquals(-1, reader.read());
60+
}
61+
}
62+
63+
@Test
64+
public void testInputSize2FilterSize1FilterLast() throws IOException {
65+
final StringReader input = new StringReader("ab");
66+
try (CharacterFilterReader reader = new CharacterFilterReader(input, 'b')) {
67+
Assert.assertEquals('a', reader.read());
68+
Assert.assertEquals(-1, reader.read());
69+
}
70+
}
71+
72+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.io.input;
18+
19+
import java.io.IOException;
20+
import java.io.StringReader;
21+
import java.util.HashSet;
22+
23+
import org.junit.Assert;
24+
import org.junit.Test;
25+
26+
public class CharacterSetFilterReaderTest {
27+
28+
@Test
29+
public void testInputSize0FilterSize0() throws IOException {
30+
final StringReader input = new StringReader("");
31+
try (CharacterSetFilterReader reader = new CharacterSetFilterReader(input, new HashSet<Integer>(0))) {
32+
Assert.assertEquals(-1, reader.read());
33+
}
34+
}
35+
36+
@Test
37+
public void testInputSize0FilterSize1() throws IOException {
38+
final StringReader input = new StringReader("");
39+
final HashSet<Integer> codePoints = new HashSet<>();
40+
codePoints.add(Integer.valueOf('a'));
41+
try (CharacterSetFilterReader reader = new CharacterSetFilterReader(input, codePoints)) {
42+
Assert.assertEquals(-1, reader.read());
43+
}
44+
}
45+
46+
@Test
47+
public void testInputSize0NullFilter() throws IOException {
48+
final StringReader input = new StringReader("");
49+
try (CharacterSetFilterReader reader = new CharacterSetFilterReader(input, null)) {
50+
Assert.assertEquals(-1, reader.read());
51+
}
52+
}
53+
54+
@Test
55+
public void testInputSize1FilterSize1() throws IOException {
56+
try (StringReader input = new StringReader("a")) {
57+
final HashSet<Integer> codePoints = new HashSet<>();
58+
codePoints.add(Integer.valueOf('a'));
59+
final CharacterSetFilterReader reader = new CharacterSetFilterReader(input, codePoints);
60+
Assert.assertEquals(-1, reader.read());
61+
}
62+
}
63+
64+
@Test
65+
public void testInputSize2FilterSize1FilterAll() throws IOException {
66+
final StringReader input = new StringReader("aa");
67+
final HashSet<Integer> codePoints = new HashSet<>();
68+
codePoints.add(Integer.valueOf('a'));
69+
try (CharacterSetFilterReader reader = new CharacterSetFilterReader(input, codePoints)) {
70+
Assert.assertEquals(-1, reader.read());
71+
}
72+
}
73+
74+
@Test
75+
public void testInputSize2FilterSize1FilterFirst() throws IOException {
76+
final StringReader input = new StringReader("ab");
77+
final HashSet<Integer> codePoints = new HashSet<>();
78+
codePoints.add(Integer.valueOf('a'));
79+
try (CharacterSetFilterReader reader = new CharacterSetFilterReader(input, codePoints)) {
80+
Assert.assertEquals('b', reader.read());
81+
Assert.assertEquals(-1, reader.read());
82+
}
83+
}
84+
85+
@Test
86+
public void testInputSize2FilterSize1FilterLast() throws IOException {
87+
final StringReader input = new StringReader("ab");
88+
final HashSet<Integer> codePoints = new HashSet<>();
89+
codePoints.add(Integer.valueOf('b'));
90+
try (CharacterSetFilterReader reader = new CharacterSetFilterReader(input, codePoints)) {
91+
Assert.assertEquals('a', reader.read());
92+
Assert.assertEquals(-1, reader.read());
93+
}
94+
}
95+
96+
@Test
97+
public void testInputSize2FilterSize2FilterFirst() throws IOException {
98+
final StringReader input = new StringReader("ab");
99+
final HashSet<Integer> codePoints = new HashSet<>();
100+
codePoints.add(Integer.valueOf('a'));
101+
codePoints.add(Integer.valueOf('y'));
102+
try (CharacterSetFilterReader reader = new CharacterSetFilterReader(input, codePoints)) {
103+
Assert.assertEquals('b', reader.read());
104+
Assert.assertEquals(-1, reader.read());
105+
}
106+
}
107+
108+
@Test
109+
public void testInputSize2FilterSize2FilterLast() throws IOException {
110+
final StringReader input = new StringReader("ab");
111+
final HashSet<Integer> codePoints = new HashSet<>();
112+
codePoints.add(Integer.valueOf('x'));
113+
codePoints.add(Integer.valueOf('b'));
114+
try (CharacterSetFilterReader reader = new CharacterSetFilterReader(input, codePoints)) {
115+
Assert.assertEquals('a', reader.read());
116+
Assert.assertEquals(-1, reader.read());
117+
}
118+
}
119+
120+
@Test
121+
public void testInputSize2FilterSize2FilterNone() throws IOException {
122+
final StringReader input = new StringReader("ab");
123+
final HashSet<Integer> codePoints = new HashSet<>();
124+
codePoints.add(Integer.valueOf('x'));
125+
codePoints.add(Integer.valueOf('y'));
126+
try (CharacterSetFilterReader reader = new CharacterSetFilterReader(input, codePoints)) {
127+
Assert.assertEquals('a', reader.read());
128+
Assert.assertEquals('b', reader.read());
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)