/* 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. * * */