/**
* 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.runtime.interpreter;
import lucee.commons.lang.ParserString;
import lucee.commons.lang.StringList;
import lucee.commons.lang.StringUtil;
import lucee.runtime.PageContext;
import lucee.runtime.PageContextImpl;
import lucee.runtime.config.NullSupportHelper;
import lucee.runtime.exp.PageException;
import lucee.runtime.op.Caster;
import lucee.runtime.type.Collection;
import lucee.runtime.type.KeyImpl;
import lucee.runtime.type.ref.VariableReference;
import lucee.runtime.type.scope.Argument;
import lucee.runtime.type.scope.CallerImpl;
import lucee.runtime.type.scope.Local;
import lucee.runtime.type.scope.Scope;
import lucee.runtime.type.scope.Undefined;
import lucee.runtime.type.scope.UndefinedImpl;
import lucee.runtime.type.scope.Variables;
import lucee.runtime.type.util.KeyConstants;
import lucee.runtime.type.util.ListUtil;
/**
* Class to check and interpret Variable Strings
*/
public final class VariableInterpreter {
private static final Object NULL = new Object();
/**
* reads a subelement from a struct
* @param pc
* @param collection
* @param var
* @return matching Object
* @throws PageException
*/
public static Object getVariable(PageContext pc, Collection collection,String var) throws PageException {
StringList list = parse(pc,new ParserString(var),false);
if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
while(list.hasNextNext()) {
collection=Caster.toCollection(collection.get(KeyImpl.init(list.next())));
}
return collection.get(KeyImpl.init(list.next()));
}
public static String scopeInt2String(int type) {
switch(type) {
case Scope.SCOPE_APPLICATION: return "application";
case Scope.SCOPE_ARGUMENTS: return "arguments";
case Scope.SCOPE_CGI: return "cgi";
case Scope.SCOPE_COOKIE: return "cookie";
case Scope.SCOPE_CLIENT: return "client";
case Scope.SCOPE_FORM: return "form";
case Scope.SCOPE_REQUEST: return "request";
case Scope.SCOPE_SESSION: return "session";
case Scope.SCOPE_SERVER: return "server";
case Scope.SCOPE_URL: return "url";
case Scope.SCOPE_VARIABLES: return "variables";
case Scope.SCOPE_CLUSTER: return "cluster";
case Scope.SCOPE_LOCAL: return "local";
}
return null;
}
public static Object getVariableEL(PageContext pc, Collection collection,String var) {
StringList list = parse(pc,new ParserString(var),false);
if(list==null) return null;
while(list.hasNextNext()) {
collection=Caster.toCollection(collection.get(list.next(),null),null);
if(collection==null) return null;
}
return collection.get(list.next(),null);
}
/**
* get a variable from page context
* @param pc Page Context
* @param var variable string to get value to
* @return the value
* @throws PageException
*/
public static Object getVariable(PageContext pc,String var) throws PageException {
StringList list = parse(pc,new ParserString(var),false);
if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
int scope=scopeString2Int(pc.ignoreScopes(),list.next());
Object coll =null;
if(scope==Scope.SCOPE_UNDEFINED) {
coll=pc.undefinedScope().get(list.current());
}
else {
coll=VariableInterpreter.scope(pc, scope, list.hasNext());
}
while(list.hasNext()) {
coll=pc.getVariableUtil().get(pc,coll,list.next());
}
return coll;
}
public static Object getVariableAsCollection(PageContext pc,String var) throws PageException {
StringList list = parse(pc,new ParserString(var),false);
if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
int scope=scopeString2Int(pc.ignoreScopes(),list.next());
Object coll =null;
if(scope==Scope.SCOPE_UNDEFINED) {
coll=pc.undefinedScope().getCollection(list.current());
}
else {
coll=VariableInterpreter.scope(pc, scope, list.hasNext());
}
while(list.hasNext()) {
coll=pc.getVariableUtil().getCollection(pc,coll,list.next());
}
return coll;
}
public static Object getVariable(PageContext pc,String str,Scope scope) throws PageException {
return _variable(pc, str,NULL, scope);
}
public static Object setVariable(PageContext pc,String str,Object value,Scope scope) throws PageException {
return _variable(pc, str,value, scope);
}
public static Object _variable(PageContext pc,String str,Object value,Scope scope) throws PageException {
// define a ohter enviroment for the function
if(scope!=null){
// Variables Scope
Variables var=null;
if(scope instanceof Variables){
var=(Variables) scope;
}
else if(scope instanceof CallerImpl){
var=((CallerImpl) scope).getVariablesScope();
}
if(var!=null){
Variables current=pc.variablesScope();
pc.setVariablesScope(var);
try{
if(value!=NULL) return setVariable(pc, str,value);
return getVariable(pc, str);
}
finally{
pc.setVariablesScope(current);
}
}
// Undefined Scope
else if(scope instanceof Undefined) {
PageContextImpl pci=(PageContextImpl) pc;
Undefined undefined=(Undefined) scope;
boolean check=undefined.getCheckArguments();
Variables orgVar=pc.variablesScope();
Argument orgArgs=pc.argumentsScope();
Local orgLocal=pc.localScope();
pci.setVariablesScope(undefined.variablesScope());
if(check)pci.setFunctionScopes(undefined.localScope(), undefined.argumentsScope());
try{
if(value!=NULL) return setVariable(pc, str,value);
return getVariable(pc, str);
}
finally{
pc.setVariablesScope(orgVar);
if(check)pci.setFunctionScopes(orgLocal,orgArgs);
}
}
}
if(value!=NULL) return setVariable(pc, str,value);
return getVariable(pc, str);
}
/**
* get a variable from page context
* @param pc Page Context
* @param var variable string to get value to
* @param defaultValue value returnded if variable was not found
* @return the value or default value if not found
*/
public static Object getVariableEL(PageContext pc,String var, Object defaultValue) {
StringList list = parse(pc,new ParserString(var),false);
if(list==null) return defaultValue;
int scope=scopeString2Int(pc.ignoreScopes(),list.next());
Object coll =null;
if(scope==Scope.SCOPE_UNDEFINED) {
coll=pc.undefinedScope().get(KeyImpl.init(list.current()),NullSupportHelper.NULL());
if(coll==NullSupportHelper.NULL()) return defaultValue;
}
else {
try {
coll=VariableInterpreter.scope(pc, scope, list.hasNext());
//coll=pc.scope(scope);
}
catch (PageException e) {
return defaultValue;
}
}
while(list.hasNext()) {
coll=pc.getVariableUtil().get(pc,coll,KeyImpl.init(list.next()),NullSupportHelper.NULL());
if(coll==NullSupportHelper.NULL()) return defaultValue;
}
return coll;
}
public static Object getVariableELAsCollection(PageContext pc,String var, Object defaultValue) {
StringList list = parse(pc,new ParserString(var),false);
if(list==null) return defaultValue;
int scope=scopeString2Int(pc.ignoreScopes(),list.next());
Object coll =null;
if(scope==Scope.SCOPE_UNDEFINED) {
try {
coll=pc.undefinedScope().getCollection(list.current());
}
catch (PageException e) {
coll=null;
}
if(coll==null) return defaultValue;
}
else {
try {
coll=VariableInterpreter.scope(pc, scope, list.hasNext());
//coll=pc.scope(scope);
}
catch (PageException e) {
return defaultValue;
}
}
while(list.hasNext()) {
coll=pc.getVariableUtil().getCollection(pc,coll,list.next(),null);
if(coll==null) return defaultValue;
}
return coll;
}
/**
* return a variable reference by string syntax ("scopename.key.key" -> "url.name")
* a variable reference, references to variable, to modifed it, with global effect.
* @param pc
* @param var variable name to get
* @return variable as Reference
* @throws PageException
*/
public static VariableReference getVariableReference(PageContext pc,String var) throws PageException {
StringList list = parse(pc,new ParserString(var),false);
if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
if(list.size()==1) {
return new VariableReference(pc.undefinedScope(),list.next());
}
int scope=scopeString2Int(pc.ignoreScopes(),list.next());
Object coll;
if(scope==Scope.SCOPE_UNDEFINED){
coll=pc.touch(pc.undefinedScope(),KeyImpl.init(list.current()));
}
else{
coll=VariableInterpreter.scope(pc, scope, list.hasNext());
//coll=pc.scope(scope);
}
while(list.hasNextNext()) {
coll=pc.touch(coll,KeyImpl.init(list.next()));
}
if(!(coll instanceof Collection))
throw new InterpreterException("invalid variable ["+var+"]");
return new VariableReference((Collection)coll,list.next());
}
public static VariableReference getVariableReference(PageContext pc,Collection.Key key, boolean keepScope) {
if(keepScope) {
Collection coll = ((UndefinedImpl)pc.undefinedScope()).getScopeFor(key,null);
if(coll!=null) return new VariableReference(coll,key);
}
return new VariableReference(pc.undefinedScope(),key);
}
public static VariableReference getVariableReference(PageContext pc,Collection.Key[] keys, boolean keepScope) throws PageException {
if(keys.length==1) {
if(keepScope) {
Collection coll = ((UndefinedImpl)pc.undefinedScope()).getScopeFor(keys[0],null);
if(coll!=null) return new VariableReference(coll,keys[0]);
}
return new VariableReference(pc.undefinedScope(),keys[0]);
}
int scope=scopeKey2Int(keys[0]);
Object coll;
if(scope==Scope.SCOPE_UNDEFINED){
coll=pc.touch(pc.undefinedScope(),keys[0]);
}
else{
coll=VariableInterpreter.scope(pc, scope, keys.length>1);
}
for(int i=1;i<(keys.length-1);i++){
coll=pc.touch(coll,keys[i]);
}
if(!(coll instanceof Collection))
throw new InterpreterException("invalid variable ["+ListUtil.arrayToList(keys, ".")+"]");
return new VariableReference((Collection)coll,keys[keys.length-1]);
}
/**
* sets a variable to page Context
* @param pc pagecontext of the new variable
* @param var String of variable definition
* @param value value to set to variable
* @return value setted
* @throws PageException
*/
public static Object setVariable(PageContext pc,String var, Object value) throws PageException {
StringList list = parse(pc,new ParserString(var),false);
if(list==null) throw new InterpreterException("invalid variable name declaration ["+var+"]");
if(list.size()==1) {
return pc.undefinedScope().set(list.next(),value);
}
// min 2 elements
int scope=scopeString2Int(pc.ignoreScopes(),list.next());
Object coll;
if(scope==Scope.SCOPE_UNDEFINED){
coll=pc.touch(pc.undefinedScope(),KeyImpl.init(list.current()));
}
else {
coll=VariableInterpreter.scope(pc, scope, true);
//coll=pc.scope(scope);
}
while(list.hasNextNext()) {
coll=pc.touch(coll,KeyImpl.init(list.next()));
}
return pc.set(coll,KeyImpl.init(list.next()),value);
}
/**
* removes a variable eith matching name from page context
* @param pc
* @param var
* @return has removed or not
* @throws PageException
*/
public static Object removeVariable(PageContext pc,String var) throws PageException {
//print.ln("var:"+var);
StringList list = parse(pc,new ParserString(var),false);
if(list==null) throw new InterpreterException("invalid variable declaration ["+var+"]");
if(list.size()==1) {
return pc.undefinedScope().remove(KeyImpl.init(list.next()));
}
int scope=scopeString2Int(pc.ignoreScopes(),list.next());
Object coll;
if(scope==Scope.SCOPE_UNDEFINED){
coll=pc.undefinedScope().get(list.current());
}
else {
coll=VariableInterpreter.scope(pc, scope, true);
//coll=pc.scope(scope);
}
while(list.hasNextNext()) {
coll=pc.get(coll,list.next());
}
return Caster.toCollection(coll).remove(KeyImpl.init(list.next()));
}
/**
* check if a variable is defined in Page Context
* @param pc PageContext to check
* @param var variable String
* @return exists or not
*/
public static boolean isDefined(PageContext pc,String var) {
StringList list = parse(pc,new ParserString(var),false);
if(list==null) return false;
try {
int scope=scopeString2Int(pc.ignoreScopes(),list.next());
Object coll =NULL;
if(scope==Scope.SCOPE_UNDEFINED) {
coll=pc.undefinedScope().get(list.current(),null);
if(coll==null)return false;
}
else {
coll=VariableInterpreter.scope(pc, scope, list.hasNext());
//coll=pc.scope(scope);
}
while(list.hasNext()) {
coll=pc.getVariableUtil().getCollection(pc,coll,list.next(),null);
if(coll==null)return false;
}
} catch (PageException e) {
return false;
}
return true;
}
/*
public static boolean isDefined(PageContext pc,String var) {
StringList list = parse(pc,new ParserString(var));
if(list==null) return false;
int scope=scopeString2Int(list.next());
Object coll =NULL;
if(scope==Scope.SCOPE_UNDEFINED) {
coll=pc.undefinedScope().get(list.current(),NULL);
if(coll==NULL) return false;
}
else {
try {
coll=pc.scope(scope);
} catch (PageException e) {
return false;
}
}
while(list.hasNext()) {
coll=pc.getVariableUtil().get(pc,coll,list.next(),NULL);
//print.out(coll);
if(coll==NULL) return false;
}
return true;
}
*/
/**
* parse a Literal variable String and return result as String List
* @param pc Page Context
* @param ps ParserString to read
* @return Variable Definition in a String List
*/
private static StringList parse(PageContext pc,ParserString ps, boolean doLowerCase) {
String id=readIdentifier(ps,doLowerCase);
if(id==null)return null;
StringList list=new StringList(id);
CFMLExpressionInterpreter interpreter=null;
while(true) {
if(ps.forwardIfCurrent('.')) {
id=readIdentifier(ps,doLowerCase);
if(id==null)return null;
list.add(id);
}
else if(ps.forwardIfCurrent('[')) {
if(interpreter==null)interpreter=new CFMLExpressionInterpreter(false);
try {
list.add(Caster.toString(interpreter.interpretPart(pc,ps)));
} catch (PageException e) {
return null;
}
if(!ps.forwardIfCurrent(']')) return null;
ps.removeSpace();
}
else break;
}
if(ps.isValidIndex()) return null;
list.reset();
return list;
}
public static StringList parse(String var, boolean doLowerCase) {
ParserString ps = new ParserString(var);
String id=readIdentifier(ps,doLowerCase);
if(id==null)return null;
StringList list=new StringList(id);
while(true) {
if(ps.forwardIfCurrent('.')) {
id=readIdentifier(ps,doLowerCase);
if(id==null)return null;
list.add(id);
}
else break;
}
if(ps.isValidIndex()) return null;
list.reset();
return list;
}
/**
* translate a string type definition to its int representation
* @param type type to translate
* @return int representation matching to given string
*/
public static int scopeString2Int(boolean ignoreScope, String type) {
type=StringUtil.toLowerCase(type);
char c=type.charAt(0);
// ignore scope only handles only reconize local,arguments as scope, the rest is ignored
if(ignoreScope) {
if('a'==c) {
if("arguments".equals(type)) return Scope.SCOPE_ARGUMENTS;
}
else if('l'==c) {
if("local".equals(type)) return Scope.SCOPE_LOCAL;// LLL
}
else if('r'==c) {
if("request".equals(type)) return Scope.SCOPE_REQUEST;
}
else if('v'==c) {
if("variables".equals(type)) return Scope.SCOPE_VARIABLES;
}
else if('s'==c) {
if("server".equals(type)) return Scope.SCOPE_SERVER;
}
return Scope.SCOPE_UNDEFINED;
}
if('a'==c) {
if("application".equals(type)) return Scope.SCOPE_APPLICATION;
else if("arguments".equals(type)) return Scope.SCOPE_ARGUMENTS;
}
else if('c'==c) {
if("cgi".equals(type)) return Scope.SCOPE_CGI;
if("cookie".equals(type)) return Scope.SCOPE_COOKIE;
if("client".equals(type)) return Scope.SCOPE_CLIENT;
if("cluster".equals(type)) return Scope.SCOPE_CLUSTER;
}
else if('f'==c) {
if("form".equals(type)) return Scope.SCOPE_FORM;
}
else if('l'==c) {
if("local".equals(type)) return Scope.SCOPE_LOCAL;// LLL
}
else if('r'==c) {
if("request".equals(type)) return Scope.SCOPE_REQUEST;
}
else if('s'==c) {
if("session".equals(type)) return Scope.SCOPE_SESSION;
if("server".equals(type)) return Scope.SCOPE_SERVER;
}
else if('u'==c) {
if("url".equals(type)) return Scope.SCOPE_URL;
}
else if('v'==c) {
if("variables".equals(type)) return Scope.SCOPE_VARIABLES;
}
return Scope.SCOPE_UNDEFINED;
}
public static int scopeKey2Int(Collection.Key type) {
char c=type.lowerCharAt(0);
if('a'==c) {
if(KeyConstants._application.equalsIgnoreCase(type)) return Scope.SCOPE_APPLICATION;
else if(KeyConstants._arguments.equalsIgnoreCase(type)) return Scope.SCOPE_ARGUMENTS;
}
else if('c'==c) {
if(KeyConstants._cgi.equalsIgnoreCase(type)) return Scope.SCOPE_CGI;
if(KeyConstants._cookie.equalsIgnoreCase(type)) return Scope.SCOPE_COOKIE;
if(KeyConstants._client.equalsIgnoreCase(type)) return Scope.SCOPE_CLIENT;
if(KeyConstants._cluster.equalsIgnoreCase(type)) return Scope.SCOPE_CLUSTER;
}
else if('f'==c) {
if(KeyConstants._form.equalsIgnoreCase(type)) return Scope.SCOPE_FORM;
}
else if('r'==c) {
if(KeyConstants._request.equalsIgnoreCase(type)) return Scope.SCOPE_REQUEST;
}
else if('s'==c) {
if(KeyConstants._session.equalsIgnoreCase(type)) return Scope.SCOPE_SESSION;
if(KeyConstants._server.equalsIgnoreCase(type)) return Scope.SCOPE_SERVER;
}
else if('u'==c) {
if(KeyConstants._url.equalsIgnoreCase(type)) return Scope.SCOPE_URL;
}
else if('v'==c) {
if(KeyConstants._variables.equalsIgnoreCase(type)) return Scope.SCOPE_VARIABLES;
}
return Scope.SCOPE_UNDEFINED;
}
private static String readIdentifier(ParserString ps, boolean doLowerCase) {
ps.removeSpace();
if(ps.isAfterLast())return null;
int start=ps.getPos();
if(!isFirstVarLetter(ps.getCurrentLower())) return null;
ps.next();
while(ps.isValidIndex()) {
if(isVarLetter(ps.getCurrentLower()))ps.next();
else break;
}
ps.removeSpace();
return doLowerCase?ps.substringLower(start,ps.getPos()-start):ps.substring(start,ps.getPos()-start);
}
private static boolean isFirstVarLetter(char c) {
return (c>='a' && c<='z') || c=='_' || c=='$';
}
private static boolean isVarLetter(char c) {
return (c>='a' && c<='z') || (c>='0' && c<='9') || c=='_' || c=='$';
}
public static Object scope(PageContext pc, int scope, boolean touch) throws PageException {
switch(scope) {
case Scope.SCOPE_UNDEFINED: return pc.undefinedScope();
case Scope.SCOPE_URL: return pc.urlScope();
case Scope.SCOPE_FORM: return pc.formScope();
case Scope.SCOPE_VARIABLES: return pc.variablesScope();
case Scope.SCOPE_REQUEST: return pc.requestScope();
case Scope.SCOPE_CGI: return pc.cgiScope();
case Scope.SCOPE_APPLICATION: return pc.applicationScope();
case Scope.SCOPE_ARGUMENTS: return pc.argumentsScope();
case Scope.SCOPE_SESSION: return pc.sessionScope();
case Scope.SCOPE_SERVER: return pc.serverScope();
case Scope.SCOPE_COOKIE: return pc.cookieScope();
case Scope.SCOPE_CLIENT: return pc.clientScope();
case Scope.SCOPE_VAR: return pc.localScope();
case Scope.SCOPE_CLUSTER: return pc.clusterScope();
case Scope.SCOPE_LOCAL:
if(touch) return ((PageContextImpl)pc).localTouch();
return ((PageContextImpl)pc).localGet();
}
return pc.variablesScope();
}
}