/**
* Copyright (c) 2014, the Railo Company Ltd.
* Copyright (c) 2015, Lucee Assosication Switzerland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package lucee.transformer.bytecode.statement.tag;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.jsp.tagext.BodyTag;
import javax.servlet.jsp.tagext.IterationTag;
import lucee.commons.lang.ClassException;
import lucee.commons.lang.ExceptionUtil;
import lucee.runtime.db.ClassDefinition;
import lucee.runtime.exp.Abort;
import lucee.runtime.reflection.Reflector;
import lucee.runtime.tag.MissingAttribute;
import lucee.runtime.type.util.ArrayUtil;
import lucee.transformer.TransformerException;
import lucee.transformer.bytecode.BytecodeContext;
import lucee.transformer.bytecode.cast.CastOther;
import lucee.transformer.bytecode.expression.type.LiteralStringArray;
import lucee.transformer.bytecode.statement.FlowControlFinal;
import lucee.transformer.bytecode.util.ASMConstants;
import lucee.transformer.bytecode.util.ASMUtil;
import lucee.transformer.bytecode.util.ExpressionUtil;
import lucee.transformer.bytecode.util.Types;
import lucee.transformer.bytecode.visitor.ArrayVisitor;
import lucee.transformer.bytecode.visitor.OnFinally;
import lucee.transformer.bytecode.visitor.TryCatchFinallyVisitor;
import lucee.transformer.bytecode.visitor.TryFinallyVisitor;
import lucee.transformer.expression.Expression;
import lucee.transformer.library.tag.TagLibTag;
import lucee.transformer.library.tag.TagLibTagAttr;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
import org.osgi.framework.BundleException;
public final class TagHelper {
private static final Type MISSING_ATTRIBUTE = Type.getType(MissingAttribute.class);
private static final Type MISSING_ATTRIBUTE_ARRAY = Type.getType(MissingAttribute[].class);
private static final Type BODY_TAG = Type.getType(BodyTag.class);
private static final Type TAG=Type.getType(javax.servlet.jsp.tagext.Tag.class);
private static final Type TAG_UTIL=Type.getType(lucee.runtime.tag.TagUtil.class);
// TagUtil.setAttributeCollection(Tag, Struct)
private static final Method SET_ATTRIBUTE_COLLECTION = new Method(
"setAttributeCollection",Types.VOID,new Type[]{Types.PAGE_CONTEXT,TAG,MISSING_ATTRIBUTE_ARRAY,Types.STRUCT,Types.INT_VALUE});
// Tag use(String)
private static final Method USE3= new Method("use",TAG,new Type[]{Types.STRING,Types.STRING,Types.INT_VALUE});
private static final Method USE5= new Method("use",TAG,new Type[]{Types.STRING,Types.STRING,Types.STRING,Types.STRING,Types.INT_VALUE});
// void setAppendix(String appendix)
private static final Method SET_APPENDIX1 = new Method("setAppendix",Type.VOID_TYPE,new Type[]{Types.STRING});
// void setAppendix(String appendix)
private static final Method SET_APPENDIX2 = new Method("setAppendix",Type.VOID_TYPE,new Type[]{Types.TAG,Types.STRING});
// void setDynamicAttribute(String uri, String name, Object value)
private static final Method SET_DYNAMIC_ATTRIBUTE = new Method(
"setDynamicAttribute",
Type.VOID_TYPE,
new Type[]{Types.STRING,Types.COLLECTION_KEY,Types.OBJECT});
// public static void setAttribute(PageContext pc,boolean doDynamic,boolean silently,Tag tag, String name,Object value) throws PageException {
private static final Method SET_ATTRIBUTE4 = new Method(
"setAttribute",
Type.VOID_TYPE,
new Type[]{Types.PAGE_CONTEXT,TAG,Types.STRING,Types.OBJECT});
private static final Method SET_META_DATA2 = new Method(
"setMetaData",
Type.VOID_TYPE,
new Type[]{Types.STRING,Types.OBJECT});
private static final Method SET_META_DATA3 = new Method(
"setMetaData",
Type.VOID_TYPE,
new Type[]{Types.TAG,Types.STRING,Types.OBJECT});
// void hasBody(boolean hasBody)
private static final Method HAS_BODY1 = new Method(
"hasBody",
Type.VOID_TYPE,
new Type[]{Types.BOOLEAN_VALUE});
// void hasBody(boolean hasBody)
private static final Method HAS_BODY2 = new Method(
"hasBody",
Type.VOID_TYPE,
new Type[]{Types.TAG,Types.BOOLEAN_VALUE});
// int doStartTag()
private static final Method DO_START_TAG = new Method(
"doStartTag",
Types.INT_VALUE,
new Type[]{});
// int doEndTag()
private static final Method DO_END_TAG = new Method(
"doEndTag",
Types.INT_VALUE,
new Type[]{});
private static final Type ABORT = Type.getType(Abort.class);
//private static final Type EXPRESSION_EXCEPTION = Type.getType(ExpressionException.class);
// ExpressionException newInstance(int)
private static final Method NEW_INSTANCE = new Method(
"newInstance",
ABORT,
new Type[]{Types.INT_VALUE});
private static final Method NEW_INSTANCE_MAX2 = new Method(
"newInstance",
MISSING_ATTRIBUTE,
new Type[]{Types.COLLECTION_KEY,Types.STRING});
private static final Method NEW_INSTANCE_MAX3 = new Method(
"newInstance",
MISSING_ATTRIBUTE,
new Type[]{Types.COLLECTION_KEY,Types.STRING,Types.STRING_ARRAY});
// void initBody(BodyTag bodyTag, int state)
private static final Method INIT_BODY = new Method(
"initBody",
Types.VOID,
new Type[]{BODY_TAG,Types.INT_VALUE});
// int doAfterBody()
private static final Method DO_AFTER_BODY = new Method(
"doAfterBody",
Types.INT_VALUE,
new Type[]{});
// void doCatch(Throwable t)
private static final Method DO_CATCH = new Method(
"doCatch",
Types.VOID,
new Type[]{Types.THROWABLE});
// void doFinally()
private static final Method DO_FINALLY = new Method(
"doFinally",
Types.VOID,
new Type[]{});
// JspWriter popBody()
private static final Method POP_BODY = new Method(
"popBody",
Types.JSP_WRITER,
new Type[]{});
// void reuse(Tag tag)
private static final Method RE_USE1 = new Method(
"reuse",
Types.VOID,
new Type[]{Types.TAG});
private static final Method RE_USE3 = new Method(
"reuse",
Types.VOID,
new Type[]{Types.TAG,Types.STRING,Types.STRING});
/**
* writes out the tag
* @param tag
* @param bc
* @param doReuse
* @throws TransformerException
* @throws BundleException
* @throws ClassException
*/
public static void writeOut(Tag tag, BytecodeContext bc, boolean doReuse, final FlowControlFinal fcf) throws TransformerException {
final GeneratorAdapter adapter = bc.getAdapter();
final TagLibTag tlt = tag.getTagLibTag();
final ClassDefinition cd = tlt.getTagClassDefinition();
final boolean fromBundle=cd.getName()!=null;
final Type currType;
if(fromBundle) {
try {
if(Reflector.isInstaneOf(cd.getClazz(), BodyTag.class)) currType=BODY_TAG;
else currType=TAG;
}
catch (Exception e) {
if(e instanceof TransformerException) throw (TransformerException)e;
throw new TransformerException(e, tag.getStart());
}
}
else currType=getTagType(tag);
final int currLocal=adapter.newLocal(currType);
Label tagBegin=new Label();
Label tagEnd=new Label();
ExpressionUtil.visitLine(bc, tag.getStart());
// TODO adapter.visitLocalVariable("tag", "L"+currType.getInternalName()+";", null, tagBegin, tagEnd, currLocal);
adapter.visitLabel(tagBegin);
// tag=pc.use(String tagClassName,String tagBundleName, String tagBundleVersion, String fullname,int attrType) throws PageException {
adapter.loadArg(0);
adapter.checkCast(Types.PAGE_CONTEXT_IMPL);
adapter.push(cd.getClassName());
// has bundle info/version
if(fromBundle) {
// name
adapter.push(cd.getName());
// version
if(cd.getVersion()!=null)
adapter.push(cd.getVersionAsString());
else
ASMConstants.NULL(adapter);
}
adapter.push(tlt.getFullName());
adapter.push(tlt.getAttributeType());
adapter.invokeVirtual(Types.PAGE_CONTEXT_IMPL, fromBundle?USE5:USE3);
if(currType!=TAG)adapter.checkCast(currType);
adapter.storeLocal(currLocal);
TryFinallyVisitor outerTcfv=new TryFinallyVisitor(new OnFinally() {
@Override
public void _writeOut(BytecodeContext bc) {
adapter.loadArg(0);
adapter.checkCast(Types.PAGE_CONTEXT_IMPL);
adapter.loadLocal(currLocal);
if(cd.getName()!=null) {
adapter.push(cd.getName());
if(cd.getVersion()!=null)
adapter.push(cd.getVersionAsString());
else
ASMConstants.NULL(adapter);
}
adapter.invokeVirtual(Types.PAGE_CONTEXT_IMPL, fromBundle?RE_USE3:RE_USE1);
}
},null);
if(doReuse)outerTcfv.visitTryBegin(bc);
// appendix
if(tlt.hasAppendix()) {
adapter.loadLocal(currLocal);
adapter.push(tag.getAppendix());
if(fromBundle) // PageContextUtil.setAppendix(tag,appendix)
ASMUtil.invoke(ASMUtil.STATIC,adapter,Types.TAG_UTIL,SET_APPENDIX2);
else // tag.setAppendix(appendix)
ASMUtil.invoke(ASMUtil.VIRTUAL,adapter,currType,SET_APPENDIX1);
}
// hasBody
boolean hasBody=tag.getBody()!=null;
if(tlt.isBodyFree() && tlt.hasBodyMethodExists()) {
adapter.loadLocal(currLocal);
adapter.push(hasBody);
if(fromBundle) // PageContextUtil.setAppendix(tag,appendix)
ASMUtil.invoke(ASMUtil.STATIC,adapter,Types.TAG_UTIL,HAS_BODY2);
else // tag.setAppendix(appendix)
ASMUtil.invoke(ASMUtil.VIRTUAL,adapter,currType,HAS_BODY1);
}
// default attributes (get overwritten by attributeCollection because of that set before)
setAttributes(bc,tag,currLocal,currType, true,fromBundle);
// attributeCollection
Attribute attrColl=tag.getAttribute("attributecollection");
if(attrColl!=null){
int attrType = tag.getTagLibTag().getAttributeType();
if(TagLibTag.ATTRIBUTE_TYPE_NONAME!=attrType) {
tag.removeAttribute("attributecollection");
// TagUtil.setAttributeCollection(Tag, Struct)
adapter.loadArg(0);
adapter.loadLocal(currLocal);
if(currType!=TAG)adapter.cast(currType, TAG);
///
TagLibTagAttr[] missings = tag.getMissingAttributes();
if(!ArrayUtil.isEmpty(missings)) {
ArrayVisitor av=new ArrayVisitor();
av.visitBegin(adapter,MISSING_ATTRIBUTE,missings.length);
int count=0;
TagLibTagAttr miss;
for(int i=0;i<missings.length;i++){
miss = missings[i];
av.visitBeginItem(adapter, count++);
bc.getFactory().registerKey(bc, bc.getFactory().createLitString(miss.getName()),false);
adapter.push(miss.getType());
if(ArrayUtil.isEmpty(miss.getAlias()))
adapter.invokeStatic(MISSING_ATTRIBUTE, NEW_INSTANCE_MAX2);
else {
new LiteralStringArray(bc.getFactory(),miss.getAlias()).writeOut(bc, Expression.MODE_REF);
adapter.invokeStatic(MISSING_ATTRIBUTE, NEW_INSTANCE_MAX3);
}
av.visitEndItem(bc.getAdapter());
}
av.visitEnd();
}
else {
ASMConstants.NULL(adapter);
}
///
attrColl.getValue().writeOut(bc, Expression.MODE_REF);
adapter.push(attrType);
adapter.invokeStatic(TAG_UTIL, SET_ATTRIBUTE_COLLECTION);
}
}
// metadata
Attribute attr;
Map<String, Attribute> metadata = tag.getMetaData();
if(metadata!=null){
Iterator<Attribute> it = metadata.values().iterator();
while(it.hasNext()) {
attr=it.next();
adapter.loadLocal(currLocal);
adapter.push(attr.getName());
attr.getValue().writeOut(bc, Expression.MODE_REF);
if(fromBundle)
ASMUtil.invoke(ASMUtil.STATIC,adapter,Types.TAG_UTIL,SET_META_DATA3);
else
ASMUtil.invoke(ASMUtil.VIRTUAL,adapter,currType,SET_META_DATA2);
}
}
// set attributes
setAttributes(bc,tag,currLocal,currType,false,fromBundle);
// Body
if(hasBody){
final int state=adapter.newLocal(Types.INT_VALUE);
// int state=tag.doStartTag();
adapter.loadLocal(currLocal);
ASMUtil.invoke(fromBundle?ASMUtil.INTERFACE:ASMUtil.VIRTUAL,adapter,currType,DO_START_TAG);
//adapter.invokeVirtual(currType, DO_START_TAG);
adapter.storeLocal(state);
// if (state!=Tag.SKIP_BODY)
Label endBody=new Label();
adapter.loadLocal(state);
adapter.push(javax.servlet.jsp.tagext.Tag.SKIP_BODY);
adapter.visitJumpInsn(Opcodes.IF_ICMPEQ, endBody);
// pc.initBody(tag, state);
adapter.loadArg(0);
adapter.loadLocal(currLocal);
adapter.loadLocal(state);
adapter.invokeVirtual(Types.PAGE_CONTEXT, INIT_BODY);
OnFinally onFinally = new OnFinally() {
@Override
public void _writeOut(BytecodeContext bc) {
Label endIf = new Label();
/*if(tlt.handleException() && fcf!=null && fcf.getAfterFinalGOTOLabel()!=null){
ASMUtil.visitLabel(adapter, fcf.getFinalEntryLabel());
}*/
adapter.loadLocal(state);
adapter.push(javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE);
adapter.visitJumpInsn(Opcodes.IF_ICMPEQ, endIf);
// ... pc.popBody();
adapter.loadArg(0);
adapter.invokeVirtual(Types.PAGE_CONTEXT, POP_BODY);
adapter.pop();
adapter.visitLabel(endIf);
// tag.doFinally();
if(tlt.handleException()) {
adapter.loadLocal(currLocal);
ASMUtil.invoke(fromBundle?ASMUtil.INTERFACE:ASMUtil.VIRTUAL,adapter,currType,DO_FINALLY);
//adapter.invokeVirtual(currType, DO_FINALLY);
}
// GOTO after execution body, used when a continue/break was called before
/*if(fcf!=null) {
Label l = fcf.getAfterFinalGOTOLabel();
if(l!=null)adapter.visitJumpInsn(Opcodes.GOTO, l);
}*/
}
};
if(tlt.handleException()) {
TryCatchFinallyVisitor tcfv=new TryCatchFinallyVisitor(onFinally,fcf);
tcfv.visitTryBegin(bc);
doTry(bc,adapter,tag,currLocal,currType,fromBundle);
int t=tcfv.visitTryEndCatchBeging(bc);
// tag.doCatch(t);
adapter.loadLocal(currLocal);
adapter.loadLocal(t);
//adapter.visitVarInsn(Opcodes.ALOAD,t);
ASMUtil.invoke(fromBundle?ASMUtil.INTERFACE:ASMUtil.VIRTUAL,adapter,currType,DO_CATCH);
//adapter.invokeVirtual(currType, DO_CATCH);
tcfv.visitCatchEnd(bc);
}
else {
TryFinallyVisitor tfv=new TryFinallyVisitor(onFinally,fcf);
tfv.visitTryBegin(bc);
doTry(bc,adapter,tag,currLocal,currType,fromBundle);
tfv.visitTryEnd(bc);
}
adapter.visitLabel(endBody);
}
else {
//tag.doStartTag();
adapter.loadLocal(currLocal);
ASMUtil.invoke(fromBundle?ASMUtil.INTERFACE:ASMUtil.VIRTUAL,adapter,currType,DO_START_TAG);
//adapter.invokeVirtual(currType, DO_START_TAG);
adapter.pop();
}
// if (tag.doEndTag()==Tag.SKIP_PAGE) throw new Abort(0<!-- SCOPE_PAGE -->);
Label endDoEndTag=new Label();
adapter.loadLocal(currLocal);
ASMUtil.invoke(fromBundle?ASMUtil.INTERFACE:ASMUtil.VIRTUAL,adapter,currType,DO_END_TAG);
//adapter.invokeVirtual(currType, DO_END_TAG);
adapter.push(javax.servlet.jsp.tagext.Tag.SKIP_PAGE);
adapter.visitJumpInsn(Opcodes.IF_ICMPNE, endDoEndTag);
adapter.push(Abort.SCOPE_PAGE);
adapter.invokeStatic(ABORT, NEW_INSTANCE);
adapter.throwException();
adapter.visitLabel(endDoEndTag);
if(doReuse) {
// } finally{pc.reuse(tag);}
outerTcfv.visitTryEnd(bc);
}
adapter.visitLabel(tagEnd);
ExpressionUtil.visitLine(bc, tag.getEnd());
}
private static void setAttributes(BytecodeContext bc, Tag tag, int currLocal, Type currType, boolean doDefault, boolean interf) throws TransformerException {
GeneratorAdapter adapter = bc.getAdapter();
Map<String,Attribute> attributes = tag.getAttributes();
String methodName;
Attribute attr;
Iterator<Attribute> it = attributes.values().iterator();
while(it.hasNext()) {
attr=it.next();
if(doDefault!=attr.isDefaultAttribute()) continue;
if(attr.isDynamicType()){
adapter.loadLocal(currLocal);
if(interf)adapter.checkCast(Types.DYNAMIC_ATTRIBUTES);
adapter.visitInsn(Opcodes.ACONST_NULL);
//adapter.push(attr.getName());
bc.getFactory().registerKey(bc, bc.getFactory().createLitString(attr.getName()),false);
attr.getValue().writeOut(bc, Expression.MODE_REF);
ASMUtil.invoke(interf?ASMUtil.INTERFACE:ASMUtil.VIRTUAL,adapter,interf?Types.DYNAMIC_ATTRIBUTES:currType,SET_DYNAMIC_ATTRIBUTE);
//adapter.invokeVirtual(currType, SET_DYNAMIC_ATTRIBUTE);
}
else {
// TagUtil.setAttribute(PageContext pc,boolean doDynamic,boolean silently,Tag tag, String name,Object value)
if(interf) {
adapter.loadArg(0); //pc
adapter.loadLocal(currLocal); // tag
bc.getFactory().createLitString(attr.getName()).writeOut(bc, Expression.MODE_REF);// name
attr.getValue().writeOut(bc, Expression.MODE_REF); // value
adapter.invokeStatic(TAG_UTIL, SET_ATTRIBUTE4);
}
else {
Type type = CastOther.getType(attr.getType());
methodName=tag.getTagLibTag().getSetter(attr,type);
adapter.loadLocal(currLocal);
attr.getValue().writeOut(bc, Types.isPrimitiveType(type)?Expression.MODE_VALUE:Expression.MODE_REF);
adapter.invokeVirtual(currType, new Method(methodName,Type.VOID_TYPE,new Type[]{type}));
}
}
}
}
private static void doTry(BytecodeContext bc, GeneratorAdapter adapter, Tag tag, int currLocal, Type currType, boolean interf) throws TransformerException {
Label beginDoWhile=new Label();
adapter.visitLabel(beginDoWhile);
bc.setCurrentTag(currLocal);
tag.getBody().writeOut(bc);
// while (tag.doAfterBody()==BodyTag.EVAL_BODY_AGAIN);
adapter.loadLocal(currLocal);
if(interf)adapter.checkCast(Types.BODY_TAG);
ASMUtil.invoke(interf?ASMUtil.INTERFACE:ASMUtil.VIRTUAL,adapter,currType,DO_AFTER_BODY);
//adapter.invokeVirtual(currType, DO_AFTER_BODY);
adapter.push(IterationTag.EVAL_BODY_AGAIN);
adapter.visitJumpInsn(Opcodes.IF_ICMPEQ, beginDoWhile);
}
private static Type getTagType(Tag tag) throws TransformerException {
TagLibTag tlt = tag.getTagLibTag();
try {
return tlt.getTagType();
} catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
throw new TransformerException(t,tag.getStart());
}
}
}