KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > jasper > compiler > JspReader


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18 package org.apache.jasper.compiler;
19
20 import java.io.CharArrayWriter JavaDoc;
21 import java.io.FileNotFoundException JavaDoc;
22 import java.io.IOException JavaDoc;
23 import java.io.InputStreamReader JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.Vector JavaDoc;
26 import java.util.jar.JarFile JavaDoc;
27 import java.net.URL JavaDoc;
28 import java.net.MalformedURLException JavaDoc;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.apache.jasper.JasperException;
33 import org.apache.jasper.JspCompilationContext;
34
35 /**
36  * JspReader is an input buffer for the JSP parser. It should allow
37  * unlimited lookahead and pushback. It also has a bunch of parsing
38  * utility methods for understanding htmlesque thingies.
39  *
40  * @author Anil K. Vijendran
41  * @author Anselm Baird-Smith
42  * @author Harish Prabandham
43  * @author Rajiv Mordani
44  * @author Mandar Raje
45  * @author Danno Ferrin
46  * @author Kin-man Chung
47  * @author Shawn Bayern
48  * @author Mark Roth
49  */

50
51 class JspReader {
52
53     /**
54      * Logger.
55      */

56     private Log log = LogFactory.getLog(JspReader.class);
57
58     /**
59      * The current spot in the file.
60      */

61     private Mark current;
62
63     /**
64      * What is this?
65      */

66     private String JavaDoc master;
67
68     /**
69      * The list of source files.
70      */

71     private List JavaDoc sourceFiles;
72
73     /**
74      * The current file ID (-1 indicates an error or no file).
75      */

76     private int currFileId;
77
78     /**
79      * Seems redundant.
80      */

81     private int size;
82
83     /**
84      * The compilation context.
85      */

86     private JspCompilationContext context;
87
88     /**
89      * The Jasper error dispatcher.
90      */

91     private ErrorDispatcher err;
92
93     /**
94      * Set to true when using the JspReader on a single file where we read up
95      * to the end and reset to the beginning many times.
96      * (as in ParserController.figureOutJspDocument()).
97      */

98     private boolean singleFile;
99
100     /**
101      * Constructor.
102      *
103      * @param ctxt The compilation context
104      * @param fname The file name
105      * @param encoding The file encoding
106      * @param jarFile ?
107      * @param err The error dispatcher
108      * @throws JasperException If a Jasper-internal error occurs
109      * @throws FileNotFoundException If the JSP file is not found (or is unreadable)
110      * @throws IOException If an IO-level error occurs, e.g. reading the file
111      */

112     public JspReader(JspCompilationContext ctxt,
113                      String JavaDoc fname,
114                      String JavaDoc encoding,
115                      JarFile JavaDoc jarFile,
116                      ErrorDispatcher err)
117             throws JasperException, FileNotFoundException JavaDoc, IOException JavaDoc {
118
119         this(ctxt, fname, encoding,
120              JspUtil.getReader(fname, encoding, jarFile, ctxt, err),
121              err);
122     }
123
124     /**
125      * Constructor: same as above constructor but with initialized reader
126      * to the file given.
127      */

128     public JspReader(JspCompilationContext ctxt,
129                      String JavaDoc fname,
130                      String JavaDoc encoding,
131                      InputStreamReader JavaDoc reader,
132                      ErrorDispatcher err)
133             throws JasperException, FileNotFoundException JavaDoc {
134
135         this.context = ctxt;
136         this.err = err;
137         sourceFiles = new Vector JavaDoc();
138         currFileId = 0;
139         size = 0;
140         singleFile = false;
141         pushFile(fname, encoding, reader);
142     }
143
144     /**
145      * @return JSP compilation context with which this JspReader is
146      * associated
147      */

148     JspCompilationContext getJspCompilationContext() {
149         return context;
150     }
151     
152     /**
153      * Returns the file at the given position in the list.
154      *
155      * @param fileid The file position in the list
156      * @return The file at that position, if found, null otherwise
157      */

158     String JavaDoc getFile(final int fileid) {
159         return (String JavaDoc) sourceFiles.get(fileid);
160     }
161        
162     /**
163      * Checks if the current file has more input.
164      *
165      * @return True if more reading is possible
166      * @throws JasperException if an error occurs
167      */

168     boolean hasMoreInput() throws JasperException {
169         if (current.cursor >= current.stream.length) {
170             if (singleFile) return false;
171             while (popFile()) {
172                 if (current.cursor < current.stream.length) return true;
173             }
174             return false;
175         }
176         return true;
177     }
178     
179     int nextChar() throws JasperException {
180         if (!hasMoreInput())
181             return -1;
182         
183         int ch = current.stream[current.cursor];
184
185         current.cursor++;
186         
187         if (ch == '\n') {
188             current.line++;
189             current.col = 0;
190         } else {
191             current.col++;
192         }
193         return ch;
194     }
195
196     /**
197      * Back up the current cursor by one char, assumes current.cursor > 0,
198      * and that the char to be pushed back is not '\n'.
199      */

200     void pushChar() {
201         current.cursor--;
202         current.col--;
203     }
204
205     String JavaDoc getText(Mark start, Mark stop) throws JasperException {
206         Mark oldstart = mark();
207         reset(start);
208         CharArrayWriter JavaDoc caw = new CharArrayWriter JavaDoc();
209         while (!stop.equals(mark()))
210             caw.write(nextChar());
211         caw.close();
212         reset(oldstart);
213         return caw.toString();
214     }
215
216     int peekChar() throws JasperException {
217         if (!hasMoreInput())
218             return -1;
219         return current.stream[current.cursor];
220     }
221
222     Mark mark() {
223         return new Mark(current);
224     }
225
226     void reset(Mark mark) {
227         current = new Mark(mark);
228     }
229
230     boolean matchesIgnoreCase(String JavaDoc string) throws JasperException {
231         Mark mark = mark();
232         int ch = 0;
233         int i = 0;
234         do {
235             ch = nextChar();
236             if (Character.toLowerCase((char) ch) != string.charAt(i++)) {
237                 reset(mark);
238                 return false;
239             }
240         } while (i < string.length());
241         reset(mark);
242         return true;
243     }
244
245     /**
246      * search the stream for a match to a string
247      * @param string The string to match
248      * @return <strong>true</strong> is one is found, the current position
249      * in stream is positioned after the search string, <strong>
250      * false</strong> otherwise, position in stream unchanged.
251      */

252     boolean matches(String JavaDoc string) throws JasperException {
253         Mark mark = mark();
254         int ch = 0;
255         int i = 0;
256         do {
257             ch = nextChar();
258             if (((char) ch) != string.charAt(i++)) {
259                 reset(mark);
260                 return false;
261             }
262         } while (i < string.length());
263         return true;
264     }
265
266     boolean matchesETag(String JavaDoc tagName) throws JasperException {
267         Mark mark = mark();
268
269         if (!matches("</" + tagName))
270             return false;
271         skipSpaces();
272         if (nextChar() == '>')
273             return true;
274
275         reset(mark);
276         return false;
277     }
278
279     boolean matchesETagWithoutLessThan(String JavaDoc tagName)
280         throws JasperException
281     {
282        Mark mark = mark();
283
284        if (!matches("/" + tagName))
285            return false;
286        skipSpaces();
287        if (nextChar() == '>')
288            return true;
289
290        reset(mark);
291        return false;
292     }
293
294
295     /**
296      * Looks ahead to see if there are optional spaces followed by
297      * the given String. If so, true is returned and those spaces and
298      * characters are skipped. If not, false is returned and the
299      * position is restored to where we were before.
300      */

301     boolean matchesOptionalSpacesFollowedBy( String JavaDoc s )
302         throws JasperException
303     {
304         Mark mark = mark();
305
306         skipSpaces();
307         boolean result = matches( s );
308         if( !result ) {
309             reset( mark );
310         }
311
312         return result;
313     }
314
315     int skipSpaces() throws JasperException {
316         int i = 0;
317         while (hasMoreInput() && isSpace()) {
318             i++;
319             nextChar();
320         }
321         return i;
322     }
323
324     /**
325      * Skip until the given string is matched in the stream.
326      * When returned, the context is positioned past the end of the match.
327      *
328      * @param s The String to match.
329      * @return A non-null <code>Mark</code> instance (positioned immediately
330      * before the search string) if found, <strong>null</strong>
331      * otherwise.
332      */

333     Mark skipUntil(String JavaDoc limit) throws JasperException {
334         Mark ret = null;
335         int limlen = limit.length();
336         int ch;
337
338     skip:
339         for (ret = mark(), ch = nextChar() ; ch != -1 ;
340                  ret = mark(), ch = nextChar()) {
341             if (ch == limit.charAt(0)) {
342                 Mark restart = mark();
343                 for (int i = 1 ; i < limlen ; i++) {
344                     if (peekChar() == limit.charAt(i))
345                         nextChar();
346                     else {
347                         reset(restart);
348                         continue skip;
349                     }
350                 }
351                 return ret;
352             }
353         }
354         return null;
355     }
356
357     /**
358      * Skip until the given string is matched in the stream, but ignoring
359      * chars initially escaped by a '\'.
360      * When returned, the context is positioned past the end of the match.
361      *
362      * @param s The String to match.
363      * @return A non-null <code>Mark</code> instance (positioned immediately
364      * before the search string) if found, <strong>null</strong>
365      * otherwise.
366      */

367     Mark skipUntilIgnoreEsc(String JavaDoc limit) throws JasperException {
368         Mark ret = null;
369         int limlen = limit.length();
370         int ch;
371         int prev = 'x'; // Doesn't matter
372

373     skip:
374         for (ret = mark(), ch = nextChar() ; ch != -1 ;
375                  ret = mark(), prev = ch, ch = nextChar()) {
376             if (ch == '\\' && prev == '\\') {
377                 ch = 0; // Double \ is not an escape char anymore
378
}
379             else if (ch == limit.charAt(0) && prev != '\\') {
380                 for (int i = 1 ; i < limlen ; i++) {
381                     if (peekChar() == limit.charAt(i))
382                         nextChar();
383                     else
384                         continue skip;
385                 }
386                 return ret;
387             }
388         }
389         return null;
390     }
391     
392     /**
393      * Skip until the given end tag is matched in the stream.
394      * When returned, the context is positioned past the end of the tag.
395      *
396      * @param tag The name of the tag whose ETag (</tag>) to match.
397      * @return A non-null <code>Mark</code> instance (positioned immediately
398      * before the ETag) if found, <strong>null</strong> otherwise.
399      */

400     Mark skipUntilETag(String JavaDoc tag) throws JasperException {
401         Mark ret = skipUntil("</" + tag);
402         if (ret != null) {
403             skipSpaces();
404             if (nextChar() != '>')
405                 ret = null;
406         }
407         return ret;
408     }
409
410     final boolean isSpace() throws JasperException {
411         // Note: If this logic changes, also update Node.TemplateText.rtrim()
412
return peekChar() <= ' ';
413     }
414
415     /**
416      * Parse a space delimited token.
417      * If quoted the token will consume all characters up to a matching quote,
418      * otherwise, it consumes up to the first delimiter character.
419      *
420      * @param quoted If <strong>true</strong> accept quoted strings.
421      */

422     String JavaDoc parseToken(boolean quoted) throws JasperException {
423         StringBuffer JavaDoc stringBuffer = new StringBuffer JavaDoc();
424         skipSpaces();
425         stringBuffer.setLength(0);
426         
427         if (!hasMoreInput()) {
428             return "";
429         }
430
431         int ch = peekChar();
432         
433         if (quoted) {
434             if (ch == '"' || ch == '\'') {
435
436                 char endQuote = ch == '"' ? '"' : '\'';
437                 // Consume the open quote:
438
ch = nextChar();
439                 for (ch = nextChar(); ch != -1 && ch != endQuote;
440                          ch = nextChar()) {
441                     if (ch == '\\')
442                         ch = nextChar();
443                     stringBuffer.append((char) ch);
444                 }
445                 // Check end of quote, skip closing quote:
446
if (ch == -1) {
447                     err.jspError(mark(), "jsp.error.quotes.unterminated");
448                 }
449             } else {
450                 err.jspError(mark(), "jsp.error.attr.quoted");
451             }
452         } else {
453             if (!isDelimiter()) {
454                 // Read value until delimiter is found:
455
do {
456                     ch = nextChar();
457                     // Take care of the quoting here.
458
if (ch == '\\') {
459                         if (peekChar() == '"' || peekChar() == '\'' ||
460                                peekChar() == '>' || peekChar() == '%')
461                             ch = nextChar();
462                     }
463                     stringBuffer.append((char) ch);
464                 } while (!isDelimiter());
465             }
466         }
467
468         return stringBuffer.toString();
469     }
470
471     void setSingleFile(boolean val) {
472         singleFile = val;
473     }
474
475
476     /**
477      * Gets the URL for the given path name.
478      *
479      * @param path Path name
480      *
481      * @return URL for the given path name.
482      *
483      * @exception MalformedURLException if the path name is not given in
484      * the correct form
485      */

486     URL JavaDoc getResource(String JavaDoc path) throws MalformedURLException JavaDoc {
487         return context.getResource(path);
488     }
489
490
491     /**
492      * Parse utils - Is current character a token delimiter ?
493      * Delimiters are currently defined to be =, &gt;, &lt;, ", and ' or any
494      * any space character as defined by <code>isSpace</code>.
495      *
496      * @return A boolean.
497      */

498     private boolean isDelimiter() throws JasperException {
499         if (! isSpace()) {
500             int ch = peekChar();
501             // Look for a single-char work delimiter:
502
if (ch == '=' || ch == '>' || ch == '"' || ch == '\''
503                     || ch == '/') {
504                 return true;
505             }
506             // Look for an end-of-comment or end-of-tag:
507
if (ch == '-') {
508                 Mark mark = mark();
509                 if (((ch = nextChar()) == '>')
510                         || ((ch == '-') && (nextChar() == '>'))) {
511                     reset(mark);
512                     return true;
513                 } else {
514                     reset(mark);
515                     return false;
516                 }
517             }
518             return false;
519         } else {
520             return true;
521         }
522     }
523
524     /**
525      * Register a new source file.
526      * This method is used to implement file inclusion. Each included file
527      * gets a unique identifier (which is the index in the array of source
528      * files).
529      *
530      * @return The index of the now registered file.
531      */

532     private int registerSourceFile(final String JavaDoc file) {
533         if (sourceFiles.contains(file)) {
534             return -1;
535         }
536
537         sourceFiles.add(file);
538         this.size++;
539
540         return sourceFiles.size() - 1;
541     }
542     
543
544     /**
545      * Unregister the source file.
546      * This method is used to implement file inclusion. Each included file
547      * gets a uniq identifier (which is the index in the array of source
548      * files).
549      *
550      * @return The index of the now registered file.
551      */

552     private int unregisterSourceFile(final String JavaDoc file) {
553         if (!sourceFiles.contains(file)) {
554             return -1;
555         }
556
557         sourceFiles.remove(file);
558         this.size--;
559         return sourceFiles.size() - 1;
560     }
561
562     /**
563      * Push a file (and its associated Stream) on the file stack. THe
564      * current position in the current file is remembered.
565      */

566     private void pushFile(String JavaDoc file, String JavaDoc encoding,
567                            InputStreamReader JavaDoc reader)
568                 throws JasperException, FileNotFoundException JavaDoc {
569
570         // Register the file
571
String JavaDoc longName = file;
572
573         int fileid = registerSourceFile(longName);
574
575         if (fileid == -1) {
576             // Bugzilla 37407: http://issues.apache.org/bugzilla/show_bug.cgi?id=37407
577
if(reader != null) {
578                 try {
579                     reader.close();
580                 } catch (Exception JavaDoc any) {
581                     if(log.isDebugEnabled()) {
582                         log.debug("Exception closing reader: ", any);
583                     }
584                 }
585             }
586
587             err.jspError("jsp.error.file.already.registered", file);
588         }
589
590         currFileId = fileid;
591
592         try {
593             CharArrayWriter JavaDoc caw = new CharArrayWriter JavaDoc();
594             char buf[] = new char[1024];
595             for (int i = 0 ; (i = reader.read(buf)) != -1 ;)
596                 caw.write(buf, 0, i);
597             caw.close();
598             if (current == null) {
599                 current = new Mark(this, caw.toCharArray(), fileid,
600                                    getFile(fileid), master, encoding);
601             } else {
602                 current.pushStream(caw.toCharArray(), fileid, getFile(fileid),
603                                    longName, encoding);
604             }
605         } catch (Throwable JavaDoc ex) {
606             log.error("Exception parsing file ", ex);
607             // Pop state being constructed:
608
popFile();
609             err.jspError("jsp.error.file.cannot.read", file);
610         } finally {
611             if (reader != null) {
612                 try {
613                     reader.close();
614                 } catch (Exception JavaDoc any) {
615                     if(log.isDebugEnabled()) {
616                         log.debug("Exception closing reader: ", any);
617                     }
618                 }
619             }
620         }
621     }
622
623     /**
624      * Pop a file from the file stack. The field "current" is retored
625      * to the value to point to the previous files, if any, and is set
626      * to null otherwise.
627      * @return true is there is a previous file on the stack.
628      * false otherwise.
629      */

630     private boolean popFile() throws JasperException {
631
632         // Is stack created ? (will happen if the Jsp file we're looking at is
633
// missing.
634
if (current == null || currFileId < 0) {
635             return false;
636         }
637
638         // Restore parser state:
639
String JavaDoc fName = getFile(currFileId);
640         currFileId = unregisterSourceFile(fName);
641         if (currFileId < -1) {
642             err.jspError("jsp.error.file.not.registered", fName);
643         }
644
645         Mark previous = current.popStream();
646         if (previous != null) {
647             master = current.baseDir;
648             current = previous;
649             return true;
650         }
651         // Note that although the current file is undefined here, "current"
652
// is not set to null just for convience, for it maybe used to
653
// set the current (undefined) position.
654
return false;
655     }
656 }
657
658
Popular Tags