/*
* Copyright 2009-2016 Tilmann Zaeschke. All rights reserved.
*
* This file is part of ZooDB.
*
* ZooDB 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 3 of the License, or
* (at your option) any later version.
*
* ZooDB 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ZooDB. If not, see <http://www.gnu.org/licenses/>.
*
* See the README and COPYING files for further information.
*/
package org.zoodb.internal.query;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.zoodb.api.impl.ZooPC;
import org.zoodb.internal.ZooClassDef;
import org.zoodb.internal.ZooFieldDef;
import org.zoodb.internal.query.QueryParser.FNCT_OP;
import static org.zoodb.internal.query.TypeConverterTools.*;
/**
* Query functions.
*
* @author ztilmann
*
*/
public class QueryFunction {
private final Class<?> returnType;
private final ZooClassDef returnTypeDef;
private final FNCT_OP fnct;
private final int fieldId;
private final Field field;
private final ZooFieldDef zField;
private final QueryFunction[] params;
private final Object constant;
private boolean isConstant; //indicate whether evaluation is constant
private QueryFunction(FNCT_OP fnct, ZooFieldDef zField,
Object constant, Class<?> returnType, ZooClassDef returnTypeDef,
QueryFunction ... params) {
this.returnType = returnType;
this.returnTypeDef = returnTypeDef;
this.fnct = fnct;
this.zField = zField;
if (zField != null) {
fieldId = zField.getFieldPos();
field = zField.getJavaField();
} else {
fieldId = -1;
field = null;
}
this.constant = constant;
this.params = params;
this.isConstant = fnct == FNCT_OP.CONSTANT;
if (params.length > 0) {
this.isConstant = true;
for (QueryFunction f: params) {
if (!f.isConstant()) {
this.isConstant = false;
break;
}
}
}
}
/**
*
* @return 'true' if this sub-tree yields a constant result, independent of parameters etc
*/
public boolean isConstant() {
return isConstant;
}
public Class<?> getReturnType() {
return returnType;
}
public static QueryFunction createConstant(Object constant) {
return new QueryFunction(FNCT_OP.CONSTANT, null, constant, constant.getClass(), null);
}
public static QueryFunction createThis(ZooClassDef baseClassDef) {
return new QueryFunction(FNCT_OP.THIS, null, null,
baseClassDef.getJavaClass(), baseClassDef);
}
public static QueryFunction createFieldRef(QueryFunction baseObjectFn, ZooFieldDef field) {
return new QueryFunction(FNCT_OP.REF, field, null, field.getJavaType(),
field.isPersistentType() ? field.getType() : null, baseObjectFn);
}
public static QueryFunction createFieldSCO(QueryFunction baseObjectFn, ZooFieldDef field) {
return new QueryFunction(FNCT_OP.FIELD, field, null, field.getJavaType(),
null, baseObjectFn);
}
public static QueryFunction createJava(FNCT_OP op, QueryFunction ... params) {
if (op == FNCT_OP.Math_abs) {
//abs() can have four different return types, depending on the parameters!
return new QueryFunction(op, null, null, params[1].getReturnType(), null, params);
}
return new QueryFunction(op, null, null, op.getReturnType(), null, params);
}
public static QueryFunction createParam(QueryParameter param) {
return new QueryFunction(FNCT_OP.PARAM, null, param, param.getType(), param.getTypeDef());
}
/**
*
* @param currentInstance The current context for calling methods
* @param globalInstance The global context for 'this'
* @return Result of the evaluation
*/
Object evaluate(Object currentInstance, Object globalInstance) {
switch (fnct) {
case REF:
case FIELD:
try {
Object localInstance = params[0].evaluate(currentInstance, globalInstance);
if (localInstance == QueryTerm.NULL || localInstance == QueryTerm.INVALID) {
return QueryTerm.INVALID;
}
if (localInstance instanceof ZooPC) {
if (((ZooPC)localInstance).jdoZooIsStateHollow()) {
((ZooPC)localInstance).zooActivateRead();
}
}
if (field.getDeclaringClass() == localInstance.getClass()) {
//Shortcut for base class, optimisation for non-polymorphism
Object ret = field.get(localInstance);
return ret == null ? QueryTerm.NULL : ret;
}
//TODO why don't we need this in QueryTerm.evaluate()????
ZooClassDef def = ((ZooPC)localInstance).jdoZooGetClassDef();
Object ret = def.getAllFields()[fieldId].getJavaField().get(localInstance);
return ret == null ? QueryTerm.NULL : ret;
} catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
throw new RuntimeException(e);
}
case CONSTANT: return constant;
case PARAM: return ((QueryParameter)constant).getValue();
case THIS: return globalInstance;
default: return evaluateFunction(params[0].evaluate(currentInstance, globalInstance),
globalInstance);
}
}
private Object evaluateFunction(Object li, Object gi) {
if (li == QueryTerm.NULL || li == QueryTerm.INVALID) {
//According to JDO spec 14.6.2, calls on 'null' result in 'false'
switch (fnct) {
case COLL_isEmpty:
case MAP_isEmpty: return li == QueryTerm.NULL;
case COLL_contains:
case MAP_containsKey:
case MAP_containsValue:
case STR_startsWith:
case STR_endsWith:
case STR_matches:
case STR_contains_NON_JDO: return false;
default: return QueryTerm.INVALID;
}
}
Object o1, o2;
Object[] arg = new Object[params.length];
for (int i = 1; i < arg.length; i++) {
Object o = getValue(li, gi, i-1);
if (o == QueryTerm.INVALID) {
return QueryTerm.INVALID;
}
//TODO add flag to enum: allowsNullParams
if (o == QueryTerm.NULL
&& fnct!=FNCT_OP.LIST_get
&& fnct!=FNCT_OP.MAP_get
&& fnct!=FNCT_OP.EQ_OBJ) {
return QueryTerm.INVALID;
}
arg[i-1] = o;
}
switch (fnct) {
case COLL_contains: return ((Collection<?>)li).contains(arg[0]);
case COLL_isEmpty: return ((Collection<?>)li).isEmpty();
case COLL_size: return ((Collection<?>)li).size();
case LIST_get:
int posL =(int)arg[0];
int sizeL = ((List<?>)li).size();
return (posL >= sizeL || posL < 0) ? QueryTerm.INVALID : ((List<?>)li).get(posL);
case MAP_get:
return ((Map<?, ?>)li).get(arg[0]);
case MAP_containsKey: return ((Map<?,?>)li).containsKey(arg[0]) ;
case MAP_containsValue: return ((Map<?,?>)li).containsValue(arg[0]);
case MAP_isEmpty: return ((Map<?,?>)li).isEmpty();
case MAP_size: return ((Map<?,?>)li).size();
case STR_startsWith: return ((String)li).startsWith((String) arg[0]);
case STR_endsWith: return ((String)li).endsWith((String) arg[0]);
case STR_matches: return ((String)li).matches((String) arg[0]);
case STR_contains_NON_JDO: return ((String)li).contains((String) arg[0]);
case STR_indexOf1: return ((String)li).indexOf((String) arg[0]);
case STR_indexOf2: return ((String)li).indexOf(
(String) arg[0],
(Integer) arg[1]);
case STR_substring1: return ((String)li).substring((Integer) arg[0]);
case STR_substring2: return ((String)li).substring(
(Integer) arg[0],
(Integer) arg[1]);
case STR_toLowerCase: return ((String)li).toLowerCase();
case STR_toUpperCase: return ((String)li).toUpperCase();
case STR_length: return ((String)li).length();
case STR_trim: return ((String)li).trim();
case Math_abs:
Object o = arg[0];
Class<?> oType = o.getClass();
if (oType == Integer.class) {
return Math.abs((Integer)o);
} else if (oType == Long.class) {
return Math.abs((Long)o);
} else if (oType == Float.class) {
return Math.abs((Float)o);
} else if (oType == Double.class) {
return Math.abs((Double)o);
}
case Math_sqrt:
double d = toDouble(arg[0]);
return d >= 0 ? Math.sqrt(d) : QueryTerm.INVALID;
case Math_sin:
return Math.sin(toDouble(arg[0]));
case Math_cos:
return Math.cos(toDouble(arg[0]));
case EQ_NUM:
case EQ_BOOL:
case EQ_OBJ:
o1 = arg[0];
o2 = arg[1];
if (o1 instanceof ZooPC) {
long oid1 = ((ZooPC)o1).jdoZooGetOid();
return (o2 instanceof ZooPC) ? ((ZooPC)o2).jdoZooGetOid() == oid1 : false;
}
return o1.equals(o2);
case PLUS_STR:
return ((String)arg[0]) + ((String)arg[1]);
case PLUS_D:
double d1 = TypeConverterTools.toDouble(arg[0]);
double d2 = TypeConverterTools.toDouble(arg[1]);
return d1 + d2;
case PLUS_L:
o1 = arg[0];
o2 = arg[1];
return TypeConverterTools.toLong(arg[0]) + TypeConverterTools.toLong(arg[1]);
// case PLUS:
// o1 = arg[0];
// o2 = arg[1];
// if (Double.class.isAssignableFrom(o1.getClass()) ||
// Double.class.isAssignableFrom(o2.getClass()) ||
// Float.class.isAssignableFrom(o1.getClass()) ||
// Float.class.isAssignableFrom(o2.getClass())) {
// double d1 = TypeConverterTools.toDouble(o1);
// double d2 = TypeConverterTools.toDouble(o2);
// return d1 + d2;
// }
// return TypeConverterTools.toLong(o1) + TypeConverterTools.toLong(o2);
// case MINUS:
// o1 = arg[0];
// o2 = arg[1];
// if (Double.class.isAssignableFrom(o1.getClass()) ||
// Double.class.isAssignableFrom(o2.getClass()) ||
// Float.class.isAssignableFrom(o1.getClass()) ||
// Float.class.isAssignableFrom(o2.getClass())) {
// double d1 = TypeConverterTools.toDouble(o1);
// double d2 = TypeConverterTools.toDouble(o2);
// return d1 - d2;
// }
// return TypeConverterTools.toLong(o1) - TypeConverterTools.toLong(o2);
// case MUL:
// o1 = arg[0];
// o2 = arg[1];
// if (Double.class.isAssignableFrom(o1.getClass()) ||
// Double.class.isAssignableFrom(o2.getClass()) ||
// Float.class.isAssignableFrom(o1.getClass()) ||
// Float.class.isAssignableFrom(o2.getClass())) {
// double d1 = TypeConverterTools.toDouble(o1);
// double d2 = TypeConverterTools.toDouble(o2);
// return d1 * d2;
// }
// return TypeConverterTools.toLong(o1) * TypeConverterTools.toLong(o2);
// case DIV:
// o1 = arg[0];
// o2 = arg[1];
// if (Double.class.isAssignableFrom(o1.getClass()) ||
// Double.class.isAssignableFrom(o2.getClass()) ||
// Float.class.isAssignableFrom(o1.getClass()) ||
// Float.class.isAssignableFrom(o2.getClass())) {
// double d1 = TypeConverterTools.toDouble(o1);
// double d2 = TypeConverterTools.toDouble(o2);
// return d1 / d2;
// }
// //This may lose some precision, but that's the same way as it happens in Java
// return TypeConverterTools.toLong(o1) / TypeConverterTools.toLong(o2);
case ENUM_ordinal:
return ((Enum<?>)li).ordinal();
case ENUM_toString:
return ((Enum<?>)li).toString();
default:
throw new UnsupportedOperationException(fnct.name());
}
// switch (fnct) {
// case COLL_contains: return ((Collection<?>)li).contains(getValue(li, gi, 0));
// case COLL_isEmpty: return ((Collection<?>)li).isEmpty();
// case COLL_size: return ((Collection<?>)li).size();
// case LIST_get:
// int posL =(int)getValue(li, gi, 0);
// int sizeL = ((List<?>)li).size();
// return posL >= sizeL ? QueryTerm.INVALID : ((List<?>)li).get(posL);
// case MAP_get:
// Object key = getValue(li, gi, 0);
// if (key == QueryTerm.INVALID) {
// return QueryTerm.INVALID;
// }
// return ((Map<?, ?>)li).get(key);
// case MAP_containsKey: return ((Map<?,?>)li).containsKey(getValue(li, gi, 0)) ;
// case MAP_containsValue: return ((Map<?,?>)li).containsValue(getValue(li, gi, 0));
// case MAP_isEmpty: return ((Map<?,?>)li).isEmpty();
// case MAP_size: return ((Map<?,?>)li).size();
// case STR_startsWith: return ((String)li).startsWith((String) getValue(li, gi, 0));
// case STR_endsWith: return ((String)li).endsWith((String) getValue(li, gi, 0));
// case STR_matches: return ((String)li).matches((String) getValue(li, gi, 0));
// case STR_contains_NON_JDO: return ((String)li).contains((String) getValue(li, gi, 0));
// case STR_indexOf1: return ((String)li).indexOf((String) getValue(li, gi, 0));
// case STR_indexOf2: return ((String)li).indexOf(
// (String) getValue(li, gi, 0),
// (Integer) getValue(li, gi, 1));
// case STR_substring1: return ((String)li).substring((Integer) getValue(li, gi, 0));
// case STR_substring2: return ((String)li).substring(
// (Integer) getValue(li, gi, 0),
// (Integer) getValue(li, gi, 1));
// case STR_toLowerCase: return ((String)li).toLowerCase();
// case STR_toUpperCase: return ((String)li).toUpperCase();
// case Math_abs:
// Object o = getValue(li, gi, 0);
// if (o == QueryTerm.NULL || o == QueryTerm.INVALID) {
// return QueryTerm.INVALID;
// }
// Class<?> oType = o.getClass();
// if (oType == Integer.class) {
// return Math.abs((Integer)o);
// } else if (oType == Long.class) {
// return Math.abs((Long)o);
// } else if (oType == Float.class) {
// return Math.abs((Float)o);
// } else if (oType == Double.class) {
// return Math.abs((Double)o);
// }
// case Math_sqrt:
// o1 = getValue(li, gi, 0);
// if (o1 == QueryTerm.NULL || o1 == QueryTerm.INVALID) {
// return QueryTerm.INVALID;
// }
// double d = toDouble(o1);
// return d >= 0 ? Math.sqrt(toDouble(o1)) : QueryTerm.INVALID;
// case Math_sin:
// o1 = getValue(li, gi, 0);
// if (o1 == QueryTerm.NULL || o1 == QueryTerm.INVALID) {
// return QueryTerm.INVALID;
// }
// return Math.sin(toDouble(o1));
// case Math_cos:
// o1 = getValue(li, gi, 0);
// if (o1 == QueryTerm.NULL || o1 == QueryTerm.INVALID) {
// return QueryTerm.INVALID;
// }
// return Math.cos(toDouble(o1));
// case EQ_NUM:
// case EQ_BOOL:
// case EQ_OBJ:
// o1 = getValue(li, gi, 0);
// o2 = getValue(li, gi, 1);
// if (o1 == QueryTerm.NULL || o1 == QueryTerm.INVALID ||
// o2 == QueryTerm.NULL || o2 == QueryTerm.INVALID) {
// return QueryTerm.INVALID;
// }
// if (o1 instanceof ZooPC) {
// long oid1 = ((ZooPC)o1).jdoZooGetOid();
// return (o2 instanceof ZooPC) ? ((ZooPC)o2).jdoZooGetOid() == oid1 : false;
// }
// return o1.equals(o2);
// case PLUS:
// o1 = (Number) getValue(li, gi, 0);
// o2 = (Number) getValue(li, gi, 1);
// if (o1 == QueryTerm.NULL || o1 == QueryTerm.INVALID ||
// o2 == QueryTerm.NULL || o2 == QueryTerm.INVALID) {
// return QueryTerm.INVALID;
// }
//
// default:
// throw new UnsupportedOperationException(fnct.name());
// }
}
private Object getValue(Object localInstance, Object globalInstance, int i) {
return params[i+1].evaluate(localInstance, globalInstance);
}
@Override
public String toString() {
// return fnct.name() + "[" + (field != null ? field.getName() : null) +
// "](" + Arrays.toString(params) + ")";
return (field != null ? field.getName() : fnct.name())
+ "(" + Arrays.toString(params) + ")";
}
public FNCT_OP op() {
return fnct;
}
public Object getConstant() {
return constant;
}
public QueryFunction[] getParams() {
return params;
}
public ZooFieldDef getFieldDef() {
return zField;
}
public boolean isPC() {
return returnTypeDef != null;
}
public ZooClassDef getReturnTypeClassDef() {
return returnTypeDef;
}
}