/*
* $Id$
*
* Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.javatest;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import com.sun.javatest.util.I18NResourceBundle;
import com.sun.javatest.util.StringArray;
//------------------------------------------------------------------------------
/**
* A filter for sets of keywords, as found on test descriptions.
*
* @see TestDescription#getKeywordTable
*/
public abstract class Keywords
{
/**
* An exception used to report errors while using a Keywords object.
*/
public static class Fault extends Exception
{
/**
* Create a Fault.
* @param i18n A resource bundle in which to find the detail message.
* @param s The key for the detail message.
*/
Fault(I18NResourceBundle i18n, String s) {
super(i18n.getString(s));
}
/**
* Create a Fault.
* @param i18n A resource bundle in which to find the detail message.
* @param s The key for the detail message.
* @param o An argument to be formatted with the detail message by
* {@link java.text.MessageFormat#format}
*/
Fault(I18NResourceBundle i18n, String s, Object o) {
super(i18n.getString(s, o));
}
/**
* Create a Fault.
* @param i18n A resource bundle in which to find the detail message.
* @param s The key for the detail message.
* @param o An array of arguments to be formatted with the detail message by
* {@link java.text.MessageFormat#format}
*/
Fault(I18NResourceBundle i18n, String s, Object[] o) {
super(i18n.getString(s, o));
}
}
/**
* Create a keywords object.
* @param type one of ALL_OF, ANY_OF, or EXPR
* @param text if the type is one of "all of" or "any of", text should
* be a white-space separated list of keywords; if type is "expr",
* text should be a boolean valued expression formed from
* keywords, '&' (and), '|' (or), '!' (not) and '(' ')' (parentheses).
* @return A Keywords object for the specified type and text.
* @throws Keywords.Fault if there are errors in the arguments.
*/
public static Keywords create(String type, String text) throws Fault {
return create(type, text, null);
}
/**
* Create a keywords object.
* @param type one of ALL_OF, ANY_OF, or EXPR
* @param text if the type is one of "all of" or "any of", text should
* be a white-space separated list of keywords; if type is "expr",
* text should be a boolean valued expression formed from
* keywords, '&' (and), '|' (or), '!' (not) and '(' ')' (parentheses).
* @param validKeywords a set of valid keywords for this test suite,
* or null.
* If not null, all the keywords in text must be in this set.
* @return A Keywords object for the specified type and text.
* @throws Keywords.Fault if there are errors in the arguments.
*/
public static Keywords create(String type, String text, Set validKeywords) throws Fault {
Set lowerCaseValidKeywords = toLowerCase(validKeywords);
if (text == null) {
text = "";
}
Keywords result = null;
if (type == null || type.equals("ignore")) {
return null;
} else if (type.equals(ALL_OF)) {
result = new AllKeywords(StringArray.split(text), lowerCaseValidKeywords);
result.setSummary(result.toString());
return result;
}
else if (type.equals(ANY_OF)) {
result = new AnyKeywords(StringArray.split(text), lowerCaseValidKeywords);
result.setSummary(result.toString());
return result;
}
else if (type.equals(EXPR)) {
ExprParser p = new ExprParser(text, lowerCaseValidKeywords);
result = p.parse();
result.setSummary(text);
return result;
}
else {
throw new Fault(i18n, "kw.badKeywordType", type);
}
}
/**
* Set the descriptive representation of the kw expression provided by the user.
* @param text Useful text rendering of current kw expression
*/
void setSummary(String text) {
this.text = text;
}
/**
* Get a human digestable version of the kw represented by this object.
* @return Human readable, fully descriptive rendering of current kw setting
*/
public String getSummary() {
return text;
}
protected String text;
/**
* A constant to indicate that all of a list of keywords should be matched.
*/
public static final String ALL_OF = "all of";
/**
* A constant to indicate that any of a list of keywords should be matched.
*/
public static final String ANY_OF = "any of";
/**
* A constant to indicate that an expression keyword should be matched.
*/
public static final String EXPR = "expr";
/**
* Allow keywords to begin with a numeric or not.
* @param allowNumericKeywords Value to be set.
*/
public static void setAllowNumericKeywords(boolean allowNumericKeywords) {
ExprParser.allowNumericKeywords = allowNumericKeywords;
}
/**
* Check if this keywords object accepts, or matches, the specified
* set of words. If the keywords type is "any of" or "all of",
* the set must have any or of all of the words specified
* in the keywords object; if the keywords type is "expr", the
* given expression must evaluate to true, when the words in the
* expression are true if they are present in the given set of words.
*
* @param s A set of words to compare against the keywords object.
* @return true if the the specified set of words are compatible
* with this keywords object.
*/
public abstract boolean accepts(Set s);
private static Set toLowerCase(Set words) {
if (words == null)
return null;
boolean allLowerCase = true;
for (Iterator iter = words.iterator(); iter.hasNext() && allLowerCase; ) {
String word = iter.next();
allLowerCase &= word.equals(word.toLowerCase());
}
if (allLowerCase)
return words;
Set s = new HashSet<>();
for (Iterator iter = words.iterator(); iter.hasNext(); ) {
String word = iter.next();
s.add(word.toLowerCase());
}
return s;
}
private static boolean isLowerCase(String s) {
for (int i = 0; i < s.length(); i++) {
if (Character.isUpperCase(s.charAt(i)))
return false;
}
return true;
}
static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(Keywords.class);
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
abstract class SetKeywords extends Keywords {
Set keys;
String allKwds = ""; // string to be used by toString()
SetKeywords(String[] kwds, Set validKeywords) throws Keywords.Fault {
if (kwds.length == 0) {
throw new Keywords.Fault(i18n, "kw.noKeywords");
}
keys = new HashSet();
for (int i = 0; i < kwds.length; i++) {
String kwd = kwds[i].toLowerCase();
if (validKeywords != null && !validKeywords.contains(kwd)) {
throw new Keywords.Fault(i18n, "kw.invalidKeyword", kwds[i]);
}
keys.add(kwd);
allKwds += kwd + " ";
}
if (allKwds.length() > 0) {
// remove last " "
allKwds = allKwds.substring(0, allKwds.length() - 1);
}
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final SetKeywords other = (SetKeywords) obj;
if (this.keys != other.keys && (this.keys == null || !this.keys.equals(other.keys))) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 3;
hash = 61 * hash + (this.keys != null ? this.keys.hashCode() : 0);
return hash;
}
}
class AllKeywords extends SetKeywords {
AllKeywords(String[] keys, Set validKeywords) throws Keywords.Fault {
super(keys, validKeywords);
}
/**
* Returns true, iff all keywords are in the set.
* @param s
* @return
*/
@Override
public boolean accepts(Set s) {
return s.containsAll(keys);
}
@Override
public String toString() {
return "all of (" + allKwds + ")";
}
}
class AnyKeywords extends SetKeywords {
AnyKeywords(String[] keys, Set validKeywords) throws Keywords.Fault {
super(keys, validKeywords);
}
/**
* @param s - the set
* @return false, if none of the keywords is in the set
*/
@Override
public boolean accepts(Set s) {
for (String kwd :keys) {
if (s.contains(kwd)) {
return true;
}
}
return false;
}
@Override
public String toString() {
return "any of (" + allKwds + ")";
}
}
//------------------------------------------------------------------------------
class ExprParser {
ExprParser(String text, Set validKeywords) {
this.text = text;
this.validKeywords = validKeywords;
nextToken();
}
ExprKeywords parse() throws Keywords.Fault {
if (text == null || text.trim().length() == 0)
throw new Keywords.Fault(i18n, "kw.noExpr");
ExprKeywords e = parseExpr();
expect(END);
return e;
}
ExprKeywords parseExpr() throws Keywords.Fault {
for (ExprKeywords e = parseTerm() ; e != null ; e = e.order()) {
switch (token) {
case AND:
nextToken();
e = new AndExprKeywords(e, parseTerm());
break;
case OR:
nextToken();
e = new OrExprKeywords(e, parseTerm());
break;
default:
return e;
}
}
// bogus return to keep compiler happy
return null;
}
ExprKeywords parseTerm() throws Keywords.Fault {
switch (token) {
case ID:
String id = idValue;
if (validKeywords != null && !validKeywords.contains(id))
throw new Keywords.Fault(i18n, "kw.invalidKeyword", id);
nextToken();
return new TermExprKeywords(id);
case NOT:
nextToken();
return new NotExprKeywords(parseTerm());
case LPAREN:
nextToken();
ExprKeywords e = parseExpr();
expect(RPAREN);
return new ParenExprKeywords(e);
default:
throw new Keywords.Fault(i18n, "kw.badKeywordExpr");
}
}
private void expect(int t) throws Keywords.Fault {
if (t == token)
nextToken();
else
throw new Keywords.Fault(i18n, "kw.badKeywordExpr");
}
private void nextToken() {
while (index < text.length()) {
char c = text.charAt(index++);
switch (c) {
case ' ':
case '\t':
continue;
case '&':
token = AND;
return;
case '|':
token = OR;
return;
case '!':
token = NOT;
return;
case '(':
token = LPAREN;
return;
case ')':
token = RPAREN;
return;
default:
if (Character.isUnicodeIdentifierStart(c) ||
(allowNumericKeywords && Character.isDigit(c))) {
idValue = String.valueOf(Character.toLowerCase(c));
while (index < text.length()
&& Character.isUnicodeIdentifierPart(text.charAt(index))) {
char ch = text.charAt(index++);
if (!Character.isIdentifierIgnorable(ch))
idValue += Character.toLowerCase(ch);
}
token = ID;
return;
}
else {
token = ERROR;
return;
}
}
}
token = END;
}
protected static boolean allowNumericKeywords =
Boolean.getBoolean("javatest.allowNumericKeywords");
private String text;
private Set validKeywords;
private int index;
private int token;
private String idValue;
private static final int
ID = 0, AND = 1, OR = 2, NOT = 3, LPAREN = 4, RPAREN = 5, END = 6, ERROR = 7;
private static I18NResourceBundle i18n = Keywords.i18n;
}
//------------------------------------------------------------------------------
abstract class ExprKeywords extends Keywords {
abstract int precedence();
ExprKeywords order() {
return this;
}
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
abstract class BinaryExprKeywords extends ExprKeywords
{
BinaryExprKeywords(ExprKeywords left, ExprKeywords right) {
this.left = left;
this.right = right;
}
@Override
ExprKeywords order() {
if (precedence() > left.precedence() && left instanceof BinaryExprKeywords) {
BinaryExprKeywords e = (BinaryExprKeywords)left;
left = e.right;
e.right = order();
return e;
} else
return this;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final BinaryExprKeywords other = (BinaryExprKeywords) obj;
if (this.left != other.left && (this.left == null || !this.left.equals(other.left))) {
return false;
}
if (this.right != other.right && (this.right == null || !this.right.equals(other.right))) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + (this.left != null ? this.left.hashCode() : 0);
hash = 97 * hash + (this.right != null ? this.right.hashCode() : 0);
return hash;
}
protected ExprKeywords left;
protected ExprKeywords right;
}
class AndExprKeywords extends BinaryExprKeywords {
AndExprKeywords(ExprKeywords left, ExprKeywords right) {
super(left, right);
}
public boolean accepts(Set s) {
return (left.accepts(s) && right.accepts(s));
}
int precedence() {
return 1;
}
@Override
public String toString() {
return "`" + left + "&" + right + "'";
}
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
class NotExprKeywords extends ExprKeywords {
NotExprKeywords(ExprKeywords expr) {
this.expr = expr;
}
public boolean accepts(Set s) {
return !expr.accepts(s);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final NotExprKeywords other = (NotExprKeywords) obj;
if (this.expr != other.expr && (this.expr == null || !this.expr.equals(other.expr))) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 3;
hash = 29 * hash + (this.expr != null ? this.expr.hashCode() : 0);
return hash;
}
int precedence() {
return 2;
}
@Override
public String toString() {
return "!" + expr;
}
private ExprKeywords expr;
}
class OrExprKeywords extends BinaryExprKeywords {
OrExprKeywords(ExprKeywords left, ExprKeywords right) {
super(left, right);
}
public boolean accepts(Set s) {
return (left.accepts(s) || right.accepts(s));
}
int precedence() {
return 0;
}
@Override
public String toString() {
return "`" + left + "|" + right + "'";
}
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
class ParenExprKeywords extends ExprKeywords {
ParenExprKeywords(ExprKeywords expr) {
this.expr = expr;
}
public boolean accepts(Set s) {
return expr.accepts(s);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ParenExprKeywords other = (ParenExprKeywords) obj;
if (this.expr != other.expr && (this.expr == null || !this.expr.equals(other.expr))) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 19 * hash + (this.expr != null ? this.expr.hashCode() : 0);
return hash;
}
int precedence() {
return 2;
}
@Override
public String toString() {
return "(" + expr + ")";
}
private ExprKeywords expr;
}
class TermExprKeywords extends ExprKeywords {
TermExprKeywords(String key) {
this.key = key;
}
public boolean accepts(Set s) {
return (s.contains(key));
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final TermExprKeywords other = (TermExprKeywords) obj;
if ((this.key == null) ? (other.key != null) : !this.key.equals(other.key)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 5;
hash = 13 * hash + (this.key != null ? this.key.hashCode() : 0);
return hash;
}
int precedence() {
return 2;
}
@Override
public String toString() {
return key;
}
private String key;
}