KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > enhydra > xml > xmlc > misc > SSIParsedStream


1 /*
2  * Enhydra Java Application Server Project
3  *
4  * The contents of this file are subject to the Enhydra Public License
5  * Version 1.1 (the "License"); you may not use this file except in
6  * compliance with the License. You may obtain a copy of the License on
7  * the Enhydra web site ( http://www.enhydra.org/ ).
8  *
9  * Software distributed under the License is distributed on an "AS IS"
10  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
11  * the License for the specific terms governing rights and limitations
12  * under the License.
13  *
14  * The Initial Developer of the Enhydra Application Server is Lutris
15  * Technologies, Inc. The Enhydra Application Server and portions created
16  * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
17  * All Rights Reserved.
18  *
19  * Contributor(s):
20  *
21  * $Id: SSIParsedStream.java,v 1.2 2005/01/26 08:29:24 jkjome Exp $
22  */

23
24 package org.enhydra.xml.xmlc.misc;
25
26 import java.io.IOException JavaDoc;
27 import java.io.Reader JavaDoc;
28
29 import org.enhydra.xml.io.InputSourceOps;
30 import org.xml.sax.InputSource JavaDoc;
31
32 /**
33  * Implements the reading and parsing of an SSI stream. When a SSI directive
34  * is encountered, a new instance of this class is created and pushed on a
35  * stack.
36  * <p>
37  * To simplified the implementation, the entire file is read into a buffer.
38  * A previous version attempted to buffer less, but this ended up being a bit
39  * tricky due to the need to scan while reading and still maintain the block
40  * read() method.
41  */

42 final class SSIParsedStream {
43     /**
44      * Character returned to indicate EOF.
45      */

46     public static final int AT_EOF = -1;
47
48     /**
49      * Character returned to indicate the beginning of an
50      * SSI directive.
51      */

52     public static final int AT_SSI = -2;
53
54     /**
55      * SSI directive prefix definitions.
56      */

57     private static final String JavaDoc SSI_PREFIX_STR = "<!--#";
58     private static final int SSI_PREFIX_LEN = SSI_PREFIX_STR.length();
59     private static final char[] SSI_PREFIX;
60
61     /**
62      * SSI directive suffix definitions.
63      */

64     private static final String JavaDoc SSI_SUFFIX_STR = "-->";
65     private static final int SSI_SUFFIX_LEN = SSI_SUFFIX_STR.length();
66     private static final char[] SSI_SUFFIX;
67
68     /** Initial buffer size to use. May grow as needed. */
69     private static final int INIT_BUFFER_SIZE = 8*1024;
70
71     /** Maximum nesting depth of SSIs. */
72     private static final int MAX_NESTING_DEPTH = 64;
73
74     /** Initial StringBuffer size for a SSI directive argument */
75     private static final int SSI_INIT_ARG_STRING_SIZE = 64;
76
77     /** Input source that was used. */
78     private InputSource JavaDoc fSource;
79
80     /**
81      * Next stream in the stack. This was the one that included this
82      * file.
83      */

84     private SSIParsedStream fPrevStream;
85
86     /**
87      * Nesting depth.
88      */

89     private int fDepth;
90
91     /**
92      * Buffer for input, contains the entire file.
93      */

94     private char[] fBuffer;
95
96     /**
97      * Index of the next character in the buffer.
98      */

99     private int fNextCharIdx;
100
101     /**
102      * Index of the last valid character in the buffer.
103      */

104     private int fLastCharIdx;
105
106     /**
107      * Index of the start of the next SSI directive in the buffer. Set to
108      * fLastCharIdx+1 if no SSI directive is contained.
109      */

110     private int fSSIStartIdx;
111
112     /**
113      * Map of stream position to source file and line number. Shared by all
114      * SSIParsedStream.
115      */

116     private LineNumberRecorder fLineNumbers;
117
118     /**
119      * Class initializer
120      */

121     static {
122         SSI_PREFIX = new char[SSI_PREFIX_LEN];
123         SSI_PREFIX_STR.getChars(0, SSI_PREFIX_LEN, SSI_PREFIX, 0);
124         SSI_SUFFIX = new char[SSI_SUFFIX_LEN];
125         SSI_SUFFIX_STR.getChars(0, SSI_SUFFIX_LEN, SSI_SUFFIX, 0);
126     }
127
128     /**
129      * Constructor. Open the named file.
130      */

131     public SSIParsedStream(InputSource JavaDoc source,
132                            LineNumberRecorder lineNumbers) throws IOException JavaDoc {
133         this(source, lineNumbers, null);
134     }
135
136     /**
137      * Constructor. Open the named file.
138      */

139     public SSIParsedStream(InputSource JavaDoc source,
140                            LineNumberRecorder lineNumbers,
141                            SSIParsedStream prevStream) throws IOException JavaDoc {
142         if (source.getSystemId() == null) {
143             throw new IOException JavaDoc("SSI InputSource must have a system id");
144         }
145         fSource = source;
146         fLineNumbers = lineNumbers;
147         fLineNumbers.pushFile(getSystemId());
148
149         if (prevStream != null) {
150             fSource.setEncoding(prevStream.fSource.getEncoding());
151
152             fPrevStream = prevStream;
153             fDepth = prevStream.fDepth + 1;
154             if (fDepth > MAX_NESTING_DEPTH) {
155                 throw new IOException JavaDoc("SSI max nesting depth exceeded at: "
156                                       + getSystemId());
157             }
158         }
159         readIntoBuffer(fSource);
160     }
161
162
163     /**
164      * Expand the buffer for the next read.
165      */

166     private void expandBuffer() {
167         char[] newBuffer = new char[fBuffer.length * 2];
168         System.arraycopy(fBuffer, 0, newBuffer, 0, fBuffer.length);
169         fBuffer = newBuffer;
170     }
171
172     /**
173      * Read the file into the buffer.
174      */

175     private void readIntoBuffer(Reader JavaDoc in) throws IOException JavaDoc {
176         int charsRead;
177         int readIdx = 0;
178         fBuffer = new char[INIT_BUFFER_SIZE];
179         while (true) {
180             if (readIdx >= fBuffer.length) {
181                 expandBuffer();
182             }
183             charsRead = in.read(fBuffer, readIdx, fBuffer.length - readIdx);
184             if (charsRead < 0) {
185                 break;
186             }
187             readIdx += charsRead;
188         }
189         fNextCharIdx = 0;
190         fLastCharIdx = readIdx - 1; // Need by scanForSSIStart below
191
fSSIStartIdx = scanForSSIStart(fNextCharIdx);
192     }
193
194     /**
195      * Open the file and read it into the buffer.
196      */

197     private void readIntoBuffer(InputSource JavaDoc source) throws IOException JavaDoc {
198         Reader JavaDoc in = InputSourceOps.open(source);
199         try {
200             readIntoBuffer(in);
201         } finally {
202             InputSourceOps.closeIfOpened(source, in);
203         }
204     }
205
206
207     /**
208      * Get the system id of the associated file.
209      */

210     public String JavaDoc getSystemId() {
211         return fSource.getSystemId();
212     }
213
214     /**
215      * Close this input stream, returning the previous stream.
216      */

217     public SSIParsedStream pop() throws IOException JavaDoc {
218         fLineNumbers.popFile();
219         return fPrevStream;
220     }
221
222     /**
223      * Check for SSI prefix at specified offset in buffer, reading more
224      * into the buffer if necessary to hold an entire prefix.
225      */

226     private boolean isSSIPrefix(int idx) throws IOException JavaDoc {
227         for (int prefixIdx = 0; prefixIdx < SSI_PREFIX_LEN; prefixIdx++, idx++) {
228             if (fBuffer[idx] != SSI_PREFIX[prefixIdx]) {
229                 return false;
230             }
231         }
232         return true;
233     }
234
235     /**
236      * Check for the SSI suffix (comment close) at specified offset in buffer.
237      */

238     private boolean isSSISuffix(int idx) throws IOException JavaDoc {
239         for (int suffixIdx = 0; suffixIdx < SSI_SUFFIX_LEN; suffixIdx++, idx++) {
240             if (fBuffer[idx] != SSI_SUFFIX[suffixIdx]) {
241                 return false;
242             }
243         }
244         return true;
245     }
246
247     /**
248      * Scan for the start of an SSI directive in the buffer. This checks for
249      * the `<!--#' syntax.
250      */

251     private int scanForSSIStart(int startIdx) throws IOException JavaDoc {
252         for (int idx = startIdx; idx <= fLastCharIdx; idx++) {
253             if ((fBuffer[idx] == '<') && isSSIPrefix(idx)) {
254                 return idx;
255             }
256         }
257         return fLastCharIdx + 1; // Not found
258
}
259
260     /**
261      * Read a character.
262      * @return The character, AT_EOF if no more characters are available,
263      * or AT_SSI if the next character in the buffer is an SSI directive.
264      */

265     public int read() throws IOException JavaDoc {
266         if (fNextCharIdx > fLastCharIdx) {
267             return AT_EOF;
268         }
269
270         // If the next character in the buffer is the start of a SSI
271
// directive, switch to a reading from that include.
272
if (fNextCharIdx == fSSIStartIdx) {
273             return AT_SSI;
274         }
275         char ch = fBuffer[fNextCharIdx++];
276         fLineNumbers.countChar(ch);
277         return ch;
278     }
279
280     /**
281      * Read characters into a portion of an array.
282      * @return The number of characters read, AT_EOF if no more characters
283      * are available, or AT_SSI if the next character in the buffer is an
284      * SSI directive.
285      */

286     public int read(char cbuf[], int off, int len) throws IOException JavaDoc {
287         if ((off < 0) || (off > cbuf.length) || (len < 0)
288             || ((off + len) > cbuf.length) || ((off + len) < 0)) {
289             throw new IndexOutOfBoundsException JavaDoc();
290         } else if (len == 0) {
291             return 0;
292         }
293
294         // Check for the end of the buffer.
295
if (fNextCharIdx > fLastCharIdx) {
296             return AT_EOF;
297         }
298
299         // If the next character in the buffer is the start of a SSI
300
// directive, switch to a reading from that include.
301
if (fNextCharIdx == fSSIStartIdx) {
302             return AT_SSI;
303         }
304
305         // Take data out of the buffer upto the start of a SSI directive.
306
int copyLen;
307         if (fSSIStartIdx <= fLastCharIdx) {
308             // Copy upto, but not including SSI start
309
copyLen = fSSIStartIdx - fNextCharIdx;
310         } else {
311             // Copy upto and including last char
312
copyLen = fLastCharIdx - fNextCharIdx + 1;
313         }
314         if (copyLen > len) {
315             // Adjust for requested amount.
316
copyLen = len;
317         }
318         System.arraycopy(fBuffer, fNextCharIdx, cbuf, off, copyLen);
319         fNextCharIdx += copyLen;
320         fLineNumbers.countChars(cbuf, off, copyLen);
321         return copyLen;
322     }
323
324     /**
325      * Advance the next character index while parsing an SSI directive,
326      * checking for hitting eof.
327      */

328     private void ssiAdvanceChar() throws IOException JavaDoc {
329         fNextCharIdx++;
330         if (fNextCharIdx > fLastCharIdx) {
331             throw new IOException JavaDoc("Unexpected EOF in SSI directive: "
332                                   + getSystemId());
333         }
334         fLineNumbers.countChar(fBuffer[fNextCharIdx]);
335     }
336
337     /**
338      * Skip zero or more whitespace characters, stopping at the next
339      * non-whitespace character. If current character is not a whitespace,
340      * do nothing.
341      */

342     private void ssiSkipWhiteSpace() throws IOException JavaDoc {
343         while (Character.isWhitespace(fBuffer[fNextCharIdx])) {
344             ssiAdvanceChar();
345         }
346     }
347
348     /**
349      * Skip zero or more non-whitespace characters, stopping at the next
350      * whitespace character. If current character is a whitespace, do
351      * nothing.
352      */

353     private void ssiNonSkipWhiteSpace() throws IOException JavaDoc {
354         while (!Character.isWhitespace(fBuffer[fNextCharIdx])) {
355             ssiAdvanceChar();
356         }
357     }
358
359     /**
360      * Parse the command name from the buffer.
361      * @return the command
362      */

363     private String JavaDoc ssiParseCmd() throws IOException JavaDoc {
364         int startIdx = fNextCharIdx;
365         ssiNonSkipWhiteSpace();
366         return new String JavaDoc(fBuffer, startIdx, (fNextCharIdx-startIdx));
367     }
368
369     /**
370      * Parse an argument name.
371      * @return the name
372      */

373     private String JavaDoc ssiParseArgName() throws IOException JavaDoc {
374         int startIdx = fNextCharIdx;
375         while (Character.isLetterOrDigit(fBuffer[fNextCharIdx])) {
376             ssiAdvanceChar();
377         }
378         if (fBuffer[fNextCharIdx] != '=') {
379             throw new IOException JavaDoc("SSI argument name not terminated with a `=': "
380                                   + getSystemId());
381         }
382         ssiAdvanceChar(); // Skip `='
383
return new String JavaDoc(fBuffer, startIdx, (fNextCharIdx-startIdx)-1);
384     }
385
386     /**
387      * Parse an argument value, handling quoting
388      * @return the value.
389      */

390     private String JavaDoc ssiParseArgValue() throws IOException JavaDoc {
391         StringBuffer JavaDoc buf = new StringBuffer JavaDoc(SSI_INIT_ARG_STRING_SIZE);
392
393         // Check for opening quote
394
if (fBuffer[fNextCharIdx] != '"') {
395             throw new IOException JavaDoc("Missing open quote in SSI argument value: "
396                                   + getSystemId());
397         }
398         ssiAdvanceChar(); // Skip `"'
399

400         // Scan for terminator, handling backslash escapes.
401
while (fBuffer[fNextCharIdx] != '"') {
402             if (fBuffer[fNextCharIdx] == '\\') {
403                 ssiAdvanceChar(); // Treat next char literally
404
}
405             buf.append(fBuffer[fNextCharIdx]);
406             ssiAdvanceChar();
407         }
408         ssiAdvanceChar(); // Skip `"'
409
return buf.toString();
410     }
411
412     /**
413      * Parse an argument name and value.
414      * @return the command
415      */

416     private void ssiParseArg(SSIDirective directive) throws IOException JavaDoc {
417         directive.addArg(ssiParseArgName(), ssiParseArgValue());
418     }
419
420     /**
421      * Parse the SSI directive that starts at the next available
422      * character in the buffer.
423      */

424     public SSIDirective parseSSIDirective() throws IOException JavaDoc {
425         // Advance past `<!--#', which must be in the buffer
426
fNextCharIdx += SSI_PREFIX_LEN;
427
428         // Parse the command name and save in result object
429
SSIDirective directive = new SSIDirective(ssiParseCmd(), getSystemId());
430         ssiSkipWhiteSpace();
431
432         // Parse arguments
433
while (!isSSISuffix(fNextCharIdx)) {
434             ssiParseArg(directive);
435             ssiSkipWhiteSpace();
436         }
437         fNextCharIdx += SSI_SUFFIX_LEN;
438         fSSIStartIdx = scanForSSIStart(fNextCharIdx);
439         return directive;
440     }
441 }
442
Popular Tags