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