001: /*
002: * Copyright 2004-2007 Gary Bentley
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may
005: * not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015: package org.josql.expressions;
016:
017: import java.util.List;
018: import java.util.Arrays;
019: import java.util.Map;
020: import java.util.TreeMap;
021:
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Modifier;
024:
025: import com.gentlyweb.utils.Getter;
026:
027: import org.josql.Query;
028: import org.josql.QueryExecutionException;
029: import org.josql.QueryParseException;
030:
031: import org.josql.internal.Utilities;
032:
033: import org.josql.functions.NotFixedResults;
034:
035: /**
036: * This class represents a Function that can be "called" in JoSQL.
037: */
038: public class Function extends ValueExpression {
039:
040: private String name = null;
041: private List params = null;
042: private Method function = null;
043: private Object handler = null;
044: private boolean fixedResult = true;
045: private Object fixedValue = null;
046: private String acc = null;
047: private Getter get = null;
048:
049: public Getter getGetter() {
050:
051: return this .get;
052:
053: }
054:
055: public String getAccessor() {
056:
057: return this .acc;
058:
059: }
060:
061: public void setAccessor(String acc) {
062:
063: this .acc = acc;
064:
065: }
066:
067: /**
068: * Get the expected return type from the function. The exact class returned is
069: * dependent upon the function (Java method) that is being called.
070: *
071: * @param q The Query object.
072: * @return The class of the expected return type.
073: */
074: public Class getExpectedReturnType(Query q) {
075:
076: if (this .get != null) {
077:
078: return this .get.getType();
079:
080: }
081:
082: return this .function.getReturnType();
083:
084: }
085:
086: /**
087: * This is a complex method that will initialise the function.
088: * Firstly all of the "arguments" to the function are inited and then
089: * their expected return types gained from calling: {@link Function#getExpectedReturnType(Query)}.
090: * Then the function handlers, user-defined and then built-in are searched until they
091: * find a match for the function name, ensure that it's a public method and that all the
092: * arguments match, widening the match where necessary.
093: *
094: * @param q The Query object.
095: * @throws QueryParseException If something goes wrong whilst initing the arguments to the
096: * function or if the function cannot be found.
097: */
098: public void init(Query q) throws QueryParseException {
099:
100: // Need to init the params (if present) first because the expected type may not be
101: // present otherwise.
102: if (this .params != null) {
103:
104: int s = this .params.size();
105:
106: for (int i = 0; i < s; i++) {
107:
108: Expression exp = (Expression) this .params.get(i);
109:
110: exp.init(q);
111:
112: }
113:
114: }
115:
116: // Try and find the relevant method, first in the user-defined function handler (if present)
117: // or the built-in handlers.
118: this .findMethod(q);
119:
120: if (this .function == null) {
121:
122: Class[] ps = null;
123:
124: if (this .params != null) {
125:
126: int s = params.size();
127:
128: ps = new Class[s];
129:
130: for (int i = 0; i < s; i++) {
131:
132: // Need to get the expected return type.
133: Expression exp = (Expression) params.get(i);
134:
135: ps[i] = exp.getExpectedReturnType(q);
136:
137: }
138:
139: }
140:
141: // Can't find the function.
142: throw new QueryParseException(
143: "Unable to find function (method): \""
144: + Utilities.formatSignature(this .name, ps)
145: + "\" in any user-defined function handlers or the default function handler");
146:
147: }
148:
149: // Now see if we have an accessor for the function.
150: if (this .acc != null) {
151:
152: this .initAccessor();
153:
154: }
155:
156: // A function has/can have a fixed result if all it's arguments
157: // also have a fixed result, if there aren't any args then assume
158: // it won't have a fixed result.
159: if ((this .params != null)
160: && (!NotFixedResults.class
161: .isAssignableFrom(this .function
162: .getDeclaringClass()))) {
163:
164: for (int i = 0; i < this .params.size(); i++) {
165:
166: Expression exp = (Expression) this .params.get(i);
167:
168: if (!exp.hasFixedResult(q)) {
169:
170: this .fixedResult = false;
171: break;
172:
173: }
174:
175: }
176:
177: } else {
178:
179: this .fixedResult = false;
180:
181: }
182:
183: }
184:
185: private void initAccessor() throws QueryParseException {
186:
187: // We have an accessor, see what the functions return type is.
188: Class retType = this .function.getReturnType();
189:
190: // Ensure that the function DOES have a return type.
191: if (Void.TYPE.isAssignableFrom(retType)) {
192:
193: // The return type is void, barf!
194: throw new QueryParseException(
195: "Function: "
196: + this
197: + " maps to method: "
198: + this .function
199: + " however methods return type is \"void\" and an accessor: "
200: + this .acc + " has been defined.");
201:
202: }
203:
204: // See if the return type is an object. If it is then defer trying to
205: // get the accessor until run-time. Have to do it this way since
206: // type comparisons are a little pointless.
207: if (!retType.getName().equals(Object.class.getName())) {
208:
209: // The return type is NOT java.lang.Object, so now try and get the
210: // accessor.
211: try {
212:
213: this .get = new Getter(this .acc, retType);
214:
215: } catch (Exception e) {
216:
217: throw new QueryParseException(
218: "Function: "
219: + this
220: + " maps to method: "
221: + this .function
222: + " and has accessor: "
223: + this .acc
224: + " however no valid accessor has been found in return type: "
225: + retType.getName(), e);
226:
227: }
228:
229: }
230:
231: }
232:
233: private void getMethodFromHandlers(Query q, List handlers)
234: throws QueryParseException {
235:
236: int s = handlers.size();
237:
238: TreeMap ms = new TreeMap();
239:
240: for (int i = 0; i < s; i++) {
241:
242: Object fh = handlers.get(i);
243:
244: this .getMethods(fh.getClass(), q, ms);
245:
246: }
247:
248: // Get the one with the highest score.
249: if (ms.size() > 0) {
250:
251: this .function = (Method) ms.get(ms.lastKey());
252:
253: Class c = this .function.getDeclaringClass();
254:
255: // What a pain!
256: for (int i = 0; i < s; i++) {
257:
258: Object o = handlers.get(i);
259:
260: if (o.getClass().isAssignableFrom(c)) {
261:
262: this .handler = o;
263:
264: }
265:
266: }
267:
268: }
269:
270: }
271:
272: private void findMethod(Query q) throws QueryParseException {
273:
274: List fhs = q.getFunctionHandlers();
275:
276: if (fhs != null) {
277:
278: this .getMethodFromHandlers(q, fhs);
279:
280: }
281:
282: if (this .function == null) {
283:
284: List dfhs = q.getDefaultFunctionHandlers();
285:
286: if (dfhs != null) {
287:
288: this .getMethodFromHandlers(q, dfhs);
289:
290: }
291:
292: }
293:
294: }
295:
296: /**
297: * Return the List of {@link Expression} objects that constitute the arguments
298: * to the function, no guarantee is made here as to whether they have been inited.
299: *
300: * @return The List of {@link Expression} objects.
301: */
302: public List getParameters() {
303:
304: return this .params;
305:
306: }
307:
308: public void setParameters(List ps) {
309:
310: this .params = ps;
311:
312: }
313:
314: public void setName(String name) {
315:
316: this .name = name;
317:
318: }
319:
320: public String getName() {
321:
322: return this .name;
323:
324: }
325:
326: /**
327: * Evaluate this function on the current object. It should be noted that not
328: * all functions will use the current object in their execution, functions from the
329: * {@link org.josql.functions.GroupingFunctions} class are notable exceptions.
330: *
331: * @param o The current object.
332: * @param q The Query object.
333: * @return The result of evaluating the function.
334: * @throws QueryExecutionException If something goes wrong during execution of the
335: * function or gaining the values to be used as arguments.
336: */
337: public Object evaluate(Object o, Query q)
338: throws QueryExecutionException {
339:
340: // See if we have a fixed result.
341: if (this .fixedResult) {
342:
343: // Evaluated once...
344: if (this .fixedValue != null) {
345:
346: return this .fixedValue;
347:
348: }
349:
350: }
351:
352: // Get the values for the parameters... if any...
353: Object[] ps = null;
354:
355: if (this .params != null) {
356:
357: int s = this .params.size();
358:
359: ps = new Object[s];
360:
361: for (int i = 0; i < s; i++) {
362:
363: Expression exp = (Expression) this .params.get(i);
364:
365: if (Expression.class.isAssignableFrom(this .function
366: .getParameterTypes()[i])) {
367:
368: // Leave this one alone.
369: ps[i] = exp;
370:
371: } else {
372:
373: // Eval this expression.
374: try {
375:
376: ps[i] = exp.getValue(o, q);
377:
378: } catch (Exception e) {
379:
380: throw new QueryExecutionException(
381: "Unable to get parameter: " + i
382: + " (\"" + exp.toString()
383: + "\") for function: "
384: + this .name, e);
385:
386: }
387:
388: }
389:
390: }
391:
392: }
393:
394: Object v = null;
395:
396: try {
397:
398: v = this .function.invoke(this .handler, ps);
399:
400: } catch (Exception e) {
401:
402: throw new QueryExecutionException(
403: "Unable to execute function: " + this .name + " (\""
404: + this .toString() + "\") with values: "
405: + Arrays.asList(ps), e);
406:
407: }
408:
409: if (v != null) {
410:
411: // See if we have an accessor.
412: if (this .acc != null) {
413:
414: // See if we have a Getter.
415: if (this .get == null) {
416:
417: // No getter, so now try and init it. This assumes that the return
418: // type won't ever change!
419: try {
420:
421: this .get = new Getter(this .acc, v.getClass());
422:
423: } catch (Exception e) {
424:
425: throw new QueryExecutionException(
426: "Unable to create accessor for: "
427: + this .acc
428: + " from return type: "
429: + v.getClass().getName()
430: + " after execution of function: "
431: + this , e);
432:
433: }
434:
435: }
436:
437: try {
438:
439: v = this .get.getValue(v);
440:
441: } catch (Exception e) {
442:
443: throw new QueryExecutionException(
444: "Unable to get value for accessor: "
445: + this .acc + " from return type: "
446: + v.getClass().getName()
447: + " after execution of function: "
448: + this , e);
449:
450: }
451:
452: }
453:
454: }
455:
456: if (this .fixedResult) {
457:
458: this .fixedValue = v;
459:
460: }
461:
462: return v;
463:
464: }
465:
466: /**
467: * Return whether the evaluation of this function (see: {@link #evaluate(Object,Query)})
468: * will result in a <code>true</code> value.
469: * See: {@link ArithmeticExpression#isTrue(Object,Query)} for details of how this is
470: * determined.
471: *
472: * @param o The current object.
473: * @param q The Query object.
474: * @return <code>true</code> if the function return value evaluates to <code>true</code>.
475: * @throws QueryExecutionException If something goes wrong with evaluating the function.
476: */
477: public boolean isTrue(Object o, Query q)
478: throws QueryExecutionException {
479:
480: o = this .evaluate(o, q);
481:
482: if (o == null) {
483:
484: return false;
485:
486: }
487:
488: if (Utilities.isNumber(o)) {
489:
490: return Utilities.getDouble(o) > 0;
491:
492: }
493:
494: if (o instanceof Boolean) {
495:
496: return ((Boolean) o).booleanValue();
497:
498: }
499:
500: // Not null so return true...
501: return true;
502:
503: }
504:
505: /**
506: * Return a string representation of the function.
507: * In the form: Name ( {@link Expression} [ , {@link Expression} ] )
508: *
509: * @return A string representation of the function.
510: */
511: public String toString() {
512:
513: StringBuffer buf = new StringBuffer();
514:
515: buf.append(this .name);
516: buf.append("(");
517:
518: if (this .params != null) {
519:
520: for (int i = 0; i < this .params.size(); i++) {
521:
522: Expression p = (Expression) this .params.get(i);
523:
524: buf.append(p);
525:
526: if (i < (this .params.size() - 1)) {
527:
528: buf.append(",");
529:
530: }
531:
532: }
533:
534: }
535:
536: buf.append(")");
537:
538: if (this .acc != null) {
539:
540: buf.append(".");
541: buf.append(this .acc);
542:
543: }
544:
545: if (this .isBracketed()) {
546:
547: buf.insert(0, "(");
548:
549: buf.append(")");
550:
551: }
552:
553: return buf.toString();
554:
555: }
556:
557: /**
558: * Return whether the function will return a fixed result, this only
559: * occurs iff all the arguments to the function also return a fixed result.
560: *
561: * @param q The Query object.
562: */
563: public boolean hasFixedResult(Query q) {
564:
565: return this .fixedResult;
566:
567: }
568:
569: private int matchMethodArgs(Class[] methArgs, Query q)
570: throws QueryParseException {
571:
572: // The score here helps in argument resolution, a more specific argument
573: // match (that is NOT expression in the method args) will score higher and
574: // thus is a better match.
575: int score = 0;
576:
577: for (int i = 0; i < methArgs.length; i++) {
578:
579: Class c = methArgs[i];
580:
581: Expression exp = (Expression) this .params.get(i);
582:
583: // See if the arg is object, which means "I can accept any type".
584: if (c.getClass().getName().equals(Object.class.getName())) {
585:
586: score += 1;
587:
588: continue;
589:
590: }
591:
592: // Now try and get the expected return type...
593: Class expC = exp.getExpectedReturnType(q);
594:
595: if (expC == null) {
596:
597: // Can't match this arg.
598: continue;
599:
600: } else {
601:
602: if (c.isAssignableFrom(expC)) {
603:
604: score += 2;
605:
606: continue;
607:
608: }
609:
610: }
611:
612: if (Expression.class.isAssignableFrom(c)) {
613:
614: score += 1;
615:
616: // This is a match... i.e. the arg is an expression and thus supported
617: // in a "special" way.
618: continue;
619:
620: }
621:
622: if ((Utilities.isNumber(expC))
623: && ((Utilities.isNumber(c)) || (c.getName()
624: .equals(Object.class.getName())))) {
625:
626: score += 1;
627:
628: // This matches...
629: continue;
630:
631: }
632:
633: if ((Utilities.isPrimitiveClass(c))
634: && (Utilities.isPrimitiveClass(expC))) {
635:
636: // It is a primitive class as well, so now see if they are compatible.
637: if (Utilities.getPrimitiveClass(c).isAssignableFrom(
638: Utilities.getPrimitiveClass(expC))) {
639:
640: score += 1;
641:
642: // They are assignable...
643: continue;
644:
645: }
646:
647: }
648:
649: // See if the expression return type is an object... this "may" mean
650: // that we can match and it may not, it will be determined at runtime.
651: if (expC.getName().equals(Object.class.getName())) {
652:
653: score += 1;
654:
655: continue;
656:
657: }
658:
659: // If we are here then we can't match this arg type...
660: // No point checking any further...
661: return 0;
662:
663: }
664:
665: // All args can be matched.
666: return score;
667:
668: }
669:
670: private void getMethods(Class c, Query q, Map matches)
671: throws QueryParseException {
672:
673: Method[] meths = c.getMethods();
674:
675: for (int i = 0; i < meths.length; i++) {
676:
677: Method m = meths[i];
678:
679: if (!m.getName().equals(this .name)) {
680:
681: continue;
682:
683: }
684:
685: // Make sure it's public...
686: if (!Modifier.isPublic(m.getModifiers())) {
687:
688: continue;
689:
690: }
691:
692: // Now check the args... sigh...
693: Class[] mpt = m.getParameterTypes();
694:
695: int ps = 0;
696: int fps = 0;
697:
698: if (mpt != null) {
699:
700: ps = mpt.length;
701:
702: }
703:
704: if (this .params != null) {
705:
706: fps = this .params.size();
707:
708: }
709:
710: if (ps != fps) {
711:
712: continue;
713:
714: }
715:
716: int score = this .matchMethodArgs(mpt, q);
717:
718: if (score > 0) {
719:
720: matches.put(Integer.valueOf(score), m);
721:
722: }
723:
724: }
725:
726: }
727:
728: }
|