All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.utils.StringExpressionMatcher Maven / Gradle / Ivy

// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
// * with the License.  You may obtain a copy of the License at                                                              *
// *                                                                                                                         *
// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
// *                                                                                                                         *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
// * specific language governing permissions and limitations under the License.                                              *
// ***************************************************************************************************************************
package org.apache.juneau.utils;

import java.text.ParseException;
import java.util.*;
import java.util.regex.*;

import org.apache.juneau.common.internal.*;
import org.apache.juneau.internal.*;

import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.StateMachineState.*;

/**
 * Utility class for matching strings against string expressions.
 *
 * 

* Supports the following string constructs: *

    *
  • "foo" - Single arguments. *
  • "foo,bar,baz" - Multiple OR'ed arguments. *
  • "foo | bar | bqz" - Multiple OR'ed arguments, pipe syntax. *
  • "foo || bar || bqz" - Multiple OR'ed arguments, Java-OR syntax. *
  • "fo*" - Patterns including '*' and '?'. *
  • "fo* & *oo" - Multiple AND'ed arguments, ampersand syntax. *
  • "fo* && *oo" - Multiple AND'ed arguments, Java-AND syntax. *
  • "fo* || (*oo || bar)" - Parenthesis. *
* *
Notes:
    *
  • AND operations take precedence over OR operations (as expected). *
  • Whitespace is ignored. *
  • null or empty expressions always match as false. *
* *
See Also:
    *
*/ public class StringExpressionMatcher { private final Exp exp; private static final AsciiSet WS = AsciiSet.create(" \t"), OP = AsciiSet.create(",|&"), META = AsciiSet.create("*?"); /** * Constructor. * * @param expression The string expression. * @throws ParseException Malformed input encountered. */ public StringExpressionMatcher(String expression) throws ParseException { this.exp = parse(expression); } /** * Returns true if the specified string matches this expression. * * @param input The input string. * @return * true if the specified string matches this expression. *
Always false if the string is null. */ public boolean matches(String input) { return input != null && exp.matches(input); } @Override /* Object */ public String toString() { return exp.toString(); } /** * Returns all the tokens used in this expression. * * @return All the tokens used in this expression. */ public Set getTokens() { Set set = new TreeSet<>(); exp.appendTokens(set); return set; } private Exp parse(String expression) throws ParseException { if (StringUtils.isEmptyOrBlank(expression)) return new Never(); expression = expression.trim(); List ors = list(); List ands = list(); StateMachineState state = S01; int i = 0, mark = -1; int pDepth = 0; boolean error = false; for (i = 0; i < expression.length(); i++) { char c = expression.charAt(i); if (state == S01) { // S01 = Looking for start if (! WS.contains(c)) { if (c == '(') { state = S02; pDepth = 0; mark = i+1; } else if (OP.contains(c)) { error = true; break; } else { state = S03; mark = i; } } } else if (state == S02) { // S02 = Found [(], looking for [)]. if (c == '(') pDepth++; if (c == ')') { if (pDepth > 0) pDepth--; else { ands.add(parse(expression.substring(mark, i))); mark = -1; state = S04; } } } else if (state == S03) { // S03 = Found [A], looking for end of A. if (WS.contains(c) || OP.contains(c)) { ands.add(parseOperand(expression.substring(mark, i))); mark = -1; if (WS.contains(c)) { state = S04; } else { i--; state = S05; } } } else if (state == S04) { // S04 = Found [A ], looking for & or | or ,. if (! WS.contains(c)) { if (OP.contains(c)) { i--; state = S05; } else { error = true; break; } } } else if (state == S05) { // S05 = Found & or | or ,. if (c == '&') { //ands.add(operand); state = S06; } else /* (c == '|' || c == ',') */ { if (ands.size() == 1) { ors.add(ands.get(0)); } else { ors.add(new And(ands)); } ands.clear(); if (c == '|') { state = S07; } else { state = S01; } } } else if (state == S06) { // S06 = Found &, looking for & or other if (! WS.contains(c)) { if (c != '&') i--; state = S01; } } else /* (state == S07) */ { // S07 = Found |, looking for | or other if (! WS.contains(c)) { if (c != '|') i--; state = S01; } } } if (error) throw new ParseException("Invalid character in expression '"+expression+"' at position " + i + ". state=" + state, i); if (state == S01) throw new ParseException("Could not find beginning of clause in '"+expression+"'", i); if (state == S02) throw new ParseException("Could not find matching parenthesis in expression '"+expression+"'", i); if (state == S05 || state == S06 || state == S07) throw new ParseException("Dangling clause in expression '"+expression+"'", i); if (mark != -1) ands.add(parseOperand(expression.substring(mark, expression.length()))); if (ands.size() == 1) ors.add(ands.get(0)); else ors.add(new And(ands)); if (ors.size() == 1) return ors.get(0); return new Or(ors); } private static Exp parseOperand(String operand) { boolean hasMeta = false; for (int i = 0; i < operand.length() && ! hasMeta; i++) { char c = operand.charAt(i); hasMeta |= META.contains(c); } return hasMeta ? new Match(operand) : new Eq(operand); } //----------------------------------------------------------------------------------------------------------------- // Expression classes //----------------------------------------------------------------------------------------------------------------- abstract static class Exp { abstract boolean matches(String input); void appendTokens(Set set) {} } static class Never extends Exp { @Override boolean matches(String input) { return false; } @Override /* Object */ public String toString() { return "(NEVER)"; } } static class And extends Exp { Exp[] clauses; And(List clauses) { this.clauses = clauses.toArray(new Exp[clauses.size()]); } @Override /* Exp */ boolean matches(String input) { for (Exp e : clauses) if (! e.matches(input)) return false; return true; } @Override /* Exp */ void appendTokens(Set set) { for (Exp clause : clauses) clause.appendTokens(set); } @Override /* Object */ public String toString() { return "(& " + StringUtils.join(clauses, " ") + ')'; } } static class Or extends Exp { Exp[] clauses; Or(List clauses) { this.clauses = clauses.toArray(new Exp[clauses.size()]); } @Override boolean matches(String input) { for (Exp e : clauses) if (e.matches(input)) return true; return false; } @Override /* Exp */ void appendTokens(Set set) { for (Exp clause : clauses) clause.appendTokens(set); } @Override /* Object */ public String toString() { return "(| " + StringUtils.join(clauses, " ") + ')'; } } static class Eq extends Exp { final String operand; Eq(String operand) { this.operand = operand; } @Override /* Exp */ boolean matches(String input) { return operand.equals(input); } @Override /* Exp */ void appendTokens(Set set) { set.add(operand); } @Override /* Object */ public String toString() { return "[= " + operand + "]"; } } static class Match extends Exp { final Pattern p; final String operand; Match(String operand) { this.operand = operand; p = StringUtils.getMatchPattern(operand); } @Override /* Exp */ boolean matches(String input) { return p.matcher(input).matches(); } @Override /* Exp */ void appendTokens(Set set) { set.add(operand); } @Override /* Object */ public String toString() { return "[* " + p.pattern().replaceAll("\\\\[QE]", "") + "]"; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy