/* * Copyright (C) 2000 - 2008 TagServlet Ltd * * This file is part of Open BlueDragon (OpenBD) CFML Server Engine. * * OpenBD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * Free Software Foundation,version 3. * * OpenBD is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenBD. If not, see http://www.gnu.org/licenses/ * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with any of the JARS listed in the README.txt (or a modified version of * (that library), containing parts covered by the terms of that JAR, the * licensors of this Program grant you additional permission to convey the * resulting work. * README.txt @ http://www.openbluedragon.org/license/README.txt * * http://www.openbluedragon.org/ */ package com.naryx.tagfusion.cfm.xml.parse; import java.io.FilterReader; import java.io.IOException; import java.io.Reader; import java.nio.CharBuffer; /** * FilterReader that filters xml character data. Subclasses can simply implement * readUnderlying to take advantage of normal xml filtering. This class provides * the framework to handle filtering and to disable filtering at any point. * * @author Matt Jacobsen * */ public abstract class XmlFilterReader extends FilterReader { protected StringBuilder localBuffer = null; protected boolean moreToRead = true; /** * Default constructor. Takes the underlying Reader. * * @param reader to filter */ public XmlFilterReader(Reader reader) { super(reader); this.localBuffer = new StringBuilder(); this.moreToRead = true; } /** * Returns true if filtering should still continue, false otherwise. * Subclasses need to return true until they decided that filtering * is no longer needed. * * @return true if filtering should still continue, false otherwise. */ protected abstract boolean stillFiltering(); /** * Inheritors must implement this method. It reads from the underlying * Reader instance and fills the localBuffer. Note, implementations should * not call any public methods in this class or infinite recursion will * result. Returns true if reading from the underlying Reader is not * limited. Returns false if the end of the data stream is reached during * this read. * * @param minCount minimum number of characters that should be read for this call * @return true if more data can be read, false otherwise * @throws IOException */ protected abstract boolean readUnderlying(int minCount) throws IOException; /** * Reads len chars from the underlying Reader into the specified * array using the specified offset. Returns the number of chars * read or -1 if the end of the stream has been reached. * * @param cbuf array to fill * @param off offset to begin filling the array at * @param len maximum number of chars to read. * @return number of characters read or -1 if the end has been reached */ public int read(char[] cbuf, int off, int len) throws IOException { if (off < 0 || off > cbuf.length || len < 0 || len > cbuf.length) throw new IOException("Offset and/or length are out of range."); if (off + len > cbuf.length) throw new IOException("Offset + length greater than the buffer size."); if (stillFiltering()) { if (localBuffer.length() >= len || !moreToRead) { int read = (len > localBuffer.length() ? localBuffer.length() : len); localBuffer.getChars(0, read, cbuf, off); localBuffer.delete(0, read); if (read == 0) read = -1; return read; } else { moreToRead = readUnderlying(len - localBuffer.length()); return this.read(cbuf, off, len); } } else { int rtnCount = 0; int leftInBuf = localBuffer.length(); if (leftInBuf > 0) { // Read some from the buffer leftInBuf = (leftInBuf > len) ? len : leftInBuf; if (leftInBuf > 0) { localBuffer.getChars(0, leftInBuf, cbuf, off); localBuffer.delete(0, leftInBuf); } len -= leftInBuf; off += leftInBuf; rtnCount += leftInBuf; } // Read the remaining amount directly if (len > 0) { int r = in.read(cbuf, off, len); if (r == -1 && rtnCount == 0) rtnCount = r; else if (r != -1) rtnCount += r; } return rtnCount; } } /** * Reads an individual char from the underlying Reader and returns * the char read or -1 to indicate the end of the stream. * * @return char read, or -1 */ public int read() throws IOException { if (localBuffer.length() > 0) { int rtn = (int)localBuffer.charAt(0); localBuffer.deleteCharAt(0); return rtn; } if (stillFiltering() && moreToRead) { moreToRead = readUnderlying(1); return this.read(); } else { return in.read(); } } /** * Reads chars from the underlying Reader into the specified array. * Returns the number of chars read or -1 if the end of the stream * has been reached. * * @param cbuf array to fill * @return number of chars read, or -1 */ public int read(char[] cbuf) throws IOException { return this.read(cbuf, 0, cbuf.length); } /** * Attempts to read characters into the specified character buffer. * The buffer is used as a repository of characters as-is: the only * changes made are the results of a put operation. No flipping or * rewinding of the buffer is performed * * @param target buffer to read characters into * @return number of characters added to the buffer, or -1 if this * source of characters is at its end */ public int read(CharBuffer target) throws IOException { char[] tmp = new char[512]; int read = read(tmp); target.put(tmp, 0, read); return read; } /** * Tell whether this stream supports the mark() operation. Always * returns false. * * @return false, always. */ public boolean markSupported() { return false; } /** * Mark the present position in the stream. This call is ignored. * * @param readAheadLimit limit on the number of characters that may be * read while still preserving the mark. After reading this * many characters, attempting to reset the stream may fail. */ public void mark(int readAheadLimit) throws IOException { // Ignore } /** * Skip characters. * * @param n number of characters to skip * @return number of characters actually skipped. * @throws IOException */ public long skip(long n) throws IOException { if (stillFiltering()) { if (localBuffer.length() >= n || !moreToRead) { long skip = (n > localBuffer.length() ? localBuffer.length() : n); localBuffer.delete(0, (int)skip); return skip; } else { moreToRead = readUnderlying((int)(n - localBuffer.length())); return this.skip(n); } } else { long rtnCount = 0; long leftInBuf = localBuffer.length(); if (leftInBuf > 0) { // Skip some out of the buffer leftInBuf = (leftInBuf > n) ? n : leftInBuf; if (leftInBuf > 0) localBuffer.delete(0, (int)leftInBuf); n -= leftInBuf; rtnCount += leftInBuf; } // Skip the remaining amount directly if (n > 0) rtnCount += in.skip(n); return rtnCount; } } /** * Reset the stream. * * @throws IOException */ public void reset() throws IOException { localBuffer.setLength(0); super.reset(); } /** * Close the stream. * * @throws IOException */ public void close() throws IOException { localBuffer.setLength(0); super.close(); } }