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 static org.apache.commons.io.IOUtils.EOF;
020
021import java.io.FilterReader;
022import java.io.IOException;
023import java.io.Reader;
024import java.util.function.IntPredicate;
025
026/**
027 * A filter reader that filters out characters where subclasses decide which characters to filter out.
028 */
029public abstract class AbstractCharacterFilterReader extends FilterReader {
030
031    /**
032     * Skips nothing.
033     *
034     * @since 2.9.0
035     */
036    protected static final IntPredicate SKIP_NONE = ch -> false;
037
038    private final IntPredicate skip;
039
040    /**
041     * Constructs a new reader.
042     *
043     * @param reader the reader to filter
044     */
045    protected AbstractCharacterFilterReader(final Reader reader) {
046        this(reader, SKIP_NONE);
047    }
048
049    /**
050     * Constructs a new reader.
051     *
052     * @param reader the reader to filter.
053     * @param skip Skip test.
054     * @since 2.9.0
055     */
056    protected AbstractCharacterFilterReader(final Reader reader, final IntPredicate skip) {
057        super(reader);
058        this.skip = skip == null ? SKIP_NONE : skip;
059    }
060
061    /**
062     * Returns true if the given character should be filtered out, false to keep the character.
063     *
064     * @param ch the character to test.
065     * @return true if the given character should be filtered out, false to keep the character.
066     */
067    protected boolean filter(final int ch) {
068        return skip.test(ch);
069    }
070
071    @Override
072    public int read() throws IOException {
073        int ch;
074        do {
075            ch = in.read();
076        } while (ch != EOF && filter(ch));
077        return ch;
078    }
079
080    @Override
081    public int read(final char[] cbuf, final int off, final int len) throws IOException {
082        final int read = super.read(cbuf, off, len);
083        if (read == EOF) {
084            return EOF;
085        }
086        int pos = off - 1;
087        for (int readPos = off; readPos < off + read; readPos++) {
088            if (filter(cbuf[readPos])) {
089                continue;
090            }
091            pos++;
092            if (pos < readPos) {
093                cbuf[pos] = cbuf[readPos];
094            }
095        }
096        return pos - off + 1;
097    }
098}