/* * #! * Ontopia Engine * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * !# */ package net.ontopia.persistence.query.jdo; import java.util.Collection; import net.ontopia.persistence.proxy.ClassInfoIF; import net.ontopia.persistence.proxy.FieldInfoIF; import net.ontopia.persistence.proxy.ObjectRelationalMappingIF; import net.ontopia.utils.OntopiaRuntimeException; /** * INTERNAL: Methods for reducing JDOExpressionIF, so that expressions * that require no input data can be removed from the query.<p> * * Return values [type int] from evaluate methods: 1 is true, -1 is * false, 0 means cannot be evaluated.<p> * */ public class JDOEvaluator { public static int evaluateExpression(JDOExpressionIF jdoexpr, ObjectRelationalMappingIF mapping, boolean reduce) { switch (jdoexpr.getType()) { // method calls case JDOExpressionIF.EQUALS: { return evaluateEquals((JDOEquals)jdoexpr, mapping); } case JDOExpressionIF.NOT_EQUALS: { return evaluateNotEquals((JDONotEquals)jdoexpr, mapping); } case JDOExpressionIF.CONTAINS: { return evaluateContains((JDOContains)jdoexpr, mapping); } case JDOExpressionIF.IS_EMPTY: { return evaluateIsEmpty((JDOIsEmpty)jdoexpr, mapping); } case JDOExpressionIF.STARTS_WITH: { return evaluateStartsWith((JDOStartsWith)jdoexpr, mapping); } case JDOExpressionIF.ENDS_WITH: { return evaluateEndsWith((JDOEndsWith)jdoexpr, mapping); } case JDOExpressionIF.LIKE: { return evaluateLike((JDOLike)jdoexpr, mapping); } // logical operators case JDOExpressionIF.AND: { return evaluateAnd((JDOAnd)jdoexpr, mapping, reduce); } case JDOExpressionIF.OR: { return evaluateOr((JDOOr)jdoexpr, mapping, reduce); } case JDOExpressionIF.NOT: { return evaluateNot((JDONot)jdoexpr, mapping, reduce); } case JDOExpressionIF.BOOLEAN: { return evaluateBoolean((JDOBoolean)jdoexpr, mapping); } case JDOExpressionIF.VALUE_EXPRESSION: { return evaluateValueExpression((JDOValueExpression)jdoexpr, mapping); } // set operators case JDOExpressionIF.SET_OPERATION: { // FIXME: return 0; } default: throw new OntopiaRuntimeException("Expression is of unknown type: '" + jdoexpr + "'"); } } public static int evaluateBoolean(JDOBoolean jdoexpr, ObjectRelationalMappingIF mapping) { return (jdoexpr.getValue() ? 1 : -1); } public static int evaluateValueExpression(JDOValueExpression jdoexpr, ObjectRelationalMappingIF mapping) { return 0; } public static int evaluateEquals(JDOEquals jdoexpr, ObjectRelationalMappingIF mapping) { JDOValueIF left = jdoexpr.getLeft(); JDOValueIF right = jdoexpr.getRight(); // Decisive only if equals if (left.equals(right)) return 1; // If both are objects compare them if (isEvaluatable(left, mapping) && isEvaluatable(right, mapping)) { Object lval = evaluateJDOValue(left, mapping); Object rval = evaluateJDOValue(right, mapping); return (lval == null ? (rval == null ? 1 : -1) : (lval.equals(rval) ? 1 : -1)); //! return (lval.equals(rval) ? 1 : -1); } // FIXME: object vs parameter can only be validated at execution. return 0; } public static int evaluateNotEquals(JDONotEquals jdoexpr, ObjectRelationalMappingIF mapping) { JDOValueIF left = jdoexpr.getLeft(); JDOValueIF right = jdoexpr.getRight(); // Decisive only if equals if (left.equals(right)) return -1; // If both are objects compare them if (isEvaluatable(left, mapping) && isEvaluatable(right, mapping)) { Object lval = evaluateJDOValue(left, mapping); Object rval = evaluateJDOValue(right, mapping); return (lval == null ? (rval == null ? -1 : 1) : (lval.equals(rval) ? -1 : 1)); //! return (lval.equals(rval) ? -1 : 1); } return 0; } public static int evaluateContains(JDOContains jdoexpr, ObjectRelationalMappingIF mapping) { JDOValueIF left = jdoexpr.getLeft(); JDOValueIF right = jdoexpr.getRight(); //! System.out.println("El: " + isEvaluatable(left, mapping) + left); //! System.out.println("Er: " + isEvaluatable(left, mapping) + right); if (isEvaluatable(left, mapping) && isEvaluatable(right, mapping)) { Collection lval = (Collection)evaluateJDOValue(left, mapping); Object rval = evaluateJDOValue(right, mapping); // NOTE: Using containsAll if rval is a collection if (rval instanceof Collection) return (lval == null ? -1 : (lval.containsAll((Collection)rval) ? 1 : -1)); else return (lval == null ? -1 : (lval.contains(rval) ? 1 : -1)); } return 0; } public static int evaluateIsEmpty(JDOIsEmpty jdoexpr, ObjectRelationalMappingIF mapping) { JDOValueIF value = jdoexpr.getValue(); if (isEvaluatable(value, mapping)) { Collection coll = (Collection)evaluateJDOValue(value, mapping); return ((coll != null && coll.isEmpty()) ? 1 : -1); } return 0; } public static int evaluateStartsWith(JDOStartsWith jdoexpr, ObjectRelationalMappingIF mapping) { JDOValueIF left = jdoexpr.getLeft(); JDOValueIF right = jdoexpr.getRight(); // If both are string, perform string operation. if (isEvaluatable(left, mapping) && isEvaluatable(right, mapping)) { String lval = (String)evaluateJDOValue(left, mapping); String rval = (String)evaluateJDOValue(right, mapping); return (lval == null ? -1 : (lval.startsWith(rval) ? 1 : -1)); //! return (lval.startsWith(rval) ? 1 : -1); } return 0; } public static int evaluateEndsWith(JDOEndsWith jdoexpr, ObjectRelationalMappingIF mapping) { JDOValueIF left = jdoexpr.getLeft(); JDOValueIF right = jdoexpr.getRight(); // If both are string, perform string operation. if (isEvaluatable(left, mapping) && isEvaluatable(right, mapping)) { String lval = (String)evaluateJDOValue(left, mapping); String rval = (String)evaluateJDOValue(right, mapping); return (lval == null ? -1 : (lval.endsWith(rval) ? 1 : -1)); //! return (lval.endsWith(rval) ? 1 : -1); } return 0; } public static int evaluateLike(JDOLike jdoexpr, ObjectRelationalMappingIF mapping) { // TODO: Do some clever stuff here. return 0; } public static int evaluateAnd(JDOAnd jdoexpr, ObjectRelationalMappingIF mapping, boolean reduce) { // 1. remove all evaluatable expressions // 2. AND - if item TRUE -> remove item // AND - if item FALSE -> expression FALSE int removed = 0; JDOExpressionIF[] exprs = jdoexpr.getExpressions(); for (int i=0; i < exprs.length; i++) { int result = evaluateExpression(exprs[i], mapping, reduce); switch (result) { case 0: // not evaluatable continue; // skip to next case 1: // true //! System.out.println("==> Removing: " + exprs[i]); exprs[i] = null; // remove item removed++; continue; case -1: // false return -1; // expression evaluates to false default: throw new OntopiaRuntimeException("Unknown result value: '" + result + "'"); } } // If an item has been removed truncate the array. if (removed > 0) { JDOExpressionIF[] _exprs = new JDOExpressionIF[exprs.length - removed]; int offset = 0; for (int i=0; i < exprs.length; i++) { if (exprs[i] != null) { _exprs[offset] = exprs[i]; offset++; } } // If no expressions remain, return true if (_exprs.length == 0) return 1; // Update AND-expression jdoexpr.setExpressions(_exprs); } return 0; } public static int evaluateOr(JDOOr jdoexpr, ObjectRelationalMappingIF mapping, boolean reduce) { // 1. remove all evaluatable expressions // 2. OR - if item TRUE -> expression TRUE // OR - if item FALSE -> remove item int removed = 0; JDOExpressionIF[] exprs = jdoexpr.getExpressions(); for (int i=0; i < exprs.length; i++) { int result = evaluateExpression(exprs[i], mapping, reduce); switch (result) { case 0: // not evaluatable continue; // skip to next case -1: // false //! System.out.println("==> Removing: " + exprs[i]); exprs[i] = null; // remove item removed++; continue; case 1: // true return 1; // expression evaluates to true default: throw new OntopiaRuntimeException("Unknown result value: '" + result + "'"); } } // If an item has been removed truncate the array. if (removed > 0) { JDOExpressionIF[] _exprs = new JDOExpressionIF[exprs.length - removed]; int offset = 0; for (int i=0; i < exprs.length; i++) { if (exprs[i] != null) { _exprs[offset] = exprs[i]; offset++; } } // If no expressions remain, return false if (_exprs.length == 0) return -1; // Update OR-expression jdoexpr.setExpressions(_exprs); } return 0; } public static int evaluateNot(JDONot jdoexpr, ObjectRelationalMappingIF mapping, boolean reduce) { int result = evaluateExpression(jdoexpr.getExpression(), mapping, reduce); switch (result) { case 0: // not evaluatable return 0; case -1: // false return 1; case 1: // true return -1; default: throw new OntopiaRuntimeException("Unknown result value: '" + result + "'"); } } public static boolean isEvaluatable(JDOValueIF jdovalue, ObjectRelationalMappingIF mapping) { switch (jdovalue.getType()) { case JDOValueIF.OBJECT: case JDOValueIF.STRING: case JDOValueIF.COLLECTION: return true; case JDOValueIF.FIELD: { JDOField field = (JDOField)jdovalue; return field.getEvaluatable() && isEvaluatable(field.getRoot(), mapping); } } return false; } public static Object evaluateJDOValue(JDOValueIF jdovalue, ObjectRelationalMappingIF mapping) { switch (jdovalue.getType()) { case JDOValueIF.OBJECT: return ((JDOObject)jdovalue).getValue(); case JDOValueIF.STRING: return ((JDOString)jdovalue).getValue(); case JDOValueIF.COLLECTION: return ((JDOCollection)jdovalue).getValue(); case JDOValueIF.FIELD: { JDOField field = (JDOField)jdovalue; JDOObject obj = (JDOObject)field.getRoot(); // TODO: Only 1:1 fields supported. // Get object field value Object value = obj.getValue(); Class ctype = value.getClass(); //! System.out.println("OVALUE: " + value); FieldInfoIF finfo = null; String[] path = field.getPath(); for (int i=0; i < path.length; i++) { // Make sure that field parent is of declared type if (mapping.isDeclared(ctype)) { ClassInfoIF cinfo = mapping.getClassInfo(ctype); finfo = cinfo.getFieldInfoByName(path[i]); if (finfo == null) throw new OntopiaRuntimeException("Parent '" + ctype + "' do not have field called '" + path[i] + "'"); try { value = finfo.getValue(value); } catch (Exception e) { throw new OntopiaRuntimeException(e); } // We'll have to stop here if the returned value is null. if (value == null) break; ctype = finfo.getValueClass(); } else throw new OntopiaRuntimeException("Parent of field '" + path[i] + "' of undeclared type: '" + ctype + "'"); } //! System.out.println("FVALUE: " + value); return value; } default: throw new OntopiaRuntimeException("Unsupported JDOValueIF: '" + jdovalue + "'"); } } }