KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > storage > search > legacy > ConstraintParser


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 package org.mmbase.storage.search.legacy;
11
12 import java.util.*;
13 import org.mmbase.bridge.Field;
14 import org.mmbase.core.CoreField;
15 import org.mmbase.module.core.*;
16 import org.mmbase.storage.StorageManagerFactory;
17 import org.mmbase.storage.search.*;
18 import org.mmbase.storage.search.implementation.*;
19 import org.mmbase.util.logging.*;
20 import org.mmbase.bridge.NodeQuery;
21
22 /**
23  * Parser, tries to parse a <em>SQL-search-condition</em> for a query to a
24  * {@link org.mmbase.storage.search.Constraint Constraint} object.
25  * <p>
26  * This class is provided for the sole purpose of alignment of old code with
27  * the new {@link org.mmbase.storage.search.SearchQuery SearchQuery} framework,
28  * and should not be called by new code.
29  * <p>
30  * A <em>SQL-search-condition</em> can be one of these forms:
31  * <ul>
32  * <li>[<b>NOT</b>] <b>(</b><em>SQL-search-condition</em><b>)</b>
33  * <li>[<b>NOT</b>] <em>simple-SQL-search-condition</em>
34  * <li><em>SQL-search-condition</em> <b>AND</b> <em>SQL-search-condition</em>
35  * <li><em>SQL-search-condition</em> <b>OR</b> <em>SQL-search-condition</em>
36  * </ul>
37  * A <em>simple-SQL-search-condition</em> string can be of one of these forms:
38  * <ul>
39  * <li><em>field</em> [<b>NOT</b>] <b>LIKE</b> <em>value</em>
40  * <li><b>UPPER(</b><em>field</em><b>)</b> [<b>NOT</b>] <b>LIKE</b> <em>value</em>
41  * <li><b>LOWER(</b><em>field</em><b>)</b> [<b>NOT</b>] <b>LIKE</b> <em>value</em>
42  * <li><em>field</em> <b>IS</b> [<b>NOT</b>] <b>NULL</b>
43  * <li><em>field</em> [<b>NOT</b>] <b>IN
44  * (</b><em>value1</em><b>,</b> <em>value2</em><b>,</b> ..<b>)</b>
45  * <li><em>field</em> [<b>NOT</b>] <b>BETWEEN</b> <em>value1</em> <b>AND</b> <em>value2</em>
46  * <li><b>UPPER(</b><em>field</em><b>)</b> [<b>NOT</b>] <b>BETWEEN</b> <em>value1</em> <b>AND</b> <em>value2</em>
47  * <li><b>LOWER(</b><em>field</em><b>)</b> [<b>NOT</b>] <b>BETWEEN</b> <em>value1</em> <b>AND</b> <em>value2</em>
48  * <li><em>field</em> <b>=</b> <em>value</em>
49  * <li><em>field</em> <b>=</b> <em>field2</em>
50  * <li><b>UPPER(</b><em>field</em><b>) =</b> <em>value</em>
51  * <li><b>LOWER(</b><em>field</em><b>) =</b> <em>value</em>
52  * <li><em>field</em> <b>==</b> <em>value</em>
53  * <li><em>field</em> <b>==</b> <em>field2</em>
54  * <li><em>field</em> <b>&lt;=</b> <em>value</em>
55  * <li><em>field</em> <b>&lt;=</b> <em>field2</em>
56  * <li><em>field</em> <b>&lt;</b> <em>value</em>
57  * <li><em>field</em> <b>&lt;</b> <em>field2</em>
58  * <li><em>field</em> <b>&gt;=</b> <em>value</em>
59  * <li><em>field</em> <b>&gt;=</b> <em>field2</em>
60  * <li><em>field</em> <b>&gt;</b> <em>value</em>
61  * <li><em>field</em> <b>&gt;</b> <em>field2</em>
62  * <li><em>field</em> <b>&lt;&gt;</b> <em>value</em>
63  * <li><em>field</em> <b>&lt;&gt;</b> <em>field2</em>
64  * <li><em>field</em> <b>!=</b> <em>value</em>
65  * <li><em>field</em> <b>!=</b> <em>field2</em>
66  * <li><em>string-search-condition</em>
67  * </ul>
68  * A <em>field</em> can be one of these forms:
69  * <ul>
70  * <li><em>stepalias</em><b>.</b><em>fieldname</em>
71  * <li><em>fieldname</em> (only when the query has just one step).
72  * </ul>
73  * A <em>value</em> can be one of these forms:
74  * <ul>
75  * <li><em>string between single quotes</em> for string fields, example: <code>'A string value'</code>
76  * <li><em>numerical value</em> for numerical fields, example: <code>123.456</code>
77  * <li><em>numerical value between single quotes</em> for numerical fields, example" <code>'123.456'</code>
78  * </ul>
79  * A <em>string-search-condition</em> can be of this form:
80  * <ul>
81  * <li><b>StringSearch(</b><em>field</em><b>,</b>PHRASE|PROXIMITY|WORD<b>,</b>
82  * FUZZY|LITERAL|SYNONYM<b>,</b>
83  * <em>searchterms</em><b>,</b>
84  * <em>casesensitive</em><b>)</b>
85  * [<b>.set(FUZZINESS,</b><em>fuzziness</em><b>)</b>]
86  * [<b>.set(PROXIMITY_LIMIT,</b><em>proximity</em><b>)</b>]
87  * </ul>
88  * <em>searchterms</em> can be of one of these forms:
89  * <ul>
90  * <li><b>'</b>term1<b>'</b>
91  * <li><b>"</b>term1<b>"</b>
92  * <li><b>'</b>term1 term2<b>'</b>
93  * <li><b>"</b>term1 term2<b>"</b>
94  * <li> etc...
95  * </ul>
96  * <em>casesensitive</em> can be of one on these forms:
97  * <ul>
98  * <li><b>true</b>
99  * <li><b>false</b>
100  * </ul>
101  * <em>fuzziness</em> must be a float value between 0.0 and 1.0,
102  * <em>proximity</em> must be a int value &gt; 0<br />
103  * <p>
104  * See {@link org.mmbase.storage.search.StringSearchConstraint
105  * StringSearchConstraint} for more info on string-search constraints.
106  * <p>
107  * A search condition that is not of one of these forms will be converted to a
108  * {@link org.mmbase.storage.search.LegacyConstraint LegacyConstraint}, i.e.
109  * in that case the search condition string will not be interpreted, but
110  * instead be used "as-is".
111  * Each time this occurs is logged with priority <code>service</code> to
112  * category <code>org.mmbase.storage.search.legacyConstraintParser.fallback</code>.
113  *
114  * @author Rob van Maris
115  * @version $Id: ConstraintParser.java,v 1.28 2006/06/06 19:58:21 michiel Exp $
116  * @since MMBase-1.7
117  */

118 public class ConstraintParser {
119
120     private final static Logger log = Logging.getLoggerInstance(ConstraintParser.class);
121
122     /** Logger instance dedicated to logging fallback to legacy constraint. */
123     private final static Logger fallbackLog = Logging.getLoggerInstance(ConstraintParser.class.getName() + ".fallback");
124
125
126     /**
127      * Converts a constraint by turning all 'quoted' fields into
128      * database supported fields.
129      * XXX: todo: escape characters for '[' and ']'.
130      * @param constraints constraint to convert
131      * @return Converted constraint
132      * @since MMBase-1.8.1 (moved from org.mmbase.bridge.util.Queries)
133      */

134     private static String JavaDoc convertClausePartToDBS(String JavaDoc constraints) {
135         StorageManagerFactory factory = MMBase.getMMBase().getStorageManagerFactory();
136         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
137         int posa = constraints.indexOf('[');
138         while (posa > -1) {
139             int posb = constraints.indexOf(']', posa);
140             if (posb == -1) {
141                 posa = -1;
142             } else {
143                 String JavaDoc fieldName = constraints.substring(posa + 1, posb);
144                 int posc = fieldName.indexOf('.');
145                 if (posc == -1) {
146                     fieldName = factory != null ? factory.getStorageIdentifier(fieldName).toString() : fieldName;
147                 } else {
148                     fieldName = fieldName.substring(0, posc + 1) + (factory != null ? factory.getStorageIdentifier(fieldName.substring(posc + 1)) : fieldName.substring(posc + 1));
149                 }
150                 result.append(constraints.substring(0, posa)).append(fieldName);
151                 constraints = constraints.substring(posb + 1);
152                 posa = constraints.indexOf('[');
153             }
154         }
155         result.append(constraints);
156         return result.toString();
157     }
158
159     /**
160      * Converts a constraint by turning all 'quoted' fields into
161      * database supported fields.
162      * XXX: todo: escape characters for '[' and ']'.
163      * @param constraints constraints to convert
164      * @return converted constraint
165      * @since MMBase-1.8.1 (moved from org.mmbase.bridge.util.Queries)
166      */

167     public static String JavaDoc convertClauseToDBS(String JavaDoc constraints) {
168         if (constraints.startsWith("MMNODE")) {
169             // wil probably not work
170
// @todo check
171
return constraints;
172         } else if (constraints.startsWith("ALTA")) {
173             // wil probably not work
174
// @todo check
175
return constraints.substring(5);
176         }
177
178         //keesj: what does this code do?
179

180         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
181         //if there is a quote in the constraints posa will not be equals -1
182

183         int quoteOpen = constraints.indexOf('\'');
184         while (quoteOpen > -1) {
185             //keesj: posb can be the same a posa maybe the method should read indexOf("\"",posa) ?
186
int quoteClose = constraints.indexOf('\'', quoteOpen + 1);
187             if (quoteClose == -1) {
188                 // unmatching quote?
189
log.warn("unbalanced quote in " + constraints);
190                 break;
191             }
192
193             //keesj:part is now the first part of the constraints if there is a quote in the query
194
String JavaDoc part = constraints.substring(0, quoteOpen);
195
196             //append to the string buffer "part" the first part
197
result.append(convertClausePartToDBS(part));
198             result.append(constraints.substring(quoteOpen, quoteClose + 1));
199
200             constraints = constraints.substring(quoteClose + 1);
201             quoteOpen = constraints.indexOf('\'');
202
203         }
204         result.append(convertClausePartToDBS(constraints));
205         return result.toString();
206     }
207
208     /**
209      * returns false, when escaping wasnt closed, or when a ";" was found outside a escaped part (to prefent spoofing)
210      * This is used by createQuery (i wonder if it still makes sense)
211      * @param constraints constraint to check
212      * @return is valid constraint
213      * @since MMBase-1.8.1 (moved from org.mmbase.bridge.util.Queries)
214      */

215     static public boolean validConstraints(String JavaDoc constraints) {
216         // first remove all the escaped "'" ('' occurences) chars...
217
String JavaDoc remaining = constraints;
218         while (remaining.indexOf("''") != -1) {
219             int start = remaining.indexOf("''");
220             int stop = start + 2;
221             if (stop < remaining.length()) {
222                 String JavaDoc begin = remaining.substring(0, start);
223                 String JavaDoc end = remaining.substring(stop);
224                 remaining = begin + end;
225             } else {
226                 remaining = remaining.substring(0, start);
227             }
228         }
229         // assume we are not escaping... and search the string..
230
// Keep in mind that at this point, the remaining string could contain different information
231
// than the original string. This doesnt matter for the next sequence...
232
// but it is important to realize!
233
while (remaining.length() > 0) {
234             if (remaining.indexOf('\'') != -1) {
235                 // we still contain a "'"
236
int start = remaining.indexOf('\'');
237
238                 // escaping started, but no stop
239
if (start == remaining.length()) {
240                     log.warn("reached end, but we are still escaping(you should sql-escape the search query inside the jsp-page?)\noriginal:" + constraints);
241                     return false;
242                 }
243
244                 String JavaDoc notEscaped = remaining.substring(0, start);
245                 if (notEscaped.indexOf(';') != -1) {
246                     log.warn("found a ';' outside the constraints(you should sql-escape the search query inside the jsp-page?)\noriginal:" + constraints + "\nnot excaped:" + notEscaped);
247                     return false;
248                 }
249
250                 int stop = remaining.substring(start + 1).indexOf('\'');
251                 if (stop < 0) {
252                     log.warn("reached end, but we are still escaping(you should sql-escape the search query inside the jsp-page?)\noriginal:" + constraints + "\nlast escaping:" + remaining.substring(start + 1));
253                     return false;
254                 }
255                 // we added one to to start, thus also add this one to stop...
256
stop = start + stop + 1;
257
258                 // when the last character was the stop of our escaping
259
if (stop == remaining.length()) {
260                     return true;
261                 }
262
263                 // cut the escaped part from the string, and continue with resting sting...
264
remaining = remaining.substring(stop + 1);
265             } else {
266                 if (remaining.indexOf(';') != -1) {
267                     log.warn("found a ';' inside our constrain:" + constraints);
268                     return false;
269                 }
270                 return true;
271             }
272         }
273         return true;
274     }
275
276
277
278     private SearchQuery query = null;
279     private List steps = null;
280
281     /**
282      * Parses string or numerical value from list of tokens, to match the type
283      * of the specified field.
284      * If the first token is not "'", it is interpreted as a numerical value,
285      * otherwise it is required to be the first token of the sequence
286      * "'", "value", "'", representing a string value for string fields, or
287      * a numerical value for numerical fields.
288      *
289      * @param iTokens Tokens iterator, must be positioned before the (first)
290      * token representing the value.
291      * @param field The field.
292      * @return A <code>String</code> or <code>Double</code> object representing
293      * the value.
294      * @throws NumberFormatException when the first token is not (the start of)
295      * a valid value expression (it may be a <em>field</em> instead).
296      */

297     // package visibility!
298
static Object JavaDoc parseValue(Iterator iTokens, StepField field) throws NumberFormatException JavaDoc {
299         Object JavaDoc result = null;
300         String JavaDoc token = (String JavaDoc) iTokens.next();
301         if (token.equals("'")) {
302             // String value.
303
result = (String JavaDoc) iTokens.next();
304             token = (String JavaDoc) iTokens.next();
305             if (!token.equals("'")) {
306                 throw new IllegalArgumentException JavaDoc("Unexpected token (expected \"'\"): \"" + token + "\"");
307              }
308
309             int fieldType = field.getType();
310             if (fieldType == Field.TYPE_BINARY || fieldType == Field.TYPE_DOUBLE ||
311                 fieldType == Field.TYPE_FLOAT || fieldType == Field.TYPE_INTEGER ||
312                 fieldType == Field.TYPE_LONG || fieldType == Field.TYPE_NODE) {
313                 // String represents a numerical value.
314
result = new Double JavaDoc((String JavaDoc) result);
315             }
316         } else {
317             result = new Double JavaDoc(token);
318         }
319         return result;
320     }
321
322     /**
323      * Parses SQL search condition string into separate tokens, discarding
324      * white spaces, concatenating strings between (single/double) quotes,
325      * and replacing escaped (single/double) quotes in strings by the
326      * original character.
327      *
328      * @param sqlConstraint The SQL constraint string.
329      * @return List of tokens.
330      */

331     // package visibility!
332
static List tokenize(String JavaDoc sqlConstraint) {
333         // Parse into separate tokens.
334
List tokens = new ArrayList();
335         StringTokenizer st = new StringTokenizer(sqlConstraint, " ()'\"=<>!,", true);
336         tokenize:
337             while (st.hasMoreTokens()) {
338                 String JavaDoc token = st.nextToken(" ()'\"=<>!,");
339
340                 // String, delimited by single or double quotes.
341
if (token.equals("'") || token.equals("\"")) {
342                     tokens.add("'");
343                     StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
344                     while (true) {
345                         String JavaDoc token2 = st.nextToken(token);
346                         if (token2.equals(token)) {
347                             if (!st.hasMoreTokens()) {
348                                 // Token 2 is end delimiter and last token.
349
tokens.add(sb.toString());
350                                 tokens.add("'");
351                                 break tokenize;
352                             } else {
353                                 String JavaDoc token3 = st.nextToken(" ()'\"=<>!,");
354                                 if (token3.equals(token)) {
355                                     // Token 2 and 3 are escaped delimiter.
356
sb.append(token);
357                                 } else {
358                                     // Token 2 is end delimiter, but not last token.
359
tokens.add(sb.toString());
360                                     tokens.add("'");
361                                     token = token3;
362                                     break;
363                                 }
364                             }
365                         } else {
366                             // Token 2 is string.
367
sb.append(token2);
368                         }
369                     }
370                 }
371
372                 // Add token, but skip white spaces.
373
if (!token.equals(" ")) {
374                     tokens.add(token);
375                 }
376             }
377             return tokens;
378     }
379
380     /**
381      * Creates <code>StepField</code> corresponding to field indicated by
382      * token, of one of the specified steps.
383      * <p>
384      * A <em>field</em> can be one of these forms:
385      * <ul>
386      * <li><em>stepalias</em><b>.</b><em>fieldname</em>
387      * <li>[<em>stepalias</em><b>.</b><em>fieldname</em>]
388      * <li><em>fieldname</em> (only when just one step is specified).
389      * <li>[<em>fieldname</em>] (only when just one step is specified).
390      * </ul>
391      *
392      * @param token The token.
393      * @param steps The steps.
394      * @return The field.
395      */

396
397     public static StepField getField(String JavaDoc token, List steps) {
398         return getField(token, steps, null);
399     }
400     /**
401      * Creates <code>StepField</code> corresponding to field indicated by
402      * token, of one of the specified steps.
403      * <p>
404      * A <em>field</em> can be one of these forms:
405      * <ul>
406      * <li><em>stepalias</em><b>.</b><em>fieldname</em>
407      * <li>[<em>stepalias</em><b>.</b><em>fieldname</em>]
408      * <li><em>fieldname</em> (only when just one step is specified, or query is an instance of NodeQuery).
409      * <li>[<em>fieldname</em>] (only when just one step is specified, or query is an instance of NodeQuery).
410      * </ul>
411      *
412      * @param token The token.
413      * @param steps The steps.
414      * @param query The used query
415      * @return The field.
416      * @since MMBase-1.7.1
417      */

418
419     static StepField getField(String JavaDoc token, List steps, SearchQuery query) {
420         BasicStep step = null;
421         int bracketOffset = (token.startsWith("[") && token.endsWith("]")) ? 1 : 0;
422         int idx = token.indexOf('.');
423         if (idx == -1) {
424             if (steps.size() > 1) {
425                 if (query != null && query instanceof NodeQuery) {
426                     step = (BasicStep) ((NodeQuery) query).getNodeStep();
427                     if (step == null) {
428                         throw new IllegalArgumentException JavaDoc( "NodeQuery has no step; Fieldname not prefixed with table alias: \"" + token + "\"");
429                     }
430                 } else {
431                     throw new IllegalArgumentException JavaDoc( "Fieldname not prefixed with table alias: \"" + token + "\"");
432                 }
433             } else {
434                 step = (BasicStep) steps.get(0);
435             }
436         } else {
437             step = getStep(token.substring(bracketOffset, idx), steps);
438         }
439         MMObjectBuilder builder = step.getBuilder();
440         String JavaDoc fieldName;
441         if (idx == -1) {
442             fieldName = token.substring(bracketOffset, token.length() - bracketOffset);
443         } else {
444             fieldName = token.substring(idx + 1, token.length() - bracketOffset);
445         }
446
447         CoreField coreField = builder.getField(fieldName);
448         // maybe the field was already escaped with getAllowedField
449
// otherwise it will definitely fail!
450
if (coreField == null) {
451             String JavaDoc escapedFieldName = MMBase.getMMBase().getStorageManagerFactory().getStorageIdentifier(fieldName).toString();
452             if (! escapedFieldName.equals(fieldName)) {
453                 coreField = builder.getField(fieldName);
454                 if (coreField == null) {
455                     throw new IllegalArgumentException JavaDoc("Unknown field (of builder " + builder.getTableName() + "): \"" + escapedFieldName + "\"");
456                 }
457             }
458         }
459         if (coreField == null) {
460             throw new IllegalArgumentException JavaDoc("Unknown field (of builder " + builder.getTableName() + "): \"" + fieldName + "\"");
461         }
462         BasicStepField field = new BasicStepField(step, coreField);
463         return field;
464     }
465
466     /**
467      * Finds step by alias.
468      *
469      * @param alias The alias.
470      * @param steps The steps
471      * @return The step.
472      */

473     private static BasicStep getStep(String JavaDoc alias, List steps) {
474         Iterator iSteps = steps.iterator();
475         while (iSteps.hasNext()) {
476             BasicStep step = (BasicStep) iSteps.next();
477             String JavaDoc alias2 = step.getAlias();
478             if (alias2 == null) {
479                 alias2 = step.getTableName();
480             }
481             if (alias2.equals(alias)) {
482                 return step;
483             }
484         }
485
486         // Not found.
487
throw new IllegalArgumentException JavaDoc("Unknown table alias: \"" + alias + "\"");
488     }
489
490     /** Creates a new instance of ConstraintParser
491      * @param query
492      */

493     public ConstraintParser(SearchQuery query) {
494         this.query = query;
495         this.steps = query.getSteps();
496     }
497
498     /**
499      * Parses <em>SQL-search-condition</em>, and produces a corresponding
500      * {@link org.mmbase.storage.search.Constraint Constraint} object.
501      * <p>
502      * See {@link ConstraintParser above} for the format of a
503      * <em>SQL-search-condition</em>.
504      *
505      * @param sqlConstraint The non-null SQL constraint string.
506      * @return The constraint.
507      */

508     public Constraint toConstraint(String JavaDoc sqlConstraint) {
509         Constraint result = null;
510         try {
511             ListIterator iTokens = tokenize(sqlConstraint).listIterator();
512             result = parseCondition(iTokens);
513
514         // If this doesn't work, fall back to legacy code.
515
} catch (Exception JavaDoc e) {
516             // Log to fallback logger.
517
if (fallbackLog.isServiceEnabled()) {
518                 fallbackLog.service(
519                     "Failed to parse Constraint from search condition string: "
520                     + "\n sqlConstraint = " + sqlConstraint
521                     + "\n exception: " + e.getMessage()
522                     + "\nFalling back to BasicLegacyConstraint...", e);
523             }
524             String JavaDoc escapedSqlConstraint = convertClauseToDBS(sqlConstraint);
525             if (! validConstraints(escapedSqlConstraint)) {
526                 throw new IllegalArgumentException JavaDoc("Invalid constraints: " + sqlConstraint);
527             }
528             result = new BasicLegacyConstraint(escapedSqlConstraint);
529         }
530
531         if (log.isDebugEnabled()) {
532             log.debug("Parsed constraint \"" + sqlConstraint + "\" to :\n" + result);
533         }
534         return result;
535     }
536
537
538     /**
539      * Parses a <em>field</em> string, and produces a corresponding
540      * <code>StepField</code> object.
541      * <p>
542      * See {@link ConstraintParser above} for the format of a
543      * <em>field</em>
544      *
545      * @param token The token.
546      * @return The field.
547      */

548     // package visibility!
549
StepField getField(String JavaDoc token) {
550         return getField(token, steps, query);
551     }
552
553     /**
554      * Parses <em>SQL-search-condition</em> string from list of tokens, and
555      * produces a corresponding <code>BasicConstraint</code> object.
556      * <p>
557      * See {@link ConstraintParser above} for the format of a
558      * <em>SQL-search-condition</em>
559      *
560      * @param iTokens Tokens iterator, must be positioned before the (first)
561      * token representing the condition.
562      * @return The constraint.
563      */

564     // package visibility!
565
BasicConstraint parseCondition(ListIterator iTokens) {
566         BasicCompositeConstraint composite = null;
567         BasicConstraint constraint= null;
568         while (iTokens.hasNext()) {
569             boolean inverse = false;
570             String JavaDoc token = (String JavaDoc) iTokens.next();
571             if (token.equalsIgnoreCase("NOT")) {
572                 // NOT.
573
inverse = true;
574                 token = (String JavaDoc) iTokens.next();
575             }
576
577             if (token.equals("(")) {
578                 // Start of (simple or composite) constraint
579
// between parenthesis.
580
constraint = parseCondition(iTokens);
581             } else {
582                 // Simple condition.
583
iTokens.previous();
584                 constraint = parseSimpleCondition(iTokens);
585             }
586             if (inverse) {
587                 constraint.setInverse(!constraint.isInverse());
588             }
589             if (composite != null) {
590                 composite.addChild(constraint);
591             }
592
593             if (iTokens.hasNext()) {
594                 token = (String JavaDoc) iTokens.next();
595                 if (token.equals(")")) {
596                     // Start of (simple or composite) constraint
597
// between parenthesis.
598
break;
599                 }
600                 int logicalOperator = 0;
601                 if (token.equalsIgnoreCase("OR")) {
602                     logicalOperator = CompositeConstraint.LOGICAL_OR;
603                 } else if (token.equalsIgnoreCase("AND")) {
604                     logicalOperator = CompositeConstraint.LOGICAL_AND;
605                 } else {
606                     throw new IllegalArgumentException JavaDoc(
607                     "Unexpected token (expected \"AND\" or \"OR\"): \""
608                     + token + "\"");
609                 }
610                 if (composite == null) {
611                     composite = new BasicCompositeConstraint(logicalOperator).
612                     addChild(constraint);
613                 }
614
615                 if (composite.getLogicalOperator() != logicalOperator) {
616                     composite = new BasicCompositeConstraint(logicalOperator).
617                     addChild(composite);
618                 }
619
620                 if (!iTokens.hasNext()) {
621                     throw new IllegalArgumentException JavaDoc("Unexpected end of tokens after \"" + token + "\"");
622                 }
623             }
624         }
625         if (composite != null) {
626             return composite;
627         } else {
628             return constraint;
629         }
630     }
631
632     /**
633      * Parses a <em>simple-SQL-search-condition</em> string from list of tokens,
634      * and produces a corresponding <code>BasicConstraint</code> object.
635      * <p>
636      * See {@link ConstraintParser above} for the format of a
637      * <em>simple-SQL-search-condition</em>
638      *
639      * @param iTokens Tokens iterator, must be positioned before the (first)
640      * token representing the condition.
641      * @return The constraint.
642      */

643     // package visibility!
644
BasicConstraint parseSimpleCondition(ListIterator iTokens) {
645         BasicConstraint result = null;
646
647         String JavaDoc token = (String JavaDoc) iTokens.next();
648         if (token.equalsIgnoreCase("StringSearch")) {
649             // StringSearch constraint.
650
return parseStringSearchCondition(iTokens);
651         }
652
653         String JavaDoc function = token.toUpperCase();
654         if (function.equals("LOWER") || function.equals("UPPER")) {
655             if (iTokens.next().equals("(")) {
656                 // Function.
657
token = (String JavaDoc) iTokens.next();
658             } else {
659                 // Not a function.
660
iTokens.previous();
661                 function = null;
662             }
663         } else {
664             function = null;
665         }
666
667         StepField field = getField(token);
668
669         token = (String JavaDoc) iTokens.next();
670         if (function != null) {
671             if (!token.equals(")")) {
672                 throw new IllegalArgumentException JavaDoc(
673                     "Unexpected token (expected \")\"): \""
674                     + token + "\"");
675             }
676             token = (String JavaDoc) iTokens.next();
677         }
678
679         boolean inverse = false;
680         if (token.equalsIgnoreCase("NOT")) {
681             // NOT LIKE/NOT IN/NOT BETWEEN
682
inverse = true;
683             token = (String JavaDoc) iTokens.next();
684             if (!token.equalsIgnoreCase("LIKE")
685                 && !token.equalsIgnoreCase("IN")
686                 && !token.equalsIgnoreCase("BETWEEN")) {
687                     throw new IllegalArgumentException JavaDoc(
688                         "Unexpected token (expected "
689                         + "\"LIKE\" OR \"IN\" OR \"BETWEEN\"): \""
690                         + token + "\"");
691             }
692         }
693
694         if (token.equalsIgnoreCase("LIKE")) {
695             // LIKE 'value'
696
String JavaDoc value = (String JavaDoc) parseValue(iTokens, field);
697             boolean caseSensitive = true;
698             if (function != null) {
699                 if ((function.equals("LOWER")
700                     && value.equals(value.toLowerCase()))
701                 || (function.equals("UPPER")
702                     && value.equals(value.toUpperCase()))) {
703                         caseSensitive = false;
704                 }
705             }
706             result = new BasicFieldValueConstraint(field, value)
707                 .setOperator(FieldCompareConstraint.LIKE)
708                 .setCaseSensitive(caseSensitive);
709
710         } else if (token.equalsIgnoreCase("IS")) {
711             // IS [NOT] NULL
712
token = (String JavaDoc) iTokens.next();
713             if (token.equalsIgnoreCase("NOT")) {
714                 inverse = !inverse;
715                 token = (String JavaDoc) iTokens.next();
716             }
717             if (token.equalsIgnoreCase("NULL")) {
718                 result = new BasicFieldNullConstraint(field);
719             } else {
720                 throw new IllegalArgumentException JavaDoc(
721                     "Unexpected token (expected \"NULL\"): \""
722                     + token + "\"");
723             }
724         } else if (token.equalsIgnoreCase("IN")) {
725             // IN (value1, value2, ...)
726
String JavaDoc separator = (String JavaDoc) iTokens.next();
727             if (!separator.equals("(")) {
728                 throw new IllegalArgumentException JavaDoc(
729                     "Unexpected token (expected \"(\"): \""
730                     + separator + "\"");
731             }
732             BasicFieldValueInConstraint fieldValueInConstraint
733                 = new BasicFieldValueInConstraint(field);
734             if (!iTokens.next().equals(")")) {
735
736                 iTokens.previous();
737                 do {
738                     Object JavaDoc value = parseValue(iTokens, field);
739                     separator = (String JavaDoc) iTokens.next();
740                     if (separator.equals(",") || separator.equals(")")) {
741                         fieldValueInConstraint.addValue(value);
742                     } else {
743                         throw new IllegalArgumentException JavaDoc(
744                             "Unexpected token (expected \",\" or \")\"): \""
745                             + separator + "\"");
746                     }
747                 } while (separator.equals(","));
748             }
749             result = fieldValueInConstraint;
750
751         } else if (token.equalsIgnoreCase("BETWEEN")) {
752             // BETWEEN value1 AND value2
753
Object JavaDoc value1 = parseValue(iTokens, field);
754             String JavaDoc separator = (String JavaDoc) iTokens.next();
755             if (!separator.equals("AND")) {
756                 throw new IllegalArgumentException JavaDoc(
757                     "Unexpected token (expected \"AND\"): \""
758                     + separator + "\"");
759             }
760             Object JavaDoc value2 = parseValue(iTokens, field);
761             boolean caseSensitive = true;
762             if (function != null
763                     && value1 instanceof String JavaDoc && value2 instanceof String JavaDoc) {
764                 String JavaDoc strValue1 = (String JavaDoc) value1;
765                 String JavaDoc strValue2 = (String JavaDoc) value2;
766                 if ((function.equals("LOWER")
767                     && strValue1.equals(strValue1.toLowerCase())
768                     && strValue2.equals(strValue2.toLowerCase()))
769                 || (function.equals("UPPER")
770                     && strValue1.equals(strValue1.toUpperCase())
771                     && strValue2.equals(strValue2.toUpperCase()))) {
772                         caseSensitive = false;
773                 }
774             }
775
776             BasicFieldValueBetweenConstraint fieldValueBetweenConstraint
777                 = (BasicFieldValueBetweenConstraint)
778                     new BasicFieldValueBetweenConstraint(field, value1, value2)
779                         .setCaseSensitive(caseSensitive);
780             result = fieldValueBetweenConstraint;
781
782         } else if (token.equals("=")) {
783             token = (String JavaDoc) iTokens.next();
784             if (token.equals("=")) {
785                 try {
786                     // == value
787
Object JavaDoc value = parseValue(iTokens, field);
788                     result = new BasicFieldValueConstraint(field, value)
789                         .setOperator(FieldCompareConstraint.EQUAL);
790                 } catch (NumberFormatException JavaDoc e) {
791                     // == field2
792
iTokens.previous();
793                     token = (String JavaDoc) iTokens.next();
794                     StepField field2 = getField(token);
795                     result = new BasicCompareFieldsConstraint(field, field2)
796                         .setOperator(FieldCompareConstraint.EQUAL);
797                 }
798             } else {
799                 iTokens.previous();
800                 try {
801                     // = value
802
Object JavaDoc value = parseValue(iTokens, field);
803                     boolean caseSensitive = true;
804                     if (function != null && value instanceof String JavaDoc) {
805                         String JavaDoc strValue = (String JavaDoc) value;
806                         if ((function.equals("LOWER")
807                             && strValue.equals(strValue.toLowerCase()))
808                         || (function.equals("UPPER")
809                             && strValue.equals(strValue.toUpperCase()))) {
810                                 caseSensitive = false;
811                         }
812                     }
813                     result = new BasicFieldValueConstraint(field, value)
814                         .setOperator(FieldCompareConstraint.EQUAL)
815                         .setCaseSensitive(caseSensitive);
816                 } catch (NumberFormatException JavaDoc e) {
817                     // = field2
818
iTokens.previous();
819                     token = (String JavaDoc) iTokens.next();
820                     StepField field2 = getField(token);
821                     result = new BasicCompareFieldsConstraint(field, field2)
822                         .setOperator(FieldCompareConstraint.EQUAL);
823                 }
824             }
825         } else if (token.equals("<")) {
826             token = (String JavaDoc) iTokens.next();
827             if (token.equals("=")) {
828                 try {
829                     // <= value
830
Object JavaDoc value = parseValue(iTokens, field);
831                     result = new BasicFieldValueConstraint(field, value)
832                         .setOperator(FieldCompareConstraint.LESS_EQUAL);
833                 } catch (NumberFormatException JavaDoc e) {
834                     // <= field2
835
iTokens.previous();
836                     token = (String JavaDoc) iTokens.next();
837                     StepField field2 = getField(token);
838                     result = new BasicCompareFieldsConstraint(field, field2)
839                         .setOperator(FieldCompareConstraint.LESS_EQUAL);
840                 }
841             } else if (token.equals(">")) {
842                 try {
843                     // <> value
844
Object JavaDoc value = parseValue(iTokens, field);
845                     result = new BasicFieldValueConstraint(field, value)
846                         .setOperator(FieldCompareConstraint.NOT_EQUAL);
847                 } catch (NumberFormatException JavaDoc e) {
848                     // <> field2
849
iTokens.previous();
850                     token = (String JavaDoc) iTokens.next();
851                     StepField field2 = getField(token);
852                     result = new BasicCompareFieldsConstraint(field, field2)
853                         .setOperator(FieldCompareConstraint.NOT_EQUAL);
854                 }
855             } else {
856                 try {
857                     // < value
858
iTokens.previous();
859                     Object JavaDoc value = parseValue(iTokens, field);
860                     result = new BasicFieldValueConstraint(field, value)
861                         .setOperator(FieldCompareConstraint.LESS);
862                 } catch (NumberFormatException JavaDoc e) {
863                     // < field2
864
iTokens.previous();
865                     token = (String JavaDoc) iTokens.next();
866                     StepField field2 = getField(token);
867                     result = new BasicCompareFieldsConstraint(field, field2)
868                         .setOperator(FieldCompareConstraint.LESS);
869                 }
870             }
871         } else if (token.equals(">")) {
872             token = (String JavaDoc) iTokens.next();
873             if (token.equals("=")) {
874                 try {
875                     // >= value
876
Object JavaDoc value = parseValue(iTokens, field);
877                     result = new BasicFieldValueConstraint(field, value)
878                         .setOperator(FieldCompareConstraint.GREATER_EQUAL);
879                 } catch (NumberFormatException JavaDoc e) {
880                     // >= field2
881
iTokens.previous();
882                     token = (String JavaDoc) iTokens.next();
883                     StepField field2 = getField(token);
884                     result = new BasicCompareFieldsConstraint(field, field2)
885                         .setOperator(FieldCompareConstraint.GREATER_EQUAL);
886                 }
887             } else {
888                 try {
889                     // > value
890
iTokens.previous();
891                     Object JavaDoc value = parseValue(iTokens, field);
892                     result = new BasicFieldValueConstraint(field, value)
893                         .setOperator(FieldCompareConstraint.GREATER);
894                 } catch (NumberFormatException JavaDoc e) {
895                     // > field2
896
iTokens.previous();
897                     token = (String JavaDoc) iTokens.next();
898                     StepField field2 = getField(token);
899                     result = new BasicCompareFieldsConstraint(field, field2)
900                         .setOperator(FieldCompareConstraint.GREATER);
901                 }
902             }
903         } else if (token.equals("!")) {
904             token = (String JavaDoc) iTokens.next();
905             if (token.equals("=")) {
906                 try {
907                     // != value
908
Object JavaDoc value = parseValue(iTokens, field);
909                     result = new BasicFieldValueConstraint(field, value)
910                         .setOperator(FieldCompareConstraint.NOT_EQUAL);
911                 } catch (NumberFormatException JavaDoc e) {
912                     // != field2
913
iTokens.previous();
914                     token = (String JavaDoc) iTokens.next();
915                     StepField field2 = getField(token);
916                     result = new BasicCompareFieldsConstraint(field, field2)
917                         .setOperator(FieldCompareConstraint.NOT_EQUAL);
918                 }
919             } else {
920                 throw new IllegalArgumentException JavaDoc("Unexpected token (expected \"=\"): \"" + token + "\"");
921             }
922         } else {
923             throw new IllegalArgumentException JavaDoc("Unexpected token: \"" + token + "\"");
924         }
925
926         if (inverse) {
927             result.setInverse(!result.isInverse());
928         }
929
930         return result;
931     }
932
933     /**
934      * Parses a <em>stringsearch-condition</em> string from list of tokens,
935      * and produces a corresponding <code>BasicStringSearchConstraint</code> object.
936      * <p>
937      * See {@link ConstraintParser above} for the format of a
938      * <em>stringsearch-condition</em>
939      *
940      * @param iTokens Tokens iterator, must be positioned after the (first)
941      * token representing the condition (e.g. after "StringSearch").
942      * @return The constraint.
943      */

944     private BasicStringSearchConstraint parseStringSearchCondition(
945             ListIterator iTokens) {
946
947         String JavaDoc token = (String JavaDoc) iTokens.next();
948         if (!token.equals("(")) {
949             throw new IllegalArgumentException JavaDoc("Unexpected token (expected \"(\"): \"" + token + "\"");
950         }
951
952         // Field
953
token = (String JavaDoc) iTokens.next();
954         StepField field = getField(token);
955
956         token = (String JavaDoc) iTokens.next();
957         if (!token.equals(",")) {
958             throw new IllegalArgumentException JavaDoc("Unexpected token (expected \",\"): \"" + token + "\"");
959         }
960
961         // Searchtype
962
int searchType;
963         token = (String JavaDoc) iTokens.next();
964         if (token.equalsIgnoreCase("PHRASE")) {
965             searchType = StringSearchConstraint.SEARCH_TYPE_PHRASE_ORIENTED;
966         } else if (token.equalsIgnoreCase("PROXIMITY")) {
967             searchType = StringSearchConstraint.SEARCH_TYPE_PROXIMITY_ORIENTED;
968         } else if (token.equalsIgnoreCase("WORD")) {
969             searchType = StringSearchConstraint.SEARCH_TYPE_WORD_ORIENTED;
970         } else {
971             throw new IllegalArgumentException JavaDoc("Invalid searchtype (expected \"PHRASE\", \"PROXIMITY\" or \"WORD\": \"" + token + "\"");
972         }
973
974         token = (String JavaDoc) iTokens.next();
975         if (!token.equals(",")) {
976             throw new IllegalArgumentException JavaDoc(
977                 "Unexpected token (expected \",\"): \""
978                 + token + "\"");
979         }
980
981         // Matchtype
982
int matchType;
983         token = (String JavaDoc) iTokens.next();
984         if (token.equalsIgnoreCase("FUZZY")) {
985             matchType = StringSearchConstraint.MATCH_TYPE_FUZZY;
986         } else if (token.equalsIgnoreCase("LITERAL")) {
987             matchType = StringSearchConstraint.MATCH_TYPE_LITERAL;
988         } else if (token.equalsIgnoreCase("SYNONYM")) {
989             matchType = StringSearchConstraint.MATCH_TYPE_SYNONYM;
990         } else {
991             throw new IllegalArgumentException JavaDoc("Invalid matchtype (expected \"FUZZY\", \"LITERAL\" or \"SYNONYM\": \"" + token + "\"");
992         }
993
994         token = (String JavaDoc) iTokens.next();
995         if (!token.equals(",")) {
996             throw new IllegalArgumentException JavaDoc("Unexpected token (expected \",\"): \"" + token + "\"");
997         }
998
999         // SearchTerms
1000
String JavaDoc searchTerms;
1001        token = (String JavaDoc) iTokens.next();
1002        if (!token.equals("'")) {
1003            throw new IllegalArgumentException JavaDoc("Unexpected token (expected \"'\" or \"\"\"): \"" + token + "\"");
1004        }
1005        searchTerms = (String JavaDoc) iTokens.next();
1006        token = (String JavaDoc) iTokens.next();
1007        if (!token.equals("'")) {
1008            throw new IllegalArgumentException JavaDoc("Unexpected token (expected \"'\" or \"\"\"): \"" + token + "\"");
1009        }
1010
1011        token = (String JavaDoc) iTokens.next();
1012        if (!token.equals(",")) {
1013            throw new IllegalArgumentException JavaDoc("Unexpected token (expected \",\"): \"" + token + "\"");
1014        }
1015
1016        // CaseSensitive property
1017
boolean caseSensitive;
1018        token = (String JavaDoc) iTokens.next();
1019        if (token.equalsIgnoreCase("true")) {
1020            caseSensitive = true;
1021        } else if (token.equalsIgnoreCase("false")) {
1022            caseSensitive = false;
1023        } else {
1024            throw new IllegalArgumentException JavaDoc("Invalid caseSensitive value (expected \"true\" " + "or \"false\": \"" + token + "\"");
1025        }
1026
1027        token = (String JavaDoc) iTokens.next();
1028        if (!token.equals(")")) {
1029            throw new IllegalArgumentException JavaDoc("Unexpected token (expected \")\"): \"" + token + "\"");
1030        }
1031
1032        BasicStringSearchConstraint result = (BasicStringSearchConstraint)
1033            new BasicStringSearchConstraint(
1034                field, searchType, matchType, searchTerms)
1035                    .setCaseSensitive(caseSensitive);
1036
1037        // .set(parametername, value)
1038
while (iTokens.hasNext()) {
1039            token = (String JavaDoc) iTokens.next();
1040            if (!token.equalsIgnoreCase(".set")) {
1041                iTokens.previous();
1042                break;
1043            }
1044
1045            token = (String JavaDoc) iTokens.next();
1046            if (!token.equals("(")) {
1047                throw new IllegalArgumentException JavaDoc("Unexpected token (expected \"(\"): \"" + token + "\"");
1048            }
1049
1050            String JavaDoc parameterName = (String JavaDoc) iTokens.next();
1051
1052            token = (String JavaDoc) iTokens.next();
1053            if (!token.equals(",")) {
1054                throw new IllegalArgumentException JavaDoc("Unexpected token (expected \",\"): \"" + token + "\"");
1055            }
1056
1057            String JavaDoc parameterValue = (String JavaDoc) iTokens.next();
1058
1059            token = (String JavaDoc) iTokens.next();
1060            if (!token.equals(")")) {
1061                throw new IllegalArgumentException JavaDoc("Unexpected token (expected \")\"): \"" + token + "\"");
1062            }
1063
1064            if (parameterName.equalsIgnoreCase("FUZZINESS")) {
1065                result.setParameter(StringSearchConstraint.PARAM_FUZZINESS,
1066                    new Float JavaDoc(parameterValue));
1067            } else if (parameterName.equalsIgnoreCase("PROXIMITY_LIMIT")) {
1068                result.setParameter(
1069                    StringSearchConstraint.PARAM_PROXIMITY_LIMIT,
1070                        new Integer JavaDoc(parameterValue));
1071            } else {
1072                throw new IllegalArgumentException JavaDoc("Invalid parameter name (expected \"FUZZINESS\" or \"PROXIMITY\": \"" + parameterName + "\"");
1073            }
1074        }
1075
1076        return result;
1077    }
1078}
1079
Popular Tags