KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > bridge > util > Queries


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9 */

10
11 package org.mmbase.bridge.util;
12 import java.util.*;
13
14 import org.mmbase.bridge.*;
15 import org.mmbase.bridge.implementation.BasicQuery;
16 import org.mmbase.module.core.ClusterBuilder;
17 import org.mmbase.module.core.MMBase;
18 import org.mmbase.storage.search.*;
19 import org.mmbase.storage.search.legacy.ConstraintParser;
20 import org.mmbase.util.*;
21 import org.mmbase.util.logging.*;
22
23 /**
24  * This class contains various utility methods for manipulating and creating query objects.
25  * Most essential methods are available on the Query object itself, but too specific or legacy-ish
26  * methods are put here.
27  *
28  * @author Michiel Meeuwissen
29  * @version $Id: Queries.java,v 1.77.2.2 2006/11/23 14:58:18 michiel Exp $
30  * @see org.mmbase.bridge.Query
31  * @since MMBase-1.7
32  */

33 abstract public class Queries {
34
35     public static final int OPERATOR_BETWEEN = -1; // not a FieldCompareConstraint (numeric)
36
public static final int OPERATOR_IN = 10000; // not a FieldCompareConstraint (non numeric)
37
public static final int OPERATOR_NULL = 10001; // FieldIsNullConstraint
38

39     private static final Logger log = Logging.getLoggerInstance(Queries.class);
40
41     /**
42      * Translates a string to a search direction constant. If the string is <code>null</code> then
43      * 'BOTH' is returned.
44      * @param search string representation of the searchdir constant
45      * @return Searchdir constant (@link RelationStep)
46      * @see ClusterBuilder#getSearchDir The same function, only with another return value if String is <code>null</code>
47      */

48     public static int getRelationStepDirection(String JavaDoc search) {
49         if (search == null) {
50             return RelationStep.DIRECTIONS_BOTH;
51         }
52         search = search.toUpperCase();
53         if ("DESTINATION".equals(search)) {
54             return RelationStep.DIRECTIONS_DESTINATION;
55         } else if ("SOURCE".equals(search)) {
56             return RelationStep.DIRECTIONS_SOURCE;
57         } else if ("BOTH".equals(search)) {
58             return RelationStep.DIRECTIONS_BOTH;
59         } else if ("ALL".equals(search)) {
60             return RelationStep.DIRECTIONS_ALL;
61         } else if ("EITHER".equals(search)) {
62             return RelationStep.DIRECTIONS_EITHER;
63         } else {
64             throw new BridgeException("'" + search + "' cannot be converted to a relation-step direction constant");
65         }
66     }
67
68     /**
69      * Creates a Query object using arguments for {@link Cloud#getList(String, String, String, String, String, String, String, boolean)}
70      * (this function is of course implemented using this utility). This is useful to convert (legacy) code which uses
71      * getList, but you want to use new Query features without rewriting the complete thing.
72      *
73      * It can also be simply handy to specify things as Strings.
74      *
75      * @param cloud
76      * @param startNodes
77      * @param nodePath
78      * @param fields
79      * @param constraints
80      * @param orderby
81      * @param directions
82      * @param searchDir
83      * @param distinct
84      * @return New query object
85      * @todo Should this method be part of Cloud itself?
86      */

87     public static Query createQuery(Cloud cloud, String JavaDoc startNodes, String JavaDoc nodePath, String JavaDoc fields, String JavaDoc constraints, String JavaDoc orderby, String JavaDoc directions, String JavaDoc searchDir, boolean distinct) {
88
89         // the bridge test case say that you may also specifiy empty string (why?)
90
if ("".equals(startNodes) || "-1".equals(startNodes)) {
91             startNodes = null;
92         }
93         if ("".equals(fields)) {
94             fields = null;
95         }
96         if ("".equals(constraints)) {
97             constraints = null;
98         }
99         if ("".equals(searchDir)) {
100             searchDir = null;
101         }
102         if ("".equals(orderby)) {
103             orderby = null;
104         }
105
106         if ("".equals(directions)) {
107             directions = null;
108         }
109         // check invalid search command
110
Encode encoder = new Encode("ESCAPE_SINGLE_QUOTE");
111         // if(startNodes != null) startNodes = encoder.encode(startNodes);
112
// if(nodePath != null) nodePath = encoder.encode(nodePath);
113
// if(fields != null) fields = encoder.encode(fields);
114
if (orderby != null) {
115             orderby = encoder.encode(orderby);
116         }
117         if (directions != null) {
118             directions = encoder.encode(directions);
119         }
120         if (searchDir != null) {
121             searchDir = encoder.encode(searchDir);
122         }
123         if (constraints != null) {
124             constraints = ConstraintParser.convertClauseToDBS(constraints);
125             if (! ConstraintParser.validConstraints(constraints)) {
126                 throw new BridgeException("invalid constraints:" + constraints);
127             }
128             if (! constraints.substring(0, 5).equalsIgnoreCase("WHERE")) {
129                 /// WHERE is used in org.mmbase.util.QueryConvertor
130
constraints = "WHERE " + constraints;
131             }
132         }
133
134
135         // create query object
136
//TODO: remove this code... classes under org.mmbase.bridge.util must not use the core
137

138         // getMultilevelSearchQuery must perhaps move to a utility container org.mmbase.storage.search.Queries or so.
139

140         ClusterBuilder clusterBuilder = MMBase.getMMBase().getClusterBuilder();
141         int search = -1;
142         if (searchDir != null) {
143             search = ClusterBuilder.getSearchDir(searchDir);
144         }
145
146         List snodes = StringSplitter.split(startNodes);
147         List tables = StringSplitter.split(nodePath);
148         List f = StringSplitter.split(fields);
149         List orderVec = StringSplitter.split(orderby);
150         List d = StringSplitter.split(directions);
151         try {
152             // pitty that we can't use cloud.createQuery for this.
153
// but all essential methods are on ClusterBuilder
154
// XXX need casting here, something's wrong!!!
155
Query query = new BasicQuery(cloud, clusterBuilder.getMultiLevelSearchQuery(snodes, f, distinct ? "YES" : "NO", tables, constraints, orderVec, d, search));
156             return query;
157         } catch (IllegalArgumentException JavaDoc iae) {
158             throw new BridgeException(iae.getMessage() + ". (arguments: startNodes='" + startNodes + "', path='" + nodePath + "', fields='" + fields + "', constraints='" + constraints + "' orderby='" + orderby + "', directions='" + directions + "', searchdir='" + searchDir + "')" , iae);
159         }
160     }
161
162
163     /**
164      * Adds a 'legacy' constraint to the query, i.e. constraint(s) represented
165      * by a string. Alreading existing constraints remain ('AND' is used).
166      *
167      * @param query query to add constraint to
168      * @param constraints string representation of constraints
169      * @return The new constraint, or null if nothing changed added.
170      */

171     public static Constraint addConstraints(Query query, String JavaDoc constraints) {
172         if (constraints == null || constraints.equals("")) {
173             return null;
174         }
175
176         // (Try to) parse constraints string to Constraint object.
177
Constraint newConstraint = new ConstraintParser(query).toConstraint(constraints);
178         addConstraint(query, newConstraint);
179         return newConstraint;
180     }
181
182     /**
183      * Adds a Constraint to the already present constraint (with AND).
184      * @param query query to add the constraint to
185      * @param newConstraint constraint to add
186      * @return The new constraint.
187      */

188     public static Constraint addConstraint(Query query, Constraint newConstraint) {
189         if (newConstraint == null) {
190             return null;
191         }
192
193         Constraint constraint = query.getConstraint();
194
195         if (constraint != null) {
196             log.debug("compositing constraint");
197             Constraint compConstraint = query.createConstraint(constraint, CompositeConstraint.LOGICAL_AND, newConstraint);
198             query.setConstraint(compConstraint);
199         } else {
200             query.setConstraint(newConstraint);
201         }
202         return newConstraint;
203     }
204
205     /**
206      * Creates a operator constant for use by createConstraint
207      * @param s String representation of operator
208      * @return FieldCompareConstraint operator constant
209      * @see #createConstraint(Query, String, int, Object)
210      * @see #createConstraint(Query, String, int, Object, Object, boolean)
211      */

212     public static int getOperator(String JavaDoc s) {
213         String JavaDoc op = s.toUpperCase();
214         // first: determine operator:
215
if (op.equals("<") || op.equals("LESS")) {
216             return FieldCompareConstraint.LESS;
217         } else if (op.equals("<=") || op.equals("LESS_EQUAL")) {
218             return FieldCompareConstraint.LESS_EQUAL;
219         } else if (op.equals("=") || op.equals("EQUAL") || op.equals("")) {
220             return FieldCompareConstraint.EQUAL;
221         } else if (op.equals("!=") || op.equals("NOT_EQUAL")) {
222             return FieldCompareConstraint.NOT_EQUAL;
223         } else if (op.equals(">") || op.equals("GREATER")) {
224             return FieldCompareConstraint.GREATER;
225         } else if (op.equals(">=") || op.equals("GREATER_EQUAL")) {
226             return FieldCompareConstraint.GREATER_EQUAL;
227         } else if (op.equals("LIKE")) {
228             return FieldCompareConstraint.LIKE;
229         } else if (op.equals("BETWEEN")) {
230             return OPERATOR_BETWEEN;
231         } else if (op.equals("IN")) {
232             return OPERATOR_IN;
233         } else if (op.equals("NULL")) {
234             return OPERATOR_NULL;
235             //} else if (op.equals("~") || op.equals("REGEXP")) {
236
// return FieldCompareConstraint.REGEXP;
237
} else {
238             throw new BridgeException("Unknown Field Compare Operator '" + op + "'");
239         }
240     }
241
242     /**
243      * Creates a part constant for use by createConstraint
244      * @param s String representation of a datetime part
245      * @return FieldValueDateConstraint part constant
246      * @see #createConstraint(Query, String, int, Object, Object, boolean, int)
247      */

248     public static int getDateTimePart(String JavaDoc s) {
249         String JavaDoc sPart = s.toUpperCase();
250         if (sPart.equals("")) {
251             return -1;
252         } else if (sPart.equals("CENTURY")) {
253             return FieldValueDateConstraint.CENTURY;
254         } else if (sPart.equals("YEAR")) {
255             return FieldValueDateConstraint.YEAR;
256         } else if (sPart.equals("QUARTER")) {
257             return FieldValueDateConstraint.QUARTER;
258         } else if (sPart.equals("MONTH")) {
259             return FieldValueDateConstraint.MONTH;
260         } else if (sPart.equals("WEEK")) {
261             return FieldValueDateConstraint.WEEK;
262         } else if (sPart.equals("DAYOFYEAR")) {
263             return FieldValueDateConstraint.DAY_OF_YEAR;
264         } else if (sPart.equals("DAY") || sPart.equals("DAYOFMONTH")) {
265             return FieldValueDateConstraint.DAY_OF_MONTH;
266         } else if (sPart.equals("DAYOFWEEK")) {
267             return FieldValueDateConstraint.DAY_OF_WEEK;
268         } else if (sPart.equals("HOUR")) {
269             return FieldValueDateConstraint.HOUR;
270         } else if (sPart.equals("MINUTE")) {
271             return FieldValueDateConstraint.MINUTE;
272         } else if (sPart.equals("SECOND")) {
273             return FieldValueDateConstraint.SECOND;
274         } else if (sPart.equals("MILLISECOND")) {
275             return FieldValueDateConstraint.MILLISECOND;
276         } else {
277             throw new BridgeException("Unknown datetime part '" + sPart + "'");
278         }
279     }
280
281     /**
282      * Used in implementation of createConstraint
283      * @param stringValue string representation of a number
284      * @return Number object
285      * @throws BridgeException when failed to convert the string
286      */

287     protected static Number JavaDoc getNumberValue(String JavaDoc stringValue) throws BridgeException {
288         if (stringValue == null) return null;
289         try {
290             return new Integer JavaDoc(stringValue);
291         } catch (NumberFormatException JavaDoc e) {
292             try {
293                 return new Double JavaDoc(stringValue);
294             } catch (NumberFormatException JavaDoc e2) {
295                 throw new BridgeException("Operator requires number value ('" + stringValue + "' is not)");
296             }
297         }
298     }
299
300     /**
301      * Used in implementation of createConstraint
302      * @param fieldType Field Type constant (@link Field)
303      * @param operator Compare operator
304      * @param value value to convert
305      * @return new Compare value
306      */

307     protected static Object JavaDoc getCompareValue(int fieldType, int operator, Object JavaDoc value) {
308         return getCompareValue(fieldType, operator, value, -1, null);
309     }
310
311     /**
312      * Used in implementation of createConstraint
313      * @param fieldType Field Type constant (@link Field)
314      * @param operator Compare operator
315      * @param value value to convert
316      * @param cloud The cloud may be used to pass locale sensitive properties which may be needed for comparisions (locales, timezones)
317      * @return new Compare value
318      * @since MMBase-1.8.2
319      */

320     protected static Object JavaDoc getCompareValue(int fieldType, int operator, Object JavaDoc value, int datePart, Cloud cloud) {
321         if (operator == OPERATOR_IN) {
322             SortedSet set;
323             if (value instanceof SortedSet) {
324                 set = (SortedSet)value;
325             } else if (value instanceof NodeList) {
326                 set = new TreeSet();
327                 NodeIterator i = ((NodeList)value).nodeIterator();
328                 while (i.hasNext()) {
329                     Node node = i.nextNode();
330                     set.add(getCompareValue(fieldType, FieldCompareConstraint.EQUAL, new Integer JavaDoc(node.getNumber())));
331                 }
332             } else if (value instanceof Collection) {
333                 set = new TreeSet();
334                 Iterator i = ((Collection)value).iterator();
335                 while (i.hasNext()) {
336                     Object JavaDoc o = i.next();
337                     set.add(getCompareValue(fieldType, FieldCompareConstraint.EQUAL, o));
338                 }
339             } else {
340                 set = new TreeSet();
341                 if (!(value == null || value.equals(""))) {
342                     set.add(getCompareValue(fieldType, FieldCompareConstraint.EQUAL, value));
343                 }
344             }
345             return set;
346         }
347         switch(fieldType) {
348         case Field.TYPE_STRING:
349             return value == null ? null : Casting.toString(value);
350         case Field.TYPE_INTEGER:
351         case Field.TYPE_FLOAT:
352         case Field.TYPE_LONG:
353         case Field.TYPE_DOUBLE:
354         case Field.TYPE_NODE:
355             if (value instanceof Number JavaDoc) {
356                 return value;
357             } else {
358                 return getNumberValue(value == null ? null : Casting.toString(value));
359             }
360         case Field.TYPE_DATETIME:
361             //TimeZone tz = cloud == null ? null : (TimeZone) cloud.getProperty("org.mmbase.timezone");
362
if (datePart > -1) {
363                 return Casting.toInteger(value);
364             } else {
365                 return Casting.toDate(value);
366             }
367         case Field.TYPE_BOOLEAN:
368             return Casting.toBoolean(value) ? Boolean.TRUE : Boolean.FALSE;
369         default:
370             return value;
371         }
372     }
373
374     /**
375      * Defaulting version of {@link #createConstraint(Query, String, int, Object, Object, boolean, int)}.
376      * Casesensitivity defaults to false, value2 to null (so 'BETWEEN' cannot be used), datePart set to -1 (so no date part comparison)
377      * @param query The query to create the constraint for
378      * @param fieldName The field to create the constraint on (as a string, so it can include the step), e.g. 'news.number'
379      * @param operator The operator to use. This constant can be produces from a string using {@link #getOperator(String)}.
380      * @param value The value to compare with, which must be of the right type. If field is number it might also be an alias.
381      * @return The new constraint, or <code>null</code> it by chance the specified arguments did not lead to a new actual constraint (e.g. if value is an empty set)
382      */

383     public static Constraint createConstraint(Query query, String JavaDoc fieldName, int operator, Object JavaDoc value) {
384         return createConstraint(query, fieldName, operator, value, null, false, -1);
385     }
386
387     /**
388      * Defaulting version of {@link #createConstraint(Query, String, int, Object, Object, boolean, int)}.
389      * DatePart set to -1 (so no date part comparison)
390      * @param query The query to create the constraint for
391      * @param fieldName The field to create the constraint on (as a string, so it can include the step), e.g. 'news.number'
392      * @param operator The operator to use. This constant can be produces from a string using {@link #getOperator(String)}.
393      * @param value The value to compare with, which must be of the right type. If field is number it might also be an alias.
394      * @param value2 The other value (only relevant if operator is BETWEEN, the only terniary operator)
395      * @param caseSensitive Whether it should happen case sensitively (not relevant for number fields)
396      * @return The new constraint, or <code>null</code> it by chance the specified arguments did not lead to a new actual constraint (e.g. if value is an empty set)
397      */

398     public static Constraint createConstraint(Query query, String JavaDoc fieldName, int operator, Object JavaDoc value, Object JavaDoc value2, boolean caseSensitive) {
399         return createConstraint(query, fieldName, operator, value, value2, caseSensitive, -1);
400     }
401
402     /**
403      * Creates a constraint smartly, depending on the type of the field, the value is cast to the
404      * right type, and the right type of constraint is created.
405      * This is used in taglib implementation, but could be useful more generally.
406      *
407      * @param query The query to create the constraint for
408      * @param fieldName The field to create the constraint on (as a string, so it can include the step), e.g. 'news.number'
409      * @param operator The operator to use. This constant can be produces from a string using {@link #getOperator(String)}.
410      * @param value The value to compare with, which must be of the right type. If field is number it might also be an alias.
411      * @param value2 The other value (only relevant if operator is BETWEEN, the only terniary operator)
412      * @param caseSensitive Whether it should happen case sensitively (not relevant for number fields)
413      * @param datePart The part of a DATETIME value that is to be checked
414      * @return The new constraint, or <code>null</code> it by chance the specified arguments did not lead to a new actual constraint (e.g. if value is an empty set)
415      */

416     public static Constraint createConstraint(Query query, String JavaDoc fieldName, int operator, Object JavaDoc value, Object JavaDoc value2, boolean caseSensitive, int datePart) {
417
418         StepField stepField = query.createStepField(fieldName);
419         if (stepField == null) {
420             throw new BridgeException("Could not create stepfield with '" + fieldName + "'");
421         }
422
423         Cloud cloud = query.getCloud();
424         FieldConstraint newConstraint;
425
426         if (value instanceof StepField) {
427             newConstraint = query.createConstraint(stepField, operator, (StepField)value);
428         } else if (operator == OPERATOR_NULL || value == null) {
429             newConstraint = query.createConstraint(stepField);
430         } else {
431             Field field = cloud.getNodeManager(stepField.getStep().getTableName()).getField(stepField.getFieldName());
432             int fieldType = field.getType();
433
434             if (fieldName.equals("number") || fieldType == Field.TYPE_NODE) {
435                 if (value instanceof String JavaDoc) { // it might be an alias!
436
if (cloud.hasNode((String JavaDoc) value)) {
437                         Node node = cloud.getNode((String JavaDoc)value);
438                         value = new Integer JavaDoc(node.getNumber());
439                     } else {
440                         value = new Integer JavaDoc(-1);
441                     }
442                 } else if (value instanceof Collection) { // or even more aliases!
443
Iterator i = ((Collection) value).iterator();
444                     value = new ArrayList();
445                     List list = (List) value;
446                     while (i.hasNext()) {
447                         Object JavaDoc v = i.next();
448                         if (v instanceof Number JavaDoc) {
449                             list.add(v);
450                         } else {
451                             String JavaDoc s = Casting.toString(v);
452                             if (cloud.hasNode(s)) {
453                                 Node node = cloud.getNode(s);
454                                 list.add(new Integer JavaDoc(node.getNumber()));
455                             } else {
456                                 list.add(new Integer JavaDoc(-1));
457                             }
458
459                         }
460                     }
461
462                 }
463             }
464             if (operator != OPERATOR_IN) { // should the elements of the collection then not be cast?
465

466                 if (fieldType == Field.TYPE_XML) {
467                     // XML's are treated as String in the query-handler so, let's anticipate that here...
468
// a bit of a hack, perhaps we need something like a 'searchCast' or so.
469
value = Casting.toString(value);
470                 } else {
471                     value = field.getDataType().cast(value, null, field);
472
473                 }
474             }
475             Object JavaDoc compareValue = getCompareValue(fieldType, operator, value, datePart, cloud);
476
477             if (operator > 0 && operator < OPERATOR_IN) {
478                 if (fieldType == Field.TYPE_DATETIME && datePart> -1) {
479                     newConstraint = query.createConstraint(stepField, operator, compareValue, datePart);
480                 } else {
481                     if (operator == FieldCompareConstraint.EQUAL && compareValue == null) {
482                         newConstraint = query.createConstraint(stepField);
483                     } else {
484                         newConstraint = query.createConstraint(stepField, operator, compareValue);
485                     }
486                 }
487             } else {
488                 if (fieldType == Field.TYPE_DATETIME && datePart> -1) {
489                     throw new RuntimeException JavaDoc("Cannot apply IN or BETWEEN to a partial date field");
490                 }
491                 switch (operator) {
492                 case OPERATOR_BETWEEN :
493                     Object JavaDoc compareValue2 = getCompareValue(fieldType, operator, value2);
494                     newConstraint = query.createConstraint(stepField, compareValue, compareValue2);
495                     break;
496                 case OPERATOR_IN :
497                     newConstraint = query.createConstraint(stepField, (SortedSet)compareValue);
498                     break;
499                 default :
500                     throw new RuntimeException JavaDoc("Unknown value for operation " + operator);
501                 }
502             }
503         }
504         query.setCaseSensitive(newConstraint, caseSensitive);
505         return newConstraint;
506
507     }
508
509     /**
510      * Takes a Constraint of a query, and takes al constraints on 'sourceStep' of it, and copies
511      * those Constraints to the given step of the receiving query.
512      *
513      * Constraints on different steps then the given 'sourceStep' are ignored. CompositeConstraints
514      * cause recursion and would work too (but same limitation are valid for the childs).
515      *
516      * @param c The constrain to be copied (for example the result of sourceQuery.getConstraint()).
517      * @param sourceStep The step in the 'source' query.
518      * @param query The receiving query
519      * @param step The step of the receiving query which must 'receive' the sort orders.
520      * @since MMBase-1.7.1
521      * @see org.mmbase.storage.search.implementation.BasicSearchQuery#copyConstraint Functions are similar
522      * @throws IllegalArgumentException If the given constraint is not compatible with the given step.
523      * @throws UnsupportedOperationException If CompareFieldsConstraints or LegacyConstraints are encountered.
524      * @return The new constraint or null
525      */

526     public static Constraint copyConstraint(Constraint c, Step sourceStep, Query query, Step step) {
527         if (c == null) return null;
528
529         if (c instanceof CompositeConstraint) {
530             CompositeConstraint constraint = (CompositeConstraint) c;
531             List constraints = new ArrayList();
532             Iterator i = constraint.getChilds().iterator();
533             while (i.hasNext()) {
534                 Constraint cons = copyConstraint((Constraint) i.next(), sourceStep, query, step);
535                 if (cons != null) constraints.add(cons);
536             }
537             int size = constraints.size();
538             if (size == 0) return null;
539             if (size == 1) return (Constraint) constraints.get(0);
540             i = constraints.iterator();
541             int op = constraint.getLogicalOperator();
542             Constraint newConstraint = query.createConstraint((Constraint) i.next(), op, (Constraint) i.next());
543             while (i.hasNext()) {
544                 newConstraint = query.createConstraint(newConstraint, op, (Constraint) i.next());
545             }
546             query.setInverse(newConstraint, constraint.isInverse());
547             return newConstraint;
548         } else if (c instanceof CompareFieldsConstraint) {
549             throw new UnsupportedOperationException JavaDoc("Cannot copy comparison between fields"); // at least not from different steps
550
}
551
552
553         FieldConstraint fieldConstraint = (FieldConstraint) c;
554         if (! fieldConstraint.getField().getStep().equals(sourceStep)) return null; // constraint is not for the request step, so don't copy.
555

556         StepField field = query.createStepField(step, fieldConstraint.getField().getFieldName());
557
558         FieldConstraint newConstraint;
559         if (c instanceof FieldValueConstraint) {
560             newConstraint = query.createConstraint(field, ((FieldValueConstraint) c).getOperator(), ((FieldValueConstraint) c).getValue());
561         } else if (c instanceof FieldNullConstraint) {
562             newConstraint = query.createConstraint(field);
563         } else if (c instanceof FieldValueBetweenConstraint) {
564             FieldValueBetweenConstraint constraint = (FieldValueBetweenConstraint) c;
565             try {
566                 newConstraint = query.createConstraint(field, constraint.getLowerLimit(), constraint.getUpperLimit());
567             } catch (NumberFormatException JavaDoc e) {
568                 newConstraint = query.createConstraint(field, constraint.getLowerLimit(), constraint.getUpperLimit());
569             }
570         } else if (c instanceof FieldValueInConstraint) {
571             FieldValueInConstraint constraint = (FieldValueInConstraint) c;
572
573             // sigh
574
SortedSet set = new TreeSet();
575             int type = field.getType();
576             Iterator k = constraint.getValues().iterator();
577             while (k.hasNext()) {
578                 Object JavaDoc value = k.next();
579                 switch(type) {
580                 case Field.TYPE_INTEGER:
581                 case Field.TYPE_LONG:
582                 case Field.TYPE_NODE:
583                     value = new Long JavaDoc(Casting.toLong(value));
584                     break;
585                 case Field.TYPE_FLOAT:
586                 case Field.TYPE_DOUBLE:
587                     value = new Double JavaDoc(Casting.toDouble(value));
588                     break;
589                 case Field.TYPE_DATETIME:
590                     value = new Date((long) 1000 * Integer.parseInt("" + value));
591                     break;
592                 default:
593                     log.debug("Unknown type " + type);
594                     break;
595                 }
596                 set.add(value);
597             }
598             newConstraint = query.createConstraint(field, set);
599         } else if (c instanceof LegacyConstraint) {
600             throw new UnsupportedOperationException JavaDoc("Cannot copy legacy constraint to other step");
601         } else {
602             throw new RuntimeException JavaDoc("Could not copy constraint " + c);
603         }
604         query.setInverse(newConstraint, fieldConstraint.isInverse());
605         query.setCaseSensitive(newConstraint, fieldConstraint.isCaseSensitive());
606         return newConstraint;
607
608     }
609     /**
610      * Copies SortOrders to a given step of another query. SortOrders which do not sort the given
611      * 'sourceStep' are ignored.
612      * @param sortOrders A list of SortOrders (for example the result of sourceQuery.getSortOrders()).
613      * @param sourceStep The step in the 'source' query.
614      * @param query The receiving query
615      * @param step The step of the receiving query which must 'receive' the sort orders.
616      * @since MMBase-1.7.1
617
618      */

619     public static void copySortOrders(List sortOrders, Step sourceStep, Query query, Step step) {
620         Iterator i = sortOrders.iterator();
621         while (i.hasNext()) {
622             SortOrder sortOrder = (SortOrder) i.next();
623             StepField sourceField = sortOrder.getField();
624             if (! sourceField.getStep().equals(sourceStep)) continue; // for another step
625
query.addSortOrder(query.createStepField(step, sourceField.getFieldName()), sortOrder.getDirection());
626         }
627     }
628
629     /**
630      * Converts a String to a SortOrder constant
631      * @param dir string representation of direction of sortorder
632      * @return SortOrder constant
633      * @since MMBase-1.7.1
634      */

635     public static int getSortOrder(String JavaDoc dir) {
636         dir = dir.toUpperCase();
637         if (dir.equals("")) {
638             return SortOrder.ORDER_ASCENDING;
639         } else if (dir.equals("DOWN")) {
640             return SortOrder.ORDER_DESCENDING;
641         } else if (dir.equals("UP")) {
642             return SortOrder.ORDER_ASCENDING;
643         } else if (dir.equals("ASCENDING")) {
644             return SortOrder.ORDER_ASCENDING;
645         } else if (dir.equals("DESCENDING")) {
646             return SortOrder.ORDER_DESCENDING;
647         } else {
648             throw new BridgeException("Unknown sort-order '" + dir + "'");
649         }
650     }
651
652     /**
653      * Adds sort orders to the query, using two strings. Like in 'getList' of Cloud. Several tag-attributes need this.
654      * @param query query to add the sortorders to
655      * @param sorted string with comma-separated fields
656      * @param directions string with comma-separated directions
657      *
658      * @todo implement for normal query.
659      * @return The new sort orders
660      */

661     public static List addSortOrders(Query query, String JavaDoc sorted, String JavaDoc directions) {
662         // following code was copied from MMObjectBuilder.setSearchQuery (bit ugly)
663
if (sorted == null) {
664             return query.getSortOrders().subList(0, 0);
665         }
666         if (directions == null) {
667             directions = "";
668         }
669         List list = query.getSortOrders();
670         int initialSize = list.size();
671
672         StringTokenizer sortedTokenizer = new StringTokenizer(sorted, ",");
673         StringTokenizer directionsTokenizer = new StringTokenizer(directions, ",");
674
675         while (sortedTokenizer.hasMoreTokens()) {
676             String JavaDoc fieldName = sortedTokenizer.nextToken().trim();
677             int dot = fieldName.indexOf('.');
678
679             StepField sf;
680             if (dot == -1 && query instanceof NodeQuery) {
681                 NodeManager nodeManager = ((NodeQuery)query).getNodeManager();
682                 sf = ((NodeQuery)query).getStepField(nodeManager.getField(fieldName));
683             } else {
684                 sf = query.createStepField(fieldName);
685             }
686
687             int dir = SortOrder.ORDER_ASCENDING;
688             if (directionsTokenizer.hasMoreTokens()) {
689                 String JavaDoc direction = directionsTokenizer.nextToken().trim();
690                 dir = getSortOrder(direction);
691             }
692             query.addSortOrder(sf, dir);
693         }
694
695         return list.subList(initialSize, list.size());
696     }
697
698     /**
699      * Returns substring of given string without the leading digits (used in 'paths')
700      * @param complete string with leading digits
701      * @return string with digits removed
702      */

703     protected static String JavaDoc removeDigits(String JavaDoc complete) {
704         int end = complete.length() - 1;
705         while (Character.isDigit(complete.charAt(end))) {
706             --end;
707         }
708         return complete.substring(0, end + 1);
709     }
710
711     /**
712      * Adds path of steps to an existing query. The query may contain steps already. Per step also
713      * the 'search direction' may be specified.
714      * @param query extend this query
715      * @param path create steps from this path
716      * @param searchDirs add steps with these relation directions
717      * @return The new steps.
718      */

719     public static List addPath(Query query, String JavaDoc path, String JavaDoc searchDirs) {
720         if (path == null || path.equals("")) {
721             return query.getSteps().subList(0, 0);
722         }
723         if (searchDirs == null) {
724             searchDirs = "";
725         }
726
727         List list = query.getSteps();
728         int initialSize = list.size();
729
730         StringTokenizer pathTokenizer = new StringTokenizer(path, ",");
731         StringTokenizer searchDirsTokenizer = new StringTokenizer(searchDirs, ",");
732
733         Cloud cloud = query.getCloud();
734
735         if (query.getSteps().size() == 0) { // if no steps yet, first step must be added with addStep
736
String JavaDoc completeFirstToken = pathTokenizer.nextToken().trim();
737             String JavaDoc firstToken = removeDigits(completeFirstToken);
738             //if (cloud.hasRole(firstToken)) {
739
// you cannot start with a role.., should we throw exception?
740
// naa, the following code will throw exception that node type does not exist.
741
//}
742
Step step = query.addStep(cloud.getNodeManager(firstToken));
743             if (!firstToken.equals(completeFirstToken)) {
744                 query.setAlias(step, completeFirstToken);
745             }
746         }
747
748         String JavaDoc searchDir = null; // outside the loop, so defaulting to previous searchDir
749
while (pathTokenizer.hasMoreTokens()) {
750             String JavaDoc completeToken = pathTokenizer.nextToken().trim();
751             String JavaDoc token = removeDigits(completeToken);
752
753             if (searchDirsTokenizer.hasMoreTokens()) {
754                 searchDir = searchDirsTokenizer.nextToken();
755             }
756
757             if (cloud.hasRole(token)) {
758                 if (!pathTokenizer.hasMoreTokens()) {
759                     throw new BridgeException("Path cannot end with a role (" + path + "/" + searchDirs + ")");
760                 }
761                 String JavaDoc nodeManagerAlias = pathTokenizer.nextToken().trim();
762                 String JavaDoc nodeManagerName = removeDigits(nodeManagerAlias);
763                 NodeManager nodeManager = cloud.getNodeManager(nodeManagerName);
764                 RelationStep relationStep = query.addRelationStep(nodeManager, token, searchDir);
765
766                 /// make it possible to postfix with numbers manually
767
if (!cloud.hasRole(completeToken)) {
768                     query.setAlias(relationStep, completeToken);
769                 }
770                 if (!nodeManagerName.equals(nodeManagerAlias)) {
771                     Step next = relationStep.getNext();
772                     query.setAlias(next, nodeManagerAlias);
773                 }
774             } else {
775                 NodeManager nodeManager = cloud.getNodeManager(token);
776                 RelationStep step = query.addRelationStep(nodeManager, null /* role */ , searchDir);
777                 if (!completeToken.equals(nodeManager.getName())) {
778                     Step next = step.getNext();
779                     query.setAlias(next, completeToken);
780                 }
781             }
782         }
783         if (searchDirsTokenizer.hasMoreTokens()) {
784             throw new BridgeException("Too many search directions (" + path + "/" + searchDirs + ")");
785         }
786         return list.subList(initialSize, list.size());
787     }
788
789     /**
790      * Adds a number of fields. Fields is represented as a comma separated string.
791      * @param query The query where the fields should be added to
792      * @param fields a comma separated string of fields
793      * @return The new stepfields
794      */

795     public static List addFields(Query query, String JavaDoc fields) {
796         List result = new ArrayList();
797         if (fields == null || fields.equals("")) {
798             return result;
799         }
800         List list = StringSplitter.split(fields);
801         Iterator i = list.iterator();
802         while (i.hasNext()) {
803             String JavaDoc fieldName = (String JavaDoc)i.next();
804             result.add(query.addField(fieldName));
805         }
806         return result;
807
808     }
809
810
811
812     /**
813      * Add startNodes to the first step with the correct type to the given query. The nodes are identified
814      * by a String, which could be prefixed with a step-alias, if you want to add the nodes to
815      * another then this found step.
816      *
817      * Furthermore may the nodes by identified by their alias, if they have one.
818      * @param query query to add the startnodes
819      * @param startNodes start nodes
820      *
821      * @see org.mmbase.module.core.ClusterBuilder#getMultiLevelSearchQuery(List, List, String, List, String, List, List, int)
822      * (this is essentially a 'bridge' version of the startnodes part)
823      */

824     public static void addStartNodes(Query query, String JavaDoc startNodes) {
825         if (startNodes == null || "".equals(startNodes) || "-1".equals(startNodes)) {
826             return;
827         }
828
829         Step firstStep = null; // the 'default' step to which nodes are added. It is the first step which corresponds with the type of the first node.
830

831         Iterator nodes = StringSplitter.split(startNodes).iterator();
832         while (nodes.hasNext()) {
833             String JavaDoc nodeAlias = (String JavaDoc) nodes.next(); // can be a string, prefixed with the step alias.
834
Step step; // the step to which the node must be added (defaults to 'firstStep').
835
String JavaDoc nodeNumber; // a node number or perhaps also a node alias.
836
{
837                 int dot = nodeAlias.indexOf('.'); // this feature is not in core. It should be considered experimental
838
if (dot == -1) {
839                     step = firstStep;
840                     nodeNumber = nodeAlias;
841                 } else {
842                     step = query.getStep(nodeAlias.substring(0, dot));
843                     nodeNumber = nodeAlias.substring(dot + 1);
844                 }
845             }
846
847             if (firstStep == null) { // firstStep not yet determined, do that now.
848
Node node;
849                 try {
850                     node = query.getCloud().getNode(nodeNumber);
851                 } catch (NotFoundException nfe) { // alias with dot?
852
node = query.getCloud().getNode(nodeAlias);
853                 }
854                 NodeManager nodeManager = node.getNodeManager();
855                 Iterator i = query.getSteps().iterator();
856                 while (i.hasNext()) {
857                     Step queryStep = (Step) i.next();
858                     NodeManager queryNodeManager = query.getCloud().getNodeManager(queryStep.getTableName());
859                     if (queryNodeManager.equals(nodeManager) || queryNodeManager.getDescendants().contains(nodeManager)) {
860                         // considering inheritance. ClusterBuilder is not doing that, but I think it is a bug.
861
firstStep = queryStep;
862                         break;
863                     }
864                 }
865                 if (firstStep == null) {
866                     // odd..
867
// See also org.mmbase.module.core.ClusterBuilder#getMultiLevelSearchQuery
868
// specified a node which is not of the type of one of the steps.
869
// take as default the 'first' step (which will make the result empty, compatible with 1.6, bug #6440).
870
firstStep = (Step) query.getSteps().get(0);
871                 }
872                 if (step == null) {
873                     step = firstStep;
874                 }
875             }
876
877             try {
878                 try {
879                     query.addNode(step, Integer.parseInt(nodeNumber));
880                 } catch (NumberFormatException JavaDoc nfe) {
881                     query.addNode(step, query.getCloud().getNode(nodeNumber)); // node was specified by alias.
882
}
883             } catch (NotFoundException nnfe) {
884                 query.addNode(step, query.getCloud().getNode(nodeAlias)); // perhas an alias containing a dot?
885
}
886         }
887     }
888
889     /**
890      * Takes the query, and does a count with the same constraints (so ignoring 'offset' and 'max')
891      * @param query query as base for the count
892      * @return number of results
893      */

894     public static int count(Query query) {
895         Cloud cloud = query.getCloud();
896         Query count = query.aggregatingClone();
897         int type = query.isDistinct() ? AggregatedField.AGGREGATION_TYPE_COUNT_DISTINCT : AggregatedField.AGGREGATION_TYPE_COUNT;
898
899         String JavaDoc resultName;
900         if (query instanceof NodeQuery) {
901             // all fields are present of the node-step, so, we could use the number field simply.
902
resultName = "number";
903             NodeQuery nq = (NodeQuery) query;
904             count.addAggregatedField(nq.getNodeStep(), nq.getNodeManager().getField(resultName), type);
905         } else {
906             List fields = query.getFields();
907             if (fields.size() == 0) { // for non-distinct queries always the number fields would be available
908
throw new IllegalArgumentException JavaDoc("Cannot count queries with less than one field: " + query);
909             }
910
911             if (query.isDistinct() && fields.size() > 1) {
912                 // aha hmm. Well, we also find it ok if all fields are of one step, and 'number' is present
913
resultName = null;
914                 Step step = null;
915                 Iterator i = fields.iterator();
916                 while (i.hasNext()) {
917                     StepField sf = (StepField) i.next();
918                     if (step == null) {
919                         step = sf.getStep();
920                     } else {
921                         if (! step.equals(sf.getStep())) {
922                             throw new UnsupportedOperationException JavaDoc("Cannot count distinct queries with fields of more than one step. Current fields: " + fields);
923                         }
924                     }
925                     if (sf.getFieldName().equals("number")) {
926                         resultName = sf.getFieldName();
927                     }
928                 }
929                 if (resultName == null) {
930                     throw new UnsupportedOperationException JavaDoc("Cannot count distinct queries with more than one field if 'number' field is missing. Current fields: " + fields);
931                 }
932                 count.addAggregatedField(step, cloud.getNodeManager(step.getTableName()).getField(resultName), type);
933             } else {
934                 // simply take this one field
935
StepField sf = (StepField) fields.get(0);
936                 Step step = sf.getStep();
937                 resultName = sf.getFieldName();
938                 count.addAggregatedField(step, cloud.getNodeManager(step.getTableName()).getField(resultName), type);
939             }
940         }
941         NodeList r = cloud.getList(count);
942         if (r.size() != 1) throw new RuntimeException JavaDoc("Count query " + query + " did not give one result but " + r);
943         Node result = r.getNode(0);
944         return result.getIntValue(resultName);
945     }
946
947     /**
948      * @since MMBase-1.8
949      */

950     protected static Object JavaDoc aggregate(Query query, StepField field, int type) {
951         Cloud cloud = query.getCloud();
952         Query aggregate = query.aggregatingClone();
953         String JavaDoc resultName = field.getFieldName();
954         Step step = field.getStep();
955         aggregate.addAggregatedField(step, cloud.getNodeManager(step.getTableName()).getField(resultName), type);
956         NodeList r = cloud.getList(aggregate);
957         if (r.size() != 1) throw new RuntimeException JavaDoc("Aggregated query " + query + " did not give one result but " + r);
958         Node result = r.getNode(0);
959         return result.getValue(resultName);
960     }
961
962     /**
963      * @since MMBase-1.8
964      */

965     public static Object JavaDoc min(Query query, StepField field) {
966         return aggregate(query, field, AggregatedField.AGGREGATION_TYPE_MIN);
967     }
968     /**
969      * @since MMBase-1.8
970      */

971     public static Object JavaDoc max(Query query, StepField field) {
972         return aggregate(query, field, AggregatedField.AGGREGATION_TYPE_MAX);
973     }
974
975     /**
976      * Searches a list of Steps for a step with a certain name. (alias or tableName)
977      * @param steps steps to search through
978      * @param stepAlias alias to search for
979      * @return The Step if found, otherwise null
980      * @throws ClassCastException if list does not contain only Steps
981      */

982     public static Step searchStep(List steps, String JavaDoc stepAlias) {
983         if (log.isDebugEnabled()) {
984             log.debug("Searching '" + stepAlias + "' in " + steps);
985         }
986         // first try aliases
987
Iterator i = steps.iterator();
988         while (i.hasNext()) {
989             Step step = (Step)i.next();
990             if (stepAlias.equals(step.getAlias())) {
991                 return step;
992             }
993         }
994         // if no aliases found, try table names
995
i = steps.iterator();
996         while (i.hasNext()) {
997             Step step = (Step)i.next();
998             if (stepAlias.equals(step.getTableName())) {
999                 return step;
1000            }
1001        }
1002        return null;
1003    }
1004
1005    /**
1006     * Returns the NodeQuery returning the given Node. This query itself is not very useful, because
1007     * you already have its result (the node), but it is convenient as a base query for many other
1008     * goals.
1009     *
1010     * If the node is uncommited, it cannot be queried, and the node query returning all nodes from
1011     * the currect type will be returned.
1012     *
1013     * @param node Node to create the query from
1014     * @return A new NodeQuery object
1015     */

1016    public static NodeQuery createNodeQuery(Node node) {
1017        NodeManager nm = node.getNodeManager();
1018        NodeQuery query = node.getCloud().createNodeQuery(); // use the version which can accept more steps
1019
Step step = query.addStep(nm);
1020        query.setNodeStep(step);
1021        if (! node.isNew()) {
1022            query.addNode(step, node);
1023        }
1024        return query;
1025    }
1026
1027    /**
1028     * Returns a query to find the nodes related to the given node.
1029     * @param node start node
1030     * @param otherNodeManager node manager on the other side of the relation
1031     * @param role role of the relation
1032     * @param direction direction of the relation
1033     * @return A new NodeQuery object
1034     */

1035    public static NodeQuery createRelatedNodesQuery(Node node, NodeManager otherNodeManager, String JavaDoc role, String JavaDoc direction) {
1036        NodeQuery query = createNodeQuery(node);
1037        if (otherNodeManager == null) otherNodeManager = node.getCloud().getNodeManager("object");
1038        RelationStep step = query.addRelationStep(otherNodeManager, role, direction);
1039        query.setNodeStep(step.getNext());
1040        return query;
1041    }
1042
1043    /**
1044     * Returns a query to find the relations nodes of the given node.
1045     * @param node start node
1046     * @param otherNodeManager node manager on the other side of the relation
1047     * @param role role of the relation
1048     * @param direction direction of the relation
1049     * @return A new NodeQuery object
1050     */

1051    public static NodeQuery createRelationNodesQuery(Node node, NodeManager otherNodeManager, String JavaDoc role, String JavaDoc direction) {
1052        NodeQuery query = createNodeQuery(node);
1053        if (otherNodeManager == null) otherNodeManager = node.getCloud().getNodeManager("object");
1054        RelationStep step = query.addRelationStep(otherNodeManager, role, direction);
1055        query.setNodeStep(step);
1056        return query;
1057    }
1058
1059    /**
1060     * Returns a query to find the relations nodes between two given nodes.
1061     *
1062     * To test <em>whether</em> to nodes are related you can use e.g.:
1063     * <code>
1064     * if (Queries.count(Queries.createRelationNodesQuery(node1, node2, "posrel", null)) > 0) {
1065     * ..
1066     * }
1067     * </code>
1068     * @param node start node
1069     * @param otherNode node on the other side of the relation
1070     * @param role role of the relation
1071     * @param direction direction of the relation
1072     * @return A new NodeQuery object
1073     * @since MMBase-1.8
1074     */

1075    public static NodeQuery createRelationNodesQuery(Node node, Node otherNode, String JavaDoc role, String JavaDoc direction) {
1076        NodeQuery query = createNodeQuery(node);
1077        NodeManager otherNodeManager = otherNode.getNodeManager();
1078        RelationStep step = query.addRelationStep(otherNodeManager, role, direction);
1079        Step nextStep = step.getNext();
1080        query.addNode(nextStep, otherNode.getNumber());
1081        query.setNodeStep(step);
1082        return query;
1083    }
1084
1085    /**
1086     * Queries a list of cluster nodes, using a {@link org.mmbase.bridge.NodeQuery} (so al fields of
1087     * one step are available), plus some fields of the relation step. The actual node can be got
1088     * from the node cache by doing a {@link org.mmbase.bridge.Node#getNodeValue} with the {@link
1089     * org.mmbase.bridge.NodeList#NODESTEP_PROPERTY} property. The fields of the relation can be got by
1090     * prefixing their names by the role and a dot (as normal in multilevel results).
1091     * @param node start node
1092     * @param otherNodeManager node manager on the other side of the relation
1093     * @param role role of the relation
1094     * @param direction direction of the relation
1095     * @param relationFields Comma separated string of fields which must be queried from the relation step
1096     * @param sortOrders Comma separated string of fields of sortorders, or the empty string or <code>null</code>
1097     * So, this methods is targeted at the use of 'posrel' and similar fields, because sorting on other fields isn't possible right now.
1098     * @since MMBase-1.8
1099     * @todo EXPERIMENTAL
1100     */

1101    public static NodeList getRelatedNodes(Node node, NodeManager otherNodeManager, String JavaDoc role, String JavaDoc direction, String JavaDoc relationFields, String JavaDoc sortOrders) {
1102        NodeQuery q = Queries.createRelatedNodesQuery(node, otherNodeManager, role, direction);
1103        addRelationFields(q, role, relationFields, sortOrders);
1104        return q.getCloud().getList(q);
1105    }
1106
1107    /**
1108     * @since MMBase-1.8
1109     */

1110    public static NodeQuery addRelationFields(NodeQuery q, String JavaDoc role, String JavaDoc relationFields, String JavaDoc sortOrders) {
1111        List list = StringSplitter.split(relationFields);
1112        List orders = StringSplitter.split(sortOrders);
1113        Iterator i = list.iterator();
1114        Iterator j = orders.iterator();
1115        while (i.hasNext()) {
1116            String JavaDoc fieldName = (String JavaDoc)i.next();
1117            StepField sf = q.addField(role + "." + fieldName);
1118            if (j.hasNext()) {
1119                String JavaDoc so = (String JavaDoc) j.next();
1120                q.addSortOrder(sf, getSortOrder(so));
1121            }
1122        }
1123        return q;
1124    }
1125
1126    /**
1127     * Add a sortorder (DESCENDING) on al the'number' fields of the query, on which there is not yet a
1128     * sortorder. This ensures that the query result is ordered uniquely.
1129     * @param q query to change
1130     * @return The changed Query
1131     */

1132    public static Query sortUniquely(final Query q) {
1133        List steps = null;
1134
1135        // remove the ones which are already sorted
1136
Iterator i = q.getSortOrders().iterator();
1137        while (i.hasNext()) {
1138            SortOrder sortOrder = (SortOrder)i.next();
1139            if (sortOrder.getField().getFieldName().equals("number")) {
1140                Step step = sortOrder.getField().getStep();
1141                if (steps == null) {
1142                    // instantiate new ArrayList only if really necessary
1143
steps = new ArrayList(q.getSteps());
1144                }
1145                steps.remove(step);
1146            }
1147        }
1148        if (steps == null) {
1149            steps = q.getSteps();
1150        }
1151        // add sort order on the remaining ones:
1152
i = steps.iterator();
1153        while (i.hasNext()) {
1154            Step step = (Step)i.next();
1155            assert step != null;
1156            StepField sf = q.createStepField(step, "number");
1157            if (sf == null) {
1158                throw new RuntimeException JavaDoc("Create stepfield for 'number' field returned null!");
1159            }
1160            q.addSortOrder(sf, SortOrder.ORDER_DESCENDING);
1161        }
1162        return q;
1163    }
1164
1165    /**
1166     * Make sure all sorted fields are queried
1167     * @since MMBase-1.8
1168     */

1169    public static Query addSortedFields(Query q) {
1170        List fields = q.getFields();
1171        Iterator i = q.getSortOrders().iterator();
1172        while (i.hasNext()) {
1173            SortOrder order = (SortOrder) i.next();
1174            StepField field = order.getField();
1175            Step s = field.getStep();
1176            StepField sf = q.createStepField(s, q.getCloud().getNodeManager(s.getTableName()).getField(field.getFieldName()));
1177            if (! fields.contains(sf)) {
1178                q.addField(s, q.getCloud().getNodeManager(s.getTableName()).getField(field.getFieldName()));
1179            }
1180        }
1181        return q;
1182    }
1183
1184    /**
1185     * Obtains a value for the field of a sortorder from a given node.
1186     * Used to set constraints based on sortorder.
1187     * @since MMBase-1.8
1188     */

1189    public static Object JavaDoc getSortOrderFieldValue(Node node, SortOrder sortOrder) {
1190        String JavaDoc fieldName = sortOrder.getField().getFieldName();
1191        if (node == null) throw new IllegalArgumentException JavaDoc("No node given");
1192        Object JavaDoc value = node.getValue(fieldName);
1193        if (value == null) {
1194            Step step = sortOrder.getField().getStep();
1195            String JavaDoc pref = step.getAlias();
1196            if (pref == null) {
1197                pref = step.getTableName();
1198            }
1199            value = node.getValue(pref+ "." + fieldName);
1200
1201        }
1202        if (value instanceof Node) {
1203            value = new Integer JavaDoc(((Node)value).getNumber());
1204        }
1205        return value;
1206    }
1207
1208
1209    /**
1210     * Compare tho nodes, with a SortOrder. This determins where a certain node is smaller or bigger than a certain other node, with respect to some SortOrder.
1211     * This is used by {@link #compare(Node, Node, List)}
1212     *
1213     * If node2 is only 'longer' then node1, but otherwise equal, then it is bigger.
1214     *
1215     * @since MMBase-1.8
1216     */

1217    public static int compare(Node node1, Node node2, SortOrder sortOrder) {
1218        return compare(getSortOrderFieldValue(node1, sortOrder),
1219                       getSortOrderFieldValue(node2, sortOrder),
1220                       sortOrder);
1221
1222    }
1223    /**
1224     * @since MMBase-1.8
1225     */

1226    public static int compare(Object JavaDoc value, Object JavaDoc value2, SortOrder sortOrder) {
1227        int result;
1228        // compare values - if they differ, detemrine whether
1229
// they are bigger or smaller and return the result
1230
// remaining fields are not of interest ionce a difference is found
1231
if (value == null) {
1232            if (value2 != null) {
1233                return 1;
1234            } else {
1235                result = 0;
1236            }
1237        } else if (value2 == null) {
1238            return -1;
1239        } else {
1240            // compare the results
1241
try {
1242                result = ((Comparable JavaDoc)value).compareTo(value2);
1243            } catch (ClassCastException JavaDoc cce) {
1244                // This should not occur, and indicates very odd values are being sorted on (i.e. byte arrays).
1245
// warn and ignore this sortorder
1246
log.warn("Cannot compare values " + value +" and " + value2 + " in sortorder field " +
1247                         sortOrder.getField().getFieldName() + " in step " + sortOrder.getField().getStep().getAlias());
1248                result = 0;
1249            }
1250        }
1251        // if the order of this field is descending,
1252
// then the result of the comparison is the reverse (the node is 'greater' if the value is 'less' )
1253
if (sortOrder.getDirection() == SortOrder.ORDER_DESCENDING) {
1254            result = -result;
1255        }
1256        return result;
1257    }
1258
1259    /**
1260     * Does a field-by-field compare of two Node objects, on the fields used to order the nodes.
1261     * This is used to determine whether a node comes after or before another, in a certain query result.
1262     *
1263     * @return -1 if node1 is smaller than node 2, 0 if both nodes are equals, and +1 is node 1 is greater than node 2.
1264     * @since MMBase-1.8
1265     */

1266    public static int compare(Node node1, Node node2, List sortOrders) {
1267        if (node1 == null) return -1;
1268        if (node2 == null) return +1;
1269        int result = 0;
1270        Iterator i = sortOrders.iterator();
1271        while (result == 0 && i.hasNext()) {
1272            SortOrder order = (SortOrder) i.next();
1273            result = compare(node1, node2, order);
1274        }
1275        // if all fields match - return 0 as if equal
1276
return result;
1277    }
1278
1279    public static void main(String JavaDoc[] argv) {
1280        System.out.println(ConstraintParser.convertClauseToDBS("(([cpsettings.status]='[A]' OR [cpsettings.status]='I') AND [users.account] != '') and (lower([users.account]) LIKE '%t[est%' OR lower([users.email]) LIKE '%te]st%' OR lower([users.firstname]) LIKE '%t[e]st%' OR lower([users.lastname]) LIKE '%]test%')"));
1281    }
1282
1283}
1284
Popular Tags