/* * Copyright 2011-2015 PrimeFaces Extensions * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.icij.extract.io; import java.io.IOException; import java.io.PushbackReader; import java.io.Reader; import java.io.StringReader; import java.util.Arrays; /** * Reader for in-place token replacements. It does not use as much memory as the {@link String#replace} method. * * Based on original code by Oleg Varaksin ([email protected]), the license of which is copied above. This * version resolves tokens to {@link Reader} instances. */ public class TokenReplacingReader extends Reader { private final String start; private final String end; private final char[] startChars; private final char[] endChars; private char[] startCharsBuffer; private char[] endCharsBuffer; private final PushbackReader pushback; private final TokenResolver<Reader> resolver; private final StringBuilder token = new StringBuilder(); private Reader resolved = null; public TokenReplacingReader(final TokenResolver<Reader> resolver, final Reader source, final String start, final String end) { if (resolver == null) { throw new IllegalArgumentException("Token resolver is null"); } if ((start == null || start.length() < 1) || (end == null || end.length() < 1)) { throw new IllegalArgumentException("Token start / end marker is null or empty"); } this.start = start; this.end = end; this.startChars = start.toCharArray(); this.endChars = end.toCharArray(); this.startCharsBuffer = new char[start.length()]; this.endCharsBuffer = new char[end.length()]; this.pushback = new PushbackReader(source, Math.max(start.length(), end.length())); this.resolver = resolver; } @Override public int read() throws IOException { if (resolved != null) { final int c = resolved.read(); if (-1 == c) { resolved.close(); resolved = null; } else { return c; } } // Read the proper number of chars into a temporary character array in order // to find token start marker. int countValidChars = readChars(startCharsBuffer); if (!Arrays.equals(startCharsBuffer, startChars)) { if (countValidChars > 0) { pushback.unread(startCharsBuffer, 0, countValidChars); } return pushback.read(); } // Found start of token, read proper number of characters into a temporary // character array in order to find token end marker. boolean endOfSource = false; token.delete(0, token.length()); countValidChars = readChars(endCharsBuffer); while (!Arrays.equals(endCharsBuffer, endChars)) { if (countValidChars == -1) { // End of source and no token end marker was found. endOfSource = true; break; } token.append(endCharsBuffer[0]); pushback.unread(endCharsBuffer, 0, countValidChars); if (pushback.read() == -1) { // End of source and no token end marker was found. endOfSource = true; break; } countValidChars = readChars(endCharsBuffer); } if (endOfSource) { resolved = new StringReader(start + token.toString()); } else { // Try to resolve the token. resolved = resolver.resolveToken(token.toString()); if (resolved == null) { // Token was not resolved. resolved = new StringReader(start + token.toString() + end); } } return resolved.read(); } private int readChars(char[] tmpChars) throws IOException { int countValidChars = -1; int length = tmpChars.length; int data = pushback.read(); for (int i = 0; i < length; i++) { if (data != -1) { tmpChars[i] = (char) data; countValidChars = i + 1; if (i + 1 < length) { data = pushback.read(); } } else { // Reset to java default value for char. tmpChars[i] = '\u0000'; } } return countValidChars; } @Override public int read(char[] buffer) throws IOException { return read(buffer, 0, buffer.length); } @Override public int read(char[] buffer, int offset, int length) throws IOException { int read = 0; for (int i = 0; i < length; i++) { int c = read(); if (c == -1) { read = i; if (read == 0) { read = -1; } break; } else { read = i + 1; } buffer[offset + i] = (char) c; } return read; } @Override public void close() throws IOException { IOException thrown = null; try { pushback.close(); } catch (IOException e) { thrown = e; } if (null != resolved) { try { resolved.close(); } catch (IOException e) { thrown = e; } } if (null != thrown) { throw thrown; } } @Override public boolean ready() throws IOException { return pushback.ready(); } @Override public boolean markSupported() { return pushback.markSupported(); } @Override public long skip(long n) throws IOException { return pushback.skip(n); } @Override public void mark(int readAheadLimit) throws IOException { pushback.mark(readAheadLimit); } @Override public void reset() throws IOException { pushback.reset(); } }