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 }