package com.openMap1.mapper.query; import java.util.Hashtable; import java.util.Iterator; import java.util.StringTokenizer; import java.util.Vector; import org.eclipse.emf.common.util.EList; import com.openMap1.mapper.LocalPropertyConversion; import com.openMap1.mapper.PropMapping; import com.openMap1.mapper.ValuePair; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.reader.objectToken; import com.openMap1.mapper.util.GenUtil; /** * a query condition, which can take one of two forms: * * - class.property (relation) constant value * - class.property (relation) class.property * * In both cases they must refer to QueryClasses, as one class may occur in different parts of a query. * * @author Robert * */ public class QueryCondition extends QueryMappingUser{ private QueryClass queryClass; public QueryClass queryClass() {return queryClass;} private String propName; public String propName() {return propName;} private String relation; public String relation() {return relation;} private String constantValue; public String constantValue() {return constantValue;} private QueryClass otherQueryClass; public QueryClass otherQueryClass() {return otherQueryClass;} private String otherPropName; public String otherPropName() {return otherPropName;} /* these appear never to be used private Hashtable<String,Boolean> leftIsDirect; private Hashtable<String,Boolean> rightIsDirect; public void setLeftIsDirect(String code, boolean direct) {leftIsDirect.put(code, new Boolean(direct));} public void setRightIsDirect(String code, boolean direct) {rightIsDirect.put(code, new Boolean(direct));} public boolean leftIsDirect(String code) {return((leftIsDirect.get(code) != null) && (leftIsDirect.get(code).booleanValue()));} public boolean rightIsDirect(String code) {return((rightIsDirect.get(code) != null) && (rightIsDirect.get(code).booleanValue()));} */ private String stringForm; public String stringForm() {return stringForm;} /** * constructor for constant value conditions * @param queryClass * @param propName * @param relation * @param constantValue */ public QueryCondition(QueryClass queryClass,String propName, String relation, String constantValue, QueryParser parser) { super(parser); this.queryClass = queryClass; this.propName = propName; this.relation = relation; this.constantValue = constantValue; this.otherQueryClass = null; this.otherPropName = null; makeStringForm(); } /** * constructor for a condition between two property values * @param queryClass * @param propName * @param relation * @param otherQueryClass * @param otherPropName */ public QueryCondition(QueryClass queryClass,String propName, String relation, QueryClass otherQueryClass, String otherPropName, QueryParser parser) { super(parser); this.queryClass = queryClass; this.propName = propName; this.relation = relation; this.constantValue = null; this.otherQueryClass = otherQueryClass; this.otherPropName = otherPropName; makeStringForm(); } /** * what kind of condition this is * @return */ public boolean isConstantCondition() {return (constantValue != null);} private void makeStringForm() { stringForm = queryClass.className() + "." + propName + " " + relation; if (isConstantCondition()) stringForm = stringForm + " '" + constantValue + "'"; else stringForm = stringForm + " " + otherQueryClass.className() + "." + otherPropName; } //------------------------------------------------------------------------------------- // test query conditions //------------------------------------------------------------------------------------- /** * Evaluate this query condition when a partial result is available. * The partial result always contains an objectToken for the QueryClass, and may or may not * contain an objectToken for the other class (which may come later in the strategy). * If the test depends on another class which comes later in the strategy, return true. * @return */ public boolean evaluate(Vector<QueryClass> strategy, Vector<objectToken> partialResult) throws MapperException { objectToken left = findObject(queryClass,strategy, partialResult); /* if the LHS object of the query has not yet been found, make the test * pass for now, as it will be evaluated later in the strategy. * but if the LHS is not represented or not found, make the test fail */ if (left == null) return true; if (left.isEmpty()) return false; if (isConstantCondition()) return test(left,null); else { objectToken right = findObject(otherQueryClass,strategy, partialResult); /* if the RHS object of the query has not yet been found, make the test * pass for now, as it will be evaluated later in the strategy. * but if the RHS is not represented or not found, make the test fail */ if (right == null) return true; if (right.isEmpty()) return false; else return test(left,right); } } /** * find the objectToken needed to evaluate one side of this condition, if the object is in the partial result; * otherwise return null. * @param objClass * @param strategy * @param partialResult * @return */ private objectToken findObject(QueryClass objClass, Vector<QueryClass> strategy, Vector<objectToken> partialResult) { objectToken oTok = null; // find the position of this QueryClass in the strategy int pos = 1000; for (int i = 0; i < strategy.size();i++) if (objClass.equals(strategy.get(i))) pos = i; // if an objectToken for the QueryClass is in the partial result, return it if (pos < partialResult.size()) oTok = partialResult.get(pos); return oTok; } /** * * @param oRep * @param otherORep * @return * @throws MapperException */ public boolean test(objectToken oRep, objectToken otherORep) throws MapperException { // empty objectTokens fail all tests if (oRep.isEmpty()) return false; String leftVal = oRep.reader().getPropertyValue(oRep, propName); String rightVal = constantValue; if (rightVal == null) { // comparisons against empty objects fail if (otherORep.isEmpty()) return false; rightVal = oRep.reader().getPropertyValue(otherORep, otherPropName); } return testOneCondition(leftVal, relation, rightVal); } /** * * @param left String value for the left-hand side * @param test String denoting the test to be applied * @param right String value of the right-hand side * @return true if the test is passed; false if not, or if any arguments are null * @throws MapperException */ public static boolean testOneCondition(String left, String test, String right) throws MapperException { boolean res = false; if (test == null) return true; // if the test is undefined, pass, as records will be filtered later // for null or empty values, the query result display shows "--". Make the tests work using that value if ((left == null)|((left != null) && (left.equals("")))) left = "--"; // if the right-hand side is null, the test returns false if (right != null) { // text equality test does not ignore case, because RDB retrieval has not ignored it if (test.equals("=")) {res = (left.equals(right));} // number tests else if ((test.equals(">"))| (test.equals(">="))| (test.equals("<"))| (test.equals("<="))) { try{ float f1 = new Float(left).floatValue(); try{ float f2 = new Float(right).floatValue(); if (test.equals(">")) {res = (f1 > f2);} else if (test.equals(">=")) {res = (f1 >= f2);} else if (test.equals("<")) {res = (f1 < f2);} else if (test.equals("<=")) {res = (f1 <= f2);} } catch (NumberFormatException e2) {throw new MapperException("Cannot convert '" + right + "' to a number. " + e2.getMessage());} } catch (NumberFormatException e1) {throw new MapperException("Cannot convert '" + left + "' to a number. "+ e1.getMessage());} } // the 'contains' and 'containedIn' tests ignore case else if (test.equalsIgnoreCase("contains")) {res = GenUtil.contains(left.toUpperCase(),right.toUpperCase());} else if (test.equalsIgnoreCase("notContains")) {res = !GenUtil.contains(left.toUpperCase(),right.toUpperCase());} // the 'startsWith' test ignores case else if (test.equalsIgnoreCase("startsWith")) {res = left.toUpperCase().startsWith(right.toUpperCase());} else if (test.equalsIgnoreCase("in")) {res = testInclusion(true,left,right);} else if (test.equalsIgnoreCase("notIn")) {res = testInclusion(false,left,right);} else if (test.equalsIgnoreCase("before")) {res = (left.compareTo(right) < 0);} else if (test.equalsIgnoreCase("after")) {res = (left.compareTo(right) > 0);} } return res; } /** * test whether the leftValue is included in a set of values denoted by the right value, * ignoring case * @param keepIncluded if true, return true iff the left value is one of the right values * if false, return true if the left value is not one of the right values * @param leftValue the value to be compared * @param rightValue the set of values - separated by a '|' symbol * @return the result of the test */ static boolean testInclusion(boolean keepIncluded, String leftValue, String rightValue) { boolean included = false; StringTokenizer st = new StringTokenizer(rightValue,"|"); while ((st.hasMoreTokens()) && (!included)) { if (st.nextToken().equalsIgnoreCase(leftValue)) included = true; } boolean result = included; if (!keepIncluded) result = !included; return result; } /** * FIXME - should allow for non-local property conversions (usiallu cannot make an SQL condition) * add all tables, columns and conditions to an SQLQuery to ensure it retrieves the smallest DOM * required to support a query */ public void buildQuery(SQLQuery query, String code) throws MapperException { // there may be several property mappings because of property conversions Vector<PropMapping> pMaps = allMappings.get(code); // 'true' means the property mapping, being involved in a query condition, is core to the query if (pMaps != null) for (Iterator<PropMapping> it = pMaps.iterator();it.hasNext();) handlePropMapping(it.next(), query, code,true); // only add an SQL condition when there are no non-local property conversions, i.e. just one property mapping for each side boolean canAddSQLCondition = false; if ((isConstantCondition()) && (getMappings(code).size() == 1)) canAddSQLCondition = true; if ((!isConstantCondition()) && (getMappings(code).size() == 2)) canAddSQLCondition = true; if (canAddSQLCondition) { PropMapping leftProp = getMappings(code).get(0); // if there is a local property conversion with defined value pairs, try to find the structure value for the supplied model value String valueToCompare = constantValue; LocalPropertyConversion conversion = leftProp.getLocalPropertyConversion(); if ((conversion != null) && (isConstantCondition())) { valueToCompare = null; EList<ValuePair> pairs = conversion.getValuePairs(); if (pairs != null) for (Iterator<ValuePair> it = pairs.iterator();it.hasNext();) { ValuePair pair = it.next(); if (pair.getModelValue().equals(constantValue)) valueToCompare = pair.getStructureValue(); } } // add the SQL condition for a fixed value - possibly for conditions other than '=' if ((isConstantCondition()) && (valueToCompare != null)) { query.addValCondition(getTableName(leftProp), getColName(leftProp.getStringRootPath()), relation, valueToCompare); } // add the SQL condition for an equality condition between two property values, else if ((!isConstantCondition()) && (relation.equals("="))) { // there are exactly two Property Mappings; the RHS property is the second of them PropMapping rightProp = getMappings(code).get(1); // we can only add an SQL link condition if neither property mapping uses a property conversion if ((leftProp.getLocalPropertyConversion() == null) && (rightProp.getLocalPropertyConversion() == null)) query.addCrossCondition(getTableName(leftProp), getColName(leftProp.getStringRootPath()), "=", getTableName(rightProp), getColName(rightProp.getStringRootPath()),true); } } } }