KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > josql > expressions > Function


1 /*
2  * Copyright 2004-2005 Gary Bentley
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may
5  * not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */

15 package org.josql.expressions;
16
17 import java.util.List JavaDoc;
18 import java.util.ArrayList JavaDoc;
19 import java.util.Arrays JavaDoc;
20 import java.util.Map JavaDoc;
21 import java.util.TreeMap JavaDoc;
22
23 import java.lang.reflect.Method JavaDoc;
24 import java.lang.reflect.Modifier JavaDoc;
25
26 import com.gentlyweb.utils.Getter;
27
28 import org.josql.Query;
29 import org.josql.QueryExecutionException;
30 import org.josql.QueryParseException;
31
32 import org.josql.internal.Utilities;
33
34 /**
35  * This class represents a Function that can be "called" in JoSQL.
36  * <p>
37  * Last Modified By: $Author: barrygently $<br />
38  * Last Modified On: $Date: 2005/01/07 17:08:07 $<br />
39  * Current Revision: $Revision: 1.6 $<br />
40  */

41 public class Function extends ValueExpression
42 {
43
44     private String JavaDoc name = null;
45     private List JavaDoc params = null;
46     private Method JavaDoc function = null;
47     private Object JavaDoc handler = null;
48     private boolean fixedResult = true;
49     private Object JavaDoc fixedValue = null;
50     private String JavaDoc acc = null;
51     private Getter get = null;
52
53     public Getter getGetter ()
54     {
55
56     return this.get;
57
58     }
59
60     public String JavaDoc getAccessor ()
61     {
62
63     return this.acc;
64
65     }
66
67     public void setAccessor (String JavaDoc acc)
68     {
69
70     this.acc = acc;
71
72     }
73
74     /**
75      * Get the expected return type from the function. The exact class returned is
76      * dependent upon the function (Java method) that is being called.
77      *
78      * @param q The Query object.
79      * @return The class of the expected return type.
80      */

81     public Class JavaDoc getExpectedReturnType (Query q)
82     {
83
84     if (this.get != null)
85     {
86
87         return this.get.getType ();
88
89     }
90
91     return this.function.getReturnType ();
92
93     }
94
95     /**
96      * This is a complex method that will initialise the function.
97      * Firstly all of the "arguments" to the function are inited and then
98      * their expected return types gained from calling: {@link Function#getExpectedReturnType(Query)}.
99      * Then the function handlers, user-defined and then built-in are searched until they
100      * find a match for the function name, ensure that it's a public method and that all the
101      * arguments match, widening the match where necessary.
102      *
103      * @param q The Query object.
104      * @throws QueryParseException If something goes wrong whilst initing the arguments to the
105      * function or if the function cannot be found.
106      */

107     public void init (Query q)
108                       throws QueryParseException
109     {
110
111     // Need to init the params (if present) first because the expected type may not be
112
// present otherwise.
113
if (this.params != null)
114     {
115
116         int s = this.params.size ();
117
118         for (int i = 0; i < s; i++)
119         {
120
121         Expression exp = (Expression) this.params.get (i);
122
123         exp.init (q);
124
125         }
126
127     }
128
129     Class JavaDoc[] ps = null;
130
131     if (this.params != null)
132     {
133
134         int s = params.size ();
135
136         ps = new Class JavaDoc[s];
137
138         for (int i = 0; i < s; i++)
139         {
140
141         // Need to get the expected return type.
142
Expression exp = (Expression) params.get (i);
143
144         ps[i] = exp.getExpectedReturnType (q);
145
146         }
147
148     }
149
150     // Try and find the relevant method, first in the user-defined function handler (if present)
151
// or the built-in handlers.
152
List JavaDoc fhs = q.getFunctionHandlers ();
153
154     if (fhs != null)
155     {
156
157         int s = fhs.size ();
158
159         TreeMap JavaDoc ms = new TreeMap JavaDoc ();
160
161         for (int i = 0; i < s; i++)
162         {
163
164         Object JavaDoc fh = fhs.get (i);
165
166         this.getMethods (fh.getClass (),
167                  q,
168                  ms);
169         
170         }
171
172         // Get the one with the highest score.
173
if (ms.size () > 0)
174         {
175
176         this.function = (Method JavaDoc) ms.get (ms.lastKey ());
177         
178         Class JavaDoc c = this.function.getDeclaringClass ();
179
180         // What a pain!
181
for (int i = 0; i < fhs.size (); i++)
182         {
183
184             Object JavaDoc o = fhs.get (i);
185
186             if (o.getClass ().isAssignableFrom (c))
187             {
188
189             this.handler = o;
190
191             }
192
193         }
194         
195         }
196
197     }
198
199     List JavaDoc dfhs = q.getDefaultFunctionHandlers ();
200
201     if (this.function == null)
202     {
203
204         TreeMap JavaDoc ms = new TreeMap JavaDoc ();
205
206         for (int i = 0; i < dfhs.size (); i++)
207         {
208         
209         Object JavaDoc dfh = dfhs.get (i);
210
211         this.getMethods (dfh.getClass (),
212                  q,
213                  ms);
214
215         }
216
217         // Get the one with the highest score.
218
if (ms.size () > 0)
219         {
220
221         this.function = (Method JavaDoc) ms.get (ms.lastKey ());
222
223         Class JavaDoc c = this.function.getDeclaringClass ();
224
225         // What a pain!
226
for (int i = 0; i < dfhs.size (); i++)
227         {
228
229             Object JavaDoc o = dfhs.get (i);
230
231             if (o.getClass ().isAssignableFrom (c))
232             {
233
234             this.handler = o;
235
236             }
237
238         }
239         
240         }
241
242     }
243
244     if (this.function == null)
245     {
246
247         // Can't find the function.
248
throw new QueryParseException ("Unable to find function (method): \"" +
249                        Utilities.formatSignature (this.name,
250                                       ps) +
251                        "\" in any user-defined function handlers or the default function handler");
252
253     }
254
255     // Now see if we have an accessor for the function.
256
if (this.acc != null)
257     {
258
259         // We have an accessor, see what the functions return type is.
260
Class JavaDoc retType = this.function.getReturnType ();
261
262         // Ensure that the function DOES have a return type.
263
if (Void.TYPE.isAssignableFrom (retType))
264         {
265
266         // The return type is void, barf!
267
throw new QueryParseException ("Function: " +
268                            this +
269                            " maps to method: " +
270                            this.function +
271                            " however methods return type is \"void\" and an accessor: " +
272                            this.acc +
273                            " has been defined.");
274
275         }
276
277         // See if the return type is an object. If it is then defer trying to
278
// get the accessor until run-time. Have to do it this way since
279
// type comparisons are a little pointless.
280
if (!retType.getName ().equals (Object JavaDoc.class.getName ()))
281         {
282
283         // The return type is NOT java.lang.Object, so now try and get the
284
// accessor.
285
try
286         {
287
288             this.get = new Getter (this.acc,
289                        retType);
290             
291         } catch (Exception JavaDoc e) {
292
293             throw new QueryParseException ("Function: " +
294                            this +
295                            " maps to method: " +
296                            this.function +
297                            " and has accessor: " +
298                            this.acc +
299                            " however no valid accessor has been found in return type: " +
300                            retType.getName (),
301                            e);
302
303         }
304
305         }
306
307     }
308
309     // A function has/can have a fixed result if all it's arguments
310
// also have a fixed result, if there aren't any args then assume
311
// it won't have a fixed result.
312
if (this.params != null)
313     {
314
315         for (int i = 0; i < this.params.size (); i++)
316         {
317
318         Expression exp = (Expression) this.params.get (i);
319
320         if (!exp.hasFixedResult (q))
321         {
322
323             this.fixedResult = false;
324             break;
325
326         }
327
328         }
329
330     } else {
331
332         this.fixedResult = false;
333
334     }
335
336     }
337
338     /**
339      * Return the List of {@link Expression} objects that constitute the arguments
340      * to the function, no guarantee is made here as to whether they have been inited.
341      *
342      * @return The List of {@link Expression} objects.
343      */

344     public List JavaDoc getParameters ()
345     {
346
347     return this.params;
348
349     }
350
351     public void setParameters (List JavaDoc ps)
352     {
353
354     this.params = ps;
355
356     }
357
358     public void setName (String JavaDoc name)
359     {
360
361
362     this.name = name;
363
364     }
365
366     public String JavaDoc getName ()
367     {
368
369     return this.name;
370
371     }
372
373     /**
374      * Evaluate this function on the current object. It should be noted that not
375      * all functions will use the current object in their execution, functions from the
376      * {@link org.josql.functions.GroupingFunctions} class are notable exceptions.
377      *
378      * @param o The current object.
379      * @param q The Query object.
380      * @return The result of evaluating the function.
381      * @throws QueryExecutionException If something goes wrong during execution of the
382      * function or gaining the values to be used as arguments.
383      */

384     public Object JavaDoc evaluate (Object JavaDoc o,
385                 Query q)
386                         throws QueryExecutionException
387     {
388
389     // See if we have a fixed result.
390
if (this.fixedResult)
391     {
392
393         // Evaluated once...
394
if (this.fixedValue != null)
395         {
396
397         return this.fixedValue;
398
399         }
400
401     }
402
403     // Get the values for the parameters... if any...
404
Object JavaDoc[] ps = null;
405
406     if (this.params != null)
407     {
408
409         int s = this.params.size ();
410
411         ps = new Object JavaDoc[s];
412
413         for (int i = 0; i < s; i++)
414         {
415
416         Expression exp = (Expression) this.params.get (i);
417
418             if (Expression.class.isAssignableFrom (this.function.getParameterTypes ()[i]))
419         {
420
421             // Leave this one alone.
422
ps[i] = exp;
423
424         } else {
425
426             // Eval this expression.
427
try
428             {
429
430             ps[i] = exp.getValue (o,
431                           q);
432
433             } catch (Exception JavaDoc e) {
434             
435             throw new QueryExecutionException ("Unable to get parameter: " +
436                                i +
437                                " (\"" +
438                                exp.toString () +
439                                "\") for function: " +
440                                this.name,
441                                e);
442             
443             }
444
445         }
446
447         }
448
449     }
450
451     Object JavaDoc v = null;
452
453     try
454     {
455
456         v = this.function.invoke (this.handler,
457                       ps);
458
459     } catch (Exception JavaDoc e) {
460
461         throw new QueryExecutionException ("Unable to execute function: " +
462                            this.name +
463                            " (\"" +
464                            this.toString () +
465                            "\") with values: " +
466                            Arrays.asList (ps),
467                            e);
468
469     }
470
471     if (v != null)
472     {
473
474         // See if we have an accessor.
475
if (this.acc != null)
476         {
477
478         // See if we have a Getter.
479
if (this.get == null)
480         {
481
482             // No getter, so now try and init it. This assumes that the return
483
// type won't ever change!
484
try
485             {
486
487             this.get = new Getter (this.acc,
488                            v.getClass ());
489             
490             } catch (Exception JavaDoc e) {
491             
492             throw new QueryExecutionException ("Unable to create accessor for: " +
493                                this.acc +
494                                " from return type: " +
495                                v.getClass ().getName () +
496                                " after execution of function: " +
497                                this,
498                                e);
499
500             }
501
502         }
503
504         try
505         {
506
507             v = this.get.getValue (v);
508             
509         } catch (Exception JavaDoc e) {
510             
511             throw new QueryExecutionException ("Unable to get value for accessor: " +
512                                this.acc +
513                                " from return type: " +
514                                v.getClass ().getName () +
515                                " after execution of function: " +
516                                this,
517                                e);
518             
519         }
520
521         }
522
523     }
524
525     if (this.fixedResult)
526     {
527
528         this.fixedValue = v;
529
530     }
531
532     return v;
533
534     }
535
536     /**
537      * Return whether the evaluation of this function (see: {@link #evaluate(Object,Query)})
538      * will result in a <code>true</code> value.
539      * See: {@link ArithmeticExpression#isTrue(Object,Query)} for details of how this is
540      * determined.
541      *
542      * @param o The current object.
543      * @param q The Query object.
544      * @return <code>true</code> if the function return value evaluates to <code>true</code>.
545      * @throws QueryExecutionException If something goes wrong with evaluating the function.
546      */

547     public boolean isTrue (Object JavaDoc o,
548                Query q)
549                        throws QueryExecutionException
550     {
551
552     o = this.evaluate (o,
553                q);
554
555     if (o == null)
556     {
557         
558         return false;
559
560     }
561
562     if (Utilities.isNumber (o))
563     {
564
565         return Utilities.getDouble (o) > 0;
566
567     }
568
569     if (o instanceof Boolean JavaDoc)
570     {
571
572         return ((Boolean JavaDoc) o).booleanValue ();
573
574     }
575
576     // Not null so return true...
577
return true;
578
579     }
580
581     /**
582      * Return a string representation of the function.
583      * In the form: Name ( {@link Expression} [ , {@link Expression} ] )
584      *
585      * @return A string representation of the function.
586      */

587     public String JavaDoc toString ()
588     {
589
590     StringBuffer JavaDoc buf = new StringBuffer JavaDoc ();
591
592     buf.append (this.name);
593     buf.append ("(");
594
595     if (this.params != null)
596     {
597
598         for (int i = 0; i < this.params.size (); i++)
599         {
600
601         Expression p = (Expression) this.params.get (i);
602
603         buf.append (p);
604
605         if (i < (this.params.size () - 1))
606         {
607
608             buf.append (",");
609
610         }
611
612         }
613
614     }
615
616     buf.append (")");
617
618     if (this.acc != null)
619     {
620
621         buf.append (".");
622         buf.append (this.acc);
623
624     }
625
626     if (this.isBracketed ())
627     {
628
629         buf.insert (0,
630             "(");
631
632         buf.append (")");
633
634     }
635
636     return buf.toString ();
637
638     }
639
640     /**
641      * Return whether the function will return a fixed result, this only
642      * occurs iff all the arguments to the function also return a fixed result.
643      *
644      * @param q The Query object.
645      */

646     public boolean hasFixedResult (Query q)
647     {
648
649     return this.fixedResult;
650
651     }
652
653     private int matchMethodArgs (Class JavaDoc[] methArgs,
654                  Query q)
655                                  throws QueryParseException
656     {
657
658     // The score here helps in argument resolution, a more specific argument
659
// match (that is NOT expression in the method args) will score higher and
660
// thus is a better match.
661
int score = 0;
662
663     for (int i = 0; i < methArgs.length; i++)
664     {
665
666         Class JavaDoc c = methArgs[i];
667         
668         Expression exp = (Expression) this.params.get (i);
669
670         // See if the arg is object, which means "I can accept any type".
671
if (c.getClass ().getName ().equals (Object JavaDoc.class.getName ()))
672         {
673
674         score += 1;
675
676         continue;
677
678         }
679
680         // Now try and get the expected return type...
681
Class JavaDoc expC = exp.getExpectedReturnType (q);
682
683         if (expC == null)
684         {
685
686         // Can't match this arg.
687
continue;
688
689         } else {
690
691         if (c.isAssignableFrom (expC))
692         {
693
694             score += 2;
695
696             continue;
697
698         }
699
700         }
701
702         if (Expression.class.isAssignableFrom (c))
703         {
704
705         score += 1;
706
707         // This is a match... i.e. the arg is an expression and thus supported
708
// in a "special" way.
709
continue;
710
711         }
712
713         if ((Utilities.isNumber (expC))
714         &&
715         ((Utilities.isNumber (c))
716          ||
717          (c.getName ().equals (Object JavaDoc.class.getName ()))
718         )
719            )
720         {
721
722         score += 1;
723
724         // This matches...
725
continue;
726
727         }
728
729         if ((Utilities.isPrimitiveClass (c))
730         &&
731         (Utilities.isPrimitiveClass (expC))
732            )
733         {
734
735         // It is a primitive class as well, so now see if they are compatible.
736
if (Utilities.getPrimitiveClass (c).isAssignableFrom (Utilities.getPrimitiveClass (expC)))
737         {
738
739             score += 1;
740
741             // They are assignable...
742
continue;
743
744         }
745
746         }
747
748         // See if the expression return type is an object... this "may" mean
749
// that we can match and it may not, it will be determined at runtime.
750
if (expC.getName ().equals (Object JavaDoc.class.getName ()))
751         {
752
753         score += 1;
754
755         continue;
756
757         }
758
759         // If we are here then we can't match this arg type...
760
// No point checking any further...
761
return 0;
762
763     }
764
765     // All args can be matched.
766
return score;
767
768     }
769
770     private void getMethods (Class JavaDoc c,
771                  Query q,
772                  Map JavaDoc matches)
773                              throws QueryParseException
774     {
775
776     Method JavaDoc[] meths = c.getMethods ();
777
778     for (int i = 0; i < meths.length; i++)
779     {
780
781         Method JavaDoc m = meths[i];
782
783         if (!m.getName ().equals (this.name))
784         {
785
786         continue;
787
788         }
789
790         // Make sure it's public...
791
if (!Modifier.isPublic (m.getModifiers ()))
792         {
793
794         continue;
795
796         }
797
798         // Now check the args... sigh...
799
Class JavaDoc[] mpt = m.getParameterTypes ();
800
801         int ps = 0;
802         int fps = 0;
803
804         if (mpt != null)
805         {
806
807         ps = mpt.length;
808
809         }
810
811         if (this.params != null)
812         {
813
814         fps = this.params.size ();
815
816         }
817
818         if (ps != fps)
819         {
820
821         continue;
822
823         }
824
825         int score = this.matchMethodArgs (mpt,
826                           q);
827
828         if (score > 0)
829         {
830
831         matches.put (new Integer JavaDoc (score),
832                  m);
833
834         }
835
836     }
837
838     }
839
840 }
841
Popular Tags