/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Michael Powers 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 */ package net.wotonomy.control; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSMutableArray; import net.wotonomy.foundation.NSMutableDictionary; import net.wotonomy.foundation.NSSelector; import net.wotonomy.foundation.NSSet; import net.wotonomy.foundation.internal.ValueConverter; import net.wotonomy.foundation.internal.WotonomyException; /** * EOQualifiers are used to perform property-based qualifications on objects: * for a set of criteria, a qualifier either qualifies or disqualifies an given * object. EOKeyValueQualifiers can be joined by EOAndQualifier and * EOOrQualifier, and so can form a tree of qualifications.
*
* * Certain qualifiers can accept a variable in place of a value; variable names * are marked by a "$", as in "$name". Variables are resolved with the * qualifierWithBindings() method. * * @author michael@mpowers.net * @author yjcheung@intersectsoft.com * @author $Author: cgruber $ * @version $Revision: 894 $ */ public abstract class EOQualifier { public static final NSSelector QualifierOperatorCaseInsensitiveLike = new OperatorCaseInsensitiveLike(); public static final NSSelector QualifierOperatorContains = new OperatorContains(); public static final NSSelector QualifierOperatorEqual = new OperatorEqual(); public static final NSSelector QualifierOperatorGreaterThan = new OperatorGreaterThan(); public static final NSSelector QualifierOperatorGreaterThanOrEqualTo = new OperatorGreaterThanOrEqualTo(); public static final NSSelector QualifierOperatorLessThan = new OperatorLessThan(); public static final NSSelector QualifierOperatorLessThanOrEqualTo = new OperatorLessThanOrEqualTo(); public static final NSSelector QualifierOperatorLike = new OperatorLike(); public static final NSSelector QualifierOperatorNotEqual = new OperatorNotEqual(); /** * Default constructor. */ public EOQualifier() { } /** * Adds all qualifier keys in this qualifier to the specified Set, which is * expected to be mutable. The tree of qualifiers is traversed and the * left-hand-side of each expression is added to the set. */ public void addQualifierKeysToSet(Set aSet) { throw new RuntimeException("Not implemented yet."); } /** * Returns a Set of all property names used for comparisons by this qualifier. * The tree of qualifiers is traversed and the left-hand-side of each expression * is added to the set. */ public NSSet allQualifierKeys() { throw new RuntimeException("Not implemented yet."); } /** * Returns a List containing the variables used at compare-time by this * qualifier. Each variable will appear only once in the list. */ public NSArray bindingKeys() { throw new RuntimeException("Not implemented yet."); } /** * Returns whether the specified object meets the criteria defined by this * qualifier. */ public boolean evaluateWithObject(Object anObject) { return true; } /** * Returns the key (which can be a key path) that is tested against the * specified binding variable. The tree is traversed looking for the first * instance of the specified variable, and the corresponding left-hand-side of * the expression is returned. */ public String keyPathForBindingKey(String aVariable) { throw new RuntimeException("Not implemented yet."); } /** * Returns a qualifier that is like this qualifier, except all variables will be * replaced with values from the specified Map whose keys match the variable * names. If requireAll is true, an exception will be thrown if there is no key * that matches on of the variables in the tree; otherwise, the qualifier * containing the unmatched variable is removed. */ public EOQualifier qualifierWithBindings(Map aMap, boolean requireAll) { throw new WotonomyException("Not implemented yet."); } /** * Tests whether all the keys in this qualifier can be applied to an object of * the specified class. * * @return A Throwable if the validation fails, otherwise returns null if the * class can be used with this qualifier. */ public Throwable validateKeysWithRootClassDescription(Class aClass) { throw new WotonomyException("Not implemented yet."); } // statics /** * Convenience to retain only those objects from the specified List that meet * the specified qualifier's requirements. */ public static void filterArrayWithQualifier(List anObjectList, EOQualifier aQualifier) { ListIterator iterator = anObjectList.listIterator(); while (iterator.hasNext()) { if (!aQualifier.evaluateWithObject(iterator.next())) { iterator.remove(); } } } /** * Convenience to return a List consisting only of those objects in the * specified List that meet the specified qualifier's requirements. */ public static NSArray filteredArrayWithQualifier(List anObjectList, EOQualifier aQualifier) { Object o; List result = new LinkedList(); Iterator iterator = anObjectList.iterator(); while (iterator.hasNext()) { o = iterator.next(); if (aQualifier.evaluateWithObject(o)) { result.add(o); } } return new NSArray((Collection) result); } /** * Convenience to create a set of EOKeyValueQualifiers joined by an * EOAndQualifier. Each pair of keys and values are used to create * EOKeyValueQualifiers. */ public static EOQualifier qualifierToMatchAllValues(Map aMap) { Object key, value; List qualifierList = new LinkedList(); Iterator iterator = aMap.keySet().iterator(); while (iterator.hasNext()) { key = iterator.next(); value = aMap.get(key); qualifierList.add(new EOKeyValueQualifier(key.toString(), QualifierOperatorEqual, value)); } return new EOAndQualifier(qualifierList); } /** * Convenience to create a set of EOKeyValueQualifiers joined by an * EOOrQualifier. Each pair of keys and values are used to create * EOKeyValueQualifiers. */ public static EOQualifier qualifierToMatchAnyValue(Map aMap) { Object key, value; List qualifierList = new LinkedList(); Iterator iterator = aMap.keySet().iterator(); while (iterator.hasNext()) { key = iterator.next(); value = aMap.get(key); qualifierList.add(new EOKeyValueQualifier(key.toString(), QualifierOperatorEqual, value)); } return new EOOrQualifier(qualifierList); } /** * Returns an EOQualifier that meets the criteria represented by the specified * string and variable length argument list. This method parses the string and * returns a tree of qualifiers. Each token beginning with "%" is replaced with * the corresponding object from the argument list. The parser recognizes the * operation tokens returned from the allQualifierOperators() method. */ public static EOQualifier qualifierWithQualifierFormat(String aString, List anArgumentList) { throw new RuntimeException("Not implemented yet."); } /** * Returns a List of operators that are supported for relational operations. * This excludes string comparison operators. */ public static NSArray relationalQualifierOperators() { NSMutableArray result = new NSMutableArray(); BaseSelector selector; Iterator iterator = allQualifierOperators().iterator(); while (iterator.hasNext()) { selector = (BaseSelector) iterator.next(); if (selector.isRelationalOperator()) { result.addObject(selector); } } return result; } /** * Returns a List of valid operators. */ public static NSArray allQualifierOperators() { return operators.allKeys(); } /** * Returns a selector the corresponds to the operation represented by the * specified string. For example, ">" represents QualifierOperatorGreaterThan. */ public static NSSelector operatorSelectorForString(String anOperatorString) { return (NSSelector) operators.objectForKey(anOperatorString); } /** * Returns a string the corresponds to the operation represented by the * specified selector. For example, QualifierOperatorGreaterThan is represented * with ">". */ public static String stringForOperatorSelector(NSSelector aSelector) { return (String) operators.allKeysForObject(aSelector).lastObject(); } /** * Returns a string representation of this qualifier. */ public String toString() { // TODO: implement this return super.toString(); } // built-in qualifiers private static NSMutableDictionary operators; static { operators = new NSMutableDictionary(); operators.setObjectForKey(QualifierOperatorCaseInsensitiveLike, QualifierOperatorCaseInsensitiveLike.toString()); operators.setObjectForKey(QualifierOperatorContains, QualifierOperatorContains.toString()); operators.setObjectForKey(QualifierOperatorEqual, QualifierOperatorEqual.toString()); operators.setObjectForKey(QualifierOperatorGreaterThan, QualifierOperatorGreaterThan.toString()); operators.setObjectForKey(QualifierOperatorGreaterThanOrEqualTo, QualifierOperatorGreaterThanOrEqualTo.toString()); operators.setObjectForKey(QualifierOperatorLessThan, QualifierOperatorLessThan.toString()); operators.setObjectForKey(QualifierOperatorLessThanOrEqualTo, QualifierOperatorLessThanOrEqualTo.toString()); operators.setObjectForKey(QualifierOperatorLike, QualifierOperatorLike.toString()); operators.setObjectForKey(QualifierOperatorNotEqual, QualifierOperatorNotEqual.toString()); } static private abstract class BaseSelector extends NSSelector { public String name() { return "BaseSelector"; } public Class[] parameterTypes() { return new Class[] { Object.class, Object.class }; } public Method methodOnClass(Class aClass) throws NoSuchMethodException { throw new NoSuchMethodException(); } public Method methodOnObject(Object anObject) throws NoSuchMethodException { throw new NoSuchMethodException(); } public boolean implementedByClass(Class aClass) { return true; } public boolean implementedByObject(Object anObject) { return true; } public boolean isRelationalOperator() { return true; } public Object invoke(Object anObject, Object[] parameters) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { return qualify(anObject, parameters[0]); } abstract protected Boolean qualify(Object target, Object parameter); protected int doCompare(Object o1, Object o2) { Class firstClass = o1.getClass(); Class secondClass = o2.getClass(); if (!(secondClass.equals(firstClass))) { Object converted = null; if (o2 instanceof Comparable) { converted = ValueConverter.convertObjectToClass(o1, secondClass); if (converted != null) { o1 = converted; } } if (converted == null && (o1 instanceof Comparable)) { converted = ValueConverter.convertObjectToClass(o2, firstClass); if (converted != null) { o2 = converted; } } if (converted == null) { throw new WotonomyException("Qualifier: Not Comparable Objects"); // no way to compare } } return ((Comparable) o2).compareTo(o1); } } static class OperatorCaseInsensitiveLike extends BaseSelector { public boolean isRelationalOperator() { return false; } protected Boolean qualify(Object o1, Object o2) { String myString1 = o1.toString(); String myString2 = o2.toString(); myString1 = myString1.toLowerCase(); myString2 = myString2.toLowerCase(); StringTokenizer st = new StringTokenizer(myString1, "%"); while (st.hasMoreTokens()) { String part = st.nextToken(); int index = myString2.indexOf(part); if (index > -1) { myString2 = myString2.substring(index + part.length()); } else { return Boolean.FALSE; } } return Boolean.TRUE; } public String toString() { return "caseInsensitiveLike"; } } static class OperatorContains extends BaseSelector { public boolean isRelationalOperator() { return false; } protected Boolean qualify(Object o1, Object o2) { String myString1 = o1.toString(); String myString2 = o2.toString(); return new Boolean(myString2.indexOf(myString1) > -1); } public String toString() { return "contains"; } } static class OperatorEqual extends BaseSelector { protected Boolean qualify(Object o1, Object o2) { return new Boolean(doCompare(o1, o2) == 0); } public String toString() { return "="; } } static class OperatorGreaterThan extends BaseSelector { protected Boolean qualify(Object o1, Object o2) { return new Boolean(doCompare(o1, o2) > 0); } public String toString() { return ">"; } } static class OperatorGreaterThanOrEqualTo extends BaseSelector { protected Boolean qualify(Object o1, Object o2) { return new Boolean(doCompare(o1, o2) >= 0); } public String toString() { return new String(" >= "); } } static class OperatorLessThan extends BaseSelector { protected Boolean qualify(Object o1, Object o2) { return new Boolean(doCompare(o1, o2) < 0); } public String toString() { return ">"; } } static class OperatorLessThanOrEqualTo extends BaseSelector { protected Boolean qualify(Object o1, Object o2) { return new Boolean(doCompare(o1, o2) <= 0); } public String toString() { return "<="; } } static class OperatorLike extends BaseSelector { public boolean isRelationalOperator() { return false; } protected Boolean qualify(Object o1, Object o2) { String myString1 = o1.toString(); String myString2 = o2.toString(); StringTokenizer st = new StringTokenizer(myString1, "%"); while (st.hasMoreTokens()) { String part = st.nextToken(); int index = myString2.indexOf(part); if (index > -1) { myString2 = myString2.substring(index + part.length()); } else { return Boolean.FALSE; } } return Boolean.TRUE; } public String toString() { return "like"; } } static class OperatorNotEqual extends BaseSelector { protected Boolean qualify(Object o1, Object o2) { return new Boolean(!(o1.equals(o2))); } public String toString() { return "!="; } } public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver ua) { String cname = (String) ua.decodeObjectForKey("class"); if (cname.equals("EOKeyValueQualifier")) return (EOQualifier) EOKeyValueQualifier.decodeWithKeyValueUnarchiver(ua); if (cname.equals("EOAndQualifier")) return (EOQualifier) EOAndQualifier.decodeWithKeyValueUnarchiver(ua); if (cname.equals("EOOrQualifier")) return (EOQualifier) EOOrQualifier.decodeWithKeyValueUnarchiver(ua); if (cname.equals("EONotQualifier")) return (EOQualifier) EONotQualifier.decodeWithKeyValueUnarchiver(ua); return null; } } /* * $Log$ Revision 1.2 2006/02/16 16:47:14 cgruber Move some classes in to * "internal" packages and re-work imports, etc. * * Also use UnsupportedOperationExceptions where appropriate, instead of * WotonomyExceptions. * * Revision 1.1 2006/02/16 13:19:57 cgruber Check in all sources in * eclipse-friendly maven-enabled packages. * * Revision 1.10 2003/08/09 01:22:51 chochos qualifiers implement * EOKeyValueArchiving * * Revision 1.9 2001/11/04 18:29:11 mpowers Better handling for non-string types * used with non-relational operators. * * Revision 1.8 2001/10/31 15:25:14 mpowers Cleanup of qualifiers. * * Revision 1.7 2001/10/30 22:57:28 mpowers EOQualifier framework is now * working. * * Revision 1.6 2001/10/30 22:16:37 mpowers Implemented operators as selectors. * * Revision 1.5 2001/09/14 14:21:28 mpowers Updated javadoc. * * Revision 1.3 2001/09/13 15:25:56 mpowers Started implementation of the * EOQualifier framework. * * Revision 1.2 2001/02/27 03:33:04 mpowers Initial draft of the key-value * qualifier. * * Revision 1.1.1.1 2000/12/21 15:46:47 mpowers Contributing wotonomy. * * Revision 1.2 2000/12/20 16:25:35 michael Added log to all files. * * */