001: /*
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 1999 The Apache Software Foundation. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution, if
020: * any, must include the following acknowlegement:
021: * "This product includes software developed by the
022: * Apache Software Foundation (http://www.apache.org/)."
023: * Alternately, this acknowlegement may appear in the software itself,
024: * if and wherever such third-party acknowlegements normally appear.
025: *
026: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
027: * Foundation" must not be used to endorse or promote products derived
028: * from this software without prior written permission. For written
029: * permission, please contact apache@apache.org.
030: *
031: * 5. Products derived from this software may not be called "Apache"
032: * nor may "Apache" appear in their names without prior written
033: * permission of the Apache Group.
034: *
035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: *
049: * This software consists of voluntary contributions made by many
050: * individuals on behalf of the Apache Software Foundation. For more
051: * information on the Apache Software Foundation, please see
052: * <http://www.apache.org/>.
053: *
054: */
055:
056: package com.sun.portal.providers.jsp.jasper3.jasper.compiler;
057:
058: import java.io.InputStreamReader;
059: import java.io.FileInputStream;
060: import java.io.InputStream;
061: import java.io.Reader;
062: import java.io.CharArrayWriter;
063: import java.io.IOException;
064: import java.io.FileNotFoundException;
065: import java.io.File;
066: import java.util.Hashtable;
067: import java.util.Vector;
068: import java.util.Stack;
069:
070: import com.sun.portal.providers.jsp.jasper3.jasper.Constants;
071: import com.sun.portal.providers.jsp.jasper3.jasper.JspCompilationContext;
072:
073: /**
074: * JspReader is an input buffer for the JSP parser. It should allow
075: * unlimited lookahead and pushback. It also has a bunch of parsing
076: * utility methods for understanding htmlesque thingies.
077: *
078: * @author Anil K. Vijendran
079: * @author Anselm Baird-Smith
080: * @author Harish Prabandham
081: * @author Rajiv Mordani
082: * @author Mandar Raje
083: */
084: public class JspReader {
085: protected Mark current = null;
086: String master = null;
087: String defEncoding = "8859_1";
088:
089: Vector sourceFiles = new Vector();
090: int currFileId = 0;
091: int size = 0;
092:
093: private JspCompilationContext context;
094:
095: public String getFile(int fileid) {
096: return (String) sourceFiles.elementAt(fileid);
097: }
098:
099: /**
100: * Register a new source file.
101: * This method is used to implement file inclusion. Each included file
102: * gets a uniq identifier (which is the index in the array of source files).
103: * @return The index of the now registered file.
104: */
105: protected int registerSourceFile(String file) {
106: if (sourceFiles.contains(file))
107: return -1;
108: sourceFiles.addElement(file);
109: this .size++;
110: return sourceFiles.size() - 1;
111: }
112:
113: /**
114: * Unregister the source file.
115: * This method is used to implement file inclusion. Each included file
116: * gets a uniq identifier (which is the index in the array of source files).
117: * @return The index of the now registered file.
118: */
119: protected int unregisterSourceFile(String file) {
120: if (!sourceFiles.contains(file))
121: return -1;
122: sourceFiles.removeElement(file);
123: this .size--;
124: return sourceFiles.size() - 1;
125: }
126:
127: /**
128: * Push a new file to be parsed onto the stack.
129: * @param inputFile The fully qualified path of the file.
130: * @param encoding Optional encoding to read the file.
131: */
132: public void pushFile(String name, String encoding)
133: throws ParseException, FileNotFoundException {
134: // Default encoding if needed:
135: if (encoding == null)
136: encoding = defEncoding;
137: else
138: defEncoding = encoding;
139:
140: String parent = master == null ? null : master.substring(0,
141: master.lastIndexOf("/") + 1);
142: boolean isAbsolute = name.startsWith("/");
143:
144: if (parent == null || isAbsolute)
145: master = name;
146: else
147: master = parent + name;
148:
149: String realPath = null;
150: File file = null;
151:
152: if (context != null) {
153: realPath = context.getRealPath(master);
154: if (realPath != null)
155: file = new File(realPath);
156: } else {
157: file = new File(master);
158: if (file.exists())
159: realPath = file.getAbsolutePath();
160: }
161:
162: if (realPath == null)
163: throw new FileNotFoundException(master);
164:
165: // Register the file, and read its content:
166: int fileid = registerSourceFile(realPath);
167:
168: if (fileid == -1)
169: throw new ParseException(Constants.getString(
170: "jsp.error.file.already.registered",
171: new Object[] { file }));
172: currFileId = fileid;
173:
174: InputStreamReader reader = null;
175: try {
176: if (context == null)
177: reader = new InputStreamReader(
178: new FileInputStream(file), encoding);
179: else {
180: InputStream in = context.getResourceAsStream(master);
181: if (in == null)
182: throw new FileNotFoundException(realPath);
183:
184: try {
185: reader = new InputStreamReader(in, encoding);
186: } catch (Exception ex) {
187: throw new FileNotFoundException(realPath + ": "
188: + ex.getMessage());
189: }
190: }
191:
192: CharArrayWriter caw = new CharArrayWriter();
193: char buf[] = new char[1024];
194: for (int i = 0; (i = reader.read(buf)) != -1;)
195: caw.write(buf, 0, i);
196: caw.close();
197: if (current == null) {
198: current = new Mark(this , caw.toCharArray(), fileid,
199: getFile(fileid), master, encoding);
200: } else {
201: current.pushStream(caw.toCharArray(), fileid,
202: getFile(fileid), master, encoding);
203: }
204:
205: } catch (FileNotFoundException fnfe) {
206: throw fnfe;
207: } catch (Exception ex) {
208: // Pop state being constructed:
209: popFile();
210: throw new ParseException(Constants
211: .getString("jsp.error.file.cannot.read",
212: new Object[] { file }));
213: } finally {
214: if (reader != null) {
215: try {
216: reader.close();
217: } catch (Exception any) {
218: }
219: }
220: }
221: }
222:
223: public boolean popFile() throws ParseException {
224: // Is stack created ? (will happen if the Jsp file we'r looking at is
225: // missing.
226: if (current == null)
227: return false;
228:
229: // Restore parser state:
230: //size--;
231: if (currFileId < 0) {
232: throw new ParseException(Constants
233: .getString("jsp.error.no.more.content"));
234: }
235:
236: String fName = getFile(currFileId);
237: currFileId = unregisterSourceFile(fName);
238: if (currFileId < -1)
239: throw new ParseException(Constants.getString(
240: "jsp.error.file.not.registered",
241: new Object[] { fName }));
242:
243: boolean r = current.popStream();
244: if (r)
245: master = current.baseDir;
246: return r;
247: }
248:
249: protected JspReader(String file, JspCompilationContext ctx,
250: String encoding) throws ParseException,
251: FileNotFoundException {
252: this .context = ctx;
253: pushFile(file, encoding);
254: }
255:
256: public static JspReader createJspReader(String file,
257: JspCompilationContext ctx, String encoding)
258: throws ParseException, FileNotFoundException {
259: return new JspReader(file, ctx, encoding);
260: }
261:
262: public boolean hasMoreInput() throws ParseException {
263: if (current.cursor >= current.stream.length) {
264: while (popFile()) {
265: if (current.cursor < current.stream.length)
266: return true;
267: }
268: return false;
269: }
270: return true;
271: }
272:
273: public int nextChar() throws ParseException {
274: if (!hasMoreInput())
275: return -1;
276:
277: int ch = current.stream[current.cursor];
278:
279: current.cursor++;
280:
281: if (ch == '\n') {
282: current.line++;
283: current.col = 0;
284: } else {
285: current.col++;
286: }
287: return ch;
288: }
289:
290: /**
291: * Gets Content until the next potential JSP element. Because all elements
292: * begin with a '<' we can just move until we see the next one.
293: */
294: String nextContent() {
295: int cur_cursor = current.cursor;
296: int len = current.stream.length;
297: char ch;
298:
299: if (peekChar() == '\n') {
300: current.line++;
301: current.col = 0;
302: } else
303: current.col++;
304:
305: // pure obsfuscated genius!
306: while ((++current.cursor < len)
307: && ((ch = current.stream[current.cursor]) != '<')) {
308:
309: if (ch == '\n') {
310: current.line++;
311: current.col = 0;
312: } else {
313: current.col++;
314: }
315: }
316:
317: return new String(current.stream, cur_cursor, current.cursor
318: - cur_cursor);
319: }
320:
321: char[] getChars(Mark start, Mark stop) throws ParseException {
322: Mark oldstart = mark();
323: reset(start);
324: CharArrayWriter caw = new CharArrayWriter();
325: while (!stop.equals(mark()))
326: caw.write(nextChar());
327: caw.close();
328: reset(oldstart);
329: return caw.toCharArray();
330: }
331:
332: public int peekChar() {
333: return current.stream[current.cursor];
334: }
335:
336: public Mark mark() {
337: return new Mark(current);
338: }
339:
340: public void reset(Mark mark) {
341: current = new Mark(mark);
342: }
343:
344: public boolean matchesIgnoreCase(String string)
345: throws ParseException {
346: Mark mark = mark();
347: int ch = 0;
348: int i = 0;
349: do {
350: ch = nextChar();
351: if (Character.toLowerCase((char) ch) != string.charAt(i++)) {
352: reset(mark);
353: return false;
354: }
355: } while (i < string.length());
356: reset(mark);
357: return true;
358: }
359:
360: public boolean matches(String string) throws ParseException {
361: Mark mark = mark();
362: int ch = 0;
363: int i = 0;
364: do {
365: ch = nextChar();
366: if (((char) ch) != string.charAt(i++)) {
367: reset(mark);
368: return false;
369: }
370: } while (i < string.length());
371: reset(mark);
372: return true;
373: }
374:
375: public void advance(int n) throws ParseException {
376: while (--n >= 0)
377: nextChar();
378: }
379:
380: public int skipSpaces() throws ParseException {
381: int i = 0;
382: while (isSpace()) {
383: i++;
384: nextChar();
385: }
386: return i;
387: }
388:
389: /**
390: * Skip until the given string is matched in the stream.
391: * When returned, the context is positioned past the end of the match.
392: * @param s The String to match.
393: * @return A non-null <code>Mark</code> instance if found,
394: * <strong>null</strong> otherwise.
395: */
396: public Mark skipUntil(String limit) throws ParseException {
397: Mark ret = null;
398: int limlen = limit.length();
399: int ch;
400:
401: skip: for (ret = mark(), ch = nextChar(); ch != -1; ret = mark(), ch = nextChar()) {
402:
403: if (ch == limit.charAt(0)) {
404: for (int i = 1; i < limlen; i++) {
405: if (Character.toLowerCase((char) nextChar()) != limit
406: .charAt(i))
407: continue skip;
408: }
409: return ret;
410: }
411: }
412: return null;
413: }
414:
415: final boolean isSpace() {
416: return peekChar() <= ' ';
417: }
418:
419: /**
420: * Parse a space delimited token.
421: * If quoted the token will consume all characters up to a matching quote,
422: * otherwise, it consumes up to the first delimiter character.
423: * @param quoted If <strong>true</strong> accept quoted strings.
424: */
425:
426: public String parseToken(boolean quoted) throws ParseException {
427: StringBuffer stringBuffer = new StringBuffer();
428: skipSpaces();
429: stringBuffer.setLength(0);
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; ch = nextChar()) {
440: if (ch == '\\')
441: ch = nextChar();
442: stringBuffer.append((char) ch);
443: }
444: // Check end of quote, skip closing quote:
445: if (ch == -1)
446: throw new ParseException(mark(), Constants
447: .getString("jsp.error.quotes.unterminated"));
448: } else
449: throw new ParseException(mark(), Constants
450: .getString("jsp.error.attr.quoted"));
451: } else {
452: if (!isDelimiter())
453: // Read value until delimiter is found:
454: do {
455: ch = nextChar();
456: // Take care of the quoting here.
457: if (ch == '\\') {
458: if (peekChar() == '"' || peekChar() == '\''
459: || peekChar() == '>'
460: || peekChar() == '%')
461: ch = nextChar();
462: }
463: stringBuffer.append((char) ch);
464: } while (!isDelimiter());
465: }
466: return stringBuffer.toString();
467: }
468:
469: /**
470: * Parse an attribute/value pair, and store it in provided hash table.
471: * The attribute/value pair is defined by:
472: * <pre>
473: * av := spaces token spaces '=' spaces token spaces
474: * </pre>
475: * Where <em>token</em> is defined by <code>parseToken</code> and
476: * <em>spaces</em> is defined by <code>skipSpaces</code>.
477: * The name is always considered case insensitive, hence stored in its
478: * lower case version.
479: * @param into The Hashtable instance to save the result to.
480: */
481:
482: private void parseAttributeValue(Hashtable into)
483: throws ParseException {
484: // Get the attribute name:
485: skipSpaces();
486: String name = parseToken(false);
487: // Check for an equal sign:
488: skipSpaces();
489: if (peekChar() != '=')
490: throw new ParseException(mark(), Constants.getString(
491: "jsp.error.attr.novalue", new Object[] { name }));
492: char ch = (char) nextChar();
493: // Get the attribute value:
494: skipSpaces();
495: String value = parseToken(true);
496: skipSpaces();
497: // Add the binding to the provided hashtable:
498: into.put(name, value);
499: return;
500: }
501:
502: /**
503: * Parse some tag attributes for Beans.
504: * The stream is assumed to be positioned right after the tag name. The
505: * syntax recognized is:
506: * <pre>
507: * tag-attrs := empty | attr-list (">" | "-->" | %>)
508: * attr-list := empty | av spaces attr-list
509: * empty := spaces
510: * </pre>
511: * Where <em>av</em> is defined by <code>parseAttributeValue</code>.
512: * @return A Hashtable mapping String instances (variable names) into
513: * String instances (variable values).
514: */
515:
516: public Hashtable parseTagAttributesBean() throws ParseException {
517: Hashtable values = new Hashtable(11);
518: while (true) {
519: skipSpaces();
520: int ch = peekChar();
521: if (ch == '>') {
522: // End of the useBean tag.
523: return values;
524:
525: } else if (ch == '/') {
526: Mark mark = mark();
527: nextChar();
528: // XMLesque Close tags
529: try {
530: if (nextChar() == '>')
531: return values;
532: } finally {
533: reset(mark);
534: }
535: }
536: if (ch == -1)
537: break;
538: // Parse as an attribute=value:
539: parseAttributeValue(values);
540: }
541: // Reached EOF:
542: throw new ParseException(mark(), Constants
543: .getString("jsp.error.tag.attr.unterminated"));
544: }
545:
546: /**
547: * Parse some tag attributes.
548: * The stream is assumed to be positioned right after the tag name. The
549: * syntax recognized is:
550: * <pre>
551: * tag-attrs := empty | attr-list (">" | "-->" | %>)
552: * attr-list := empty | av spaces attr-list
553: * empty := spaces
554: * </pre>
555: * Where <em>av</em> is defined by <code>parseAttributeValue</code>.
556: * @return A Hashtable mapping String instances (variable names) into
557: * String instances (variable values).
558: */
559:
560: public Hashtable parseTagAttributes() throws ParseException {
561: Hashtable values = new Hashtable(11);
562: while (true) {
563: skipSpaces();
564: int ch = peekChar();
565: if (ch == '>') {
566: return values;
567: }
568: if (ch == '-') {
569: Mark mark = mark();
570: nextChar();
571: // Close NCSA like attributes "->"
572: try {
573: if (nextChar() == '-' && nextChar() == '>')
574: return values;
575: } finally {
576: reset(mark);
577: }
578: } else if (ch == '%') {
579: Mark mark = mark();
580: nextChar();
581: // Close variable like attributes "%>"
582: try {
583: if (nextChar() == '>')
584: return values;
585: } finally {
586: reset(mark);
587: }
588: } else if (ch == '/') {
589: Mark mark = mark();
590: nextChar();
591: // XMLesque Close tags
592: try {
593: if (nextChar() == '>')
594: return values;
595: } finally {
596: reset(mark);
597: }
598: }
599: if (ch == -1)
600: break;
601: // Parse as an attribute=value:
602: parseAttributeValue(values);
603: }
604: // Reached EOF:
605: throw new ParseException(mark(), Constants
606: .getString("jsp.error.tag.attr.unterminated"));
607: }
608:
609: /**
610: * Parse PARAM tag attributes into the given hashtable.
611: * Parses the PARAM tag as defined by:
612: * <pre>
613: * <PARAM tag-attributes %gt;
614: * </pre>
615: * Two special tag attributes are recognized here:
616: * <ol>
617: * <li>The <strong>name</strong> attribute,
618: * <li>The <strong>value</strong> attribute.
619: * </ol>
620: * The resulting name, value pair is stored in the provided hash table.
621: * @param into Storage for parameter values.
622: */
623: public void parseParamTag(Hashtable into) throws ParseException {
624: // Really check for a param tag:
625: if (matches("param")) {
626: advance(6);
627: parseParams(into);
628: } else {
629: // False alarm, just skip it
630: }
631: }
632:
633: /**
634: * Parse jsp:param tag attributes into the given hashtable.
635: * Parses the jsp:param tag as defined by:
636: * <pre>
637: * <jsp:param tag-attributes %gt;
638: * </pre>
639: * Two special tag attributes are recognized here:
640: * <ol>
641: * <li>The <strong>name</strong> attribute,
642: * <li>The <strong>value</strong> attribute.
643: * </ol>
644: * The resulting name, value pair is stored in the provided hash table.
645: * @param into Storage for parameter values.
646: */
647: public void parsePluginParamTag(Hashtable into)
648: throws ParseException {
649: // Really check for a param tag:
650: if (matches("<jsp:param")) {
651: advance(11);
652: parseParams(into);
653: } else {
654: // False alarm, just skip it
655: }
656: }
657:
658: private void parseParams(Hashtable into) throws ParseException {
659: Hashtable attrs = parseTagAttributes();
660: // Check attributes (name and value):
661: String name = (String) attrs.get("name");
662: String value = (String) attrs.get("value");
663: if (name == null)
664: throw new ParseException(mark(), Constants
665: .getString("jsp.error.param.noname"));
666: if (value == null)
667: throw new ParseException(mark(), Constants
668: .getString("jsp.error.param.novalue"));
669: // Put that new binding into the params hashatble:
670: String oldval[] = (String[]) into.get(name);
671: if (oldval == null) {
672: String newval[] = new String[1];
673: newval[0] = value;
674: into.put(name, newval);
675: } else {
676: String newval[] = new String[oldval.length + 1];
677: System.arraycopy(oldval, 0, newval, 0, oldval.length);
678: newval[oldval.length] = value;
679: into.put(name, newval);
680: }
681: }
682:
683: /**
684: * Parse utils - Is current character a token delimiter ?
685: * Delimiters are currently defined to be =, >, <, ", and ' or any
686: * any space character as defined by <code>isSpace</code>.
687: * @return A boolean.
688: */
689: private boolean isDelimiter() throws ParseException {
690: if (!isSpace()) {
691: int ch = peekChar();
692: // Look for a single-char work delimiter:
693: if (ch == '=' || ch == '>' || ch == '"' || ch == '\''
694: || ch == '/')
695: return true;
696: // Look for an end-of-comment or end-of-tag:
697: if (ch == '-') {
698: Mark mark = mark();
699: if (((ch = nextChar()) == '>')
700: || ((ch == '-') && (nextChar() == '>'))) {
701: reset(mark);
702: return true;
703: } else {
704: reset(mark);
705: return false;
706: }
707: }
708: return false;
709: } else {
710: return true;
711: }
712: }
713: }
|