QueryExpStringTest.java revision 11:41d9c673dd9d
1/*
2 * Copyright 2003 Sun Microsystems, Inc.  All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
20 * CA 95054 USA or visit www.sun.com if you need additional information or
21 * have any questions.
22 */
23
24/*
25 * @test
26 * @bug 4886011
27 * @summary Test that QueryExp.toString() is reversible
28 * @author Eamonn McManus
29 * @run clean QueryExpStringTest
30 * @run build QueryExpStringTest
31 * @run main QueryExpStringTest
32 */
33
34// This test is mostly obsolete, since we now have Query.fromString.
35// The test includes its own parser, from which Query.fromString was derived.
36// The parsers are not identical and the one here is no longer maintained.
37
38import java.util.*;
39import javax.management.*;
40
41public class QueryExpStringTest {
42
43    private static final ValueExp
44        attr = Query.attr("attr"),
45        qattr = Query.attr("className", "attr"),
46        aa = Query.attr("A"),
47        bb = Query.attr("B"),
48        cc = Query.attr("C"),
49        dd = Query.attr("D"),
50        zero = Query.value(0),
51        classattr = Query.classattr(),
52        simpleString = Query.value("simpleString"),
53        complexString = Query.value("a'b\\'\""),
54        intValue = Query.value(12345678),
55        integerValue = Query.value(new Integer(12345678)),
56        longValue = Query.value(12345678L),
57        floatValue = Query.value(2.5f),
58        doubleValue = Query.value(2.5d),
59        booleanValue = Query.value(true),
60        plusValue = Query.plus(intValue, integerValue),
61        timesValue = Query.times(doubleValue, floatValue),
62        minusValue = Query.minus(floatValue, doubleValue),
63        divValue = Query.div(doubleValue, floatValue);
64
65    private static final QueryExp
66        gt = Query.gt(intValue, floatValue),
67        geq = Query.geq(intValue, floatValue),
68        leq = Query.leq(intValue, floatValue),
69        lt = Query.lt(intValue, floatValue),
70        eq = Query.eq(intValue, floatValue),
71        between = Query.between(intValue, floatValue, doubleValue),
72        match = Query.match((AttributeValueExp) attr,
73                            (StringValueExp) simpleString),
74        initial = Query.initialSubString((AttributeValueExp) attr,
75                                         (StringValueExp) simpleString),
76        initialStar = Query.initialSubString((AttributeValueExp) attr,
77                                             Query.value("*")),
78        initialPercent = Query.initialSubString((AttributeValueExp) attr,
79                                                Query.value("%")),
80        any = Query.anySubString((AttributeValueExp) attr,
81                                 (StringValueExp) simpleString),
82        anyStar = Query.anySubString((AttributeValueExp) attr,
83                                     Query.value("*")),
84        anyPercent = Query.anySubString((AttributeValueExp) attr,
85                                        Query.value("%")),
86        ffinal = Query.finalSubString((AttributeValueExp) attr,
87                                      (StringValueExp) simpleString),
88        finalMagic = Query.finalSubString((AttributeValueExp) attr,
89                                          Query.value("?*[\\")),
90        in = Query.in(intValue, new ValueExp[] {intValue, floatValue}),
91        and = Query.and(gt, lt),
92        or = Query.or(gt, lt),
93        not = Query.not(gt),
94        aPlusB_PlusC = Query.gt(Query.plus(Query.plus(aa, bb), cc), zero),
95        aPlus_BPlusC = Query.gt(Query.plus(aa, Query.plus(bb, cc)), zero);
96
97    // Commented-out tests below require change to implementation
98
99    private static final Object tests[] = {
100        attr, "attr",
101//      qattr, "className.attr",
102// Preceding form now appears as className#attr, an incompatible change
103// which we don't mind much because nobody uses the two-arg Query.attr.
104        classattr, "Class",
105        simpleString, "'simpleString'",
106        complexString, "'a''b\\\''\"'",
107        intValue, "12345678",
108        integerValue, "12345678",
109        longValue, "12345678",
110        floatValue, "2.5",
111        doubleValue, "2.5",
112        booleanValue, "true",
113        plusValue, "12345678 + 12345678",
114        timesValue, "2.5 * 2.5",
115        minusValue, "2.5 - 2.5",
116        divValue, "2.5 / 2.5",
117        gt, "(12345678) > (2.5)",
118        geq, "(12345678) >= (2.5)",
119        leq, "(12345678) <= (2.5)",
120        lt, "(12345678) < (2.5)",
121        eq, "(12345678) = (2.5)",
122        between, "(12345678) between (2.5) and (2.5)",
123        match, "attr like 'simpleString'",
124        initial, "attr like 'simpleString%'",
125        initialStar, "attr like '\\*%'",
126        initialPercent, "attr like '\\%%'",
127        any, "attr like '%simpleString%'",
128        anyStar, "attr like '%\\*%'",
129        anyPercent, "attr like '%\\%%'",
130        ffinal, "attr like '%simpleString'",
131        finalMagic, "attr like '%\\?\\*\\[\\\\'",
132        in, "12345678 in (12345678, 2.5)",
133        and, "((12345678) > (2.5)) and ((12345678) < (2.5))",
134        or, "((12345678) > (2.5)) or ((12345678) < (2.5))",
135        not, "not ((12345678) > (2.5))",
136        aPlusB_PlusC, "(A + B + C) > (0)",
137//        aPlus_BPlusC, "(A + (B + C)) > (0)",
138    };
139
140    public static void main(String[] args) throws Exception {
141        System.out.println("Testing QueryExp.toString()");
142
143        boolean ok = true;
144
145        for (int i = 0; i < tests.length; i += 2) {
146            String testString = tests[i].toString();
147            String expected = (String) tests[i + 1];
148            if (expected.equals(testString))
149                System.out.println("OK: " + expected);
150            else {
151                System.err.println("Expected: {" + expected + "}; got: {" +
152                                   testString + "}");
153                ok = false;
154            }
155
156            try {
157                Object parsed;
158                String[] expectedref = new String[] {expected};
159                if (tests[i] instanceof ValueExp)
160                    parsed = parseExp(expectedref);
161                else
162                    parsed = parseQuery(expectedref);
163                if (expectedref[0].length() > 0)
164                    throw new Exception("Junk after parse: " + expectedref[0]);
165                String parsedString = parsed.toString();
166                if (parsedString.equals(expected))
167                    System.out.println("OK: parsed " + parsedString);
168                else {
169                    System.err.println("Parse differs: expected: {" +
170                                       expected + "}; got: {" +
171                                       parsedString + "}");
172                    ok = false;
173                }
174            } catch (Exception e) {
175                System.err.println("Parse got exception: {" + expected +
176                                   "}: " + e);
177                ok = false;
178            }
179        }
180
181        if (ok)
182            System.out.println("Test passed");
183        else {
184            System.out.println("TEST FAILED");
185            System.exit(1);
186        }
187    }
188
189    private static QueryExp parseQuery(String[] ss) throws Exception {
190        if (skip(ss, "("))
191            return parseQueryAfterParen(ss);
192
193        if (skip(ss, "not (")) {
194            QueryExp not = parseQuery(ss);
195            if (!skip(ss, ")"))
196                throw new Exception("Expected ) after not (...");
197            return Query.not(not);
198        }
199
200        ValueExp exp = parseExp(ss);
201
202        if (skip(ss, " like ")) {
203            ValueExp pat = parseExp(ss);
204            if (!(exp instanceof AttributeValueExp &&
205                  pat instanceof StringValueExp)) {
206                throw new Exception("Expected types `attr like string': " +
207                                    exp + " like " + pat);
208            }
209            StringValueExp spat = (StringValueExp) pat;
210            spat = Query.value(translateMatch(spat.getValue()));
211            return Query.match((AttributeValueExp) exp, spat);
212        }
213
214        if (skip(ss, " in (")) {
215            List values = new ArrayList();
216            if (!skip(ss, ")")) {
217                do {
218                    values.add(parseExp(ss));
219                } while (skip(ss, ", "));
220                if (!skip(ss, ")"))
221                    throw new Exception("Expected ) after in (...");
222            }
223            return Query.in(exp, (ValueExp[]) values.toArray(new ValueExp[0]));
224        }
225
226        throw new Exception("Expected in or like after expression");
227    }
228
229    private static String translateMatch(String s) {
230        StringBuilder sb = new StringBuilder();
231        for (int i = 0; i < s.length(); i++) {  // logic not correct for wide chars
232            char c = s.charAt(i);
233            switch (c) {
234                case '\\':
235                    sb.append(c).append(s.charAt(++i)); break;
236                case '%':
237                    sb.append('*'); break;
238                case '_':
239                    sb.append('?'); break;
240                case '*':
241                    sb.append("\\*"); break;
242                case '?':
243                    sb.append("\\?"); break;
244                default:
245                    sb.append(c); break;
246            }
247        }
248        return sb.toString();
249    }
250
251    private static QueryExp parseQueryAfterParen(String[] ss)
252            throws Exception {
253        /* This is very ugly.  We might have "(q1) and (q2)" here, or
254           we might have "(e1) < (e2)".  Since the syntax for a query
255           (q1) is not the same as for an expression (e1), but can
256           begin with one, we try to parse the query, and if we get an
257           exception we then try to parse an expression.  It's a hacky
258           kind of look-ahead.  */
259        String start = ss[0];
260        try {
261            QueryExp lhs = parseQuery(ss);
262            QueryExp result;
263
264            if (skip(ss, ") and ("))
265                result = Query.and(lhs, parseQuery(ss));
266            else if (skip(ss, ") or ("))
267                result = Query.or(lhs, parseQuery(ss));
268            else
269                throw new Exception("Expected `) and/or ('");
270            if (!skip(ss, ")"))
271                throw new Exception("Expected `)' after subquery");
272            return result;
273        } catch (Exception e) {
274            ss[0] = start;
275            ValueExp lhs = parseExp(ss);
276            if (!skip(ss, ") "))
277                throw new Exception("Expected `) ' after subexpression: " + ss[0]);
278            String op = scanWord(ss);
279            if (!skip(ss, " ("))
280                throw new Exception("Expected ` (' after `" + op + "'");
281            ValueExp rhs = parseExp(ss);
282            if (!skip(ss, ")"))
283                throw new Exception("Expected `)' after subexpression");
284            if (op.equals("="))
285                return Query.eq(lhs, rhs);
286            if (op.equals("<"))
287                return Query.lt(lhs, rhs);
288            if (op.equals(">"))
289                return Query.gt(lhs, rhs);
290            if (op.equals("<="))
291                return Query.leq(lhs, rhs);
292            if (op.equals(">="))
293                return Query.geq(lhs, rhs);
294            if (!op.equals("between"))
295                throw new Exception("Unknown operator `" + op + "'");
296            if (!skip(ss, " and ("))
297                throw new Exception("Expected ` and (' after between");
298            ValueExp high = parseExp(ss);
299            if (!skip(ss, ")"))
300                throw new Exception("Expected `)' after subexpression");
301            return Query.between(lhs, rhs, high);
302        }
303    }
304
305    private static ValueExp parseExp(String[] ss) throws Exception {
306        ValueExp lhs = parsePrimary(ss);
307
308        while (true) {
309        /* Look ahead to see if we have an arithmetic operator. */
310        String back = ss[0];
311        if (!skip(ss, " "))
312                return lhs;
313        if (ss[0].equals("") || "+-*/".indexOf(ss[0].charAt(0)) < 0) {
314            ss[0] = back;
315                return lhs;
316        }
317
318        final String op = scanWord(ss);
319        if (op.length() != 1)
320            throw new Exception("Expected arithmetic operator after space");
321        if ("+-*/".indexOf(op) < 0)
322            throw new Exception("Unknown arithmetic operator: " + op);
323        if (!skip(ss, " "))
324            throw new Exception("Expected space after arithmetic operator");
325            ValueExp rhs = parsePrimary(ss);
326        switch (op.charAt(0)) {
327            case '+': lhs = Query.plus(lhs, rhs); break;
328            case '-': lhs = Query.minus(lhs, rhs); break;
329            case '*': lhs = Query.times(lhs, rhs); break;
330            case '/': lhs = Query.div(lhs, rhs); break;
331        default: throw new Exception("Can't happen: " + op.charAt(0));
332        }
333    }
334    }
335
336    private static ValueExp parsePrimary(String[] ss) throws Exception {
337        String s = ss[0];
338
339        if (s.length() == 0)
340            throw new Exception("Empty string found, expression expected");
341
342        char first = s.charAt(0);
343
344        if (first == ' ')
345            throw new Exception("Space found, expression expected");
346
347        if (first == '-' || Character.isDigit(first))
348            return parseNumberExp(ss);
349
350        if (first == '\'')
351            return parseString(ss);
352
353        if (matchWord(ss, "true"))
354            return Query.value(true);
355
356        if (matchWord(ss, "false"))
357            return Query.value(false);
358
359        if (matchWord(ss, "Class"))
360            return Query.classattr();
361
362        String word = scanWord(ss);
363        int lastDot = word.lastIndexOf('.');
364        if (lastDot < 0)
365            return Query.attr(word);
366        else
367            return Query.attr(word.substring(0, lastDot),
368                              word.substring(lastDot + 1));
369    }
370
371    private static String scanWord(String[] ss) throws Exception {
372        String s = ss[0];
373        int space = s.indexOf(' ');
374        int rpar = s.indexOf(')');
375        if (space < 0 && rpar < 0) {
376            ss[0] = "";
377            return s;
378        }
379        int stop;
380        if (space >= 0 && rpar >= 0)  // string has both space and ), stop at first
381            stop = Math.min(space, rpar);
382        else                          // string has only one, stop at it
383            stop = Math.max(space, rpar);
384        String word = s.substring(0, stop);
385        ss[0] = s.substring(stop);
386        return word;
387    }
388
389    private static boolean matchWord(String[] ss, String word)
390            throws Exception {
391        String s = ss[0];
392        if (s.startsWith(word)) {
393            int len = word.length();
394            if (s.length() == len || s.charAt(len) == ' '
395                || s.charAt(len) == ')') {
396                ss[0] = s.substring(len);
397                return true;
398            }
399        }
400        return false;
401    }
402
403    private static ValueExp parseNumberExp(String[] ss) throws Exception {
404        String s = ss[0];
405        int len = s.length();
406        boolean isFloat = false;
407        int i;
408        for (i = 0; i < len; i++) {
409            char c = s.charAt(i);
410            if (Character.isDigit(c) || c == '-' || c == '+')
411                continue;
412            if (c == '.' || c == 'e' || c == 'E') {
413                isFloat = true;
414                continue;
415            }
416            break;
417        }
418        ss[0] = s.substring(i);
419        s = s.substring(0, i);
420        if (isFloat)
421            return Query.value(Double.parseDouble(s));
422        else
423            return Query.value(Long.parseLong(s));
424    }
425
426    private static ValueExp parseString(String[] ss) throws Exception {
427        if (!skip(ss, "'"))
428            throw new Exception("Expected ' at start of string");
429        String s = ss[0];
430        int len = s.length();
431        StringBuffer buf = new StringBuffer();
432        int i;
433        for (i = 0; i < len; i++) {
434            char c = s.charAt(i);
435            if (c == '\'') {
436                ++i;
437                if (i >= len || s.charAt(i) != '\'') {
438                    ss[0] = s.substring(i);
439                return Query.value(buf.toString());
440            }
441            }
442            buf.append(c);
443        }
444        throw new Exception("No closing ' at end of string");
445    }
446
447    private static boolean skip(String[] ss, String skip) {
448        if (ss[0].startsWith(skip)) {
449            ss[0] = ss[0].substring(skip.length());
450            return true;
451        } else
452            return false;
453    }
454}
455