001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.jsp;
031:
032: import com.caucho.bytecode.*;
033: import com.caucho.log.Log;
034: import com.caucho.util.L10N;
035:
036: import javax.annotation.Resource;
037: import javax.ejb.EJB;
038: import javax.servlet.jsp.tagext.*;
039: import javax.xml.ws.WebServiceRef;
040: import java.io.InputStream;
041: import java.lang.reflect.Field;
042: import java.lang.reflect.Method;
043: import java.util.HashMap;
044: import java.util.logging.Level;
045: import java.util.logging.Logger;
046:
047: /**
048: * Analyzes the class for tag.
049: *
050: * Resin performs optimizations in the java code it produces from a jsp
051: * depending on the nature of the taglib's that are used. For example, if a
052: * taglib class does not use doAfterBody() then Resin can optimize the code it
053: * produces for the jsp that uses that tag.
054: *
055: * In order to determine the nature of a certain tag, and thus the
056: * optimizations that can be performed, Resin analyzes the tag's class.
057: * It does this in two stages: first it uses reflection to look at the class
058: * and then it uses bytecode analysis to look at the class.
059: *
060: * @see com.caucho.jsp.AnalyzedTag
061: */
062: public class TagAnalyzer {
063: private static final Logger log = Logger
064: .getLogger(TagAnalyzer.class.getName());
065:
066: static final L10N L = new L10N(TagAnalyzer.class);
067:
068: private HashMap<Class, AnalyzedTag> _analyzedTags = new HashMap<Class, AnalyzedTag>();
069:
070: /**
071: * Analyzes a tag.
072: */
073: public AnalyzedTag analyze(Class tagClass) {
074: if (tagClass == null)
075: return null;
076:
077: AnalyzedTag analyzedTag = _analyzedTags.get(tagClass);
078: if (analyzedTag != null)
079: return analyzedTag;
080:
081: if (!JspTag.class.isAssignableFrom(tagClass)) {
082: return null;
083: }
084:
085: if (tagClass.isInterface()) {
086: return null;
087: }
088:
089: AnalyzedTag parent = analyze(tagClass.getSuperclass());
090:
091: String name = tagClass.getName().replace('.', '/') + ".class";
092: ClassLoader loader = Thread.currentThread()
093: .getContextClassLoader();
094:
095: AnalyzedTag tag = new AnalyzedTag();
096: tag.setParent(parent);
097:
098: try {
099: analyzeByReflection(tagClass, tag, parent);
100:
101: InputStream is = loader.getResourceAsStream(name);
102:
103: if (is == null)
104: return tag;
105:
106: try {
107: JavaClass javaClass = new ByteCodeParser().parse(is);
108: tag.setJavaClass(javaClass);
109:
110: analyze(tag, "doStartTag", "()I",
111: new StartAnalyzer(tag));
112: analyze(tag, "doEndTag", "()I", new EndAnalyzer(tag));
113:
114: if (IterationTag.class.isAssignableFrom(tagClass)) {
115: analyze(tag, "doAfterBody", "()I",
116: new AfterAnalyzer(tag));
117: }
118:
119: if (BodyTag.class.isAssignableFrom(tagClass)) {
120: analyze(tag, "doInitBody", "()V",
121: new InitAnalyzer());
122: }
123:
124: if (TryCatchFinally.class.isAssignableFrom(tagClass)) {
125: analyze(tag, "doCatch", "(Ljava/lang/Throwable;)V",
126: new CatchAnalyzer());
127: analyze(tag, "doFinally", "()V",
128: new FinallyAnalyzer());
129: }
130: } finally {
131: is.close();
132: }
133: } catch (Exception e) {
134: log.log(Level.WARNING, e.toString(), e);
135: }
136:
137: return tag;
138: }
139:
140: /**
141: * Analyzes the tag by reflection.
142: */
143: public void analyzeByReflection(Class tagClass, AnalyzedTag tag,
144: AnalyzedTag parent) {
145: tag.setBodyTag(BodyTag.class.isAssignableFrom(tagClass));
146:
147: Method doStartMethod = getMethod(tagClass, "doStartTag",
148: new Class[0]);
149:
150: if (doStartMethod != null
151: && doStartMethod.getDeclaringClass().equals(tagClass)) {
152: if (TagSupport.class.equals(tagClass)) {
153: tag.setDoStart(false);
154: tag.setStartReturnsSkip(false);
155: tag.setStartReturnsInclude(true);
156: tag.setStartReturnsBuffered(false);
157: } else if (BodyTagSupport.class.equals(tagClass)) {
158: tag.setDoStart(false);
159: tag.setStartReturnsSkip(false);
160: tag.setStartReturnsInclude(false);
161: tag.setStartReturnsBuffered(true);
162: } else if (BodyTag.class.isAssignableFrom(tagClass)) {
163: tag.setDoStart(true);
164: tag.setStartReturnsSkip(true);
165: tag.setStartReturnsInclude(true);
166: tag.setStartReturnsBuffered(true);
167: } else {
168: tag.setDoStart(true);
169: tag.setStartReturnsSkip(true);
170: tag.setStartReturnsInclude(true);
171: tag.setStartReturnsBuffered(false);
172: }
173: } else if (parent != null) {
174: tag.setDoStart(parent.getDoStart());
175: tag.setStartReturnsSkip(parent.getStartReturnsSkip());
176: tag.setStartReturnsInclude(parent.getStartReturnsInclude());
177: tag.setStartReturnsBuffered(parent
178: .getStartReturnsBufferedAsParent());
179: }
180:
181: Method doEndMethod = getMethod(tagClass, "doEndTag",
182: new Class[0]);
183:
184: if (doEndMethod != null
185: && doEndMethod.getDeclaringClass().equals(tagClass)) {
186: if (TagSupport.class.equals(tagClass)
187: || BodyTagSupport.class.equals(tagClass)) {
188: tag.setDoEnd(false);
189: tag.setEndReturnsSkip(false);
190: tag.setEndReturnsEval(true);
191: } else {
192: tag.setDoEnd(true);
193: tag.setEndReturnsSkip(true);
194: tag.setEndReturnsEval(true);
195: }
196: } else if (parent != null) {
197: tag.setDoEnd(parent.getDoEnd());
198: tag.setEndReturnsSkip(parent.getEndReturnsSkip());
199: tag.setEndReturnsEval(parent.getEndReturnsEval());
200: }
201:
202: Method doAfterBody = getMethod(tagClass, "doAfterBody",
203: new Class[0]);
204:
205: if (doAfterBody != null
206: && doAfterBody.getDeclaringClass().equals(tagClass)) {
207: if (TagSupport.class.equals(tagClass)
208: || BodyTagSupport.class.equals(tagClass)) {
209: tag.setDoAfter(false);
210: tag.setAfterReturnsAgain(false);
211: } else if (!IterationTag.class.isAssignableFrom(tagClass)) {
212: tag.setDoAfter(false);
213: tag.setAfterReturnsAgain(false);
214: } else {
215: tag.setDoAfter(true);
216: tag.setAfterReturnsAgain(true);
217: }
218: } else if (parent != null) {
219: tag.setDoAfter(parent.getDoAfter());
220: tag.setAfterReturnsAgain(parent.getAfterReturnsAgain());
221: }
222:
223: Method doInitBody = getMethod(tagClass, "doInitBody",
224: new Class[0]);
225:
226: if (doInitBody != null
227: && doInitBody.getDeclaringClass().equals(tagClass)) {
228: if (BodyTagSupport.class.equals(tagClass)) {
229: tag.setDoInit(false);
230: } else if (!BodyTag.class.isAssignableFrom(tagClass)) {
231: tag.setDoInit(false);
232: } else {
233: tag.setDoInit(true);
234: }
235: } else if (parent != null) {
236: tag.setDoInit(parent.getDoInit());
237: }
238:
239: Method doCatch = getMethod(tagClass, "doCatch",
240: new Class[] { Throwable.class });
241:
242: if (doCatch != null
243: && doCatch.getDeclaringClass().equals(tagClass)) {
244: if (!TryCatchFinally.class.isAssignableFrom(tagClass)) {
245: tag.setDoCatch(false);
246: } else {
247: tag.setDoCatch(true);
248: }
249: } else if (parent != null) {
250: tag.setDoCatch(parent.getDoCatch());
251: }
252:
253: Method doFinally = getMethod(tagClass, "doFinally",
254: new Class[0]);
255:
256: if (doFinally != null
257: && doFinally.getDeclaringClass().equals(tagClass)) {
258: if (!TryCatchFinally.class.isAssignableFrom(tagClass)) {
259: tag.setDoFinally(false);
260: } else {
261: tag.setDoFinally(true);
262: }
263: } else if (parent != null) {
264: tag.setDoFinally(parent.getDoFinally());
265: }
266:
267: // check for @Resource injection
268: for (Method method : tagClass.getDeclaredMethods()) {
269: if (method.getName().startsWith("set")
270: && (method.isAnnotationPresent(Resource.class)
271: || method.isAnnotationPresent(EJB.class) || method
272: .isAnnotationPresent(WebServiceRef.class))) {
273: tag.setHasInjection(true);
274: }
275: }
276:
277: for (Field field : tagClass.getDeclaredFields()) {
278: if (field.isAnnotationPresent(Resource.class)
279: || field.isAnnotationPresent(EJB.class)
280: || field.isAnnotationPresent(WebServiceRef.class)) {
281: tag.setHasInjection(true);
282: }
283: }
284: }
285:
286: private Method getMethod(Class tagClass, String name, Class[] args) {
287: try {
288: return tagClass.getMethod(name, args);
289: } catch (Throwable e) {
290: return null;
291: }
292: }
293:
294: /**
295: * Analyzes the code for a method
296: */
297: private void analyze(AnalyzedTag tag, String name,
298: String signature, Analyzer analyzer) {
299: JavaClass javaClass = null;
300: JavaMethod method = null;
301:
302: for (AnalyzedTag defTag = tag; defTag != null; defTag = defTag
303: .getParent()) {
304: method = defTag.getJavaClass().findMethod(name, signature);
305:
306: if (method != null) {
307: javaClass = defTag.getJavaClass();
308: break;
309: }
310: }
311:
312: if (method == null)
313: return;
314:
315: CodeAttribute codeAttribute = method.getCode();
316:
317: if (codeAttribute == null)
318: return;
319:
320: CodeVisitor visitor = new CodeVisitor(javaClass, codeAttribute);
321: try {
322: visitor.analyze(analyzer);
323: } catch (Exception e) {
324: log.log(Level.WARNING, e.toString(), e);
325: }
326:
327: analyzer.complete(tag);
328: }
329:
330: static IntMethodAnalyzer analyzeIntMethod(AnalyzedTag tag,
331: String name, String signature) {
332: if (!"()I".equals(signature))
333: return null;
334:
335: JavaMethod method = null;
336: JavaClass javaClass = tag.getJavaClass();
337:
338: while (method == null && javaClass != null) {
339: method = javaClass.findMethod(name, signature);
340:
341: if (method == null) {
342: JClass parent = javaClass.getSuperClass();
343:
344: if (parent == null || !(parent instanceof JavaClass))
345: return null;
346:
347: javaClass = (JavaClass) parent;
348: }
349: }
350:
351: if (method == null)
352: return null;
353:
354: IntMethodAnalyzer analyzer = new IntMethodAnalyzer();
355:
356: CodeAttribute codeAttribute = method.getCode();
357:
358: if (codeAttribute == null)
359: return null;
360:
361: CodeVisitor visitor = new CodeVisitor(javaClass, codeAttribute);
362: try {
363: visitor.analyze(analyzer);
364: } catch (Exception e) {
365: log.log(Level.WARNING, e.toString(), e);
366: }
367:
368: if (analyzer.isUnique())
369: return analyzer;
370: else
371: return null;
372: }
373:
374: static class Analyzer extends com.caucho.bytecode.Analyzer {
375: public void analyze(CodeVisitor visitor) {
376: }
377:
378: public void complete(AnalyzedTag tag) {
379: }
380: }
381:
382: /**
383: * Callback analyzing the methods.
384: */
385: static class AbstractTagMethodAnalyzer extends Analyzer {
386: private AnalyzedTag _tag;
387:
388: private boolean _hasCode;
389:
390: private int _count = 0;
391: private int _value = -1;
392:
393: protected AbstractTagMethodAnalyzer(AnalyzedTag tag) {
394: _tag = tag;
395: }
396:
397: protected boolean hasCode() {
398: return _hasCode;
399: }
400:
401: protected void setHasCode() {
402: _hasCode = true;
403: }
404:
405: public void analyze(CodeVisitor visitor) {
406: int count = _count++;
407:
408: switch (visitor.getOpcode()) {
409: case CodeVisitor.IRETURN:
410: if (count != 1)
411: _hasCode = true;
412:
413: addReturnValue(_value);
414: break;
415:
416: case CodeVisitor.ICONST_M1:
417: case CodeVisitor.ICONST_0:
418: case CodeVisitor.ICONST_1:
419: case CodeVisitor.ICONST_2:
420: case CodeVisitor.ICONST_3:
421: case CodeVisitor.ICONST_4:
422: case CodeVisitor.ICONST_5:
423: if (count != 0)
424: _hasCode = true;
425:
426: _value = visitor.getOpcode() - CodeVisitor.ICONST_0;
427: break;
428:
429: case CodeVisitor.BIPUSH:
430: if (count != 0)
431: _hasCode = true;
432:
433: _value = visitor.getByteArg();
434: break;
435:
436: case CodeVisitor.SIPUSH:
437: if (count != 0)
438: _hasCode = true;
439:
440: _value = visitor.getShortArg();
441: break;
442:
443: case CodeVisitor.ALOAD_0:
444: if (count != 0)
445: _hasCode = true;
446: break;
447:
448: case CodeVisitor.INVOKEVIRTUAL:
449: case CodeVisitor.INVOKESPECIAL: {
450: // matching int methods have an extra opcode for 'this'
451: if (count != 1)
452: _hasCode = true;
453:
454: _value = -1;
455:
456: int index = visitor.getShortArg();
457: JavaClass jClass = visitor.getJavaClass();
458:
459: MethodRefConstant methodRef = jClass.getConstantPool()
460: .getMethodRef(index);
461:
462: IntMethodAnalyzer value = analyzeIntMethod(_tag,
463: methodRef.getName(), methodRef.getType());
464:
465: if (value != null) {
466: _value = value.getValue();
467:
468: // reset count since the subcall checks for side-effect code
469: if (count == 1)
470: _count = 1;
471:
472: if (value.hasCode()) {
473: _hasCode = true;
474: }
475: } else {
476: _hasCode = true;
477: }
478: }
479: break;
480:
481: default:
482: _hasCode = true;
483: _value = -1;
484: break;
485: }
486: }
487:
488: protected void addReturnValue(int value) {
489: }
490: }
491:
492: /**
493: * Callback analyzing the doStartTag method.
494: */
495: static class StartAnalyzer extends AbstractTagMethodAnalyzer {
496: private boolean _hasSkip;
497: private boolean _hasInclude;
498: private boolean _hasBuffered;
499:
500: StartAnalyzer(AnalyzedTag tag) {
501: super (tag);
502: }
503:
504: @Override
505: protected void addReturnValue(int value) {
506: if (value == Tag.SKIP_BODY)
507: _hasSkip = true;
508: else if (value == Tag.EVAL_BODY_INCLUDE)
509: _hasInclude = true;
510: else if (value == BodyTag.EVAL_BODY_BUFFERED)
511: _hasBuffered = true;
512: else {
513: _hasSkip = true;
514: _hasInclude = true;
515: _hasBuffered = true;
516: setHasCode();
517: }
518: }
519:
520: public void complete(AnalyzedTag tag) {
521: tag.setDoStart(hasCode());
522: tag.setStartReturnsSkip(_hasSkip);
523: tag.setStartReturnsInclude(_hasInclude);
524: tag.setStartReturnsBuffered(_hasBuffered);
525: }
526: }
527:
528: /**
529: * Callback analyzing the doEndTag method.
530: */
531: static class EndAnalyzer extends AbstractTagMethodAnalyzer {
532: private boolean _hasSkip;
533: private boolean _hasEval;
534:
535: EndAnalyzer(AnalyzedTag tag) {
536: super (tag);
537: }
538:
539: @Override
540: protected void addReturnValue(int value) {
541: if (value == Tag.SKIP_PAGE)
542: _hasSkip = true;
543: else if (value == Tag.EVAL_PAGE)
544: _hasEval = true;
545: else {
546: _hasSkip = true;
547: _hasEval = true;
548: setHasCode();
549: }
550: }
551:
552: public void complete(AnalyzedTag tag) {
553: tag.setDoEnd(hasCode());
554: tag.setEndReturnsSkip(_hasSkip);
555: tag.setEndReturnsEval(_hasEval);
556: }
557:
558: public String toString() {
559: return (getClass().getSimpleName() + "[end:" + hasCode()
560: + ",skip:" + _hasSkip + ",eval:" + _hasEval + "]");
561: }
562: }
563:
564: /**
565: * Callback analyzing the doAfterBody method.
566: */
567: static class AfterAnalyzer extends AbstractTagMethodAnalyzer {
568: private boolean _hasAgain;
569:
570: AfterAnalyzer(AnalyzedTag tag) {
571: super (tag);
572: }
573:
574: @Override
575: protected void addReturnValue(int value) {
576: if (value == IterationTag.EVAL_BODY_AGAIN)
577: _hasAgain = true;
578: else if (value == IterationTag.SKIP_BODY
579: || value == BodyTag.SKIP_PAGE) {
580: } else {
581: _hasAgain = true;
582: setHasCode();
583: }
584: }
585:
586: public void complete(AnalyzedTag tag) {
587: tag.setDoAfter(hasCode());
588: tag.setAfterReturnsAgain(_hasAgain);
589: }
590: }
591:
592: /**
593: * Callback analyzing the doInitBody method.
594: */
595: static class InitAnalyzer extends Analyzer {
596: private boolean _hasCode;
597:
598: private int _count = 0;
599:
600: public void analyze(CodeVisitor visitor) {
601: int count = _count++;
602:
603: switch (visitor.getOpcode()) {
604: case CodeVisitor.RETURN:
605: if (count != 0)
606: _hasCode = true;
607: break;
608:
609: default:
610: _hasCode = true;
611: break;
612: }
613: }
614:
615: public void complete(AnalyzedTag tag) {
616: tag.setDoInit(_hasCode);
617: }
618: }
619:
620: /**
621: * Callback analyzing the doCatch method.
622: */
623: static class CatchAnalyzer extends Analyzer {
624: private boolean _hasCode;
625:
626: private int _count = 0;
627:
628: public void analyze(CodeVisitor visitor) {
629: int count = _count++;
630:
631: switch (visitor.getOpcode()) {
632: case CodeVisitor.RETURN:
633: if (count != 0)
634: _hasCode = true;
635: break;
636:
637: default:
638: _hasCode = true;
639: break;
640: }
641: }
642:
643: public void complete(AnalyzedTag tag) {
644: tag.setDoCatch(_hasCode);
645: }
646: }
647:
648: /**
649: * Callback analyzing the doFinally method.
650: */
651: static class FinallyAnalyzer extends Analyzer {
652: private boolean _hasCode;
653:
654: private int _count = 0;
655:
656: public void analyze(CodeVisitor visitor) {
657: int count = _count++;
658:
659: switch (visitor.getOpcode()) {
660: case CodeVisitor.RETURN:
661: if (count != 0)
662: _hasCode = true;
663: break;
664:
665: default:
666: _hasCode = true;
667: break;
668: }
669: }
670:
671: public void complete(AnalyzedTag tag) {
672: tag.setDoFinally(_hasCode);
673: }
674: }
675:
676: /**
677: * Callback analyzing a zero-arg int method (for constant values).
678: */
679: static class IntMethodAnalyzer extends Analyzer {
680: private int _count = 0;
681: private int _value = -1;
682:
683: private boolean _hasCode;
684:
685: private int _resultValue = -1;
686: private int _resultValueCount = 0;
687:
688: public boolean isUnique() {
689: return _resultValueCount == 1;
690: }
691:
692: public boolean hasCode() {
693: return _hasCode;
694: }
695:
696: public int getValue() {
697: return _resultValue;
698: }
699:
700: public void analyze(CodeVisitor visitor) {
701: int count = _count++;
702:
703: switch (visitor.getOpcode()) {
704: case CodeVisitor.IRETURN:
705: if (count > 1)
706: _hasCode = true;
707:
708: if (_resultValueCount == 0) {
709: _resultValue = _value;
710: _resultValueCount = 1;
711: } else if (_value != _resultValue)
712: _resultValueCount++;
713: break;
714:
715: case CodeVisitor.ICONST_M1:
716: case CodeVisitor.ICONST_0:
717: case CodeVisitor.ICONST_1:
718: case CodeVisitor.ICONST_2:
719: case CodeVisitor.ICONST_3:
720: case CodeVisitor.ICONST_4:
721: case CodeVisitor.ICONST_5:
722: if (count > 0)
723: _hasCode = true;
724:
725: _value = visitor.getOpcode() - CodeVisitor.ICONST_0;
726: break;
727:
728: case CodeVisitor.BIPUSH:
729: if (count > 0)
730: _hasCode = true;
731:
732: _value = visitor.getByteArg();
733: break;
734:
735: case CodeVisitor.SIPUSH:
736: if (count > 0)
737: _hasCode = true;
738:
739: _value = visitor.getShortArg();
740: break;
741:
742: default:
743: _hasCode = true;
744:
745: _value = -1;
746: break;
747: }
748: }
749: }
750: }
|