1 /* 2 * $Id$ 3 * 4 * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved. 5 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 6 * 7 * This code is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License version 2 only, as 9 * published by the Free Software Foundation. Oracle designates this 10 * particular file as subject to the "Classpath" exception as provided 11 * by Oracle in the LICENSE file that accompanied this code. 12 * 13 * This code is distributed in the hope that it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16 * version 2 for more details (a copy is included in the LICENSE file that 17 * accompanied this code). 18 * 19 * You should have received a copy of the GNU General Public License version 20 * 2 along with this work; if not, write to the Free Software Foundation, 21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 22 * 23 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 24 * or visit www.oracle.com if you need additional information or have any 25 * questions. 26 */ 27 package com.sun.javatest; 28 29 import java.util.HashSet; 30 import java.util.Iterator; 31 import java.util.Set; 32 import com.sun.javatest.util.I18NResourceBundle; 33 import com.sun.javatest.util.StringArray; 34 35 //------------------------------------------------------------------------------ 36 37 /** 38 * A filter for sets of keywords, as found on test descriptions. 39 * 40 * @see TestDescription#getKeywordTable 41 */ 42 public abstract class Keywords 43 { 44 /** 45 * An exception used to report errors while using a Keywords object. 46 */ 47 public static class Fault extends Exception 48 { 49 /** 50 * Create a Fault. 51 * @param i18n A resource bundle in which to find the detail message. 52 * @param s The key for the detail message. 53 */ 54 Fault(I18NResourceBundle i18n, String s) { 55 super(i18n.getString(s)); 56 } 57 58 /** 59 * Create a Fault. 60 * @param i18n A resource bundle in which to find the detail message. 61 * @param s The key for the detail message. 62 * @param o An argument to be formatted with the detail message by 63 * {@link java.text.MessageFormat#format} 64 */ 65 Fault(I18NResourceBundle i18n, String s, Object o) { 66 super(i18n.getString(s, o)); 67 } 68 69 /** 70 * Create a Fault. 71 * @param i18n A resource bundle in which to find the detail message. 72 * @param s The key for the detail message. 73 * @param o An array of arguments to be formatted with the detail message by 74 * {@link java.text.MessageFormat#format} 75 */ 76 Fault(I18NResourceBundle i18n, String s, Object[] o) { 77 super(i18n.getString(s, o)); 78 } 79 } 80 81 /** 82 * Create a keywords object. 83 * @param type one of ALL_OF, ANY_OF, or EXPR 84 * @param text if the type is one of "all of" or "any of", text should 85 * be a white-space separated list of keywords; if type is "expr", 86 * text should be a boolean valued expression formed from 87 * keywords, '&' (and), '|' (or), '!' (not) and '(' ')' (parentheses). 88 * @return A Keywords object for the specified type and text. 89 * @throws Keywords.Fault if there are errors in the arguments. 90 */ 91 public static Keywords create(String type, String text) throws Fault { 92 return create(type, text, null); 93 } 94 95 /** 96 * Create a keywords object. 97 * @param type one of ALL_OF, ANY_OF, or EXPR 98 * @param text if the type is one of "all of" or "any of", text should 99 * be a white-space separated list of keywords; if type is "expr", 100 * text should be a boolean valued expression formed from 101 * keywords, '&' (and), '|' (or), '!' (not) and '(' ')' (parentheses). 102 * @param validKeywords a set of valid keywords for this test suite, 103 * or null. 104 * If not null, all the keywords in <i>text</i> must be in this set. 105 * @return A Keywords object for the specified type and text. 106 * @throws Keywords.Fault if there are errors in the arguments. 107 */ 108 public static Keywords create(String type, String text, Set<String> validKeywords) throws Fault { 109 Set<String> lowerCaseValidKeywords = toLowerCase(validKeywords); 110 if (text == null) { 111 text = ""; 112 } 113 114 Keywords result = null; 115 if (type == null || type.equals("ignore")) { 116 return null; 117 } else if (type.equals(ALL_OF)) { 118 result = new AllKeywords(StringArray.split(text), lowerCaseValidKeywords); 119 result.setSummary(result.toString()); 120 return result; 121 } 122 else if (type.equals(ANY_OF)) { 123 result = new AnyKeywords(StringArray.split(text), lowerCaseValidKeywords); 124 result.setSummary(result.toString()); 125 return result; 126 } 127 else if (type.equals(EXPR)) { 128 ExprParser p = new ExprParser(text, lowerCaseValidKeywords); 129 result = p.parse(); 130 result.setSummary(text); 131 return result; 132 } 133 else { 134 throw new Fault(i18n, "kw.badKeywordType", type); 135 } 136 } 137 138 139 /** 140 * Set the descriptive representation of the kw expression provided by the user. 141 * @param text Useful text rendering of current kw expression 142 */ 143 void setSummary(String text) { 144 this.text = text; 145 } 146 147 /** 148 * Get a human digestable version of the kw represented by this object. 149 * @return Human readable, fully descriptive rendering of current kw setting 150 */ 151 public String getSummary() { 152 return text; 153 } 154 155 protected String text; 156 157 /** 158 * A constant to indicate that all of a list of keywords should be matched. 159 */ 160 public static final String ALL_OF = "all of"; 161 162 /** 163 * A constant to indicate that any of a list of keywords should be matched. 164 */ 165 public static final String ANY_OF = "any of"; 166 167 /** 168 * A constant to indicate that an expression keyword should be matched. 169 */ 170 public static final String EXPR = "expr"; 171 172 /** 173 * Allow keywords to begin with a numeric or not. 174 * @param allowNumericKeywords Value to be set. 175 */ 176 public static void setAllowNumericKeywords(boolean allowNumericKeywords) { 177 ExprParser.allowNumericKeywords = allowNumericKeywords; 178 } 179 180 /** 181 * Check if this keywords object accepts, or matches, the specified 182 * set of words. If the keywords type is "any of" or "all of", 183 * the set must have any or of all of the words specified 184 * in the keywords object; if the keywords type is "expr", the 185 * given expression must evaluate to true, when the words in the 186 * expression are true if they are present in the given set of words. 187 * 188 * @param s A set of words to compare against the keywords object. 189 * @return true if the the specified set of words are compatible 190 * with this keywords object. 191 */ 192 public abstract boolean accepts(Set<String> s); 193 194 private static Set<String> toLowerCase(Set<String> words) { 195 if (words == null) 196 return null; 197 198 boolean allLowerCase = true; 199 for (Iterator<String> iter = words.iterator(); iter.hasNext() && allLowerCase; ) { 200 String word = iter.next(); 201 allLowerCase &= word.equals(word.toLowerCase()); 202 } 203 204 if (allLowerCase) 205 return words; 206 207 Set<String> s = new HashSet<>(); 208 for (Iterator<String> iter = words.iterator(); iter.hasNext(); ) { 209 String word = iter.next(); 210 s.add(word.toLowerCase()); 211 } 212 213 return s; 214 } 215 216 private static boolean isLowerCase(String s) { 217 for (int i = 0; i < s.length(); i++) { 218 if (Character.isUpperCase(s.charAt(i))) 219 return false; 220 } 221 return true; 222 } 223 224 static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(Keywords.class); 225 226 } 227 228 //------------------------------------------------------------------------------ 229 230 231 //------------------------------------------------------------------------------ 232 233 abstract class SetKeywords extends Keywords { 234 Set<String> keys; 235 String allKwds = ""; // string to be used by toString() 236 237 SetKeywords(String[] kwds, Set<String> validKeywords) throws Keywords.Fault { 238 if (kwds.length == 0) { 239 throw new Keywords.Fault(i18n, "kw.noKeywords"); 240 } 241 242 keys = new HashSet<String>(); 243 for (int i = 0; i < kwds.length; i++) { 244 String kwd = kwds[i].toLowerCase(); 245 if (validKeywords != null && !validKeywords.contains(kwd)) { 246 throw new Keywords.Fault(i18n, "kw.invalidKeyword", kwds[i]); 247 } 248 keys.add(kwd); 249 allKwds += kwd + " "; 250 } 251 if (allKwds.length() > 0) { 252 // remove last " " 253 allKwds = allKwds.substring(0, allKwds.length() - 1); 254 } 255 } 256 257 @Override 258 public boolean equals(Object obj) { 259 if (obj == null) { 260 return false; 261 } 262 if (getClass() != obj.getClass()) { 263 return false; 264 } 265 final SetKeywords other = (SetKeywords) obj; 266 if (this.keys != other.keys && (this.keys == null || !this.keys.equals(other.keys))) { 267 return false; 268 } 269 return true; 270 } 271 272 @Override 273 public int hashCode() { 274 int hash = 3; 275 hash = 61 * hash + (this.keys != null ? this.keys.hashCode() : 0); 276 return hash; 277 } 278 279 } 280 class AllKeywords extends SetKeywords { 281 AllKeywords(String[] keys, Set<String> validKeywords) throws Keywords.Fault { 282 super(keys, validKeywords); 283 } 284 285 286 /** 287 * Returns true, iff all keywords are in the set. 288 * @param s 289 * @return 290 */ 291 @Override 292 public boolean accepts(Set<String> s) { 293 return s.containsAll(keys); 294 } 295 296 @Override 297 public String toString() { 298 return "all of (" + allKwds + ")"; 299 } 300 } 301 302 303 class AnyKeywords extends SetKeywords { 304 AnyKeywords(String[] keys, Set<String> validKeywords) throws Keywords.Fault { 305 super(keys, validKeywords); 306 } 307 308 /** 309 * @param s - the set 310 * @return false, if none of the keywords is in the set 311 */ 312 @Override 313 public boolean accepts(Set<String> s) { 314 for (String kwd :keys) { 315 if (s.contains(kwd)) { 316 return true; 317 } 318 } 319 return false; 320 } 321 322 @Override 323 public String toString() { 324 return "any of (" + allKwds + ")"; 325 } 326 } 327 328 //------------------------------------------------------------------------------ 329 330 class ExprParser { 331 ExprParser(String text, Set<String> validKeywords) { 332 this.text = text; 333 this.validKeywords = validKeywords; 334 nextToken(); 335 } 336 337 ExprKeywords parse() throws Keywords.Fault { 338 if (text == null || text.trim().length() == 0) 339 throw new Keywords.Fault(i18n, "kw.noExpr"); 340 341 ExprKeywords e = parseExpr(); 342 expect(END); 343 return e; 344 } 345 346 ExprKeywords parseExpr() throws Keywords.Fault { 347 for (ExprKeywords e = parseTerm() ; e != null ; e = e.order()) { 348 switch (token) { 349 case AND: 350 nextToken(); 351 e = new AndExprKeywords(e, parseTerm()); 352 break; 353 case OR: 354 nextToken(); 355 e = new OrExprKeywords(e, parseTerm()); 356 break; 357 default: 358 return e; 359 } 360 } 361 // bogus return to keep compiler happy 362 return null; 363 } 364 365 ExprKeywords parseTerm() throws Keywords.Fault { 366 switch (token) { 367 case ID: 368 String id = idValue; 369 if (validKeywords != null && !validKeywords.contains(id)) 370 throw new Keywords.Fault(i18n, "kw.invalidKeyword", id); 371 nextToken(); 372 return new TermExprKeywords(id); 373 case NOT: 374 nextToken(); 375 return new NotExprKeywords(parseTerm()); 376 case LPAREN: 377 nextToken(); 378 ExprKeywords e = parseExpr(); 379 expect(RPAREN); 380 return new ParenExprKeywords(e); 381 default: 382 throw new Keywords.Fault(i18n, "kw.badKeywordExpr"); 383 } 384 } 385 386 private void expect(int t) throws Keywords.Fault { 387 if (t == token) 388 nextToken(); 389 else 390 throw new Keywords.Fault(i18n, "kw.badKeywordExpr"); 391 } 392 393 private void nextToken() { 394 while (index < text.length()) { 395 char c = text.charAt(index++); 396 switch (c) { 397 case ' ': 398 case '\t': 399 continue; 400 case '&': 401 token = AND; 402 return; 403 case '|': 404 token = OR; 405 return; 406 case '!': 407 token = NOT; 408 return; 409 case '(': 410 token = LPAREN; 411 return; 412 case ')': 413 token = RPAREN; 414 return; 415 default: 416 if (Character.isUnicodeIdentifierStart(c) || 417 (allowNumericKeywords && Character.isDigit(c))) { 418 idValue = String.valueOf(Character.toLowerCase(c)); 419 while (index < text.length() 420 && Character.isUnicodeIdentifierPart(text.charAt(index))) { 421 char ch = text.charAt(index++); 422 if (!Character.isIdentifierIgnorable(ch)) 423 idValue += Character.toLowerCase(ch); 424 } 425 token = ID; 426 return; 427 } 428 else { 429 token = ERROR; 430 return; 431 } 432 } 433 } 434 token = END; 435 } 436 437 protected static boolean allowNumericKeywords = 438 Boolean.getBoolean("javatest.allowNumericKeywords"); 439 private String text; 440 private Set<String> validKeywords; 441 private int index; 442 private int token; 443 private String idValue; 444 private static final int 445 ID = 0, AND = 1, OR = 2, NOT = 3, LPAREN = 4, RPAREN = 5, END = 6, ERROR = 7; 446 447 private static I18NResourceBundle i18n = Keywords.i18n; 448 } 449 450 //------------------------------------------------------------------------------ 451 452 abstract class ExprKeywords extends Keywords { 453 454 abstract int precedence(); 455 456 ExprKeywords order() { 457 return this; 458 } 459 } 460 461 //------------------------------------------------------------------------------ 462 463 464 //------------------------------------------------------------------------------ 465 466 abstract class BinaryExprKeywords extends ExprKeywords 467 { 468 BinaryExprKeywords(ExprKeywords left, ExprKeywords right) { 469 this.left = left; 470 this.right = right; 471 } 472 473 @Override 474 ExprKeywords order() { 475 if (precedence() > left.precedence() && left instanceof BinaryExprKeywords) { 476 BinaryExprKeywords e = (BinaryExprKeywords)left; 477 left = e.right; 478 e.right = order(); 479 return e; 480 } else 481 return this; 482 } 483 484 @Override 485 public boolean equals(Object obj) { 486 if (obj == null) { 487 return false; 488 } 489 if (getClass() != obj.getClass()) { 490 return false; 491 } 492 final BinaryExprKeywords other = (BinaryExprKeywords) obj; 493 if (this.left != other.left && (this.left == null || !this.left.equals(other.left))) { 494 return false; 495 } 496 if (this.right != other.right && (this.right == null || !this.right.equals(other.right))) { 497 return false; 498 } 499 return true; 500 } 501 502 @Override 503 public int hashCode() { 504 int hash = 7; 505 hash = 97 * hash + (this.left != null ? this.left.hashCode() : 0); 506 hash = 97 * hash + (this.right != null ? this.right.hashCode() : 0); 507 return hash; 508 } 509 510 protected ExprKeywords left; 511 protected ExprKeywords right; 512 } 513 class AndExprKeywords extends BinaryExprKeywords { 514 515 AndExprKeywords(ExprKeywords left, ExprKeywords right) { 516 super(left, right); 517 } 518 519 public boolean accepts(Set<String> s) { 520 return (left.accepts(s) && right.accepts(s)); 521 } 522 523 int precedence() { 524 return 1; 525 } 526 527 @Override 528 public String toString() { 529 return "`" + left + "&" + right + "'"; 530 } 531 } 532 533 //------------------------------------------------------------------------------ 534 535 536 //------------------------------------------------------------------------------ 537 538 class NotExprKeywords extends ExprKeywords { 539 NotExprKeywords(ExprKeywords expr) { 540 this.expr = expr; 541 } 542 543 public boolean accepts(Set<String> s) { 544 return !expr.accepts(s); 545 } 546 547 @Override 548 public boolean equals(Object obj) { 549 if (obj == null) { 550 return false; 551 } 552 if (getClass() != obj.getClass()) { 553 return false; 554 } 555 final NotExprKeywords other = (NotExprKeywords) obj; 556 if (this.expr != other.expr && (this.expr == null || !this.expr.equals(other.expr))) { 557 return false; 558 } 559 return true; 560 } 561 562 @Override 563 public int hashCode() { 564 int hash = 3; 565 hash = 29 * hash + (this.expr != null ? this.expr.hashCode() : 0); 566 return hash; 567 } 568 569 570 int precedence() { 571 return 2; 572 } 573 574 @Override 575 public String toString() { 576 return "!" + expr; 577 } 578 579 private ExprKeywords expr; 580 } 581 class OrExprKeywords extends BinaryExprKeywords { 582 OrExprKeywords(ExprKeywords left, ExprKeywords right) { 583 super(left, right); 584 } 585 586 public boolean accepts(Set<String> s) { 587 return (left.accepts(s) || right.accepts(s)); 588 } 589 590 int precedence() { 591 return 0; 592 } 593 594 @Override 595 public String toString() { 596 return "`" + left + "|" + right + "'"; 597 } 598 } 599 600 //------------------------------------------------------------------------------ 601 602 603 //------------------------------------------------------------------------------ 604 605 class ParenExprKeywords extends ExprKeywords { 606 ParenExprKeywords(ExprKeywords expr) { 607 this.expr = expr; 608 } 609 610 public boolean accepts(Set<String> s) { 611 return expr.accepts(s); 612 } 613 614 @Override 615 public boolean equals(Object obj) { 616 if (obj == null) { 617 return false; 618 } 619 if (getClass() != obj.getClass()) { 620 return false; 621 } 622 final ParenExprKeywords other = (ParenExprKeywords) obj; 623 if (this.expr != other.expr && (this.expr == null || !this.expr.equals(other.expr))) { 624 return false; 625 } 626 return true; 627 } 628 629 @Override 630 public int hashCode() { 631 int hash = 7; 632 hash = 19 * hash + (this.expr != null ? this.expr.hashCode() : 0); 633 return hash; 634 } 635 636 637 int precedence() { 638 return 2; 639 } 640 641 @Override 642 public String toString() { 643 return "(" + expr + ")"; 644 } 645 646 private ExprKeywords expr; 647 } 648 class TermExprKeywords extends ExprKeywords { 649 TermExprKeywords(String key) { 650 this.key = key; 651 } 652 653 public boolean accepts(Set<String> s) { 654 return (s.contains(key)); 655 } 656 657 @Override 658 public boolean equals(Object obj) { 659 if (obj == null) { 660 return false; 661 } 662 if (getClass() != obj.getClass()) { 663 return false; 664 } 665 final TermExprKeywords other = (TermExprKeywords) obj; 666 if ((this.key == null) ? (other.key != null) : !this.key.equals(other.key)) { 667 return false; 668 } 669 return true; 670 } 671 672 @Override 673 public int hashCode() { 674 int hash = 5; 675 hash = 13 * hash + (this.key != null ? this.key.hashCode() : 0); 676 return hash; 677 } 678 679 680 int precedence() { 681 return 2; 682 } 683 684 @Override 685 public String toString() { 686 return key; 687 } 688 689 private String key; 690 }