001: ///////////////////////////////
002: // Makumba, Makumba tag library
003: // Copyright (C) 2000-2003 http://www.makumba.org
004: //
005: // This library is free software; you can redistribute it and/or
006: // modify it under the terms of the GNU Lesser General Public
007: // License as published by the Free Software Foundation; either
008: // version 2.1 of the License, or (at your option) any later version.
009: //
010: // This library is distributed in the hope that it will be useful,
011: // but WITHOUT ANY WARRANTY; without even the implied warranty of
012: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: // Lesser General Public License for more details.
014: //
015: // You should have received a copy of the GNU Lesser General Public
016: // License along with this library; if not, write to the Free Software
017: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: //
019: // -------------
020: // $Id: JspParseData.java 2157 2007-12-20 00:09:01Z rosso_nero $
021: // $Name$
022: /////////////////////////////////////
023:
024: package org.makumba.analyser.engine;
025:
026: import java.io.File;
027: import java.lang.reflect.Method;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.Map;
031: import java.util.logging.Level;
032: import java.util.logging.Logger;
033: import java.util.regex.Matcher;
034: import java.util.regex.Pattern;
035:
036: import javax.servlet.jsp.tagext.Tag;
037:
038: import org.makumba.analyser.TagData;
039: import org.makumba.analyser.interfaces.JspAnalyzer;
040: import org.makumba.commons.NamedResourceFactory;
041: import org.makumba.commons.NamedResources;
042: import org.makumba.commons.RuntimeWrappedException;
043:
044: /**
045: * This class performs a rudimentary detection of JSP-relevant tags in a JSP page.
046: *
047: * @author Cristian Bogdan
048: * @version $Id: JspParseData.java 2157 2007-12-20 00:09:01Z rosso_nero $
049: */
050: public class JspParseData implements
051: SourceSyntaxPoints.PreprocessorClient {
052: /** The JSP file path */
053: File file;
054:
055: /** The analyzer plugged in. */
056: JspAnalyzer analyzer;
057:
058: /** The syntax points of this page. */
059: SourceSyntaxPoints syntaxPoints;
060:
061: /** The holder of the analysis status, and partial results. */
062: Object holder;
063:
064: /** The JSP URI, for debugging purposes. */
065: String uri;
066:
067: /** Indicates whether the JSP file uses the Hibernate implementation or not. * */
068: private boolean usingHibernate = false;
069:
070: /** the path of the webapp root */
071: private String root;
072:
073: /** The patterns used to parse the page. */
074: static private Pattern JspSystemTagPattern, JspTagPattern,
075: JspCommentPattern, JspScriptletPattern, JspIncludePattern,
076: JspTagAttributePattern, JspExpressionLanguagePattern, Word,
077: TagName;
078:
079: static private String[] JspCommentPatternNames = { "JspComment",
080: "JspScriptlet" };
081:
082: static private Pattern[] JspCommentPatterns;
083:
084: /** Cache of all page analyses. */
085: static int analyzedPages = NamedResources.makeStaticCache(
086: "JSP page analyses", new NamedResourceFactory() {
087:
088: private static final long serialVersionUID = 1L;
089:
090: public Object getHashObject(Object o) {
091: Object[] o1 = (Object[]) o;
092: return ((String) o1[0] + o1[2])
093: + o1[1].getClass().getName();
094: }
095:
096: public Object makeResource(Object o, Object hashName)
097: throws Throwable {
098: Object[] o1 = (Object[]) o;
099: return new JspParseData((String) o1[0],
100: (JspAnalyzer) o1[1], (String) o1[2]);
101: }
102: }, true);
103:
104: static String attribute(String attName) {
105: return "(" + attribute(attName, "\"") + "|"
106: + attribute(attName, "\'") + ")";
107: }
108:
109: /**
110: * This helps to create regex for the 'attribute' pattern easily.
111: *
112: * @param attName
113: * the name of the attribute
114: * @param quote
115: * the kind of quote used. Is either " or ' .
116: */
117: static String attribute(String attName, String quote) {
118: String bs = "\\"; // backslash in a java String (escaped)
119: String q = bs + quote;
120: String backslash = bs + bs; // backslash in a regex in a java String (escaped)
121:
122: // BAD TRY: pattern is (?s)\s*\w+\s*=\s*"(.*?[^\\])??" or idem with single quote '
123: // the pattern is \s*\w+\s*=\s*"(\\.|[^"\\])*?" or idem with single quote '
124: return bs + "s*" + attName + bs + "s*=" + bs + "s*" + q + "("
125: + backslash + ".|[^" + q + backslash + "])*?" + q;
126: }
127:
128: /**
129: * Initialiser for the class variables.
130: */
131: static {
132: String attribute = attribute("\\w+");
133:
134: try {
135: JspTagAttributePattern = Pattern.compile(attribute);
136: JspSystemTagPattern = Pattern.compile("<%@\\s*\\w+("
137: + attribute + ")*\\s*%>");
138: JspIncludePattern = Pattern.compile("<%@\\s*include"
139: + attribute("file") + "\\s*%>");
140: JspTagPattern = Pattern.compile("<((\\s*\\w+:\\w+("
141: + attribute + ")*\\s*)/?|(/\\w+:\\w+\\s*))>");
142: // JspCommentPattern= Pattern.compile("<%--([^-]|(-[^-])|(--[^%])|(--%[^>]))*--%>", Pattern.DOTALL);
143: JspCommentPattern = Pattern.compile("<%--.*?[^-]--%>",
144: Pattern.DOTALL);
145: JspScriptletPattern = Pattern.compile("<%[^@].*?%>",
146: Pattern.DOTALL);
147: JspExpressionLanguagePattern = Pattern
148: .compile("\\$\\{[^\\}]*\\}");
149: Pattern[] cp = { JspCommentPattern, JspScriptletPattern };
150: JspCommentPatterns = cp;
151: Word = Pattern.compile("\\w+");
152: TagName = Pattern.compile("\\w+:\\w+");
153: } catch (Throwable t) {
154: t.printStackTrace();
155: }
156: }
157:
158: /**
159: * Performs the analysis if not performed already, or if the file has changed. The method is synchronized, so other
160: * accesses are blocked if the current access determines that an analysis needs be performed
161: *
162: * @param initStatus
163: * an initial status to be passed to the JspAnalyzer. For example, the pageContext for an example-based
164: * analyzer
165: */
166: public synchronized Object getAnalysisResult(Object initStatus) {
167: if (getSyntaxPoints() == null || !getSyntaxPoints().unchanged())
168: try {
169: parse(initStatus);
170: } catch (Error e) {
171: holder = e;
172: throw e;
173: } catch (RuntimeException re) {
174: holder = re;
175: throw re;
176: }
177: return holder;
178: }
179:
180: /**
181: * Returns the pageData of the page at the given path in the given webapp. This is the only way for clients of this
182: * class to obtain instances of JspPageData.
183: *
184: * @param webappRoot
185: * the root of the webapp
186: * @param path
187: * the path to the file
188: * @param the
189: * JspAnalyzer used to parse the page
190: */
191: static public JspParseData getParseData(String webappRoot,
192: String path, JspAnalyzer an) {
193: Object arg[] = { webappRoot, an, path };
194: return (JspParseData) NamedResources.getStaticCache(
195: analyzedPages).getResource(arg);
196: }
197:
198: /**
199: * Gets the collection of syntax points.
200: */
201: public SourceSyntaxPoints getSyntaxPoints() {
202: return syntaxPoints;
203: }
204:
205: /**
206: * Private constructor, construction can only be made by getParseData().
207: *
208: * @param path
209: * the path to the page
210: * @param an
211: * the analyzer used for analysis
212: * @param uri
213: * the uri, for debugging purposes
214: */
215: private JspParseData(String path, JspAnalyzer an, String uri) {
216: this .root = path;
217: this .file = new File(root + uri);
218: this .uri = uri;
219: this .analyzer = an;
220: }
221:
222: /**
223: * Parses the file.
224: *
225: * @param initStatus
226: * an initial status to be passed to the JspAnalyzer. For example, the pageContext for an example-based
227: * analyzer
228: */
229: void parse(Object initStatus) {
230: long start = new java.util.Date().getTime();
231: syntaxPoints = new SourceSyntaxPoints(file, this );
232:
233: holder = analyzer.makeStatusHolder(initStatus);
234:
235: // treat JSP Expression Language
236: treatEL(syntaxPoints.getContent(), analyzer);
237:
238: // the page analysis as such:
239: treatTags(syntaxPoints.getContent(), analyzer);
240:
241: holder = analyzer.endPage(holder);
242:
243: java.util.logging.Logger.getLogger(
244: "org.makumba." + "jspparser.time").info(
245: "analysis of " + uri + " took "
246: + (new java.util.Date().getTime() - start)
247: + " ms");
248: }
249:
250: /**
251: * Identifies tag attributes from a tag string and puts them in a Map. Sets the attribute syntax points.
252: *
253: * @param s
254: * the tag string to be parsed
255: * @param origin
256: * the origin of the string in the parsed page
257: */
258: Map<String, String> parseAttributes(String s, int origin) {
259: Map<String, String> attributes = new HashMap<String, String>(13);
260: // System.out.println("tag = " + s); //debugging
261:
262: Matcher m = JspTagAttributePattern.matcher(s);
263: while (m.find()) {
264: // here we have an attributeName="attributeValue"
265: String attr = s.substring(m.start(), m.end());
266: int n = attr.indexOf('='); // position of the equal sign
267: String attName = attr.substring(0, n).trim();
268: String attValQuoted = attr.substring(n + 1).trim(); // the part after the equal sign.
269: char chQuote = attValQuoted.charAt(0);
270:
271: // the following assertion must be ensured by the attributePattern matching
272: if (attValQuoted.charAt(0) != attValQuoted
273: .charAt(attValQuoted.length() - 1))
274: throw new RuntimeException(
275: "Properly quoted string expected, found "
276: + attValQuoted);
277:
278: // unescape the "escaped quotes" in the attributeValue
279: if (chQuote == '\"') {
280: attValQuoted = attValQuoted.replaceAll("\\\\\"", "\""); // replace \" by "
281: } else if (chQuote == '\'') {
282: attValQuoted = attValQuoted.replaceAll("\\\\\'", "\'"); // replace \' by '
283: }
284: attValQuoted = attValQuoted.replaceAll("(\\\\){2}", "\\\\"); // replace \\ by \
285:
286: String attValue = attValQuoted.substring(1, attValQuoted
287: .length() - 1);
288: attributes.put(attName, attValue);
289:
290: if (origin != -1) {
291: // syntax points, only set if the origin is given
292: syntaxPoints.addSyntaxPoints(origin, origin + n,
293: "JSPTagAttributeName", null);
294: syntaxPoints.addSyntaxPoints(origin + n,
295: origin + n + 1, "JSPTagAttributeEquals", null);
296: syntaxPoints.addSyntaxPoints(origin + n + 1, origin
297: + s.length(), "JSPTagAttributeValue", null);
298: }
299: // debug
300: Logger log = java.util.logging.Logger
301: .getLogger("org.makumba."
302: + "jspparser.tags.attribute");
303: log.finest("< Attribute : " + attr);
304: log.finest("> AttrParse : " + attName + " = " + attValue);
305: }
306: return attributes;
307: }
308:
309: /**
310: * Treats include directives in the page
311: *
312: * @param position
313: * position of the include directive
314: * @param includeDirective
315: * include directive to be treated
316: * @param host
317: * SourceSyntaxPoints object that is going to host this included page
318: */
319: public void treatInclude(int position, String includeDirective,
320: SourceSyntaxPoints host) {
321: Map m = parseAttributes(includeDirective, -1);
322: String fileName = (String) m.get("file");
323: String dir = fileName.startsWith("/") ? root : host.file
324: .getParent();
325: host.include(new File(dir, fileName), position,
326: includeDirective);
327: }
328:
329: public Pattern[] getCommentPatterns() {
330: return JspCommentPatterns;
331: }
332:
333: public String[] getCommentPatternNames() {
334: return JspCommentPatternNames;
335: }
336:
337: public Pattern getIncludePattern() {
338: return JspIncludePattern;
339: }
340:
341: public String getIncludePatternName() {
342: return "JspInclude";
343: }
344:
345: /**
346: * Go through the expression language in the page.
347: *
348: * @param content
349: * the content of the page
350: * @param an
351: * the JspAnalyzer used to analyze the page
352: */
353: void treatEL(String content, JspAnalyzer an) {
354: Matcher m = JspExpressionLanguagePattern.matcher(content);
355: while (m.find()) {
356: SyntaxPoint end = syntaxPoints.addSyntaxPoints(m.start(), m
357: .end(), "ExpressionLanguage", null);
358: SyntaxPoint start = (SyntaxPoint) end.getOtherInfo();
359: }
360: }
361:
362: /**
363: * Go through the tags in the page.
364: *
365: * @param content
366: * the content of the page
367: * @param an
368: * the JspAnalyzer used to analyze the page
369: */
370: void treatTags(String content, JspAnalyzer an) {
371: Matcher tags = JspTagPattern.matcher(content);
372: Matcher systemTags = JspSystemTagPattern.matcher(content);
373:
374: int tagStart = Integer.MAX_VALUE;
375: if (tags.find())
376: tagStart = tags.start();
377: int systemStart = Integer.MAX_VALUE;
378: if (systemTags.find())
379: systemStart = systemTags.start();
380:
381: while (true) {
382: if (tagStart < systemStart) {
383: treatTag(tags, content, an);
384: tagStart = Integer.MAX_VALUE;
385: if (tags.find())
386: tagStart = tags.start();
387: } else if (systemStart < tagStart) {
388: treatSystemTag(systemTags, content, an);
389: systemStart = Integer.MAX_VALUE;
390: if (systemTags.find())
391: systemStart = systemTags.start();
392: }
393: if (tagStart == Integer.MAX_VALUE
394: && systemStart == Integer.MAX_VALUE)
395: break;
396: }
397: }
398:
399: /**
400: * Treats a jsp or taglib tag: parses its different parts and stores the analysis.
401: *
402: * @param m
403: * the Matcher used to parse the tag
404: * @param content
405: * the content of the page
406: * @param an
407: * the JspAnalyzer used to analyze the page
408: */
409: void treatTag(Matcher m, String content, JspAnalyzer an) {
410: String tag = content.substring(m.start(), m.end());
411: boolean tagEnd = tag.startsWith("</");
412: boolean tagClosed = tag.endsWith("/>");
413: Matcher m1 = TagName.matcher(tag);
414: m1.find();
415: syntaxPoints.addSyntaxPoints(m.start() + m1.start(), m.start()
416: + m1.end(), "JSPTagName", null);
417:
418: String type = tagEnd ? "JspTagEnd"
419: : (tagClosed ? "JspTagSimple" : "JspTagBegin");
420:
421: SyntaxPoint end = syntaxPoints.addSyntaxPoints(m.start(), m
422: .end(), type, null);
423: SyntaxPoint start = (SyntaxPoint) end.getOtherInfo();
424:
425: String tagName = tag.substring(m1.start(), m1.end());
426:
427: TagData td = null;
428: td = new TagData();
429: td.name = tagName;
430: td.parseData = this ;
431: td.start = start;
432: td.end = end;
433:
434: if (!tagEnd)
435: td.attributes = parseAttributes(tag, m.start());
436:
437: Logger log = java.util.logging.Logger.getLogger("org.makumba."
438: + "jspparser.tags");
439:
440: // we avoid evaluation of the logging expression
441: if (log.isLoggable(Level.FINE))
442: log.fine(uri
443: + ":"
444: + start.line
445: + ":"
446: + start.column
447: + ": "
448: + (tagEnd ? ("/" + tagName)
449: : (td.name + " " + td.attributes)));
450: if (tagEnd) {
451: an.endTag(td, holder);
452: return;
453: }
454:
455: if (tagClosed)
456: an.simpleTag(td, holder);
457: else
458: an.startTag(td, holder);
459: }
460:
461: /**
462: * Treats a system tag: parses its different parts and stores the analysis.
463: *
464: * @param m
465: * the Matcher used to parse the tag
466: * @param content
467: * the content of the page
468: * @param an
469: * the JspAnalyzer used to analyze the page
470: */
471: void treatSystemTag(Matcher m, String content, JspAnalyzer an) {
472: String tag = content.substring(m.start(), m.end());
473: SyntaxPoint end = syntaxPoints.addSyntaxPoints(m.start(), m
474: .end(), "JSPSystemTag", null);
475:
476: Matcher m1 = Word.matcher(tag);
477: m1.find();
478: syntaxPoints.addSyntaxPoints(m.start() + m1.start(), m.start()
479: + m1.end(), "JSPSystemTagName", null);
480: SyntaxPoint start = (SyntaxPoint) end.getOtherInfo();
481:
482: TagData td = new TagData();
483: td.name = tag.substring(m1.start(), m1.end());
484: td.parseData = this ;
485: td.attributes = parseAttributes(tag, m.start());
486: td.start = start;
487: td.end = end;
488:
489: if (td.name.equals("taglib")) { // find out whether we have a taglib tag
490: if (td.attributes.get("uri") != null
491: && td.attributes.get("uri").toString().startsWith(
492: "http://www.makumba.org/")) {
493: if (!td.attributes.get("uri").equals(
494: "http://www.makumba.org/presentation")) {
495: // every other makumba.org tag-lib than www.makumba.org/presentation is treated to be hibernate
496: usingHibernate = true;
497: }
498: }
499: }
500:
501: Logger log = java.util.logging.Logger.getLogger("org.makumba."
502: + "jspparser.tags");
503:
504: // we avoid evaluation of the logging expression
505: if (log.isLoggable(Level.FINE))
506: log.fine(uri + ":" + start.line + ":" + start.column + ": "
507: + td.name + " " + td.attributes);
508:
509: an.systemTag(td, holder);
510: }
511:
512: /**
513: * Prints the line of a tag and points the beginning of the tag. Seems to be a nice illustration for parse-error
514: * messages.
515: *
516: * @param td
517: * the TagData of the tag
518: * @param sb
519: * the StringBuffer used to print out
520: */
521: public static void tagDataLine(TagData td, StringBuffer sb) {
522: sb.append("\n").append(
523: td.start.sourceFile.getLineText(td.start.getLine()))
524: .append('\n');
525: for (int i = 1; i < td.start.getColumn(); i++)
526: sb.append(' ');
527: sb.append('^');
528: }
529:
530: /**
531: * Fills the data for a tag by invoking the setter method through Java reflexion
532: *
533: * @param t
534: * the tag to be filled with data
535: * @param attributes
536: * the attributes of the tag
537: */
538: public static void fill(Tag t, Map attributes) {
539: Class c = t.getClass();
540: Class[] argTypes = { String.class };
541: Object[] args = new Object[1];
542:
543: for (Iterator i = attributes.entrySet().iterator(); i.hasNext();) {
544: Map.Entry me = (Map.Entry) i.next();
545: String s = (String) me.getKey();
546: String methodName = "set"
547: + Character.toUpperCase(s.charAt(0))
548: + s.substring(1);
549: try {
550: Method m = c.getMethod(methodName, argTypes);
551: args[0] = me.getValue();
552: m.invoke(t, args);
553: } catch (java.lang.reflect.InvocationTargetException ite) {
554: System.out.println("error invoking method "
555: + methodName + " on object of class "
556: + c.getName() + " with argument " + args[0]);
557: throw new RuntimeWrappedException(ite
558: .getTargetException());
559: } catch (Throwable thr) {
560: System.out.println("error invoking method "
561: + methodName + " on object of class "
562: + c.getName() + " with argument " + args[0]);
563: throw new RuntimeWrappedException(thr);
564: }
565: }
566: }
567:
568: // ==========================================================================================
569: //
570: // THIS CLASS ALSO HAS AN INNER INTERFACE, AND INNER CLASS DEFINITION:
571: //
572: // ==========================================================================================
573:
574: public boolean isUsingHibernate() {
575: return usingHibernate;
576: }
577:
578: public String[] getLiteralPatternNames() {
579: return null;
580: }
581:
582: public Pattern[] getLiteralPatterns() {
583: return null;
584: }
585:
586: } // end class
|