1 /*
   2  * Copyright (c) 2003, 2008, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * 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 import java.util.*;
  35 import javax.management.*;
  36 
  37 public class QueryExpStringTest {
  38 
  39     private static final ValueExp
  40         attr = Query.attr("attr"),
  41         qattr = Query.attr("className", "attr"),
  42         aa = Query.attr("A"),
  43         bb = Query.attr("B"),
  44         cc = Query.attr("C"),
  45         dd = Query.attr("D"),
  46         zero = Query.value(0),
  47         classattr = Query.classattr(),
  48         simpleString = Query.value("simpleString"),
  49         complexString = Query.value("a'b\\'\""),
  50         intValue = Query.value(12345678),
  51         integerValue = Query.value(new Integer(12345678)),
  52         longValue = Query.value(12345678L),
  53         floatValue = Query.value(2.5f),
  54         doubleValue = Query.value(2.5d),
  55         booleanValue = Query.value(true),
  56         plusValue = Query.plus(intValue, integerValue),
  57         timesValue = Query.times(doubleValue, floatValue),
  58         minusValue = Query.minus(floatValue, doubleValue),
  59         divValue = Query.div(doubleValue, floatValue);
  60 
  61     private static final QueryExp
  62         gt = Query.gt(intValue, floatValue),
  63         geq = Query.geq(intValue, floatValue),
  64         leq = Query.leq(intValue, floatValue),
  65         lt = Query.lt(intValue, floatValue),
  66         eq = Query.eq(intValue, floatValue),
  67         between = Query.between(intValue, floatValue, doubleValue),
  68         match = Query.match((AttributeValueExp) attr,
  69                             (StringValueExp) simpleString),
  70         initial = Query.initialSubString((AttributeValueExp) attr,
  71                                          (StringValueExp) simpleString),
  72         initialStar = Query.initialSubString((AttributeValueExp) attr,
  73                                              Query.value("*")),
  74         initialPercent = Query.initialSubString((AttributeValueExp) attr,
  75                                                 Query.value("%")),
  76         any = Query.anySubString((AttributeValueExp) attr,
  77                                  (StringValueExp) simpleString),
  78         anyStar = Query.anySubString((AttributeValueExp) attr,
  79                                      Query.value("*")),
  80         anyPercent = Query.anySubString((AttributeValueExp) attr,
  81                                         Query.value("%")),
  82         ffinal = Query.finalSubString((AttributeValueExp) attr,
  83                                       (StringValueExp) simpleString),
  84         finalMagic = Query.finalSubString((AttributeValueExp) attr,
  85                                           Query.value("?*[\\")),
  86         in = Query.in(intValue, new ValueExp[] {intValue, floatValue}),
  87         and = Query.and(gt, lt),
  88         or = Query.or(gt, lt),
  89         not = Query.not(gt),
  90         aPlusB_PlusC = Query.gt(Query.plus(Query.plus(aa, bb), cc), zero),
  91         aPlus_BPlusC = Query.gt(Query.plus(aa, Query.plus(bb, cc)), zero);
  92 
  93     // Commented-out tests below require change to implementation
  94 
  95     private static final Object tests[] = {
  96         attr, "attr",
  97 //      qattr, "className.attr",
  98 // Preceding form now appears as className#attr, an incompatible change
  99 // which we don't mind much because nobody uses the two-arg Query.attr.
 100         classattr, "Class",
 101         simpleString, "'simpleString'",
 102         complexString, "'a''b\\\''\"'",
 103         intValue, "12345678",
 104         integerValue, "12345678",
 105         longValue, "12345678",
 106         floatValue, "2.5",
 107         doubleValue, "2.5",
 108         booleanValue, "true",
 109         plusValue, "12345678 + 12345678",
 110         timesValue, "2.5 * 2.5",
 111         minusValue, "2.5 - 2.5",
 112         divValue, "2.5 / 2.5",
 113         gt, "(12345678) > (2.5)",
 114         geq, "(12345678) >= (2.5)",
 115         leq, "(12345678) <= (2.5)",
 116         lt, "(12345678) < (2.5)",
 117         eq, "(12345678) = (2.5)",
 118         between, "(12345678) between (2.5) and (2.5)",
 119         match, "attr like 'simpleString'",
 120         initial, "attr like 'simpleString*'",
 121         initialStar, "attr like '\\**'",
 122         initialPercent, "attr like '%*'",
 123         any, "attr like '*simpleString*'",
 124         anyStar, "attr like '*\\**'",
 125         anyPercent, "attr like '*%*'",
 126         ffinal, "attr like '*simpleString'",
 127         finalMagic, "attr like '*\\?\\*\\[\\\\'",
 128         in, "12345678 in (12345678, 2.5)",
 129         and, "((12345678) > (2.5)) and ((12345678) < (2.5))",
 130         or, "((12345678) > (2.5)) or ((12345678) < (2.5))",
 131         not, "not ((12345678) > (2.5))",
 132         aPlusB_PlusC, "(A + B + C) > (0)",
 133 //        aPlus_BPlusC, "(A + (B + C)) > (0)",
 134     };
 135 
 136     public static void main(String[] args) throws Exception {
 137         System.out.println("Testing QueryExp.toString()");
 138 
 139         boolean ok = true;
 140 
 141         for (int i = 0; i < tests.length; i += 2) {
 142             String testString = tests[i].toString();
 143             String expected = (String) tests[i + 1];
 144             if (expected.equals(testString))
 145                 System.out.println("OK: " + expected);
 146             else {
 147                 System.err.println("Expected: {" + expected + "}; got: {" +
 148                                    testString + "}");
 149                 ok = false;
 150             }
 151 
 152             try {
 153                 Object parsed;
 154                 String[] expectedref = new String[] {expected};
 155                 if (tests[i] instanceof ValueExp)
 156                     parsed = parseExp(expectedref);
 157                 else
 158                     parsed = parseQuery(expectedref);
 159                 if (expectedref[0].length() > 0)
 160                     throw new Exception("Junk after parse: " + expectedref[0]);
 161                 String parsedString = parsed.toString();
 162                 if (parsedString.equals(expected))
 163                     System.out.println("OK: parsed " + parsedString);
 164                 else {
 165                     System.err.println("Parse differs: expected: {" +
 166                                        expected + "}; got: {" +
 167                                        parsedString + "}");
 168                     ok = false;
 169                 }
 170             } catch (Exception e) {
 171                 System.err.println("Parse got exception: {" + expected +
 172                                    "}: " + e);
 173                 ok = false;
 174             }
 175         }
 176 
 177         if (ok)
 178             System.out.println("Test passed");
 179         else {
 180             System.out.println("TEST FAILED");
 181             System.exit(1);
 182         }
 183     }
 184 
 185     private static QueryExp parseQuery(String[] ss) throws Exception {
 186         if (skip(ss, "("))
 187             return parseQueryAfterParen(ss);
 188 
 189         if (skip(ss, "not (")) {
 190             QueryExp not = parseQuery(ss);
 191             if (!skip(ss, ")"))
 192                 throw new Exception("Expected ) after not (...");
 193             return Query.not(not);
 194         }
 195 
 196         ValueExp exp = parseExp(ss);
 197 
 198         if (skip(ss, " like ")) {
 199             ValueExp pat = parseExp(ss);
 200             if (!(exp instanceof AttributeValueExp &&
 201                   pat instanceof StringValueExp)) {
 202                 throw new Exception("Expected types `attr like string': " +
 203                                     exp + " like " + pat);
 204             }
 205             StringValueExp spat = (StringValueExp) pat;
 206             return Query.match((AttributeValueExp) exp, spat);
 207         }
 208 
 209         if (skip(ss, " in (")) {
 210             List values = new ArrayList();
 211             if (!skip(ss, ")")) {
 212                 do {
 213                     values.add(parseExp(ss));
 214                 } while (skip(ss, ", "));
 215                 if (!skip(ss, ")"))
 216                     throw new Exception("Expected ) after in (...");
 217             }
 218             return Query.in(exp, (ValueExp[]) values.toArray(new ValueExp[0]));
 219         }
 220 
 221         throw new Exception("Expected in or like after expression");
 222     }
 223 
 224     private static QueryExp parseQueryAfterParen(String[] ss)
 225             throws Exception {
 226         /* This is very ugly.  We might have "(q1) and (q2)" here, or
 227            we might have "(e1) < (e2)".  Since the syntax for a query
 228            (q1) is not the same as for an expression (e1), but can
 229            begin with one, we try to parse the query, and if we get an
 230            exception we then try to parse an expression.  It's a hacky
 231            kind of look-ahead.  */
 232         String start = ss[0];
 233         try {
 234             QueryExp lhs = parseQuery(ss);
 235             QueryExp result;
 236 
 237             if (skip(ss, ") and ("))
 238                 result = Query.and(lhs, parseQuery(ss));
 239             else if (skip(ss, ") or ("))
 240                 result = Query.or(lhs, parseQuery(ss));
 241             else
 242                 throw new Exception("Expected `) and/or ('");
 243             if (!skip(ss, ")"))
 244                 throw new Exception("Expected `)' after subquery");
 245             return result;
 246         } catch (Exception e) {
 247             ss[0] = start;
 248             ValueExp lhs = parseExp(ss);
 249             if (!skip(ss, ") "))
 250                 throw new Exception("Expected `) ' after subexpression: " + ss[0]);
 251             String op = scanWord(ss);
 252             if (!skip(ss, " ("))
 253                 throw new Exception("Expected ` (' after `" + op + "'");
 254             ValueExp rhs = parseExp(ss);
 255             if (!skip(ss, ")"))
 256                 throw new Exception("Expected `)' after subexpression");
 257             if (op.equals("="))
 258                 return Query.eq(lhs, rhs);
 259             if (op.equals("<"))
 260                 return Query.lt(lhs, rhs);
 261             if (op.equals(">"))
 262                 return Query.gt(lhs, rhs);
 263             if (op.equals("<="))
 264                 return Query.leq(lhs, rhs);
 265             if (op.equals(">="))
 266                 return Query.geq(lhs, rhs);
 267             if (!op.equals("between"))
 268                 throw new Exception("Unknown operator `" + op + "'");
 269             if (!skip(ss, " and ("))
 270                 throw new Exception("Expected ` and (' after between");
 271             ValueExp high = parseExp(ss);
 272             if (!skip(ss, ")"))
 273                 throw new Exception("Expected `)' after subexpression");
 274             return Query.between(lhs, rhs, high);
 275         }
 276     }
 277 
 278     private static ValueExp parseExp(String[] ss) throws Exception {
 279         ValueExp lhs = parsePrimary(ss);
 280 
 281         while (true) {
 282         /* Look ahead to see if we have an arithmetic operator. */
 283         String back = ss[0];
 284         if (!skip(ss, " "))
 285                 return lhs;
 286         if (ss[0].equals("") || "+-*/".indexOf(ss[0].charAt(0)) < 0) {
 287             ss[0] = back;
 288                 return lhs;
 289         }
 290 
 291         final String op = scanWord(ss);
 292         if (op.length() != 1)
 293             throw new Exception("Expected arithmetic operator after space");
 294         if ("+-*/".indexOf(op) < 0)
 295             throw new Exception("Unknown arithmetic operator: " + op);
 296         if (!skip(ss, " "))
 297             throw new Exception("Expected space after arithmetic operator");
 298             ValueExp rhs = parsePrimary(ss);
 299         switch (op.charAt(0)) {
 300             case '+': lhs = Query.plus(lhs, rhs); break;
 301             case '-': lhs = Query.minus(lhs, rhs); break;
 302             case '*': lhs = Query.times(lhs, rhs); break;
 303             case '/': lhs = Query.div(lhs, rhs); break;
 304         default: throw new Exception("Can't happen: " + op.charAt(0));
 305         }
 306     }
 307     }
 308 
 309     private static ValueExp parsePrimary(String[] ss) throws Exception {
 310         String s = ss[0];
 311 
 312         if (s.length() == 0)
 313             throw new Exception("Empty string found, expression expected");
 314 
 315         char first = s.charAt(0);
 316 
 317         if (first == ' ')
 318             throw new Exception("Space found, expression expected");
 319 
 320         if (first == '-' || Character.isDigit(first))
 321             return parseNumberExp(ss);
 322 
 323         if (first == '\'')
 324             return parseString(ss);
 325 
 326         if (matchWord(ss, "true"))
 327             return Query.value(true);
 328 
 329         if (matchWord(ss, "false"))
 330             return Query.value(false);
 331 
 332         if (matchWord(ss, "Class"))
 333             return Query.classattr();
 334 
 335         String word = scanWord(ss);
 336         int lastDot = word.lastIndexOf('.');
 337         if (lastDot < 0)
 338             return Query.attr(word);
 339         else
 340             return Query.attr(word.substring(0, lastDot),
 341                               word.substring(lastDot + 1));
 342     }
 343 
 344     private static String scanWord(String[] ss) throws Exception {
 345         String s = ss[0];
 346         int space = s.indexOf(' ');
 347         int rpar = s.indexOf(')');
 348         if (space < 0 && rpar < 0) {
 349             ss[0] = "";
 350             return s;
 351         }
 352         int stop;
 353         if (space >= 0 && rpar >= 0)  // string has both space and ), stop at first
 354             stop = Math.min(space, rpar);
 355         else                          // string has only one, stop at it
 356             stop = Math.max(space, rpar);
 357         String word = s.substring(0, stop);
 358         ss[0] = s.substring(stop);
 359         return word;
 360     }
 361 
 362     private static boolean matchWord(String[] ss, String word)
 363             throws Exception {
 364         String s = ss[0];
 365         if (s.startsWith(word)) {
 366             int len = word.length();
 367             if (s.length() == len || s.charAt(len) == ' '
 368                 || s.charAt(len) == ')') {
 369                 ss[0] = s.substring(len);
 370                 return true;
 371             }
 372         }
 373         return false;
 374     }
 375 
 376     private static ValueExp parseNumberExp(String[] ss) throws Exception {
 377         String s = ss[0];
 378         int len = s.length();
 379         boolean isFloat = false;
 380         int i;
 381         for (i = 0; i < len; i++) {
 382             char c = s.charAt(i);
 383             if (Character.isDigit(c) || c == '-' || c == '+')
 384                 continue;
 385             if (c == '.' || c == 'e' || c == 'E') {
 386                 isFloat = true;
 387                 continue;
 388             }
 389             break;
 390         }
 391         ss[0] = s.substring(i);
 392         s = s.substring(0, i);
 393         if (isFloat)
 394             return Query.value(Double.parseDouble(s));
 395         else
 396             return Query.value(Long.parseLong(s));
 397     }
 398 
 399     private static ValueExp parseString(String[] ss) throws Exception {
 400         if (!skip(ss, "'"))
 401             throw new Exception("Expected ' at start of string");
 402         String s = ss[0];
 403         int len = s.length();
 404         StringBuffer buf = new StringBuffer();
 405         int i;
 406         for (i = 0; i < len; i++) {
 407             char c = s.charAt(i);
 408             if (c == '\'') {
 409                 ++i;
 410                 if (i >= len || s.charAt(i) != '\'') {
 411                     ss[0] = s.substring(i);
 412                 return Query.value(buf.toString());
 413             }
 414             }
 415             buf.append(c);
 416         }
 417         throw new Exception("No closing ' at end of string");
 418     }
 419 
 420     private static boolean skip(String[] ss, String skip) {
 421         if (ss[0].startsWith(skip)) {
 422             ss[0] = ss[0].substring(skip.length());
 423             return true;
 424         } else
 425             return false;
 426     }
 427 }