/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.jsp;
import com.caucho.bytecode.*;
import com.caucho.util.L10N;
import javax.annotation.Resource;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.ejb.EJBs;
import javax.inject.Inject;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceUnit;
import javax.servlet.jsp.tagext.*;
//import javax.xml.ws.WebServiceRef;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Analyzes the class for tag.
*
* Resin performs optimizations in the java code it produces from a jsp
* depending on the nature of the taglib's that are used. For example, if a
* taglib class does not use doAfterBody() then Resin can optimize the code it
* produces for the jsp that uses that tag.
*
* In order to determine the nature of a certain tag, and thus the
* optimizations that can be performed, Resin analyzes the tag's class.
* It does this in two stages: first it uses reflection to look at the class
* and then it uses bytecode analysis to look at the class.
*
* @see com.caucho.jsp.AnalyzedTag
*/
public class TagAnalyzer
{
private static final Logger log
= Logger.getLogger(TagAnalyzer.class.getName());
static final L10N L = new L10N(TagAnalyzer.class);
private HashMap<Class<?>,AnalyzedTag> _analyzedTags =
new HashMap<Class<?>,AnalyzedTag>();
/**
* Analyzes a tag.
*/
public AnalyzedTag analyze(Class<?> tagClass)
{
if (tagClass == null)
return null;
AnalyzedTag analyzedTag = _analyzedTags.get(tagClass);
if (analyzedTag != null)
return analyzedTag;
if (! JspTag.class.isAssignableFrom(tagClass)) {
return null;
}
if (tagClass.isInterface()) {
return null;
}
AnalyzedTag parent = analyze(tagClass.getSuperclass());
String name = tagClass.getName().replace('.', '/') + ".class";
ClassLoader loader = Thread.currentThread().getContextClassLoader();
AnalyzedTag tag = new AnalyzedTag();
tag.setParent(parent);
try {
analyzeByReflection(tagClass, tag, parent);
InputStream is = loader.getResourceAsStream(name);
if (is == null)
return tag;
try {
JavaClass javaClass = new ByteCodeParser().parse(is);
tag.setJavaClass(javaClass);
analyze(tag, "doStartTag", "()I", new StartAnalyzer(tag));
analyze(tag, "doEndTag", "()I", new EndAnalyzer(tag));
if (IterationTag.class.isAssignableFrom(tagClass)) {
analyze(tag, "doAfterBody", "()I", new AfterAnalyzer(tag));
}
if (BodyTag.class.isAssignableFrom(tagClass)) {
analyze(tag, "doInitBody", "()V", new InitAnalyzer());
}
if (TryCatchFinally.class.isAssignableFrom(tagClass)) {
analyze(tag, "doCatch", "(Ljava/lang/Throwable;)V",
new CatchAnalyzer());
analyze(tag, "doFinally", "()V", new FinallyAnalyzer());
}
} finally {
is.close();
}
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
}
return tag;
}
/**
* Analyzes the tag by reflection.
*/
public void analyzeByReflection(Class<?> tagClass,
AnalyzedTag tag, AnalyzedTag parent)
{
tag.setBodyTag(BodyTag.class.isAssignableFrom(tagClass));
Method doStartMethod = getMethod(tagClass, "doStartTag", new Class[0]);
if (doStartMethod != null &&
doStartMethod.getDeclaringClass().equals(tagClass)) {
if (TagSupport.class.equals(tagClass)) {
tag.setDoStart(false);
tag.setStartReturnsSkip(false);
tag.setStartReturnsInclude(true);
tag.setStartReturnsBuffered(false);
}
else if (BodyTagSupport.class.equals(tagClass)) {
tag.setDoStart(false);
tag.setStartReturnsSkip(false);
tag.setStartReturnsInclude(false);
tag.setStartReturnsBuffered(true);
}
else if (BodyTag.class.isAssignableFrom(tagClass)) {
tag.setDoStart(true);
tag.setStartReturnsSkip(true);
tag.setStartReturnsInclude(true);
tag.setStartReturnsBuffered(true);
}
else {
tag.setDoStart(true);
tag.setStartReturnsSkip(true);
tag.setStartReturnsInclude(true);
tag.setStartReturnsBuffered(false);
}
}
else if (parent != null) {
tag.setDoStart(parent.getDoStart());
tag.setStartReturnsSkip(parent.getStartReturnsSkip());
tag.setStartReturnsInclude(parent.getStartReturnsInclude());
tag.setStartReturnsBuffered(parent.getStartReturnsBufferedAsParent());
}
Method doEndMethod = getMethod(tagClass, "doEndTag", new Class[0]);
if (doEndMethod != null &&
doEndMethod.getDeclaringClass().equals(tagClass)) {
if (TagSupport.class.equals(tagClass) ||
BodyTagSupport.class.equals(tagClass)) {
tag.setDoEnd(false);
tag.setEndReturnsSkip(false);
tag.setEndReturnsEval(true);
}
else {
tag.setDoEnd(true);
tag.setEndReturnsSkip(true);
tag.setEndReturnsEval(true);
}
}
else if (parent != null) {
tag.setDoEnd(parent.getDoEnd());
tag.setEndReturnsSkip(parent.getEndReturnsSkip());
tag.setEndReturnsEval(parent.getEndReturnsEval());
}
Method doAfterBody = getMethod(tagClass, "doAfterBody", new Class[0]);
if (doAfterBody != null &&
doAfterBody.getDeclaringClass().equals(tagClass)) {
if (TagSupport.class.equals(tagClass) ||
BodyTagSupport.class.equals(tagClass)) {
tag.setDoAfter(false);
tag.setAfterReturnsAgain(false);
}
else if (! IterationTag.class.isAssignableFrom(tagClass)) {
tag.setDoAfter(false);
tag.setAfterReturnsAgain(false);
}
else {
tag.setDoAfter(true);
tag.setAfterReturnsAgain(true);
}
}
else if (parent != null) {
tag.setDoAfter(parent.getDoAfter());
tag.setAfterReturnsAgain(parent.getAfterReturnsAgain());
}
Method doInitBody = getMethod(tagClass, "doInitBody", new Class[0]);
if (doInitBody != null &&
doInitBody.getDeclaringClass().equals(tagClass)) {
if (BodyTagSupport.class.equals(tagClass)) {
tag.setDoInit(false);
}
else if (! BodyTag.class.isAssignableFrom(tagClass)) {
tag.setDoInit(false);
}
else {
tag.setDoInit(true);
}
}
else if (parent != null) {
tag.setDoInit(parent.getDoInit());
}
Method doCatch = getMethod(tagClass, "doCatch",
new Class[] { Throwable.class });
if (doCatch != null &&
doCatch.getDeclaringClass().equals(tagClass)) {
if (! TryCatchFinally.class.isAssignableFrom(tagClass)) {
tag.setDoCatch(false);
}
else {
tag.setDoCatch(true);
}
}
else if (parent != null) {
tag.setDoCatch(parent.getDoCatch());
}
Method doFinally = getMethod(tagClass, "doFinally", new Class[0]);
if (doFinally != null &&
doFinally.getDeclaringClass().equals(tagClass)) {
if (! TryCatchFinally.class.isAssignableFrom(tagClass)) {
tag.setDoFinally(false);
}
else {
tag.setDoFinally(true);
}
}
else if (parent != null) {
tag.setDoFinally(parent.getDoFinally());
}
// check for @Resource injection
analyzeInject(tag, tagClass);
}
private void analyzeInject(AnalyzedTag tag, Class<?> cl)
{
if (cl == null)
return;
if (cl.isAnnotationPresent(EJB.class)
|| cl.isAnnotationPresent(EJBs.class)) {
tag.setHasInjection(true);
}
for (Method method : cl.getDeclaredMethods()) {
if (method.getDeclaringClass() == Object.class)
continue;
if (method.getName().startsWith("set")
&& (method.isAnnotationPresent(Resource.class)
|| method.isAnnotationPresent(EJB.class)
|| method.isAnnotationPresent(Inject.class)
|| method.isAnnotationPresent(PersistenceContext.class)
|| method.isAnnotationPresent(PersistenceUnit.class))) {
tag.setHasInjection(true);
}
else if (method.isAnnotationPresent(PostConstruct.class)) {
tag.setHasInjection(true);
}
}
for (Field field : cl.getDeclaredFields()) {
if (field.isAnnotationPresent(Resource.class)
|| field.isAnnotationPresent(EJB.class)
|| field.isAnnotationPresent(Inject.class)
|| field.isAnnotationPresent(PersistenceContext.class)
|| field.isAnnotationPresent(PersistenceUnit.class)) {
tag.setHasInjection(true);
}
}
analyzeInject(tag, cl.getSuperclass());
}
private Method getMethod(Class<?> tagClass, String name, Class<?> []args)
{
try {
return tagClass.getMethod(name, args);
} catch (Throwable e) {
return null;
}
}
/**
* Analyzes the code for a method
*/
private void analyze(AnalyzedTag tag,
String name,
String signature,
Analyzer analyzer)
{
JavaClass javaClass = null;
JavaMethod method = null;
for (AnalyzedTag defTag = tag;
defTag != null;
defTag = defTag.getParent()) {
method = defTag.getJavaClass().findMethod(name, signature);
if (method != null) {
javaClass = defTag.getJavaClass();
break;
}
}
if (method == null)
return;
CodeAttribute codeAttribute = method.getCode();
if (codeAttribute == null)
return;
CodeVisitor visitor = new CodeVisitor(javaClass, codeAttribute);
try {
visitor.analyze(analyzer);
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
}
analyzer.complete(tag);
}
static IntMethodAnalyzer analyzeIntMethod(AnalyzedTag tag,
String name,
String signature)
{
if (! "()I".equals(signature))
return null;
JavaMethod method = null;
JavaClass javaClass = tag.getJavaClass();
while (method == null && javaClass != null) {
method = javaClass.findMethod(name, signature);
if (method == null) {
JClass parent = javaClass.getSuperClass();
if (parent == null || ! (parent instanceof JavaClass))
return null;
javaClass = (JavaClass) parent;
}
}
if (method == null)
return null;
IntMethodAnalyzer analyzer = new IntMethodAnalyzer();
CodeAttribute codeAttribute = method.getCode();
if (codeAttribute == null)
return null;
CodeVisitor visitor = new CodeVisitor(javaClass, codeAttribute);
try {
visitor.analyze(analyzer);
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
}
if (analyzer.isUnique())
return analyzer;
else
return null;
}
static class Analyzer extends com.caucho.bytecode.Analyzer {
public void analyze(CodeVisitor visitor)
{
}
public void complete(AnalyzedTag tag)
{
}
}
/**
* Callback analyzing the methods.
*/
static class AbstractTagMethodAnalyzer extends Analyzer
{
private AnalyzedTag _tag;
private boolean _hasCode;
private int _count = 0;
private int _value = -1;
protected AbstractTagMethodAnalyzer(AnalyzedTag tag)
{
_tag = tag;
}
protected boolean hasCode()
{
return _hasCode;
}
protected void setHasCode()
{
_hasCode = true;
}
public void analyze(CodeVisitor visitor)
{
int count = _count++;
switch (visitor.getOpcode()) {
case CodeVisitor.IRETURN:
if (count != 1)
_hasCode = true;
addReturnValue(_value);
break;
case CodeVisitor.ICONST_M1:
case CodeVisitor.ICONST_0:
case CodeVisitor.ICONST_1:
case CodeVisitor.ICONST_2:
case CodeVisitor.ICONST_3:
case CodeVisitor.ICONST_4:
case CodeVisitor.ICONST_5:
if (count != 0)
_hasCode = true;
_value = visitor.getOpcode() - CodeVisitor.ICONST_0;
break;
case CodeVisitor.BIPUSH:
if (count != 0)
_hasCode = true;
_value = visitor.getByteArg();
break;
case CodeVisitor.SIPUSH:
if (count != 0)
_hasCode = true;
_value = visitor.getShortArg();
break;
case CodeVisitor.ALOAD_0:
if (count != 0)
_hasCode = true;
break;
case CodeVisitor.INVOKEVIRTUAL:
case CodeVisitor.INVOKESPECIAL:
{
// matching int methods have an extra opcode for 'this'
if (count != 1)
_hasCode = true;
_value = -1;
int index = visitor.getShortArg();
JavaClass jClass = visitor.getJavaClass();
MethodRefConstant methodRef =
jClass.getConstantPool().getMethodRef(index);
IntMethodAnalyzer value =
analyzeIntMethod(_tag, methodRef.getName(), methodRef.getType());
if (value != null) {
_value = value.getValue();
// reset count since the subcall checks for side-effect code
if (count == 1)
_count = 1;
if (value.hasCode()) {
_hasCode = true;
}
} else {
_hasCode = true;
}
}
break;
default:
_hasCode = true;
_value = -1;
break;
}
}
protected void addReturnValue(int value)
{
}
}
/**
* Callback analyzing the doStartTag method.
*/
static class StartAnalyzer extends AbstractTagMethodAnalyzer {
private boolean _hasSkip;
private boolean _hasInclude;
private boolean _hasBuffered;
StartAnalyzer(AnalyzedTag tag)
{
super(tag);
}
@Override
protected void addReturnValue(int value)
{
if (value == Tag.SKIP_BODY)
_hasSkip = true;
else if (value == Tag.EVAL_BODY_INCLUDE)
_hasInclude = true;
else if (value == BodyTag.EVAL_BODY_BUFFERED)
_hasBuffered = true;
else {
_hasSkip = true;
_hasInclude = true;
_hasBuffered = true;
setHasCode();
}
}
public void complete(AnalyzedTag tag)
{
tag.setDoStart(hasCode());
tag.setStartReturnsSkip(_hasSkip);
tag.setStartReturnsInclude(_hasInclude);
tag.setStartReturnsBuffered(_hasBuffered);
}
}
/**
* Callback analyzing the doEndTag method.
*/
static class EndAnalyzer extends AbstractTagMethodAnalyzer {
private boolean _hasSkip;
private boolean _hasEval;
EndAnalyzer(AnalyzedTag tag)
{
super(tag);
}
@Override
protected void addReturnValue(int value)
{
if (value == Tag.SKIP_PAGE)
_hasSkip = true;
else if (value == Tag.EVAL_PAGE)
_hasEval = true;
else {
_hasSkip = true;
_hasEval = true;
setHasCode();
}
}
public void complete(AnalyzedTag tag)
{
tag.setDoEnd(hasCode());
tag.setEndReturnsSkip(_hasSkip);
tag.setEndReturnsEval(_hasEval);
}
public String toString()
{
return (getClass().getSimpleName() + "[end:" + hasCode()
+ ",skip:" + _hasSkip + ",eval:" + _hasEval + "]");
}
}
/**
* Callback analyzing the doAfterBody method.
*/
static class AfterAnalyzer extends AbstractTagMethodAnalyzer
{
private boolean _hasAgain;
AfterAnalyzer(AnalyzedTag tag)
{
super(tag);
}
@Override
protected void addReturnValue(int value)
{
if (value == IterationTag.EVAL_BODY_AGAIN)
_hasAgain = true;
else if (value == IterationTag.SKIP_BODY || value == BodyTag.SKIP_PAGE) {
} else {
_hasAgain = true;
setHasCode();
}
}
public void complete(AnalyzedTag tag)
{
tag.setDoAfter(hasCode());
tag.setAfterReturnsAgain(_hasAgain);
}
}
/**
* Callback analyzing the doInitBody method.
*/
static class InitAnalyzer extends Analyzer {
private boolean _hasCode;
private int _count = 0;
public void analyze(CodeVisitor visitor)
{
int count = _count++;
switch (visitor.getOpcode()) {
case CodeVisitor.RETURN:
if (count != 0)
_hasCode = true;
break;
default:
_hasCode = true;
break;
}
}
public void complete(AnalyzedTag tag)
{
tag.setDoInit(_hasCode);
}
}
/**
* Callback analyzing the doCatch method.
*/
static class CatchAnalyzer extends Analyzer {
private boolean _hasCode;
private int _count = 0;
public void analyze(CodeVisitor visitor)
{
int count = _count++;
switch (visitor.getOpcode()) {
case CodeVisitor.RETURN:
if (count != 0)
_hasCode = true;
break;
default:
_hasCode = true;
break;
}
}
public void complete(AnalyzedTag tag)
{
tag.setDoCatch(_hasCode);
}
}
/**
* Callback analyzing the doFinally method.
*/
static class FinallyAnalyzer extends Analyzer {
private boolean _hasCode;
private int _count = 0;
public void analyze(CodeVisitor visitor)
{
int count = _count++;
switch (visitor.getOpcode()) {
case CodeVisitor.RETURN:
if (count != 0)
_hasCode = true;
break;
default:
_hasCode = true;
break;
}
}
public void complete(AnalyzedTag tag)
{
tag.setDoFinally(_hasCode);
}
}
/**
* Callback analyzing a zero-arg int method (for constant values).
*/
static class IntMethodAnalyzer extends Analyzer
{
private int _count = 0;
private int _value = -1;
private boolean _hasCode;
private int _resultValue = -1;
private int _resultValueCount = 0;
public boolean isUnique()
{
return _resultValueCount == 1;
}
public boolean hasCode()
{
return _hasCode;
}
public int getValue()
{
return _resultValue;
}
public void analyze(CodeVisitor visitor)
{
int count = _count++;
switch (visitor.getOpcode()) {
case CodeVisitor.IRETURN:
if (count > 1)
_hasCode = true;
if (_resultValueCount == 0) {
_resultValue = _value;
_resultValueCount = 1;
}
else if (_value != _resultValue)
_resultValueCount++;
break;
case CodeVisitor.ICONST_M1:
case CodeVisitor.ICONST_0:
case CodeVisitor.ICONST_1:
case CodeVisitor.ICONST_2:
case CodeVisitor.ICONST_3:
case CodeVisitor.ICONST_4:
case CodeVisitor.ICONST_5:
if (count > 0)
_hasCode = true;
_value = visitor.getOpcode() - CodeVisitor.ICONST_0;
break;
case CodeVisitor.BIPUSH:
if (count > 0)
_hasCode = true;
_value = visitor.getByteArg();
break;
case CodeVisitor.SIPUSH:
if (count > 0)
_hasCode = true;
_value = visitor.getShortArg();
break;
default:
_hasCode = true;
_value = -1;
break;
}
}
}
}