/**
* Copyright (c) 2015, Lucee Assosication Switzerland. All rights reserved.
*
* 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.expression.var;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import lucee.commons.lang.StringUtil;
import lucee.commons.lang.types.RefInteger;
import lucee.commons.lang.types.RefIntegerImpl;
import lucee.runtime.db.ClassDefinition;
import lucee.runtime.exp.TemplateException;
import lucee.runtime.type.scope.Scope;
import lucee.runtime.type.util.ArrayUtil;
import lucee.runtime.type.util.UDFUtil;
import lucee.runtime.util.PageContextUtil;
import lucee.transformer.Context;
import lucee.transformer.Factory;
import lucee.transformer.Position;
import lucee.transformer.TransformerException;
import lucee.transformer.bytecode.BytecodeContext;
import lucee.transformer.bytecode.cast.CastOther;
import lucee.transformer.bytecode.expression.ExpressionBase;
import lucee.transformer.bytecode.util.ASMConstants;
import lucee.transformer.bytecode.util.ASMUtil;
import lucee.transformer.bytecode.util.ExpressionUtil;
import lucee.transformer.bytecode.util.TypeScope;
import lucee.transformer.bytecode.util.Types;
import lucee.transformer.bytecode.visitor.ArrayVisitor;
import lucee.transformer.expression.ExprString;
import lucee.transformer.expression.Expression;
import lucee.transformer.expression.literal.LitString;
import lucee.transformer.expression.var.DataMember;
import lucee.transformer.expression.var.Member;
import lucee.transformer.expression.var.Variable;
import lucee.transformer.library.function.FunctionLibFunction;
import lucee.transformer.library.function.FunctionLibFunctionArg;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
public class VariableImpl extends ExpressionBase implements Variable {
// java.lang.Object get(Key)
final static Method METHOD_SCOPE_GET_KEY = new Method("get",Types.OBJECT,new Type[]{Types.COLLECTION_KEY});
// Object getCollection(Key)
final static Method METHOD_SCOPE_GET_COLLECTION_KEY= new Method("getCollection",Types.OBJECT,new Type[]{Types.COLLECTION_KEY});
//public Object get(PageContext pc,Object coll, Key[] keys, Object defaultValue) {
/*???*/private final static Method CALLER_UTIL_GET = new Method("get",Types.OBJECT, new Type[]{Types.PAGE_CONTEXT,Types.OBJECT,Types.COLLECTION_KEY_ARRAY,Types.OBJECT});
final static Method INIT= new Method("init",
Types.COLLECTION_KEY,
new Type[]{Types.STRING});
final static Method TO_KEY= new Method("toKey",
Types.COLLECTION_KEY,
new Type[]{Types.OBJECT});
private static final int TWO=0;
private static final int THREE=1;
private static final int THREE2=2;
// Object getCollection (Object,Key[,Object])
private final static Method[] GET_COLLECTION =new Method[]{
new Method("getCollection",Types.OBJECT,new Type[]{Types.OBJECT,Types.COLLECTION_KEY}),
new Method("getCollection",Types.OBJECT,new Type[]{Types.OBJECT,Types.COLLECTION_KEY,Types.OBJECT})
};
// Object get (Object,Key)
private final static Method[] GET = new Method[]{
new Method("get",Types.OBJECT,new Type[]{Types.OBJECT,Types.COLLECTION_KEY}),
new Method("get",Types.OBJECT,new Type[]{Types.OBJECT,Types.COLLECTION_KEY,Types.OBJECT})
};
private final static Method[] GET_FUNCTION = new Method[]{
new Method("getFunction",Types.OBJECT,new Type[]{Types.OBJECT,Types.COLLECTION_KEY,Types.OBJECT_ARRAY}),
new Method("getFunction",Types.OBJECT,new Type[]{Types.OBJECT,Types.COLLECTION_KEY,Types.OBJECT_ARRAY,Types.OBJECT}),
new Method("getFunction2",Types.OBJECT,new Type[]{Types.OBJECT,Types.COLLECTION_KEY,Types.OBJECT_ARRAY,Types.OBJECT})
};
// Object getFunctionWithNamedValues (Object,String,Object[])
private final static Method[] GET_FUNCTION_WITH_NAMED_ARGS = new Method[]{
new Method("getFunctionWithNamedValues",Types.OBJECT,new Type[]{Types.OBJECT,Types.COLLECTION_KEY,Types.OBJECT_ARRAY}),
new Method("getFunctionWithNamedValues",Types.OBJECT,new Type[]{Types.OBJECT,Types.COLLECTION_KEY,Types.OBJECT_ARRAY,Types.OBJECT}),
new Method("getFunctionWithNamedValues2",Types.OBJECT,new Type[]{Types.OBJECT,Types.COLLECTION_KEY,Types.OBJECT_ARRAY,Types.OBJECT})
};
private static final Method RECORDCOUNT = new Method("recordcount",
Types.OBJECT,
new Type[]{Types.PAGE_CONTEXT,Types.OBJECT});
private static final Method CURRENTROW = new Method("currentrow",
Types.OBJECT,
new Type[]{Types.PAGE_CONTEXT,Types.OBJECT});
private static final Method COLUMNLIST = new Method("columnlist",
Types.OBJECT,
new Type[]{Types.PAGE_CONTEXT,Types.OBJECT});
// THIS
private static final Method THIS_GET0 = new Method("thisGet",Types.OBJECT,new Type[]{});
private static final Method THIS_TOUCH0 = new Method("thisTouch",Types.OBJECT,new Type[]{});
private static final Method THIS_GET1 = new Method("thisGet",Types.OBJECT,new Type[]{Types.OBJECT});
private static final Method THIS_TOUCH1 = new Method("thisTouch",Types.OBJECT,new Type[]{Types.OBJECT});
// STATIC
private static final Method STATIC_GET0 = new Method("staticGet",Types.OBJECT,new Type[]{});
private static final Method STATIC_TOUCH0 = new Method("staticTouch",Types.OBJECT,new Type[]{});
private static final Method STATIC_GET1 = new Method("staticGet",Types.OBJECT,new Type[]{Types.OBJECT});
private static final Method STATIC_TOUCH1 = new Method("staticTouch",Types.OBJECT,new Type[]{Types.OBJECT});
private static final Method INVOKE = new Method("invoke",Types.OBJECT,
new Type[]{Types.PAGE_CONTEXT,Types.OBJECT_ARRAY,Types.STRING,Types.STRING,Types.STRING});
private int scope=Scope.SCOPE_UNDEFINED;
List<Member> members=new ArrayList<Member>();
int countDM=0;
int countFM=0;
private boolean ignoredFirstMember;
private boolean fromHash=false;
private Expression defaultValue;
private Boolean asCollection;
private Assign assign;
public VariableImpl(Factory factory,Position start,Position end) {
super(factory,start,end);
}
public VariableImpl(Factory factory,int scope,Position start,Position end) {
super(factory,start,end);
this.scope=scope;
}
@Override
public Expression getDefaultValue() {
return defaultValue;
}
@Override
public void setDefaultValue(Expression defaultValue) {
this.defaultValue = defaultValue;
}
@Override
public Boolean getAsCollection() {
return asCollection;
}
@Override
public void setAsCollection(Boolean asCollection) {
this.asCollection = asCollection;
}
@Override
public int getScope() {
return scope;
}
/**
* @param scope the scope to set
*/
public void setScope(int scope) {
this.scope = scope;
}
@Override
public void addMember(Member member) {
if(member instanceof DataMember)countDM++;
else countFM++;
member.setParent(this);
members.add(member);
}
@Override
public Member removeMember(int index) {
Member rtn = members.remove(index);
if(rtn instanceof DataMember)countDM--;
else countFM--;
return rtn;
}
@Override
public final Type writeOutCollection(Context c, int mode) throws TransformerException {
BytecodeContext bc=(BytecodeContext) c;
ExpressionUtil.visitLine(bc, getStart());
Type type = _writeOut(bc,mode, Boolean.TRUE);
ExpressionUtil.visitLine(bc, getEnd());
return type;
}
@Override
public Type _writeOut(BytecodeContext bc, int mode) throws TransformerException {
if(defaultValue!=null && countFM==0 && countDM!=0)
return _writeOutCallerUtil(bc, mode);
return _writeOut(bc, mode, asCollection);
}
private Type _writeOut(BytecodeContext bc, int mode,Boolean asCollection) throws TransformerException {
GeneratorAdapter adapter = bc.getAdapter();
final int count=countFM+countDM;
// count 0
if(count==0) return _writeOutEmpty(bc);
boolean doOnlyScope=scope==Scope.SCOPE_LOCAL;
//boolean last;
int c=0;
for(int i=doOnlyScope?0:1;i<count;i++) {
Member member=(members.get((count-1)-c));
c++;
adapter.loadArg(0);
if(member.getSafeNavigated() && member instanceof UDF) adapter.checkCast(Types.PAGE_CONTEXT_IMPL);
}
Type rtn=_writeOutFirst(bc, (members.get(0)),mode,count==1,doOnlyScope,null,null);
// pc.get(
for(int i=doOnlyScope?0:1;i<count;i++) {
Member member=(members.get(i));
boolean last=(i+1)==count;
// Data Member
if(member instanceof DataMember) {
ExprString name = ((DataMember)member).getName();
if(last && ASMUtil.isDotKey(name)){
LitString ls = (LitString)name;
if(ls.getString().equalsIgnoreCase("RECORDCOUNT")){
adapter.invokeStatic(Types.VARIABLE_UTIL_IMPL, RECORDCOUNT);
}
else if(ls.getString().equalsIgnoreCase("CURRENTROW")){
adapter.invokeStatic(Types.VARIABLE_UTIL_IMPL, CURRENTROW);
}
else if(ls.getString().equalsIgnoreCase("COLUMNLIST")){
adapter.invokeStatic(Types.VARIABLE_UTIL_IMPL, COLUMNLIST);
}
else {
getFactory().registerKey(bc,name,false);
// safe nav
int type;
if(member.getSafeNavigated()) {
Expression val = member.getSafeNavigatedValue();
if(val==null)ASMConstants.NULL(adapter);
else val.writeOut(bc, Expression.MODE_REF);
type=THREE;
}
else type=TWO;
adapter.invokeVirtual(Types.PAGE_CONTEXT,asCollection(asCollection, last)?GET_COLLECTION[type]:GET[type]);
}
}
else{
getFactory().registerKey(bc,name,false);
// safe nav
int type;
if(member.getSafeNavigated()) {
Expression val = member.getSafeNavigatedValue();
if(val==null)ASMConstants.NULL(adapter);
else val.writeOut(bc, Expression.MODE_REF);
type=THREE;
}
else type=TWO;
adapter.invokeVirtual(Types.PAGE_CONTEXT,asCollection(asCollection, last)?GET_COLLECTION[type]:GET[type]);
}
rtn=Types.OBJECT;
}
// UDF
else if(member instanceof UDF) {
rtn= _writeOutUDF(bc,(UDF) member);
}
}
return rtn;
}
private Type _writeOutCallerUtil(BytecodeContext bc, int mode) throws TransformerException {
GeneratorAdapter adapter = bc.getAdapter();
final int count=countFM+countDM;
// count 0
if(count==0) return _writeOutEmpty(bc);
// pc
adapter.loadArg(0);
// collection
RefInteger startIndex=new RefIntegerImpl();
_writeOutFirst(bc, (members.get(0)),mode,count==1,true,defaultValue,startIndex);
// keys
Iterator<Member> it = members.iterator();
ArrayVisitor av=new ArrayVisitor();
av.visitBegin(adapter,Types.COLLECTION_KEY,countDM-startIndex.toInt());
int index=0, i=0;
while(it.hasNext()) {
DataMember member=(DataMember) it.next();
if(i++<startIndex.toInt()) continue;
av.visitBeginItem(adapter, index++);
getFactory().registerKey(bc,member.getName(),false);
av.visitEndItem(bc.getAdapter());
}
av.visitEnd();
// defaultValue
defaultValue.writeOut(bc, MODE_REF);
bc.getAdapter().invokeStatic(Types.CALLER_UTIL, CALLER_UTIL_GET);
return Types.OBJECT;
}
private boolean asCollection(Boolean asCollection, boolean last) {
if(!last) return true;
return asCollection!=null && asCollection.booleanValue();
}
/**
* outputs a empty Variable, only scope
* Example: pc.formScope();
* @param adapter
* @throws TemplateException
*/
private Type _writeOutEmpty(BytecodeContext bc) throws TransformerException {
if(ignoredFirstMember && (scope==Scope.SCOPE_LOCAL || scope==Scope.SCOPE_VAR))
return Types.VOID;
GeneratorAdapter adapter = bc.getAdapter();
adapter.loadArg(0);
Method m;
Type t=Types.PAGE_CONTEXT;
if(scope==Scope.SCOPE_ARGUMENTS) {
getFactory().TRUE().writeOut(bc, MODE_VALUE);
m = TypeScope.METHOD_ARGUMENT_BIND;
}
else if(scope==Scope.SCOPE_LOCAL) {
t=Types.PAGE_CONTEXT;
getFactory().TRUE().writeOut(bc, MODE_VALUE);
m = TypeScope.METHOD_LOCAL_BIND;
}
else if(scope==Scope.SCOPE_VAR) {
t=Types.PAGE_CONTEXT;
getFactory().TRUE().writeOut(bc, MODE_VALUE);
m = TypeScope.METHOD_VAR_BIND;
}
else m = TypeScope.METHODS[scope];
TypeScope.invokeScope(adapter,m,t);
return m.getReturnType();
}
private Type _writeOutFirst(BytecodeContext bc, Member member, int mode, boolean last, boolean doOnlyScope, Expression defaultValue, RefInteger startIndex) throws TransformerException {
if(member instanceof DataMember)
return _writeOutFirstDataMember(bc,(DataMember)member, scope,last , doOnlyScope,defaultValue,startIndex);
else if(member instanceof UDF)
return _writeOutFirstUDF(bc,(UDF)member,scope,doOnlyScope);
else
return _writeOutFirstBIF(bc,(BIF)member,mode,last,getStart());
}
static Type _writeOutFirstBIF(BytecodeContext bc, BIF bif, int mode,boolean last,Position line) throws TransformerException {
GeneratorAdapter adapter = bc.getAdapter();
adapter.loadArg(0);
// class
ClassDefinition bifCD = bif.getClassDefinition();
Class clazz=null;
try {
clazz = bifCD.getClazz();
}
catch (Exception e) {e.printStackTrace();
//throw new TransformerException(e,line);
}
Type rtnType=Types.toType(bif.getReturnType());
if(rtnType==Types.VOID)rtnType=Types.STRING;
// arguments
Argument[] args = bif.getArguments();
Type[] argTypes;
boolean core=bif.getFlf().isCore(); // MUST setting this to false need to work !!!
if(bif.getArgType()==FunctionLibFunction.ARG_FIX && !bifCD.isBundle() && core) {
if(isNamed(bif.getFlf().getName(),args)) {
NamedArgument[] nargs=toNamedArguments(args);
String[] names=new String[nargs.length];
// get all names
for(int i=0;i<nargs.length;i++){
names[i] = getName(nargs[i].getName());
}
ArrayList<FunctionLibFunctionArg> list = bif.getFlf().getArg();
Iterator<FunctionLibFunctionArg> it = list.iterator();
argTypes=new Type[list.size()+1];
argTypes[0]=Types.PAGE_CONTEXT;
FunctionLibFunctionArg flfa;
int index=0;
VT vt;
while(it.hasNext()) {
flfa =it.next();
vt = getMatchingValueAndType(bc.getFactory(),flfa,nargs,names,line);
if(vt.index!=-1)
names[vt.index]=null;
argTypes[++index]=Types.toType(vt.type);
if(vt.value==null)ASMConstants.NULL(bc.getAdapter());
else vt.value.writeOut(bc, Types.isPrimitiveType(argTypes[index])?MODE_VALUE:MODE_REF);
}
for(int y=0;y<names.length;y++){
if(names[y]!=null) {
TransformerException bce = new TransformerException("argument ["+names[y]+"] is not allowed for function ["+bif.getFlf().getName()+"]", args[y].getStart());
UDFUtil.addFunctionDoc(bce, bif.getFlf());
throw bce;
}
}
}
else {
argTypes=new Type[args.length+1];
argTypes[0]=Types.PAGE_CONTEXT;
for(int y=0;y<args.length;y++) {
argTypes[y+1]=Types.toType(args[y].getStringType());
args[y].writeOutValue(bc, Types.isPrimitiveType(argTypes[y+1])?MODE_VALUE:MODE_REF);
}
// if no method exists for the exact match of arguments, call the method with all arguments (when exists)
if(methodExists(clazz,"call",argTypes,rtnType)==Boolean.FALSE) {
ArrayList<FunctionLibFunctionArg> _args = bif.getFlf().getArg();
Type[] tmp = new Type[_args.size()+1];
// fill the existing
for(int i=0;i<argTypes.length;i++){
tmp[i]=argTypes[i];
}
// get the rest with default values
FunctionLibFunctionArg flfa;
VT def;
for(int i=argTypes.length;i<tmp.length;i++){
flfa = _args.get(i-1);
tmp[i]=Types.toType(flfa.getTypeAsString());
def = getDefaultValue(bc.getFactory(),flfa);
if(def.value!=null)def.value.writeOut(bc, Types.isPrimitiveType(tmp[i])?MODE_VALUE:MODE_REF);
else ASMConstants.NULL(bc.getAdapter());
}
argTypes=tmp;
}
}
}
// Arg Type DYN or bundle based
else {
///////////////////////////////////////////////////////////////
if(bif.getArgType()==FunctionLibFunction.ARG_FIX) {
if(isNamed(bif.getFlf().getName(),args)) {
NamedArgument[] nargs=toNamedArguments(args);
String[] names=getNames(nargs);
ArrayList<FunctionLibFunctionArg> list = bif.getFlf().getArg();
Iterator<FunctionLibFunctionArg> it = list.iterator();
LinkedList<Argument> tmpArgs = new LinkedList<Argument>();
LinkedList<Boolean> nulls = new LinkedList<Boolean>();
FunctionLibFunctionArg flfa;
VT vt;
while(it.hasNext()) {
flfa =it.next();
vt = getMatchingValueAndType(bc.getFactory(),flfa,nargs,names,line);
if(vt.index!=-1)
names[vt.index]=null;
if(vt.value==null)
tmpArgs.add(new Argument(bif.getFactory().createNull(),"any")); // has to by any otherwise a caster is set
else
tmpArgs.add(new Argument(vt.value, vt.type));
nulls.add(vt.value==null);
}
for(int y=0;y<names.length;y++){
if(names[y]!=null) {
TransformerException bce = new TransformerException("argument ["+names[y]+"] is not allowed for function ["+bif.getFlf().getName()+"]", args[y].getStart());
UDFUtil.addFunctionDoc(bce, bif.getFlf());
throw bce;
}
}
// remove null at the end
Boolean tmp;
while((tmp=nulls.pollLast())!=null) {
if(!tmp.booleanValue()) break;
tmpArgs.pollLast();
}
args=tmpArgs.toArray(new Argument[tmpArgs.size()]);
}
}
///////////////////////////////////////////////////////////////
argTypes=new Type[2];
argTypes[0]=Types.PAGE_CONTEXT;
argTypes[1]=Types.OBJECT_ARRAY;
ExpressionUtil.writeOutExpressionArray(bc, Types.OBJECT, args);
}
// core
if(core && !bifCD.isBundle()) {
adapter.invokeStatic(Type.getType(clazz),new Method("call",rtnType,argTypes));
}
// external
else {
//in that case we need 3 addional args
// className
if(bifCD.getClassName()!=null)adapter.push(bifCD.getClassName());
else ASMConstants.NULL(adapter);
if(bifCD.getName()!=null)adapter.push(bifCD.getName());// bundle name
else ASMConstants.NULL(adapter);
if(bifCD.getVersionAsString()!=null)adapter.push(bifCD.getVersionAsString());// bundle version
else ASMConstants.NULL(adapter);
adapter.invokeStatic(Types.FUNCTION_HANDLER_POOL,INVOKE);
rtnType=Types.OBJECT;
}
if(mode==MODE_REF || !last) {
if(Types.isPrimitiveType(rtnType)) {
adapter.invokeStatic(Types.CASTER,new Method("toRef",Types.toRefType(rtnType),new Type[]{rtnType}));
rtnType=Types.toRefType(rtnType);
}
}
return rtnType;
}
/**
* checks if a method exists
* @param clazz
* @param methodName
* @param args
* @param returnType
* @return returns null when checking fi
*/
private static Boolean methodExists(Class clazz, String methodName, Type[] args, Type returnType) {
try {
//Class _clazz=Types.toClass(clazz);
Class<?>[] _args=new Class[args.length];
for(int i=0;i<_args.length;i++){
_args[i]=Types.toClass(args[i]);
}
Class<?> rtn = Types.toClass(returnType);
try {
java.lang.reflect.Method m = clazz.getMethod(methodName, _args);
return m.getReturnType()==rtn;
}
catch (Exception e) {
return false;
}
}
catch (Exception e) {e.printStackTrace();
return null;
}
}
static Type _writeOutFirstUDF(BytecodeContext bc, UDF udf, int scope, boolean doOnlyScope) throws TransformerException {
GeneratorAdapter adapter = bc.getAdapter();
// pc.getFunction (Object,String,Object[])
// pc.getFunctionWithNamedValues (Object,String,Object[])
adapter.loadArg(0);
if(udf.getSafeNavigated()) adapter.checkCast(Types.PAGE_CONTEXT_IMPL);// FUTURE remove if no longer necessary to have PageContextImpl
if(!doOnlyScope)adapter.loadArg(0);
Type rtn = TypeScope.invokeScope(adapter, scope);
if(doOnlyScope) return rtn;
return _writeOutUDF(bc,udf);
}
private static Type _writeOutUDF(BytecodeContext bc, UDF udf) throws TransformerException {
bc.getFactory().registerKey(bc,udf.getName(),false);
Argument[] args = udf.getArguments();
// no arguments
if(args.length==0) {
bc.getAdapter().getStatic(Types.CONSTANTS, "EMPTY_OBJECT_ARRAY", Types.OBJECT_ARRAY);
}
else ExpressionUtil.writeOutExpressionArray(bc, Types.OBJECT, args);
int type;
if(udf.getSafeNavigated()) {
type=THREE;
Expression val = udf.getSafeNavigatedValue();
if(val==null) {
ASMConstants.NULL(bc.getAdapter());
type=THREE;
}
else {
val.writeOut(bc, Expression.MODE_REF);
type=THREE2;
}
}
else type=TWO;
bc.getAdapter().invokeVirtual(udf.getSafeNavigated()?Types.PAGE_CONTEXT_IMPL:Types.PAGE_CONTEXT,
udf.hasNamedArgs()?GET_FUNCTION_WITH_NAMED_ARGS[type]:GET_FUNCTION[type]);
return Types.OBJECT;
}
Type _writeOutFirstDataMember(BytecodeContext bc, DataMember member, int scope, boolean last, boolean doOnlyScope, Expression defaultValue, RefInteger startIndex) throws TransformerException {
GeneratorAdapter adapter = bc.getAdapter();
if(startIndex!=null)startIndex.setValue(doOnlyScope?0:1);
// this/static
if(scope==Scope.SCOPE_UNDEFINED) {
ExprString name = member.getName();
if(ASMUtil.isDotKey(name)){
LitString ls = (LitString)name;
// THIS
if(ls.getString().equalsIgnoreCase("THIS")){
if(startIndex!=null)startIndex.setValue(1);
adapter.loadArg(0);
adapter.checkCast(Types.PAGE_CONTEXT_IMPL);
if(defaultValue!=null) {
defaultValue.writeOut(bc, MODE_REF);
adapter.invokeVirtual(Types.PAGE_CONTEXT_IMPL,(countFM+countDM)==1?THIS_GET1:THIS_TOUCH1);
}
else adapter.invokeVirtual(Types.PAGE_CONTEXT_IMPL,(countFM+countDM)==1?THIS_GET0:THIS_TOUCH0);
return Types.OBJECT;
}
// STATIC
if(ls.getString().equalsIgnoreCase("STATIC")){
if(startIndex!=null)startIndex.setValue(1);
adapter.loadArg(0);
adapter.checkCast(Types.PAGE_CONTEXT_IMPL);
if(defaultValue!=null) {
defaultValue.writeOut(bc, MODE_REF);
adapter.invokeVirtual(Types.PAGE_CONTEXT_IMPL,(countFM+countDM)==1?STATIC_GET1:STATIC_TOUCH1);
}
else adapter.invokeVirtual(Types.PAGE_CONTEXT_IMPL,(countFM+countDM)==1?STATIC_GET0:STATIC_TOUCH0);
return Types.OBJECT;
}
}
}
if(member.getSafeNavigated())
adapter.loadArg(0);
// collection
Type rtn;
if(scope==Scope.SCOPE_LOCAL && defaultValue!=null) { // local
adapter.loadArg(0);
adapter.checkCast(Types.PAGE_CONTEXT_IMPL);
getFactory().FALSE().writeOut(bc, MODE_VALUE);
defaultValue.writeOut(bc, MODE_VALUE);
adapter.invokeVirtual(Types.PAGE_CONTEXT_IMPL, TypeScope.METHOD_LOCAL_EL);
rtn= Types.OBJECT;
}
else { // all other scopes
adapter.loadArg(0);
rtn = TypeScope.invokeScope(adapter, scope);
}
if(doOnlyScope) return rtn;
getFactory().registerKey(bc,member.getName(),false);
boolean _last=!last && scope==Scope.SCOPE_UNDEFINED;
if(!member.getSafeNavigated()) {
adapter.invokeInterface(TypeScope.SCOPES[scope],
_last?METHOD_SCOPE_GET_COLLECTION_KEY:METHOD_SCOPE_GET_KEY);
}
else {
Expression val = member.getSafeNavigatedValue();
if(val==null)ASMConstants.NULL(bc.getAdapter());
else val.writeOut(bc, Expression.MODE_REF);
adapter.invokeVirtual(Types.PAGE_CONTEXT,_last?GET_COLLECTION[THREE]:GET[THREE]);
}
return Types.OBJECT;
}
@Override
public List<Member> getMembers() {
return members;
}
@Override
public Member getFirstMember() {
if(members.isEmpty()) return null;
return members.get(0);
}
@Override
public Member getLastMember() {
if(members.isEmpty()) return null;
return members.get(members.size()-1);
}
@Override
public void ignoredFirstMember(boolean b) {
this.ignoredFirstMember=b;
}
@Override
public boolean ignoredFirstMember() {
return ignoredFirstMember;
}
private static VT getMatchingValueAndType(Factory factory,FunctionLibFunctionArg flfa, NamedArgument[] nargs,String[] names, Position line) throws TransformerException {
String flfan=flfa.getName();
// first search if a argument match
for(int i=0;i<nargs.length;i++){
if(names[i]!=null && names[i].equalsIgnoreCase(flfan)) {
nargs[i].setValue(nargs[i].getRawValue(),flfa.getTypeAsString());
return new VT(nargs[i].getValue(),flfa.getTypeAsString(),i);
}
}
// then check if a alias match
String alias=flfa.getAlias();
if(!StringUtil.isEmpty(alias)) {
//String[] arrAlias = lucee.runtime.type.List.toStringArray(lucee.runtime.type.List.trimItems(lucee.runtime.type.List.listToArrayRemoveEmpty(alias, ',')));
for(int i=0;i<nargs.length;i++){
if(names[i]!=null && lucee.runtime.type.util.ListUtil.listFindNoCase(alias, names[i], ",")!=-1){
nargs[i].setValue(nargs[i].getRawValue(),flfa.getTypeAsString());
return new VT(nargs[i].getValue(),flfa.getTypeAsString(),i);
}
}
}
// if not required return the default value
if(!flfa.getRequired()) {
return getDefaultValue(factory,flfa);
}
TransformerException be = new TransformerException("missing required argument ["+flfan+"] for function ["+flfa.getFunction().getName()+"]",line);
UDFUtil.addFunctionDoc(be, flfa.getFunction());
throw be;
}
private static VT getDefaultValue(Factory factory,FunctionLibFunctionArg flfa) {
String defaultValue = flfa.getDefaultValue();
String type = flfa.getTypeAsString();
if(defaultValue==null) {
if(type.equals("boolean") || type.equals("bool"))
return new VT(factory.FALSE(),type,-1);
if(type.equals("number") || type.equals("numeric") || type.equals("double"))
return new VT(factory.DOUBLE_ZERO(),type,-1);
return new VT(null,type,-1);
}
return new VT(CastOther.toExpression(factory.createLitString(defaultValue), type),type,-1);
}
private static String getName(Expression expr) throws TransformerException {
String name = ASMUtil.toString(expr);
if(name==null) throw new TransformerException("cannot extract a string from a object of type ["+expr.getClass().getName()+"]",null);
return name;
}
private static String[] getNames(NamedArgument[] args) throws TransformerException {
String[] names=new String[args.length];
for(int i=0;i<args.length;i++){
names[i] = getName(args[i].getName());
}
return names;
}
/**
* translate a array of arguments to a araay of NamedArguments, attention no check if the elements are really named arguments
* @param args
* @return
*/
private static NamedArgument[] toNamedArguments(Argument[] args) {
NamedArgument[] nargs=new NamedArgument[args.length];
for(int i=0;i<args.length;i++){
nargs[i]=(NamedArgument) args[i];
}
return nargs;
}
/**
* check if the arguments are named arguments or regular arguments, throws a exception when mixed
* @param funcName
* @param args
* @param line
* @return
* @throws TransformerException
*/
private static boolean isNamed(String funcName,Argument[] args) throws TransformerException {
if(ArrayUtil.isEmpty(args)) return false;
boolean named=false;
for(int i=0;i<args.length;i++){
if(args[i] instanceof NamedArgument)named=true;
else if(named)
throw new TransformerException("invalid argument for function "+funcName+", you can not mix named and unnamed arguments", args[i].getStart());
}
return named;
}
@Override
public void fromHash(boolean fromHash) {
this.fromHash=fromHash;
}
@Override
public boolean fromHash() {
return fromHash;
}
@Override
public int getCount() {
return countDM+countFM;
}
@Override
public void assign(Assign assign) {
this.assign=assign;
}
@Override
public Assign assign() {
return assign;
}
}
class VT {
Expression value;
String type;
int index;
public VT(Expression value, String type, int index) {
this.value=value;
this.type=type;
this.index=index;
}
}