View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2000 Michael Powers
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
17  */
18  
19  package net.wotonomy.control;
20  
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import java.util.Collection;
24  import java.util.Iterator;
25  import java.util.LinkedList;
26  import java.util.List;
27  import java.util.ListIterator;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.StringTokenizer;
31  
32  import net.wotonomy.foundation.NSArray;
33  import net.wotonomy.foundation.NSMutableArray;
34  import net.wotonomy.foundation.NSMutableDictionary;
35  import net.wotonomy.foundation.NSSelector;
36  import net.wotonomy.foundation.NSSet;
37  import net.wotonomy.foundation.internal.ValueConverter;
38  import net.wotonomy.foundation.internal.WotonomyException;
39  
40  /***
41  * EOQualifiers are used to perform property-based
42  * qualifications on objects: for a set of criteria,
43  * a qualifier either qualifies or disqualifies an
44  * given object.  EOKeyValueQualifiers can be joined
45  * by EOAndQualifier and EOOrQualifier, and so can
46  * form a tree of qualifications.  <br><br>
47  *
48  * Certain qualifiers
49  * can accept a variable in place of a value; variable
50  * names are marked by a "$", as in "$name".  Variables
51  * are resolved with the qualifierWithBindings() method.
52  *
53  * @author michael@mpowers.net
54  * @author yjcheung@intersectsoft.com
55  * @author $Author: cgruber $
56  * @version $Revision: 894 $
57  */
58  public abstract class EOQualifier
59  {
60      public static final NSSelector 
61          QualifierOperatorCaseInsensitiveLike = new OperatorCaseInsensitiveLike();
62      public static final NSSelector 
63          QualifierOperatorContains = new OperatorContains();
64      public static final NSSelector 
65          QualifierOperatorEqual = new OperatorEqual();
66      public static final NSSelector 
67          QualifierOperatorGreaterThan = new OperatorGreaterThan();
68      public static final NSSelector 
69          QualifierOperatorGreaterThanOrEqualTo = new OperatorGreaterThanOrEqualTo();
70      public static final NSSelector 
71          QualifierOperatorLessThan = new OperatorLessThan();
72      public static final NSSelector 
73          QualifierOperatorLessThanOrEqualTo = new OperatorLessThanOrEqualTo();
74      public static final NSSelector 
75          QualifierOperatorLike = new OperatorLike();
76      public static final NSSelector 
77          QualifierOperatorNotEqual = new OperatorNotEqual();
78  
79      /***
80      * Default constructor.
81      */
82      public EOQualifier ()
83      {
84      }
85  
86      /***
87      * Adds all qualifier keys in this qualifier to
88      * the specified Set, which is expected to be
89      * mutable.  The tree of qualifiers is traversed
90      * and the left-hand-side of each expression is
91      * added to the set.
92      */
93      public void addQualifierKeysToSet ( Set aSet )
94      {
95          throw new RuntimeException( "Not implemented yet." );
96      }
97  
98      /***
99      * Returns a Set of all property names used for
100     * comparisons by this qualifier.  The tree of
101     * qualifiers is traversed and the left-hand-side
102     * of each expression is added to the set.
103     */
104     public NSSet allQualifierKeys ()
105     {
106         throw new RuntimeException( "Not implemented yet." );
107     }
108 
109     /***
110     * Returns a List containing the variables used
111     * at compare-time by this qualifier.  Each variable
112     * will appear only once in the list.
113     */
114     public NSArray bindingKeys ()
115     {
116         throw new RuntimeException( "Not implemented yet." );
117     }
118 
119     /***
120     * Returns whether the specified object meets the
121     * criteria defined by this qualifier.
122     */
123     public boolean evaluateWithObject ( Object anObject )
124     {
125         return true;
126     }
127     
128     /***
129     * Returns the key (which can be a key path) that
130     * is tested against the specified binding variable.
131     * The tree is traversed looking for the first instance
132     * of the specified variable, and the corresponding
133     * left-hand-side of the expression is returned.
134     */
135     public String keyPathForBindingKey ( String aVariable )
136     {
137         throw new RuntimeException( "Not implemented yet." );
138     }
139 
140     /***
141     * Returns a qualifier that is like this qualifier,
142     * except all variables will be replaced with values
143     * from the specified Map whose keys match the variable
144     * names.  If requireAll is true, an exception will be
145     * thrown if there is no key that matches on of the
146     * variables in the tree; otherwise, the qualifier
147     * containing the unmatched variable is removed.
148     */
149     public EOQualifier qualifierWithBindings (
150         Map aMap,
151         boolean requireAll )
152     {
153         throw new WotonomyException( "Not implemented yet." );
154     }
155 
156 
157     /***
158     * Tests whether all the keys in this qualifier can
159     * be applied to an object of the specified class.
160     * @return A Throwable if the validation fails,
161     * otherwise returns null if the class can be used
162     * with this qualifier.
163     */
164     public Throwable validateKeysWithRootClassDescription (
165         Class aClass )
166     {
167         throw new WotonomyException( "Not implemented yet." );
168     }
169 
170     // statics
171 
172     /***
173     * Convenience to retain only those objects from the specified
174     * List that meet the specified qualifier's requirements.
175     */
176     public static void filterArrayWithQualifier (
177         List anObjectList, EOQualifier aQualifier )
178     {
179         ListIterator iterator = anObjectList.listIterator();
180         while ( iterator.hasNext() )
181         {
182             if ( ! aQualifier.evaluateWithObject( iterator.next() ) )
183             {
184                 iterator.remove();
185             }
186         }
187     }
188 
189     /***
190     * Convenience to return a List consisting only
191     * of those objects in the specified List that meet
192     * the specified qualifier's requirements.
193     */
194     public static NSArray filteredArrayWithQualifier (
195         List anObjectList, EOQualifier aQualifier )
196     {
197         Object o;
198         List result = new LinkedList();
199         Iterator iterator = anObjectList.iterator();
200         while ( iterator.hasNext() )
201         {
202             o = iterator.next();
203             if ( aQualifier.evaluateWithObject( o ) )
204             {
205                 result.add( o );
206             }
207         }
208         return new NSArray( (Collection) result );
209     }
210 
211     /***
212     * Convenience to create a set of EOKeyValueQualifiers
213     * joined by an EOAndQualifier. Each pair of keys and
214     * values are used to create EOKeyValueQualifiers.
215     */
216     public static EOQualifier
217     qualifierToMatchAllValues ( Map aMap )
218     {
219         Object key, value;
220         List qualifierList = new LinkedList();
221         Iterator iterator = aMap.keySet().iterator();
222         while ( iterator.hasNext() )
223         {
224             key = iterator.next();
225             value = aMap.get( key );
226             qualifierList.add( new EOKeyValueQualifier( 
227                 key.toString(), QualifierOperatorEqual, value ) );
228         }
229         return new EOAndQualifier( qualifierList );
230     }
231 
232     /***
233     * Convenience to create a set of EOKeyValueQualifiers
234     * joined by an EOOrQualifier.  Each pair of keys and
235     * values are used to create EOKeyValueQualifiers.
236     */
237     public static EOQualifier
238     qualifierToMatchAnyValue ( Map aMap )
239     {
240         Object key, value;
241         List qualifierList = new LinkedList();
242         Iterator iterator = aMap.keySet().iterator();
243         while ( iterator.hasNext() )
244         {
245             key = iterator.next();
246             value = aMap.get( key );
247             qualifierList.add( new EOKeyValueQualifier( 
248                 key.toString(), QualifierOperatorEqual, value ) );
249         }
250         return new EOOrQualifier( qualifierList );
251     }
252 
253     /***
254     * Returns an EOQualifier that meets the criteria
255     * represented by the specified string and variable
256     * length argument list.  This method parses the string
257     * and returns a tree of qualifiers.  Each token beginning
258     * with "%" is replaced with the corresponding object
259     * from the argument list.  The parser recognizes the
260     * operation tokens returned from the allQualifierOperators()
261     * method.
262     */
263     public static EOQualifier qualifierWithQualifierFormat (
264         String aString, List anArgumentList )
265     {
266         throw new RuntimeException( "Not implemented yet." );
267     }
268 
269     /***
270     * Returns a List of operators that are supported for
271     * relational operations.  This excludes string comparison
272     * operators.
273     */
274     public static NSArray relationalQualifierOperators ()
275     {
276         NSMutableArray result = new NSMutableArray();
277         BaseSelector selector;
278         Iterator iterator = allQualifierOperators().iterator();
279         while ( iterator.hasNext() )
280         {
281             selector = (BaseSelector) iterator.next();
282             if ( selector.isRelationalOperator() )
283             {
284                 result.addObject( selector );
285             }
286         }
287         return result;
288     }
289 
290     /***
291     * Returns a List of valid operators.
292     */
293     public static NSArray allQualifierOperators ()
294     {
295         return operators.allKeys();
296     }
297 
298     /***
299     * Returns a selector the corresponds to the operation
300     * represented by the specified string.  For example,
301     * ">" represents QualifierOperatorGreaterThan.
302     */
303     public static NSSelector operatorSelectorForString (
304         String anOperatorString )
305     {
306         return (NSSelector)
307             operators.objectForKey( anOperatorString );
308     }
309 
310     /***
311     * Returns a string the corresponds to the operation
312     * represented by the specified selector.  For example,
313     * QualifierOperatorGreaterThan is represented with ">".
314     */
315     public static String stringForOperatorSelector (
316         NSSelector aSelector )
317     {
318         return (String) 
319             operators.allKeysForObject( aSelector ).lastObject();
320     }
321 
322     /***
323     * Returns a string representation of this qualifier.
324     */
325     public String toString()
326     {
327          //TODO: implement this
328          return super.toString();
329     }
330 
331     // built-in qualifiers
332     
333     private static NSMutableDictionary operators;
334     static 
335     {
336         operators = new NSMutableDictionary();
337         operators.setObjectForKey( 
338             QualifierOperatorCaseInsensitiveLike, 
339             QualifierOperatorCaseInsensitiveLike.toString() );
340         operators.setObjectForKey( 
341             QualifierOperatorContains, 
342             QualifierOperatorContains.toString() );
343         operators.setObjectForKey( 
344             QualifierOperatorEqual, 
345             QualifierOperatorEqual.toString() );
346         operators.setObjectForKey( 
347             QualifierOperatorGreaterThan, 
348             QualifierOperatorGreaterThan.toString() );
349         operators.setObjectForKey( 
350             QualifierOperatorGreaterThanOrEqualTo, 
351             QualifierOperatorGreaterThanOrEqualTo.toString() );
352         operators.setObjectForKey( 
353             QualifierOperatorLessThan, 
354             QualifierOperatorLessThan.toString() );
355         operators.setObjectForKey( 
356             QualifierOperatorLessThanOrEqualTo, 
357             QualifierOperatorLessThanOrEqualTo.toString() );
358         operators.setObjectForKey( 
359             QualifierOperatorLike, 
360             QualifierOperatorLike.toString() );
361         operators.setObjectForKey( 
362             QualifierOperatorNotEqual, 
363             QualifierOperatorNotEqual.toString() );
364     }
365 
366     static private abstract class BaseSelector extends NSSelector
367     {
368         public String name ()
369         {
370             return "BaseSelector";
371         }
372     
373         public Class[] parameterTypes ()
374         {
375             return new Class[] { Object.class, Object.class };
376         }
377     
378         public Method methodOnClass (Class aClass) 
379             throws NoSuchMethodException
380         {
381             throw new NoSuchMethodException();
382         }
383         
384         public Method methodOnObject (Object anObject)
385             throws NoSuchMethodException
386         {
387             throw new NoSuchMethodException();
388         }
389         
390         public boolean implementedByClass (Class aClass)
391         {
392             return true;
393         }
394         
395         public boolean implementedByObject (Object anObject)
396         {
397             return true;
398         }
399         
400         public boolean isRelationalOperator()
401         {
402             return true;
403         }
404         
405         public Object invoke (Object anObject, Object[] parameters) 
406             throws IllegalAccessException, IllegalArgumentException, 
407                 InvocationTargetException, NoSuchMethodException
408         {
409             return qualify( anObject, parameters[0] );
410         }
411         
412         abstract protected Boolean qualify( Object target, Object parameter );
413 
414         protected int doCompare(Object o1, Object o2)
415         {
416             Class firstClass = o1.getClass();
417             Class secondClass = o2.getClass();
418     
419             if ( ! ( secondClass.equals( firstClass ) ) )
420             {
421                 Object converted = null;
422                 if ( o2 instanceof Comparable )
423                 {
424                     converted = ValueConverter.convertObjectToClass( o1, secondClass );
425                     if (converted != null)
426                     {
427                         o1 = converted;
428                     }
429                 }
430                 
431                 if (converted == null && (o1 instanceof Comparable))
432                 {
433                     converted = ValueConverter.convertObjectToClass( o2, firstClass );
434                     if ( converted != null )
435                     {
436                         o2 = converted;
437                     }
438                 }
439                 
440                 if (converted == null)
441                 {
442                     throw new WotonomyException("Qualifier: Not Comparable Objects");
443                     // no way to compare
444                 }
445             }
446             
447             return ((Comparable)o2).compareTo( o1 );
448         }
449     
450     }
451     
452     
453     static class OperatorCaseInsensitiveLike extends BaseSelector {
454         
455         public boolean isRelationalOperator()
456         {
457             return false;
458         }
459         
460         protected Boolean qualify( Object o1, Object o2 )
461         {
462             String myString1 = o1.toString();
463             String myString2 = o2.toString();
464             myString1 = myString1.toLowerCase();
465             myString2 = myString2.toLowerCase();
466             StringTokenizer st = new StringTokenizer(myString1, "%");
467             
468             while (st.hasMoreTokens()) {
469                  String part = st.nextToken();
470                  int index = myString2.indexOf(part);
471                  if (index > -1)
472                  {
473                      myString2 = myString2.substring(index + part.length());
474                  }
475                  else
476                  {
477                      return Boolean.FALSE;
478                  }
479              }
480             return Boolean.TRUE;
481             
482         }
483         
484         public String toString()
485         {
486             return "caseInsensitiveLike";
487         }
488     }
489     
490     static class OperatorContains extends BaseSelector {
491         
492         public boolean isRelationalOperator()
493         {
494             return false;
495         }
496         
497         protected Boolean qualify( Object o1, Object o2 )
498         {
499             String myString1 = o1.toString();
500             String myString2 = o2.toString();
501             return new Boolean(
502                 myString2.indexOf(myString1) > -1 );
503         }
504         
505         public String toString()
506         {
507             return "contains";
508         }
509     }
510     
511     static class OperatorEqual extends BaseSelector {
512     
513         protected Boolean qualify( Object o1, Object o2 )
514         {
515             return new Boolean( doCompare(o1, o2) == 0 );
516         }
517         
518         public String toString()
519         {
520             return "=";
521         }
522     }
523     
524     static class OperatorGreaterThan extends BaseSelector {
525     
526         protected Boolean qualify( Object o1, Object o2 )
527         {
528             return new Boolean( doCompare(o1, o2) > 0 );
529         }
530         
531         public String toString()
532         {
533             return ">";
534         }
535     }
536     
537     static class OperatorGreaterThanOrEqualTo extends BaseSelector {
538         
539         protected Boolean qualify( Object o1, Object o2 )
540         {
541             return new Boolean( doCompare(o1, o2) >= 0 );
542         }
543         
544         public String toString()
545         {
546             return new String(" >= ");
547         }
548     }
549     
550     static class OperatorLessThan extends BaseSelector {
551         
552         protected Boolean qualify( Object o1, Object o2 )
553         {
554             return new Boolean( doCompare(o1, o2) < 0 ); 
555         }
556         
557         public String toString()
558         {
559             return ">";
560         }
561     }
562     
563     static class OperatorLessThanOrEqualTo extends BaseSelector {
564         
565         protected Boolean qualify( Object o1, Object o2 )
566         {
567             return new Boolean (doCompare(o1, o2) <= 0);
568         }
569         
570         public String toString()
571         {
572             return "<=";
573         }
574     }
575     
576     static class OperatorLike extends BaseSelector {
577         
578         public boolean isRelationalOperator()
579         {
580             return false;
581         }
582         
583         protected Boolean qualify( Object o1, Object o2 )
584         {
585             String myString1 = o1.toString();
586             String myString2 = o2.toString();
587             StringTokenizer st = new StringTokenizer(myString1, "%");
588             while (st.hasMoreTokens()) {
589                  String part = st.nextToken();
590                  int index = myString2.indexOf(part);
591                  if (index > -1)
592                  {
593                      myString2 = myString2.substring(index + part.length());
594                  }
595                  else
596                  {
597                      return Boolean.FALSE;
598                  }
599             }
600             return Boolean.TRUE;
601         }
602         
603         public String toString()
604         {
605             return "like";
606         }
607     }
608     
609     static class OperatorNotEqual extends BaseSelector {
610 
611         protected Boolean qualify( Object o1, Object o2 )
612         {
613             return new Boolean(!(o1.equals(o2)));
614         }
615         
616         public String toString()
617         {
618             return "!=";
619         }
620     }
621  
622 
623 	public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver ua) {
624 		String cname = (String)ua.decodeObjectForKey("class");
625 		if (cname.equals("EOKeyValueQualifier"))
626 			return (EOQualifier)EOKeyValueQualifier.decodeWithKeyValueUnarchiver(ua);
627 		if (cname.equals("EOAndQualifier"))
628 			return (EOQualifier)EOAndQualifier.decodeWithKeyValueUnarchiver(ua);
629 		if (cname.equals("EOOrQualifier"))
630 			return (EOQualifier)EOOrQualifier.decodeWithKeyValueUnarchiver(ua);
631 		if (cname.equals("EONotQualifier"))
632 			return (EOQualifier)EONotQualifier.decodeWithKeyValueUnarchiver(ua);
633 		return null;
634 	}
635 
636 }
637 /*
638  * $Log$
639  * Revision 1.2  2006/02/16 16:47:14  cgruber
640  * Move some classes in to "internal" packages and re-work imports, etc.
641  *
642  * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions.
643  *
644  * Revision 1.1  2006/02/16 13:19:57  cgruber
645  * Check in all sources in eclipse-friendly maven-enabled packages.
646  *
647  * Revision 1.10  2003/08/09 01:22:51  chochos
648  * qualifiers implement EOKeyValueArchiving
649  *
650  * Revision 1.9  2001/11/04 18:29:11  mpowers
651  * Better handling for non-string types used with non-relational operators.
652  *
653  * Revision 1.8  2001/10/31 15:25:14  mpowers
654  * Cleanup of qualifiers.
655  *
656  * Revision 1.7  2001/10/30 22:57:28  mpowers
657  * EOQualifier framework is now working.
658  *
659  * Revision 1.6  2001/10/30 22:16:37  mpowers
660  * Implemented operators as selectors.
661  *
662  * Revision 1.5  2001/09/14 14:21:28  mpowers
663  * Updated javadoc.
664  *
665  * Revision 1.3  2001/09/13 15:25:56  mpowers
666  * Started implementation of the EOQualifier framework.
667  *
668  * Revision 1.2  2001/02/27 03:33:04  mpowers
669  * Initial draft of the key-value qualifier.
670  *
671  * Revision 1.1.1.1  2000/12/21 15:46:47  mpowers
672  * Contributing wotonomy.
673  *
674  * Revision 1.2  2000/12/20 16:25:35  michael
675  * Added log to all files.
676  *
677  *
678  */
679 
680