001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
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, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.transformer.bytecode.statement.tag;
020
021import java.util.Iterator;
022import java.util.Map;
023
024import javax.servlet.jsp.tagext.BodyTag;
025import javax.servlet.jsp.tagext.IterationTag;
026
027import lucee.commons.lang.ClassException;
028import lucee.runtime.exp.Abort;
029import lucee.runtime.tag.MissingAttribute;
030import lucee.runtime.type.util.ArrayUtil;
031import lucee.transformer.bytecode.BytecodeContext;
032import lucee.transformer.bytecode.BytecodeException;
033import lucee.transformer.bytecode.cast.CastOther;
034import lucee.transformer.bytecode.expression.Expression;
035import lucee.transformer.bytecode.expression.type.LiteralStringArray;
036import lucee.transformer.bytecode.expression.var.Variable;
037import lucee.transformer.bytecode.literal.LitString;
038import lucee.transformer.bytecode.statement.FlowControlFinal;
039import lucee.transformer.bytecode.util.ASMConstants;
040import lucee.transformer.bytecode.util.ExpressionUtil;
041import lucee.transformer.bytecode.util.Types;
042import lucee.transformer.bytecode.visitor.ArrayVisitor;
043import lucee.transformer.bytecode.visitor.OnFinally;
044import lucee.transformer.bytecode.visitor.TryCatchFinallyVisitor;
045import lucee.transformer.bytecode.visitor.TryFinallyVisitor;
046import lucee.transformer.library.tag.TagLibTag;
047import lucee.transformer.library.tag.TagLibTagAttr;
048
049import org.objectweb.asm.Label;
050import org.objectweb.asm.Opcodes;
051import org.objectweb.asm.Type;
052import org.objectweb.asm.commons.GeneratorAdapter;
053import org.objectweb.asm.commons.Method;
054
055public final class TagHelper {
056        private static final Type MISSING_ATTRIBUTE = Type.getType(MissingAttribute.class);
057        private static final Type MISSING_ATTRIBUTE_ARRAY = Type.getType(MissingAttribute[].class);
058        private static final Type TAG=Type.getType(javax.servlet.jsp.tagext.Tag.class);
059        private static final Type TAG_UTIL=Type.getType(lucee.runtime.tag.TagUtil.class);
060        
061        // TagUtil.setAttributeCollection(Tag, Struct)
062        private static final Method SET_ATTRIBUTE_COLLECTION = new Method(
063                        "setAttributeCollection",Types.VOID,new Type[]{Types.PAGE_CONTEXT,TAG,MISSING_ATTRIBUTE_ARRAY,Types.STRUCT,Types.INT_VALUE});
064        
065        // Tag use(String)
066        private static final Method USE1= new Method("use",TAG,new Type[]{Types.STRING});
067        private static final Method USE3= new Method("use",TAG,new Type[]{Types.STRING,Types.STRING,Types.INT_VALUE});
068        
069        // void setAppendix(String appendix)
070        private static final Method SET_APPENDIX = new Method("setAppendix",Type.VOID_TYPE,new Type[]{Types.STRING});
071        
072        // void setDynamicAttribute(String uri, String name, Object value)
073        private static final Method SET_DYNAMIC_ATTRIBUTE = new Method(
074                        "setDynamicAttribute",
075                        Type.VOID_TYPE,
076                        new Type[]{Types.STRING,Types.COLLECTION_KEY,Types.OBJECT});
077        
078        private static final Method SET_META_DATA = new Method(
079                        "setMetaData",
080                        Type.VOID_TYPE,
081                        new Type[]{Types.STRING,Types.OBJECT});
082
083        // void hasBody(boolean hasBody)
084        private static final Method HAS_BODY = new Method(
085                        "hasBody",
086                        Type.VOID_TYPE,
087                        new Type[]{Types.BOOLEAN_VALUE});
088
089        // int doStartTag()
090        private static final Method DO_START_TAG = new Method(
091                        "doStartTag",
092                        Types.INT_VALUE,
093                        new Type[]{});
094
095        // int doEndTag()
096        private static final Method DO_END_TAG =  new Method(
097                        "doEndTag",
098                        Types.INT_VALUE,
099                        new Type[]{});
100
101        private static final Type ABORT = Type.getType(Abort.class);
102        //private static final Type EXPRESSION_EXCEPTION = Type.getType(ExpressionException.class);
103        private static final Type BODY_TAG = Type.getType(BodyTag.class);
104
105        // ExpressionException newInstance(int)
106        private static final Method NEW_INSTANCE =  new Method(
107                        "newInstance",
108                        ABORT,
109                        new Type[]{Types.INT_VALUE});
110        private static final Method NEW_INSTANCE_MAX2 =  new Method(
111                        "newInstance",
112                        MISSING_ATTRIBUTE,
113                        new Type[]{Types.COLLECTION_KEY,Types.STRING});
114        
115        private static final Method NEW_INSTANCE_MAX3 =  new Method(
116                        "newInstance",
117                        MISSING_ATTRIBUTE,
118                        new Type[]{Types.COLLECTION_KEY,Types.STRING,Types.STRING_ARRAY});
119        
120        
121        
122
123        // void initBody(BodyTag bodyTag, int state)
124        private static final Method INIT_BODY = new Method(
125                        "initBody",
126                        Types.VOID,
127                        new Type[]{BODY_TAG,Types.INT_VALUE});
128
129        // int doAfterBody()
130        private static final Method DO_AFTER_BODY = new Method(
131                        "doAfterBody",
132                        Types.INT_VALUE,
133                        new Type[]{});
134
135        // void doCatch(Throwable t)
136        private static final Method DO_CATCH = new Method(
137                        "doCatch",
138                        Types.VOID,
139                        new Type[]{Types.THROWABLE});
140
141        // void doFinally()
142        private static final Method DO_FINALLY = new Method(
143                        "doFinally",
144                        Types.VOID,
145                        new Type[]{});
146
147        // JspWriter popBody()
148        private static final Method POP_BODY = new Method(
149                        "popBody",
150                        Types.JSP_WRITER,
151                        new Type[]{});
152
153        // void reuse(Tag tag)
154        private static final Method RE_USE = new Method(
155                        "reuse",
156                        Types.VOID,
157                        new Type[]{Types.TAG});
158        
159        /**
160         * writes out the tag
161         * @param tag
162         * @param bc
163         * @param doReuse
164         * @throws BytecodeException
165         */
166        public static void writeOut(Tag tag, BytecodeContext bc, boolean doReuse, final FlowControlFinal fcf) throws BytecodeException {
167                final GeneratorAdapter adapter = bc.getAdapter();
168                final TagLibTag tlt = tag.getTagLibTag();
169                final Type currType=getTagType(tag);
170                
171                final int currLocal=adapter.newLocal(currType);
172                Label tagBegin=new Label();
173                Label tagEnd=new Label();
174                ExpressionUtil.visitLine(bc, tag.getStart());
175                // TODO adapter.visitLocalVariable("tag", "L"+currType.getInternalName()+";", null, tagBegin, tagEnd, currLocal);
176
177                adapter.visitLabel(tagBegin);
178                
179        // tag=pc.use(str);
180                adapter.loadArg(0);
181                adapter.checkCast(Types.PAGE_CONTEXT_IMPL);
182                adapter.push(tlt.getTagClassName());
183                adapter.push(tlt.getFullName());
184                adapter.push(tlt.getAttributeType());
185                adapter.invokeVirtual(Types.PAGE_CONTEXT_IMPL, USE3);
186                adapter.checkCast(currType);
187                adapter.storeLocal(currLocal);
188        
189        TryFinallyVisitor outerTcfv=new TryFinallyVisitor(new OnFinally() {
190                public void _writeOut(BytecodeContext bc) {
191
192                        adapter.loadArg(0);
193                        adapter.loadLocal(currLocal);
194                        adapter.invokeVirtual(Types.PAGE_CONTEXT, RE_USE);
195                }
196        },null);
197        if(doReuse)outerTcfv.visitTryBegin(bc);
198                
199        // appendix
200                if(tlt.hasAppendix()) {
201                        adapter.loadLocal(currLocal);
202                        adapter.push(tag.getAppendix());
203                        adapter.invokeVirtual(currType, SET_APPENDIX);
204                }
205        
206        // hasBody
207                boolean hasBody=tag.getBody()!=null;
208                if(tlt.isBodyFree() && tlt.hasBodyMethodExists()) {
209                        adapter.loadLocal(currLocal);
210                        adapter.push(hasBody);
211                        adapter.invokeVirtual(currType, HAS_BODY);
212                }
213
214                // default attributes (get overwritten by attributeCollection because of that set before)
215                setAttributes(bc,tag,currLocal,currType, true);
216                
217                // attributeCollection
218                Attribute attrColl=tag.getAttribute("attributecollection");
219                if(attrColl!=null){
220                        int attrType = tag.getTagLibTag().getAttributeType();
221                        if(TagLibTag.ATTRIBUTE_TYPE_NONAME!=attrType) {
222                                tag.removeAttribute("attributecollection");
223                                // TagUtil.setAttributeCollection(Tag, Struct)
224                                adapter.loadArg(0);
225                                adapter.loadLocal(currLocal);
226                                adapter.cast(currType, TAG);
227                                
228                                ///
229                                TagLibTagAttr[] missings = tag.getMissingAttributes();
230                                if(!ArrayUtil.isEmpty(missings)) {
231                                        ArrayVisitor av=new ArrayVisitor();
232                            av.visitBegin(adapter,MISSING_ATTRIBUTE,missings.length);
233                            int count=0;
234                            TagLibTagAttr miss;
235                            for(int i=0;i<missings.length;i++){
236                                miss = missings[i];
237                                av.visitBeginItem(adapter, count++);
238                                                Variable.registerKey(bc, LitString.toExprString(miss.getName()));
239                                                adapter.push(miss.getType());
240                                                if(ArrayUtil.isEmpty(miss.getAlias()))
241                                                        adapter.invokeStatic(MISSING_ATTRIBUTE, NEW_INSTANCE_MAX2);
242                                                else {
243                                                        new LiteralStringArray(miss.getAlias()).writeOut(bc, Expression.MODE_REF); 
244                                                        adapter.invokeStatic(MISSING_ATTRIBUTE, NEW_INSTANCE_MAX3);
245                                                }
246                                        av.visitEndItem(bc.getAdapter());
247                            }
248                            av.visitEnd();
249                                }
250                                else {
251                                        ASMConstants.NULL(adapter);
252                                }
253                                ///
254                                attrColl.getValue().writeOut(bc, Expression.MODE_REF);
255                                
256                                adapter.push(attrType);
257                                adapter.invokeStatic(TAG_UTIL, SET_ATTRIBUTE_COLLECTION);
258                        }
259                }
260
261
262                // metadata
263                Attribute attr;
264                Map<String, Attribute> metadata = tag.getMetaData();
265                if(metadata!=null){
266                        Iterator<Attribute> it = metadata.values().iterator();
267                        while(it.hasNext()) {
268                                attr=it.next();
269                                        adapter.loadLocal(currLocal);
270                                        adapter.push(attr.getName());
271                                        attr.getValue().writeOut(bc, Expression.MODE_REF);
272                                        adapter.invokeVirtual(currType, SET_META_DATA);
273                        }
274                }
275
276                // set attributes
277                setAttributes(bc,tag,currLocal,currType,false);
278
279        // Body
280                if(hasBody){
281                        final int state=adapter.newLocal(Types.INT_VALUE);
282                        
283                        // int state=tag.doStartTag();
284                        adapter.loadLocal(currLocal);
285                        adapter.invokeVirtual(currType, DO_START_TAG);
286                        adapter.storeLocal(state);
287                        
288                        // if (state!=Tag.SKIP_BODY)
289                        Label endBody=new Label();
290                        adapter.loadLocal(state);
291                        adapter.push(javax.servlet.jsp.tagext.Tag.SKIP_BODY);
292                        adapter.visitJumpInsn(Opcodes.IF_ICMPEQ, endBody);
293                                // pc.initBody(tag, state);
294                                adapter.loadArg(0);
295                                adapter.loadLocal(currLocal);
296                                adapter.loadLocal(state);
297                                adapter.invokeVirtual(Types.PAGE_CONTEXT, INIT_BODY);
298                                
299                                
300                                OnFinally onFinally = new OnFinally() {
301                                        
302                                        public void _writeOut(BytecodeContext bc) {
303                                                Label endIf = new Label();
304                                                /*if(tlt.handleException() && fcf!=null && fcf.getAfterFinalGOTOLabel()!=null){
305                                                        ASMUtil.visitLabel(adapter, fcf.getFinalEntryLabel());
306                                                }*/
307                                                adapter.loadLocal(state);
308                                                adapter.push(javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE);
309                                                adapter.visitJumpInsn(Opcodes.IF_ICMPEQ, endIf);
310                                                        // ... pc.popBody();
311                                                        adapter.loadArg(0);
312                                                        adapter.invokeVirtual(Types.PAGE_CONTEXT, POP_BODY);
313                                                        adapter.pop();
314                                                adapter.visitLabel(endIf);
315                                        
316                                                // tag.doFinally();
317                                                if(tlt.handleException()) {
318                                                        adapter.loadLocal(currLocal);
319                                                        adapter.invokeVirtual(currType, DO_FINALLY);
320                                                }
321                                                // GOTO after execution body, used when a continue/break was called before
322                                                /*if(fcf!=null) {
323                                                        Label l = fcf.getAfterFinalGOTOLabel();
324                                                        if(l!=null)adapter.visitJumpInsn(Opcodes.GOTO, l);
325                                                }*/
326                                                
327                                        }
328                                };
329                                
330                                
331                                if(tlt.handleException()) {
332                                        TryCatchFinallyVisitor tcfv=new TryCatchFinallyVisitor(onFinally,fcf);
333                                        tcfv.visitTryBegin(bc);
334                                                doTry(bc,adapter,tag,currLocal,currType);
335                                        int t=tcfv.visitTryEndCatchBeging(bc);
336                                                // tag.doCatch(t);
337                                                adapter.loadLocal(currLocal);
338                                                adapter.loadLocal(t);
339                                                //adapter.visitVarInsn(Opcodes.ALOAD,t);
340                                                adapter.invokeVirtual(currType, DO_CATCH);
341                                        tcfv.visitCatchEnd(bc);
342                                }
343                                else {
344                                        TryFinallyVisitor tfv=new TryFinallyVisitor(onFinally,fcf);
345                                        tfv.visitTryBegin(bc);
346                                                doTry(bc,adapter,tag,currLocal,currType);
347                                        tfv.visitTryEnd(bc);
348                                }
349                                
350
351                        adapter.visitLabel(endBody);
352                                
353                }
354                else {
355                        //tag.doStartTag();
356                        adapter.loadLocal(currLocal);
357                        adapter.invokeVirtual(currType, DO_START_TAG);
358                        adapter.pop();
359                }
360                
361                // if (tag.doEndTag()==Tag.SKIP_PAGE) throw new Abort(0<!-- SCOPE_PAGE -->);
362                Label endDoEndTag=new Label();
363                adapter.loadLocal(currLocal);
364                adapter.invokeVirtual(currType, DO_END_TAG);
365                adapter.push(javax.servlet.jsp.tagext.Tag.SKIP_PAGE);
366                adapter.visitJumpInsn(Opcodes.IF_ICMPNE, endDoEndTag);
367                        adapter.push(Abort.SCOPE_PAGE);
368                        adapter.invokeStatic(ABORT, NEW_INSTANCE);
369                        adapter.throwException();
370                adapter.visitLabel(endDoEndTag);
371                
372                
373                if(doReuse) {
374                        // } finally{pc.reuse(tag);}
375                        outerTcfv.visitTryEnd(bc);
376                }
377                
378
379                adapter.visitLabel(tagEnd);
380                ExpressionUtil.visitLine(bc, tag.getEnd());
381        }
382        
383        private static void setAttributes(BytecodeContext bc, Tag tag, int currLocal, Type currType, boolean doDefault) throws BytecodeException {
384                GeneratorAdapter adapter = bc.getAdapter();
385                Map<String,Attribute> attributes = tag.getAttributes();
386
387                String methodName;
388                Attribute attr;
389                Iterator<Attribute> it = attributes.values().iterator();
390                while(it.hasNext()) {
391                        attr=it.next();
392                        if(doDefault!=attr.isDefaultAttribute()) continue;
393                        
394                        if(attr.isDynamicType()){
395                                adapter.loadLocal(currLocal);
396                                adapter.visitInsn(Opcodes.ACONST_NULL);
397                                //adapter.push(attr.getName());
398                                Variable.registerKey(bc, LitString.toExprString(attr.getName()));
399                                attr.getValue().writeOut(bc, Expression.MODE_REF);
400                                adapter.invokeVirtual(currType, SET_DYNAMIC_ATTRIBUTE);
401                        }
402                        else {
403                                Type type = CastOther.getType(attr.getType());
404                                methodName=tag.getTagLibTag().getSetter(attr,type);
405                                adapter.loadLocal(currLocal);
406                                attr.getValue().writeOut(bc, Types.isPrimitiveType(type)?Expression.MODE_VALUE:Expression.MODE_REF);
407                                adapter.invokeVirtual(currType, new Method(methodName,Type.VOID_TYPE,new Type[]{type}));
408                        }
409                }
410        }
411
412        private static void doTry(BytecodeContext bc, GeneratorAdapter adapter, Tag tag, int currLocal, Type currType) throws BytecodeException {
413                Label beginDoWhile=new Label();
414                adapter.visitLabel(beginDoWhile);
415                        bc.setCurrentTag(currLocal);
416                        tag.getBody().writeOut(bc);
417                        
418                // while (tag.doAfterBody()==BodyTag.EVAL_BODY_AGAIN);
419                        adapter.loadLocal(currLocal);
420                        adapter.invokeVirtual(currType, DO_AFTER_BODY);
421                        adapter.push(IterationTag.EVAL_BODY_AGAIN);
422                adapter.visitJumpInsn(Opcodes.IF_ICMPEQ, beginDoWhile);
423        }
424
425        private static Type getTagType(Tag tag) throws BytecodeException {
426                TagLibTag tlt = tag.getTagLibTag();
427                try {
428                        return tlt.getTagType();
429                } catch (ClassException e) {
430                        throw new BytecodeException(e,tag.getStart());
431                }
432        }
433}