KickJava   Java API By Example, From Geeks To Geeks.

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


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.FileNotFoundException JavaDoc;
21 import java.io.IOException JavaDoc;
22 import java.io.InputStreamReader JavaDoc;
23 import java.net.JarURLConnection JavaDoc;
24 import java.net.URL JavaDoc;
25 import java.util.Stack JavaDoc;
26 import java.util.jar.JarFile JavaDoc;
27
28 import org.apache.jasper.JasperException;
29 import org.apache.jasper.JspCompilationContext;
30 import org.apache.jasper.xmlparser.XMLEncodingDetector;
31 import org.xml.sax.Attributes JavaDoc;
32
33 /**
34  * Controller for the parsing of a JSP page.
35  * <p>
36  * The same ParserController instance is used for a JSP page and any JSP
37  * segments included by it (via an include directive), where each segment may
38  * be provided in standard or XML syntax. This class selects and invokes the
39  * appropriate parser for the JSP page and its included segments.
40  *
41  * @author Pierre Delisle
42  * @author Jan Luehe
43  */

44 class ParserController implements TagConstants {
45
46     private static final String JavaDoc CHARSET = "charset=";
47
48     private JspCompilationContext ctxt;
49     private Compiler JavaDoc compiler;
50     private ErrorDispatcher err;
51
52     /*
53      * Indicates the syntax (XML or standard) of the file being processed
54      */

55     private boolean isXml;
56
57     /*
58      * A stack to keep track of the 'current base directory'
59      * for include directives that refer to relative paths.
60      */

61     private Stack JavaDoc baseDirStack = new Stack JavaDoc();
62
63     private boolean isEncodingSpecifiedInProlog;
64     private boolean isBomPresent;
65
66     private String JavaDoc sourceEnc;
67
68     private boolean isDefaultPageEncoding;
69     private boolean isTagFile;
70     private boolean directiveOnly;
71
72     /*
73      * Constructor
74      */

75     public ParserController(JspCompilationContext ctxt, Compiler JavaDoc compiler) {
76         this.ctxt = ctxt;
77         this.compiler = compiler;
78         this.err = compiler.getErrorDispatcher();
79     }
80
81     public JspCompilationContext getJspCompilationContext () {
82         return ctxt;
83     }
84
85     public Compiler JavaDoc getCompiler () {
86         return compiler;
87     }
88
89     /**
90      * Parses a JSP page or tag file. This is invoked by the compiler.
91      *
92      * @param inFileName The path to the JSP page or tag file to be parsed.
93      */

94     public Node.Nodes parse(String JavaDoc inFileName)
95     throws FileNotFoundException JavaDoc, JasperException, IOException JavaDoc {
96         // If we're parsing a packaged tag file or a resource included by it
97
// (using an include directive), ctxt.getTagFileJar() returns the
98
// JAR file from which to read the tag file or included resource,
99
// respectively.
100
isTagFile = ctxt.isTagFile();
101         directiveOnly = false;
102         return doParse(inFileName, null, ctxt.getTagFileJarUrl());
103     }
104
105     /**
106      * Processes an include directive with the given path.
107      *
108      * @param inFileName The path to the resource to be included.
109      * @param parent The parent node of the include directive.
110      * @param jarFile The JAR file from which to read the included resource,
111      * or null of the included resource is to be read from the filesystem
112      */

113     public Node.Nodes parse(String JavaDoc inFileName, Node parent,
114             URL JavaDoc jarFileUrl)
115     throws FileNotFoundException JavaDoc, JasperException, IOException JavaDoc {
116         // For files that are statically included, isTagfile and directiveOnly
117
// remain unchanged.
118
return doParse(inFileName, parent, jarFileUrl);
119     }
120
121     /**
122      * Extracts tag file directive information from the tag file with the
123      * given name.
124      *
125      * This is invoked by the compiler
126      *
127      * @param inFileName The name of the tag file to be parsed.
128      */

129     public Node.Nodes parseTagFileDirectives(String JavaDoc inFileName)
130     throws FileNotFoundException JavaDoc, JasperException, IOException JavaDoc {
131         boolean isTagFileSave = isTagFile;
132         boolean directiveOnlySave = directiveOnly;
133         isTagFile = true;
134         directiveOnly = true;
135         Node.Nodes page = doParse(inFileName, null,
136                 ctxt.getTagFileJarUrl(inFileName));
137         directiveOnly = directiveOnlySave;
138         isTagFile = isTagFileSave;
139         return page;
140     }
141
142     /**
143      * Parses the JSP page or tag file with the given path name.
144      *
145      * @param inFileName The name of the JSP page or tag file to be parsed.
146      * @param parent The parent node (non-null when processing an include
147      * directive)
148      * @param isTagFile true if file to be parsed is tag file, and false if it
149      * is a regular JSP page
150      * @param directivesOnly true if the file to be parsed is a tag file and
151      * we are only interested in the directives needed for constructing a
152      * TagFileInfo.
153      * @param jarFile The JAR file from which to read the JSP page or tag file,
154      * or null if the JSP page or tag file is to be read from the filesystem
155      */

156     private Node.Nodes doParse(String JavaDoc inFileName,
157             Node parent,
158             URL JavaDoc jarFileUrl)
159     throws FileNotFoundException JavaDoc, JasperException, IOException JavaDoc {
160
161         Node.Nodes parsedPage = null;
162         isEncodingSpecifiedInProlog = false;
163         isBomPresent = false;
164         isDefaultPageEncoding = false;
165
166         JarFile JavaDoc jarFile = getJarFile(jarFileUrl);
167         String JavaDoc absFileName = resolveFileName(inFileName);
168         String JavaDoc jspConfigPageEnc = getJspConfigPageEncoding(absFileName);
169
170         // Figure out what type of JSP document and encoding type we are
171
// dealing with
172
determineSyntaxAndEncoding(absFileName, jarFile, jspConfigPageEnc);
173
174         if (parent != null) {
175             // Included resource, add to dependent list
176
compiler.getPageInfo().addDependant(absFileName);
177         }
178
179         if ((isXml && isEncodingSpecifiedInProlog) || isBomPresent) {
180             /*
181              * Make sure the encoding explicitly specified in the XML
182              * prolog (if any) matches that in the JSP config element
183              * (if any), treating "UTF-16", "UTF-16BE", and "UTF-16LE" as
184              * identical.
185              */

186             if (jspConfigPageEnc != null && !jspConfigPageEnc.equals(sourceEnc)
187                     && (!jspConfigPageEnc.startsWith("UTF-16")
188                             || !sourceEnc.startsWith("UTF-16"))) {
189                 err.jspError("jsp.error.prolog_config_encoding_mismatch",
190                         sourceEnc, jspConfigPageEnc);
191             }
192         }
193
194         // Dispatch to the appropriate parser
195
if (isXml) {
196             // JSP document (XML syntax)
197
// InputStream for jspx page is created and properly closed in
198
// JspDocumentParser.
199
parsedPage = JspDocumentParser.parse(this, absFileName,
200                     jarFile, parent,
201                     isTagFile, directiveOnly,
202                     sourceEnc,
203                     jspConfigPageEnc,
204                     isEncodingSpecifiedInProlog,
205                     isBomPresent);
206         } else {
207             // Standard syntax
208
InputStreamReader JavaDoc inStreamReader = null;
209             try {
210                 inStreamReader = JspUtil.getReader(absFileName, sourceEnc,
211                         jarFile, ctxt, err);
212                 JspReader jspReader = new JspReader(ctxt, absFileName,
213                         sourceEnc, inStreamReader,
214                         err);
215                 parsedPage = Parser.parse(this, jspReader, parent, isTagFile,
216                         directiveOnly, jarFileUrl,
217                         sourceEnc, jspConfigPageEnc,
218                         isDefaultPageEncoding, isBomPresent);
219             } finally {
220                 if (inStreamReader != null) {
221                     try {
222                         inStreamReader.close();
223                     } catch (Exception JavaDoc any) {
224                     }
225                 }
226             }
227         }
228
229         if (jarFile != null) {
230             try {
231                 jarFile.close();
232             } catch (Throwable JavaDoc t) {}
233         }
234
235         baseDirStack.pop();
236
237         return parsedPage;
238     }
239
240     /*
241      * Checks to see if the given URI is matched by a URL pattern specified in
242      * a jsp-property-group in web.xml, and if so, returns the value of the
243      * <page-encoding> element.
244      *
245      * @param absFileName The URI to match
246      *
247      * @return The value of the <page-encoding> attribute of the
248      * jsp-property-group with matching URL pattern
249      */

250     private String JavaDoc getJspConfigPageEncoding(String JavaDoc absFileName)
251     throws JasperException {
252
253         JspConfig jspConfig = ctxt.getOptions().getJspConfig();
254         JspConfig.JspProperty jspProperty
255             = jspConfig.findJspProperty(absFileName);
256         return jspProperty.getPageEncoding();
257     }
258
259     /**
260      * Determines the syntax (standard or XML) and page encoding properties
261      * for the given file, and stores them in the 'isXml' and 'sourceEnc'
262      * instance variables, respectively.
263      */

264     private void determineSyntaxAndEncoding(String JavaDoc absFileName,
265             JarFile JavaDoc jarFile,
266             String JavaDoc jspConfigPageEnc)
267     throws JasperException, IOException JavaDoc {
268
269         isXml = false;
270
271         /*
272          * 'true' if the syntax (XML or standard) of the file is given
273          * from external information: either via a JSP configuration element,
274          * the ".jspx" suffix, or the enclosing file (for included resources)
275          */

276         boolean isExternal = false;
277
278         /*
279          * Indicates whether we need to revert from temporary usage of
280          * "ISO-8859-1" back to "UTF-8"
281          */

282         boolean revert = false;
283
284         JspConfig jspConfig = ctxt.getOptions().getJspConfig();
285         JspConfig.JspProperty jspProperty = jspConfig.findJspProperty(
286                 absFileName);
287         if (jspProperty.isXml() != null) {
288             // If <is-xml> is specified in a <jsp-property-group>, it is used.
289
isXml = JspUtil.booleanValue(jspProperty.isXml());
290             isExternal = true;
291         } else if (absFileName.endsWith(".jspx")
292                 || absFileName.endsWith(".tagx")) {
293             isXml = true;
294             isExternal = true;
295         }
296
297         if (isExternal && !isXml) {
298             // JSP (standard) syntax. Use encoding specified in jsp-config
299
// if provided.
300
sourceEnc = jspConfigPageEnc;
301             if (sourceEnc != null) {
302                 return;
303             }
304             // We don't know the encoding, so use BOM to determine it
305
sourceEnc = "ISO-8859-1";
306         } else {
307             // XML syntax or unknown, (auto)detect encoding ...
308
Object JavaDoc[] ret = XMLEncodingDetector.getEncoding(absFileName,
309                     jarFile, ctxt, err);
310             sourceEnc = (String JavaDoc) ret[0];
311             if (((Boolean JavaDoc) ret[1]).booleanValue()) {
312                 isEncodingSpecifiedInProlog = true;
313             }
314             if (((Boolean JavaDoc) ret[2]).booleanValue()) {
315                 isBomPresent = true;
316             }
317
318             if (!isXml && sourceEnc.equals("UTF-8")) {
319                 /*
320                  * We don't know if we're dealing with XML or standard syntax.
321                  * Therefore, we need to check to see if the page contains
322                  * a <jsp:root> element.
323                  *
324                  * We need to be careful, because the page may be encoded in
325                  * ISO-8859-1 (or something entirely different), and may
326                  * contain byte sequences that will cause a UTF-8 converter to
327                  * throw exceptions.
328                  *
329                  * It is safe to use a source encoding of ISO-8859-1 in this
330                  * case, as there are no invalid byte sequences in ISO-8859-1,
331                  * and the byte/character sequences we're looking for (i.e.,
332                  * <jsp:root>) are identical in either encoding (both UTF-8
333                  * and ISO-8859-1 are extensions of ASCII).
334                  */

335                 sourceEnc = "ISO-8859-1";
336                 revert = true;
337             }
338         }
339
340         if (isXml) {
341             // (This implies 'isExternal' is TRUE.)
342
// We know we're dealing with a JSP document (via JSP config or
343
// ".jspx" suffix), so we're done.
344
return;
345         }
346
347         /*
348          * At this point, 'isExternal' or 'isXml' is FALSE.
349          * Search for jsp:root action, in order to determine if we're dealing
350          * with XML or standard syntax (unless we already know what we're
351          * dealing with, i.e., when 'isExternal' is TRUE and 'isXml' is FALSE).
352          * No check for XML prolog, since nothing prevents a page from
353          * outputting XML and still using JSP syntax (in this case, the
354          * XML prolog is treated as template text).
355          */

356         JspReader jspReader = null;
357         try {
358             jspReader = new JspReader(ctxt, absFileName, sourceEnc, jarFile,
359                     err);
360         } catch (FileNotFoundException JavaDoc ex) {
361             throw new JasperException(ex);
362         }
363         jspReader.setSingleFile(true);
364         Mark startMark = jspReader.mark();
365         if (!isExternal) {
366             jspReader.reset(startMark);
367             if (hasJspRoot(jspReader)) {
368                 if (revert) {
369                     sourceEnc = "UTF-8";
370                 }
371                 isXml = true;
372                 return;
373             } else {
374                 if (revert && isBomPresent) {
375                     sourceEnc = "UTF-8";
376                 }
377                 isXml = false;
378             }
379         }
380
381         /*
382          * At this point, we know we're dealing with JSP syntax.
383          * If an XML prolog is provided, it's treated as template text.
384          * Determine the page encoding from the page directive, unless it's
385          * specified via JSP config.
386          */

387         if (!isBomPresent) {
388             sourceEnc = jspConfigPageEnc;
389             if (sourceEnc == null) {
390                 sourceEnc = getPageEncodingForJspSyntax(jspReader, startMark);
391                 if (sourceEnc == null) {
392                     // Default to "ISO-8859-1" per JSP spec
393
sourceEnc = "ISO-8859-1";
394                     isDefaultPageEncoding = true;
395                 }
396             }
397         }
398         
399     }
400
401     /*
402      * Determines page source encoding for page or tag file in JSP syntax,
403      * by reading (in this order) the value of the 'pageEncoding' page
404      * directive attribute, or the charset value of the 'contentType' page
405      * directive attribute.
406      *
407      * @return The page encoding, or null if not found
408      */

409     private String JavaDoc getPageEncodingForJspSyntax(JspReader jspReader,
410             Mark startMark)
411     throws JasperException {
412
413         String JavaDoc encoding = null;
414         String JavaDoc saveEncoding = null;
415
416         jspReader.reset(startMark);
417
418         /*
419          * Determine page encoding from directive of the form <%@ page %>,
420          * <%@ tag %>, <jsp:directive.page > or <jsp:directive.tag >.
421          */

422         while (true) {
423             if (jspReader.skipUntil("<") == null) {
424                 break;
425             }
426             // If this is a comment, skip until its end
427
if (jspReader.matches("%--")) {
428                 if (jspReader.skipUntil("--%>") == null) {
429                     // error will be caught in Parser
430
break;
431                 }
432                 continue;
433             }
434             boolean isDirective = jspReader.matches("%@");
435             if (isDirective) {
436                 jspReader.skipSpaces();
437             }
438             else {
439                 isDirective = jspReader.matches("jsp:directive.");
440             }
441             if (!isDirective) {
442                 continue;
443             }
444
445             // compare for "tag ", so we don't match "taglib"
446
if (jspReader.matches("tag ") || jspReader.matches("page")) {
447
448                 jspReader.skipSpaces();
449                 Attributes JavaDoc attrs = Parser.parseAttributes(this, jspReader);
450                 encoding = getPageEncodingFromDirective(attrs, "pageEncoding");
451                 if (encoding != null) {
452                     break;
453                 }
454                 encoding = getPageEncodingFromDirective(attrs, "contentType");
455                 if (encoding != null) {
456                     saveEncoding = encoding;
457                 }
458             }
459         }
460
461         if (encoding == null) {
462             encoding = saveEncoding;
463         }
464
465         return encoding;
466     }
467
468     /*
469      * Scans the given attributes for the attribute with the given name,
470      * which is either 'pageEncoding' or 'contentType', and returns the
471      * specified page encoding.
472      *
473      * In the case of 'contentType', the page encoding is taken from the
474      * content type's 'charset' component.
475      *
476      * @param attrs The page directive attributes
477      * @param attrName The name of the attribute to search for (either
478      * 'pageEncoding' or 'contentType')
479      *
480      * @return The page encoding, or null
481      */

482     private String JavaDoc getPageEncodingFromDirective(Attributes JavaDoc attrs,
483             String JavaDoc attrName) {
484         String JavaDoc value = attrs.getValue(attrName);
485         if (attrName.equals("pageEncoding")) {
486             return value;
487         }
488
489         // attrName = contentType
490
String JavaDoc contentType = value;
491         String JavaDoc encoding = null;
492         if (contentType != null) {
493             int loc = contentType.indexOf(CHARSET);
494             if (loc != -1) {
495                 encoding = contentType.substring(loc + CHARSET.length());
496             }
497         }
498
499         return encoding;
500     }
501
502     /*
503      * Resolve the name of the file and update baseDirStack() to keep track of
504      * the current base directory for each included file.
505      * The 'root' file is always an 'absolute' path, so no need to put an
506      * initial value in the baseDirStack.
507      */

508     private String JavaDoc resolveFileName(String JavaDoc inFileName) {
509         String JavaDoc fileName = inFileName.replace('\\', '/');
510         boolean isAbsolute = fileName.startsWith("/");
511         fileName = isAbsolute ? fileName
512                 : (String JavaDoc) baseDirStack.peek() + fileName;
513         String JavaDoc baseDir =
514             fileName.substring(0, fileName.lastIndexOf("/") + 1);
515         baseDirStack.push(baseDir);
516         return fileName;
517     }
518
519     /*
520      * Checks to see if the given page contains, as its first element, a <root>
521      * element whose prefix is bound to the JSP namespace, as in:
522      *
523      * <wombat:root xmlns:wombat="http://java.sun.com/JSP/Page" version="1.2">
524      * ...
525      * </wombat:root>
526      *
527      * @param reader The reader for this page
528      *
529      * @return true if this page contains a root element whose prefix is bound
530      * to the JSP namespace, and false otherwise
531      */

532     private boolean hasJspRoot(JspReader reader) throws JasperException {
533
534         // <prefix>:root must be the first element
535
Mark start = null;
536         while ((start = reader.skipUntil("<")) != null) {
537             int c = reader.nextChar();
538             if (c != '!' && c != '?') break;
539         }
540         if (start == null) {
541             return false;
542         }
543         Mark stop = reader.skipUntil(":root");
544         if (stop == null) {
545             return false;
546         }
547         // call substring to get rid of leading '<'
548
String JavaDoc prefix = reader.getText(start, stop).substring(1);
549
550         start = stop;
551         stop = reader.skipUntil(">");
552         if (stop == null) {
553             return false;
554         }
555
556         // Determine namespace associated with <root> element's prefix
557
String JavaDoc root = reader.getText(start, stop);
558         String JavaDoc xmlnsDecl = "xmlns:" + prefix;
559         int index = root.indexOf(xmlnsDecl);
560         if (index == -1) {
561             return false;
562         }
563         index += xmlnsDecl.length();
564         while (index < root.length()
565                 && Character.isWhitespace(root.charAt(index))) {
566             index++;
567         }
568         if (index < root.length() && root.charAt(index) == '=') {
569             index++;
570             while (index < root.length()
571                     && Character.isWhitespace(root.charAt(index))) {
572                 index++;
573             }
574             if (index < root.length() && root.charAt(index++) == '"'
575                 && root.regionMatches(index, JSP_URI, 0,
576                         JSP_URI.length())) {
577                 return true;
578             }
579         }
580
581         return false;
582     }
583
584     private JarFile JavaDoc getJarFile(URL JavaDoc jarFileUrl) throws IOException JavaDoc {
585         JarFile JavaDoc jarFile = null;
586
587         if (jarFileUrl != null) {
588             JarURLConnection JavaDoc conn = (JarURLConnection JavaDoc) jarFileUrl.openConnection();
589             conn.setUseCaches(false);
590             conn.connect();
591             jarFile = conn.getJarFile();
592         }
593
594         return jarFile;
595     }
596
597 }
598
Popular Tags