ReaderSource.java
package com.ctc.wstx.io;
import java.io.CharConversionException;
import java.io.IOException;
import java.io.Reader;
import javax.xml.stream.XMLStreamException;
import com.ctc.wstx.api.ReaderConfig;
import com.ctc.wstx.cfg.XmlConsts;
import com.ctc.wstx.exc.WstxException;
/**
* Input source that reads input via a Reader.
*/
public class ReaderSource
extends BaseInputSource
{
final ReaderConfig mConfig;
/**
* Underlying Reader to read character data from
*/
protected Reader mReader;
/**
* If true, will close the underlying Reader when this source is closed;
* if false will leave it open.
*/
final boolean mDoRealClose;
int mInputProcessed = 0;
int mInputRow = 1;
int mInputRowStart = 0;
/**
* Whether characters read from {@link #mReader} need to be checked for
* XML 1.1 "restricted characters" (the C1 control range U+007F–U+009F,
* except U+0085 NEL) that are only legal when expressed via character
* references.
*<p>
* This is only enabled when (a) the document is XML 1.1 and (b) the
* underlying Reader is an application- (or JDK-) supplied one rather than
* one of Woodstox's own decoding Readers ({@link BaseReader}), which already
* perform this check while decoding bytes. Without this, the same logical
* document is accepted or rejected depending solely on whether it was handed
* to the parser as an {@code InputStream} or as a {@code Reader}.
*/
boolean mCheckXml11Chars = false;
public ReaderSource(ReaderConfig cfg, WstxInputSource parent, String fromEntity,
String pubId, SystemId sysId,
Reader r, boolean realClose)
{
super(parent, fromEntity, pubId, sysId);
mConfig = cfg;
mReader = r;
mDoRealClose = realClose;
int bufSize = cfg.getInputBufferLength();
mBuffer = cfg.allocFullCBuffer(bufSize);
}
/**
* Method called to change the default offsets this source has. Generally
* done when the underlying Reader had been partially read earlier (like
* reading the xml declaration before starting real parsing).
*/
public void setInputOffsets(int proc, int row, int rowStart)
{
mInputProcessed = proc;
mInputRow = row;
mInputRowStart = rowStart;
}
/**
* Method called to indicate the XML version this source is being read as.
* For XML 1.1 we need to verify the "restricted characters" of the C1
* control range unless the underlying Reader already does so while decoding
* bytes (i.e. is one of Woodstox's own {@link BaseReader}s).
*/
public void setXmlCompliancy(int xmlVersion)
{
mCheckXml11Chars = (xmlVersion == XmlConsts.XML_V_11)
&& !(mReader instanceof BaseReader);
}
/**
* Input location is easy to set, as we'll start from the beginning
* of a File.
*/
@Override
protected void doInitInputLocation(WstxInputData reader)
{
reader.mCurrInputProcessed = mInputProcessed;
reader.mCurrInputRow = mInputRow;
reader.mCurrInputRowStart = mInputRowStart;
}
/**
* This is a hard-coded assumption, for now this source is
* only created from external entities
*/
@Override
public boolean fromInternalEntity() {
return false;
}
@Override
public int readInto(WstxInputData reader)
throws IOException, XMLStreamException
{
// Shouldn't really try to read after closing, but it may be easier
// for caller not to have to keep track of closure...
if (mBuffer == null) {
return -1;
}
int count = mReader.read(mBuffer, 0, mBuffer.length);
if (count < 1) {
// Let's prevent caller from accidentally being able to access
// data, first.
mInputLast = 0;
reader.mInputPtr = 0;
reader.mInputEnd = 0;
if (count == 0) {
// Sanity check; should never happen with correctly written
// Readers:
throw new WstxException("Reader (of type "+mReader.getClass().getName()+") returned 0 characters, even when asked to read up to "+mBuffer.length, getLocation());
}
return -1;
}
if (mCheckXml11Chars) {
verifyXml11Chars(mBuffer, 0, count, reader.mCurrInputProcessed);
}
reader.mInputBuffer = mBuffer;
reader.mInputPtr = 0;
mInputLast = count;
reader.mInputEnd = count;
return count;
}
@Override
public boolean readMore(WstxInputData reader, int minAmount)
throws IOException, XMLStreamException
{
/* Shouldn't really try to read after closing, but it may be easier
* for caller not to have to keep track of closure...
*/
if (mBuffer == null) {
return false;
}
int ptr = reader.mInputPtr;
int currAmount = mInputLast - ptr;
// Let's first adjust caller's data appropriately:
/* Since we are essentially removing 'ptr' chars that we
* have used already, they count as past chars. Also, since
* offsets are reduced by 'ptr', need to adjust linefeed offset
* marker as well.
*/
reader.mCurrInputProcessed += ptr;
reader.mCurrInputRowStart -= ptr;
// Existing data to move?
if (currAmount > 0) {
System.arraycopy(mBuffer, ptr, mBuffer, 0, currAmount);
minAmount -= currAmount;
}
reader.mInputBuffer = mBuffer;
reader.mInputPtr = 0;
mInputLast = currAmount;
while (minAmount > 0) {
int amount = mBuffer.length - currAmount;
int actual = mReader.read(mBuffer, currAmount, amount);
if (actual < 1) {
if (actual == 0) { // sanity check:
throw new WstxException("Reader (of type "+mReader.getClass().getName()+") returned 0 characters, even when asked to read up to "+amount, getLocation());
}
reader.mInputEnd = mInputLast = currAmount;
return false;
}
if (mCheckXml11Chars) {
verifyXml11Chars(mBuffer, currAmount, currAmount + actual, reader.mCurrInputProcessed);
}
currAmount += actual;
minAmount -= actual;
}
reader.mInputEnd = mInputLast = currAmount;
return true;
}
/**
* Verifies that the given character range does not contain XML 1.1
* "restricted characters" from the C1 control range (U+007F–U+009F,
* excluding U+0085 NEL) that are only legal when written as character
* references. The C0 control range (below U+0020) is left to the tokenizer,
* which already rejects those regardless of input source.
*<p>
* Mirrors the check performed by {@link UTF8Reader} (and the other
* {@link BaseReader} subclasses) for {@code InputStream}-based input, so
* that {@code Reader}-based input is validated identically.
*
* @param baseProcessed Number of characters processed before
* {@code buf[0]} (i.e. {@code reader.mCurrInputProcessed}); used so the
* reported character position is the absolute document offset rather than
* a buffer-local one.
*/
private void verifyXml11Chars(char[] buf, int start, int end, long baseProcessed)
throws IOException
{
for (int i = start; i < end; ++i) {
char c = buf[i];
if (c >= 0x7F && c <= 0x9F && c != 0x85) {
reportInvalidXml11(c, baseProcessed + i);
}
}
}
private void reportInvalidXml11(int value, long charPos)
throws IOException
{
// Matches BaseReader.reportInvalidXml11 so behavior is the same whether
// the document was supplied as an InputStream or as a Reader.
throw new CharConversionException("Invalid character 0x"
+Integer.toHexString(value)
+", can only be included in xml 1.1 using character entities (at char #"+charPos+")");
}
@Override
public void close() throws IOException
{
/* Buffer gets nullified by call to close() or closeCompletely(),
* no need to call second time
*/
if (mBuffer != null) { // so that it's ok to call multiple times
closeAndRecycle(mDoRealClose);
}
}
@Override
public void closeCompletely() throws IOException
{
/* Only need to call if the Reader is not yet null... since
* buffer may have been cleaned by a call to close()
*/
if (mReader != null) { // so that it's ok to call multiple times
closeAndRecycle(true);
}
}
private void closeAndRecycle(boolean fullClose)
throws IOException
{
char[] buf = mBuffer;
// Can we recycle buffers?
if (buf != null) {
mBuffer = null;
mConfig.freeFullCBuffer(buf);
}
// How about Reader; close and/or recycle its buffers?
if (mReader != null) {
if (mReader instanceof BaseReader) {
((BaseReader) mReader).freeBuffers();
}
if (fullClose) {
Reader r = mReader;
mReader = null;
r.close();
}
}
}
}