
org.enhydra.xml.xmlc.misc.SSIParsedStream Maven / Gradle / Ivy
/*
* Enhydra Java Application Server Project
*
* The contents of this file are subject to the Enhydra Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License on
* the Enhydra web site ( http://www.enhydra.org/ ).
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific terms governing rights and limitations
* under the License.
*
* The Initial Developer of the Enhydra Application Server is Lutris
* Technologies, Inc. The Enhydra Application Server and portions created
* by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
* All Rights Reserved.
*
* Contributor(s):
*
* $Id: SSIParsedStream.java,v 1.2 2005/01/26 08:29:24 jkjome Exp $
*/
package org.enhydra.xml.xmlc.misc;
import java.io.IOException;
import java.io.Reader;
import org.enhydra.xml.io.InputSourceOps;
import org.xml.sax.InputSource;
/**
* Implements the reading and parsing of an SSI stream. When a SSI directive
* is encountered, a new instance of this class is created and pushed on a
* stack.
*
* To simplified the implementation, the entire file is read into a buffer.
* A previous version attempted to buffer less, but this ended up being a bit
* tricky due to the need to scan while reading and still maintain the block
* read() method.
*/
final class SSIParsedStream {
/**
* Character returned to indicate EOF.
*/
public static final int AT_EOF = -1;
/**
* Character returned to indicate the beginning of an
* SSI directive.
*/
public static final int AT_SSI = -2;
/**
* SSI directive prefix definitions.
*/
private static final String SSI_PREFIX_STR = "";
private static final int SSI_SUFFIX_LEN = SSI_SUFFIX_STR.length();
private static final char[] SSI_SUFFIX;
/** Initial buffer size to use. May grow as needed. */
private static final int INIT_BUFFER_SIZE = 8*1024;
/** Maximum nesting depth of SSIs. */
private static final int MAX_NESTING_DEPTH = 64;
/** Initial StringBuffer size for a SSI directive argument */
private static final int SSI_INIT_ARG_STRING_SIZE = 64;
/** Input source that was used. */
private InputSource fSource;
/**
* Next stream in the stack. This was the one that included this
* file.
*/
private SSIParsedStream fPrevStream;
/**
* Nesting depth.
*/
private int fDepth;
/**
* Buffer for input, contains the entire file.
*/
private char[] fBuffer;
/**
* Index of the next character in the buffer.
*/
private int fNextCharIdx;
/**
* Index of the last valid character in the buffer.
*/
private int fLastCharIdx;
/**
* Index of the start of the next SSI directive in the buffer. Set to
* fLastCharIdx+1 if no SSI directive is contained.
*/
private int fSSIStartIdx;
/**
* Map of stream position to source file and line number. Shared by all
* SSIParsedStream.
*/
private LineNumberRecorder fLineNumbers;
/**
* Class initializer
*/
static {
SSI_PREFIX = new char[SSI_PREFIX_LEN];
SSI_PREFIX_STR.getChars(0, SSI_PREFIX_LEN, SSI_PREFIX, 0);
SSI_SUFFIX = new char[SSI_SUFFIX_LEN];
SSI_SUFFIX_STR.getChars(0, SSI_SUFFIX_LEN, SSI_SUFFIX, 0);
}
/**
* Constructor. Open the named file.
*/
public SSIParsedStream(InputSource source,
LineNumberRecorder lineNumbers) throws IOException {
this(source, lineNumbers, null);
}
/**
* Constructor. Open the named file.
*/
public SSIParsedStream(InputSource source,
LineNumberRecorder lineNumbers,
SSIParsedStream prevStream) throws IOException {
if (source.getSystemId() == null) {
throw new IOException("SSI InputSource must have a system id");
}
fSource = source;
fLineNumbers = lineNumbers;
fLineNumbers.pushFile(getSystemId());
if (prevStream != null) {
fSource.setEncoding(prevStream.fSource.getEncoding());
fPrevStream = prevStream;
fDepth = prevStream.fDepth + 1;
if (fDepth > MAX_NESTING_DEPTH) {
throw new IOException("SSI max nesting depth exceeded at: "
+ getSystemId());
}
}
readIntoBuffer(fSource);
}
/**
* Expand the buffer for the next read.
*/
private void expandBuffer() {
char[] newBuffer = new char[fBuffer.length * 2];
System.arraycopy(fBuffer, 0, newBuffer, 0, fBuffer.length);
fBuffer = newBuffer;
}
/**
* Read the file into the buffer.
*/
private void readIntoBuffer(Reader in) throws IOException {
int charsRead;
int readIdx = 0;
fBuffer = new char[INIT_BUFFER_SIZE];
while (true) {
if (readIdx >= fBuffer.length) {
expandBuffer();
}
charsRead = in.read(fBuffer, readIdx, fBuffer.length - readIdx);
if (charsRead < 0) {
break;
}
readIdx += charsRead;
}
fNextCharIdx = 0;
fLastCharIdx = readIdx - 1; // Need by scanForSSIStart below
fSSIStartIdx = scanForSSIStart(fNextCharIdx);
}
/**
* Open the file and read it into the buffer.
*/
private void readIntoBuffer(InputSource source) throws IOException {
Reader in = InputSourceOps.open(source);
try {
readIntoBuffer(in);
} finally {
InputSourceOps.closeIfOpened(source, in);
}
}
/**
* Get the system id of the associated file.
*/
public String getSystemId() {
return fSource.getSystemId();
}
/**
* Close this input stream, returning the previous stream.
*/
public SSIParsedStream pop() throws IOException {
fLineNumbers.popFile();
return fPrevStream;
}
/**
* Check for SSI prefix at specified offset in buffer, reading more
* into the buffer if necessary to hold an entire prefix.
*/
private boolean isSSIPrefix(int idx) throws IOException {
for (int prefixIdx = 0; prefixIdx < SSI_PREFIX_LEN; prefixIdx++, idx++) {
if (fBuffer[idx] != SSI_PREFIX[prefixIdx]) {
return false;
}
}
return true;
}
/**
* Check for the SSI suffix (comment close) at specified offset in buffer.
*/
private boolean isSSISuffix(int idx) throws IOException {
for (int suffixIdx = 0; suffixIdx < SSI_SUFFIX_LEN; suffixIdx++, idx++) {
if (fBuffer[idx] != SSI_SUFFIX[suffixIdx]) {
return false;
}
}
return true;
}
/**
* Scan for the start of an SSI directive in the buffer. This checks for
* the `