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.functions;
016:
017: import java.util.List;
018: import java.util.ArrayList;
019: import java.util.Map;
020: import java.util.HashMap;
021: import java.util.LinkedHashMap;
022: import java.util.Collections;
023:
024: import com.gentlyweb.utils.GeneralComparator;
025:
026: import org.josql.Query;
027: import org.josql.QueryExecutionException;
028:
029: import org.josql.expressions.Expression;
030:
031: import org.josql.internal.Utilities;
032:
033: /**
034: * Defines a set of functions that operate on "collections" of objects in some way.
035: */
036: public class CollectionFunctions extends AbstractFunctionHandler {
037:
038: private Map foreachQueryCache = null;
039:
040: /**
041: * The id that can be used to get the "CollectionFunctions" handler object from
042: * the Query object.
043: */
044: public static final String HANDLER_ID = "_internal_collection";
045:
046: /**
047: * Sort a list according to it's "natural" ordering (see {@link Collections#sort(List)}).
048: *
049: * @param objs The list of objects to sort.
050: * @return The sorted list, according to their natural ordering.
051: */
052: public List sort(List objs) {
053:
054: Collections.sort(objs);
055:
056: return objs;
057:
058: }
059:
060: /**
061: * Sort a Map by the keys in ascending order (for more optionality in the sort and ordering
062: * see: {@link #sort(Map,String,String)}).
063: *
064: * @param m The map to sort.
065: * @return A List sorted according to the key in ascending order.
066: */
067: public List sort(Map m) {
068:
069: return this .sort(m, "key", GeneralComparator.ASC);
070:
071: }
072:
073: /**
074: * Sort a Map by it's keys or values in ascending order (for more optionality in the sort and ordering
075: * see: {@link #sort(Map,String,String)}).
076: *
077: * @param m The map to sort.
078: * @param type Should be either: "key" or "value" to indicate which item to sort on.
079: * Use <code>null</code> for key.
080: * @return A List sorted according to the key in ascending order.
081: */
082: public List sort(Map m, String type) {
083:
084: return this .sort(m, type, GeneralComparator.ASC);
085:
086: }
087:
088: /**
089: * Sort a Map by either it's key or value.
090: *
091: * @param m The map to sort.
092: * @param type Should be either: "key" or "value" to indicate which item to sort on.
093: * Use <code>null</code> for key.
094: * @param dir The direction you want to sort on, either "asc" or "desc". Use <code>null</code>
095: * for "asc".
096: * @return A List sorted according to the key or value.
097: */
098: public List sort(Map m, String type, String dir) {
099:
100: String acc = "key";
101:
102: if ((type != null) && (type.equalsIgnoreCase("value"))) {
103:
104: acc = "value";
105:
106: }
107:
108: String d = GeneralComparator.ASC;
109:
110: if (dir != null) {
111:
112: dir = dir.toUpperCase();
113:
114: if (dir.equals(GeneralComparator.DESC)) {
115:
116: d = GeneralComparator.DESC;
117:
118: }
119:
120: }
121:
122: GeneralComparator gc = new GeneralComparator(Map.Entry.class);
123:
124: gc.addField(acc, d);
125:
126: List l = new ArrayList(m.entrySet());
127:
128: Collections.sort(l, gc);
129:
130: return l;
131:
132: }
133:
134: /**
135: * Get a value from the specified Map.
136: *
137: * @param m The map of objects.
138: * @param exp The expression is evaluated (in the context of the current object) and the
139: * value returned used as the key to the Map, the value it maps to
140: * (which may be null) is returned.
141: * @return The value that the <b>exp</b> value maps to, may be null.
142: */
143: public Object get(Map m, Expression exp)
144: throws QueryExecutionException {
145:
146: // Evaluate the expression.
147: // Get the current object.
148: return m.get(exp.getValue(this .q.getCurrentObject(), this .q));
149:
150: }
151:
152: /**
153: * Get a value from the specified List.
154: *
155: * @param l The list of objects.
156: * @param n The index, indices start at 0.
157: * @return The value of the <b>i</b>th element from the list of objects. Return <code>null</code>
158: * if <b>n</b> is out of range.
159: */
160: public Object get(List l, Number n) {
161:
162: int i = n.intValue();
163:
164: if ((i > l.size()) || (i < 0)) {
165:
166: return null;
167:
168: }
169:
170: return l.get(i);
171:
172: }
173:
174: /**
175: * For each of the objects in the <b>objs</b> List get the value from each one
176: * using the <b>accessor</b> and compare it to the <b>value</b> parameter. The value
177: * param is converted to a string and then to a Boolean value using: {@link Boolean#valueOf(String)}.
178: *
179: * @param objs The list of objects to iterate over.
180: * @param exp The expression to use to get the value from the object in the List.
181: * @param value The value to compare the result of the accessor against. If the parm is <code>null</code>
182: * then it defaults to {@link Boolean#FALSE}.
183: * @return A count of how many times the accessor evaluated to the same value of the
184: * <b>value</b> parm.
185: * @throws QueryExecutionException If the value from the accessor cannot be gained or if
186: * the compare cannot be performed.
187: */
188: public int count(List objs, Expression exp, Object value)
189: throws QueryExecutionException {
190:
191: Boolean b = Boolean.FALSE;
192:
193: if (value != null) {
194:
195: b = Boolean.valueOf(value.toString());
196:
197: }
198:
199: int count = 0;
200:
201: Object currobj = this .q.getCurrentObject();
202: List allobjs = this .q.getAllObjects();
203:
204: this .q.setAllObjects(objs);
205:
206: int size = objs.size();
207:
208: for (int i = 0; i < size; i++) {
209:
210: Object o = objs.get(i);
211:
212: this .q.setCurrentObject(o);
213:
214: try {
215:
216: if (Utilities.compare(exp.getValue(o, this .q), b) == 0) {
217:
218: count++;
219:
220: }
221:
222: } catch (Exception e) {
223:
224: // Restore the currobj and allobjs.
225: this .q.setCurrentObject(currobj);
226: this .q.setAllObjects(allobjs);
227:
228: throw new QueryExecutionException(
229: "Unable to get value from expression: " + exp
230: + " for item: " + i
231: + " from the list of objects.", e);
232:
233: }
234:
235: }
236:
237: // Restore the currobj and allobjs.
238: this .q.setCurrentObject(currobj);
239: this .q.setAllObjects(allobjs);
240:
241: return count;
242:
243: }
244:
245: public int count(Expression exp) throws QueryExecutionException {
246:
247: return this .count((List) this .q
248: .getVariable(Query.ALL_OBJS_VAR_NAME), exp);
249:
250: }
251:
252: public int count(List allobjs, Expression exp)
253: throws QueryExecutionException {
254:
255: int count = 0;
256:
257: Object currobj = this .q.getCurrentObject();
258: List currall = this .q.getAllObjects();
259:
260: this .q.setAllObjects(allobjs);
261:
262: int size = allobjs.size();
263:
264: for (int i = 0; i < size; i++) {
265:
266: Object o = allobjs.get(i);
267:
268: this .q.setCurrentObject(o);
269:
270: try {
271:
272: if (exp.isTrue(o, this .q)) {
273:
274: count++;
275:
276: }
277:
278: } catch (Exception e) {
279:
280: // Restore the currobj and allobjs.
281: this .q.setCurrentObject(currobj);
282: this .q.setAllObjects(currall);
283:
284: throw new QueryExecutionException(
285: "Unable to determine whether expression: \""
286: + exp
287: + "\" is true for object at index: "
288: + i + " from the list of objects.", e);
289:
290: }
291:
292: }
293:
294: // Restore the currobj and allobjs.
295: this .q.setCurrentObject(currobj);
296: this .q.setAllObjects(currall);
297:
298: return count;
299:
300: }
301:
302: public List toList(List allobjs, Expression exp,
303: String saveValueName) throws QueryExecutionException {
304:
305: return this .collect(allobjs, exp, saveValueName);
306:
307: }
308:
309: public List unique(List objs) {
310:
311: /**
312: Strangely the method below is consistently slower than the method employed!
313: return new ArrayList (new java.util.LinkedHashSet (objs));
314: */
315:
316: Map m = new LinkedHashMap();
317:
318: int s = objs.size();
319:
320: for (int i = 0; i < s; i++) {
321:
322: m.put(objs.get(i), null);
323:
324: }
325:
326: return new ArrayList(m.keySet());
327:
328: }
329:
330: public List unique(Expression exp) throws QueryExecutionException {
331:
332: return this .unique((List) this .q
333: .getVariable(Query.ALL_OBJS_VAR_NAME), exp);
334:
335: }
336:
337: public List unique(List objs, Expression exp)
338: throws QueryExecutionException {
339:
340: /**
341: Strangely the method below is consistently slower than the method employed!
342: return new ArrayList (new java.util.LinkedHashSet (objs));
343: */
344:
345: Map m = new HashMap();
346:
347: Object currobj = this .q.getCurrentObject();
348: List allobjs = this .q.getAllObjects();
349:
350: this .q.setAllObjects(objs);
351:
352: int s = objs.size();
353:
354: for (int i = 0; i < s; i++) {
355:
356: Object o = objs.get(i);
357:
358: this .q.setCurrentObject(o);
359:
360: o = exp.getValue(o, this .q);
361:
362: m.put(o, null);
363:
364: }
365:
366: // Restore the currobj and allobjs.
367: this .q.setCurrentObject(currobj);
368: this .q.setAllObjects(allobjs);
369:
370: return new ArrayList(m.keySet());
371:
372: }
373:
374: public List collect(List objs, Expression exp, String saveValueName)
375: throws QueryExecutionException {
376:
377: if (saveValueName != null) {
378:
379: Object o = this .q.getSaveValue(saveValueName);
380:
381: if (o != null) {
382:
383: return (List) o;
384:
385: }
386:
387: }
388:
389: List retVals = new ArrayList();
390:
391: int s = objs.size();
392:
393: List allobjs = this .q.getAllObjects();
394: Object co = this .q.getCurrentObject();
395:
396: this .q.setAllObjects(objs);
397:
398: for (int i = 0; i < s; i++) {
399:
400: Object o = objs.get(i);
401:
402: this .q.setCurrentObject(o);
403:
404: // Execute the function.
405: try {
406:
407: retVals.add(exp.getValue(o, this .q));
408:
409: } catch (Exception e) {
410:
411: // Reset the current object.
412: this .q.setCurrentObject(co);
413: this .q.setAllObjects(allobjs);
414:
415: throw new QueryExecutionException(
416: "Unable to execute expression: \"" + exp
417: + " on object at index: " + i
418: + " from the list of objects.", e);
419:
420: }
421:
422: }
423:
424: if (saveValueName != null) {
425:
426: this .q.setSaveValue(saveValueName, retVals);
427:
428: }
429:
430: // Reset the current object.
431: this .q.setCurrentObject(co);
432: this .q.setAllObjects(allobjs);
433:
434: return retVals;
435:
436: }
437:
438: public List collect(Expression exp) throws QueryExecutionException {
439:
440: return this .collect((List) this .q
441: .getVariable(Query.ALL_OBJS_VAR_NAME), exp);
442:
443: }
444:
445: public List collect(List allobjs, Expression exp)
446: throws QueryExecutionException {
447:
448: return this .collect(allobjs, exp, null);
449:
450: }
451:
452: public List toList(Expression exp) throws QueryExecutionException {
453:
454: return this .toList((List) this .q
455: .getVariable(Query.ALL_OBJS_VAR_NAME), exp);
456:
457: }
458:
459: public List toList(List allobjs, Expression exp)
460: throws QueryExecutionException {
461:
462: return this .collect(allobjs, exp, null);
463:
464: }
465:
466: public List foreach(Expression exp) throws QueryExecutionException {
467:
468: return this .foreach((List) this .q
469: .getVariable(Query.ALL_OBJS_VAR_NAME), exp);
470:
471: }
472:
473: public List foreach(List allobjs, Expression exp)
474: throws QueryExecutionException {
475:
476: if (allobjs == null) {
477:
478: return null;
479:
480: }
481:
482: List currall = this .q.getAllObjects();
483: Object currobj = this .q.getCurrentObject();
484:
485: this .q.setAllObjects(allobjs);
486:
487: List res = new ArrayList();
488:
489: int s = allobjs.size();
490:
491: for (int i = 0; i < s; i++) {
492:
493: Object o = allobjs.get(i);
494:
495: this .q.setCurrentObject(o);
496:
497: res.add(exp.getValue(o, this .q));
498:
499: }
500:
501: // Reset the current object.
502: this .q.setCurrentObject(currobj);
503: this .q.setAllObjects(allobjs);
504:
505: return res;
506:
507: }
508:
509: /**
510: * Given a list of objects, execute the expression against each one and return
511: * those objects that return a <code>true</code> value for the expression.
512: * In effect this is equivalent to executing the WHERE clause of a JoSQL statement
513: * against each object (which in fact is what happens internally). The class
514: * for the objects if found by examining the list passed in.
515: *
516: * @param objs The list of objects.
517: * @param exp The expression (basically a where clause, it is ok for the expression
518: * to start with "WHERE", case-insensitive) to execute for each of the
519: * objects.
520: * @return The list of matching objects.
521: */
522: public List foreach(List objs, String exp)
523: throws QueryExecutionException {
524:
525: List l = new ArrayList();
526:
527: if ((objs == null) || (objs.size() == 0)) {
528:
529: return l;
530:
531: }
532:
533: Query q = null;
534:
535: // See if we have the expression in our cache.
536: if (this .foreachQueryCache != null) {
537:
538: q = (Query) this .foreachQueryCache.get(exp);
539:
540: }
541:
542: if (q == null) {
543:
544: // Init our query.
545: Class c = null;
546:
547: Object o = objs.get(0);
548:
549: if (o == null) {
550:
551: int s = objs.size() - 1;
552:
553: // Bugger now need to cycle until we get a class.
554: for (int i = s; s > -1; i--) {
555:
556: o = objs.get(i);
557:
558: if (o != null) {
559:
560: c = o.getClass();
561: break;
562:
563: }
564:
565: }
566:
567: } else {
568:
569: c = o.getClass();
570:
571: }
572:
573: if (exp.toLowerCase().trim().startsWith("where")) {
574:
575: exp = exp.trim().substring(5);
576:
577: }
578:
579: String query = "SELECT * FROM " + c.getName() + " WHERE "
580: + exp;
581:
582: q = new Query();
583:
584: try {
585:
586: q.parse(query);
587:
588: } catch (Exception e) {
589:
590: throw new QueryExecutionException(
591: "Unable to create statement using WHERE clause: "
592: + exp
593: + " and class: "
594: + c.getName()
595: + " (gained from objects in list passed in)",
596: e);
597:
598: }
599:
600: // Cache it.
601: if (this .foreachQueryCache == null) {
602:
603: this .foreachQueryCache = new HashMap();
604:
605: }
606:
607: this .foreachQueryCache.put(exp, q);
608:
609: }
610:
611: return q.execute(objs).getResults();
612:
613: }
614:
615: public List foreach(Expression listFunction, Expression exp)
616: throws QueryExecutionException {
617:
618: // Execute the list function.
619: Object o = listFunction.getValue(this .q.getCurrentObject(),
620: this .q);
621:
622: if (!(o instanceof List)) {
623:
624: throw new QueryExecutionException("Expected expression: "
625: + listFunction + " to return instance of: "
626: + List.class.getName()
627: + " but returned instance of: "
628: + o.getClass().getName());
629:
630: }
631:
632: List l = (List) o;
633:
634: return this .foreach(l, exp);
635:
636: }
637:
638: /**
639: * Find objects from the List based upon the expression passed in. If
640: * the expression evaluates to <code>true</code> then the object will
641: * be returned.
642: * Note: in accordance with the general operating methodology for the Query
643: * object, the ":_allobjs" special bind variable will be set to the
644: * the List passed in and the "_currobj" will be set to the relevant
645: * object in the List.
646: *
647: * @param objs The List of objects to search.
648: * @param exp The expression to evaulate against each object in the List.
649: * @return The List of matching objects, if none match then an empty list is returned.
650: * @throws QueryExecutionException If the expression cannot be evaulated against each
651: * object.
652: */
653: public List find(List objs, Expression exp)
654: throws QueryExecutionException {
655:
656: // Get the current object, it's important that we leave the Query in the
657: // same state at the end of this function as when we started!
658: Object currobj = this .q.getCurrentObject();
659: List allobjs = this .q.getAllObjects();
660:
661: this .q.setAllObjects(objs);
662:
663: List r = new ArrayList();
664:
665: int s = objs.size();
666:
667: for (int i = 0; i < s; i++) {
668:
669: Object o = objs.get(i);
670:
671: this .q.setCurrentObject(o);
672:
673: try {
674:
675: if (exp.isTrue(o, this .q)) {
676:
677: r.add(o);
678:
679: }
680:
681: } catch (Exception e) {
682:
683: // Restore the currobj and allobjs.
684: this .q.setCurrentObject(currobj);
685: this .q.setAllObjects(allobjs);
686:
687: throw new QueryExecutionException(
688: "Unable to evaulate expression: " + exp
689: + " against object: " + i + " (class: "
690: + o.getClass().getName() + ")", e);
691:
692: }
693:
694: }
695:
696: // Restore the currobj and allobjs.
697: this .q.setCurrentObject(currobj);
698: this .q.setAllObjects(allobjs);
699:
700: return r;
701:
702: }
703:
704: /**
705: * Group objects from the List based upon the expression passed in. The expression
706: * is evaulated for each object, by calling: {@link Expression#getValue(Object,Query)}
707: * and the return value used as the key to the Map. All objects with that value are
708: * added to a List held against the key. To maintain the ordering of the keys (if
709: * desirable) a {@link LinkedHashMap} is used as the return Map.
710: *
711: * Note: in accordance with the general operating methodology for the Query
712: * object, the ":_allobjs" special bind variable will be set to the
713: * the List passed in and the "_currobj" will be set to the relevant
714: * object in the List.
715: *
716: * @param objs The List of objects to search.
717: * @param exp The expression to evaulate against each object in the List.
718: * @return The LinkedHashMap of matching objects, grouped according to the return value
719: * of executing the expression against each object in the input List.
720: * @throws QueryExecutionException If the expression cannot be evaulated against each
721: * object.
722: */
723: public Map grp(List objs, Expression exp)
724: throws QueryExecutionException {
725:
726: // Get the current object, it's important that we leave the Query in the
727: // same state at the end of this function as when we started!
728: Object currobj = this .q.getCurrentObject();
729: List allobjs = this .q.getAllObjects();
730:
731: this .q.setAllObjects(objs);
732:
733: Map r = new LinkedHashMap();
734:
735: int s = objs.size();
736:
737: for (int i = 0; i < s; i++) {
738:
739: Object o = objs.get(i);
740:
741: this .q.setCurrentObject(o);
742:
743: try {
744:
745: Object v = exp.getValue(o, this .q);
746:
747: List vs = (List) r.get(v);
748:
749: if (vs == null) {
750:
751: vs = new ArrayList();
752:
753: r.put(v, vs);
754:
755: }
756:
757: vs.add(v);
758:
759: } catch (Exception e) {
760:
761: // Restore the currobj and allobjs.
762: this .q.setCurrentObject(currobj);
763: this .q.setAllObjects(allobjs);
764:
765: throw new QueryExecutionException(
766: "Unable to evaulate expression: " + exp
767: + " against object: " + i + " (class: "
768: + o.getClass().getName() + ")", e);
769:
770: }
771:
772: }
773:
774: // Restore the currobj and allobjs.
775: this.q.setCurrentObject(currobj);
776: this.q.setAllObjects(allobjs);
777:
778: return r;
779:
780: }
781:
782: }
|