import com.google.common.base.Splitter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
public class FilterParser {
private static final String DOUBLE_QUOTES = "\"";
-
private static final Splitter CRITERIA_SPLITTER = Splitter.on(Pattern.compile(" and ", Pattern.CASE_INSENSITIVE)).trimResults().omitEmptyStrings();
private static final Splitter IN_VALUES_SPLITTER = Splitter.on(",").trimResults().omitEmptyStrings();
-
- private static final Pattern PATTERN = Pattern.compile("(\\w+)\\s*([<>]?[=]?)\\s*(.*)", Pattern.CASE_INSENSITIVE);
- private static final Pattern PATTERN_HAVING_VALUES = Pattern.compile("(\\w+)\\s+(in)\\s+\\((.*)\\)", Pattern.CASE_INSENSITIVE);
+ private static final Pattern PATTERN_WITH_COMPARISON_OPERATOR = Pattern.compile("(\\w+)\\s*+(<=?|>=?|=)\\s*+([^<>=]*+)", Pattern.CASE_INSENSITIVE);
+ private static final Pattern PATTERN_WITHOUT_OPERATOR = Pattern.compile("(\\w+)\\s*+", Pattern.CASE_INSENSITIVE);
+ private static final Pattern PATTERN_WITH_IN_OPERATOR = Pattern.compile("(\\w+)\\s+(in)\\s+\\(([^()]*)\\)", Pattern.CASE_INSENSITIVE);
private FilterParser() {
// Only static methods
private static Criterion parseCriterion(String rawCriterion) {
try {
- Criterion criterion = tryParsingCriterionHavingValues(rawCriterion);
- if (criterion != null) {
- return criterion;
- }
- criterion = tryParsingCriterionNotHavingValues(rawCriterion);
- if (criterion != null) {
- return criterion;
- }
- throw new IllegalArgumentException("Criterion is invalid");
+ return Stream.of(
+ tryParsingCriterionWithoutOperator(rawCriterion),
+ tryParsingCriterionWithInOperator(rawCriterion),
+ tryParsingCriterionWithComparisonOperator(rawCriterion)
+ )
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Criterion is invalid"));
} catch (Exception e) {
throw new IllegalArgumentException(String.format("Cannot parse '%s' : %s", rawCriterion, e.getMessage()), e);
}
}
- @CheckForNull
- private static Criterion tryParsingCriterionNotHavingValues(String criterion) {
- Matcher matcher = PATTERN.matcher(criterion);
- if (!matcher.find()) {
- return null;
+ private static Optional<Criterion> tryParsingCriterionWithoutOperator(String criterion) {
+ Matcher matcher = PATTERN_WITHOUT_OPERATOR.matcher(criterion);
+ if (!matcher.matches()) {
+ return Optional.empty();
+ }
+ Criterion.Builder builder = new Criterion.Builder();
+ builder.setKey(matcher.group(1));
+ return Optional.of(builder.build());
+ }
+
+ private static Optional<Criterion> tryParsingCriterionWithComparisonOperator(String criterion) {
+ Matcher matcher = PATTERN_WITH_COMPARISON_OPERATOR.matcher(criterion);
+ if (!matcher.matches()) {
+ return Optional.empty();
}
Criterion.Builder builder = new Criterion.Builder();
builder.setKey(matcher.group(1));
builder.setOperator(ProjectMeasuresQuery.Operator.getByValue(operatorValue));
builder.setValue(sanitizeValue(value));
}
- return builder.build();
+ return Optional.of(builder.build());
}
- @CheckForNull
- private static Criterion tryParsingCriterionHavingValues(String criterion) {
- Matcher matcher = PATTERN_HAVING_VALUES.matcher(criterion);
- if (!matcher.find()) {
- return null;
+ private static Optional<Criterion> tryParsingCriterionWithInOperator(String criterion) {
+ Matcher matcher = PATTERN_WITH_IN_OPERATOR.matcher(criterion);
+ if (!matcher.matches()) {
+ return Optional.empty();
}
Criterion.Builder builder = new Criterion.Builder();
builder.setKey(matcher.group(1));
builder.setOperator(ProjectMeasuresQuery.Operator.IN);
builder.setValues(IN_VALUES_SPLITTER.splitToList(matcher.group(3)));
- return builder.build();
+ return Optional.of(builder.build());
}
@CheckForNull
*/
package org.sonar.server.component.ws;
+import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.sonar.server.component.ws.FilterParser.Criterion;
tuple("ncloc", IN, asList("80", "90"), null));
}
+ @Test
+ public void parse_filter_having_value_containing_operator_characters() {
+ List<Criterion> criterion = FilterParser.parse("languages IN (java, python, <null>)");
+
+ assertThat(criterion)
+ .extracting(Criterion::getKey, Criterion::getOperator, Criterion::getValues, Criterion::getValue)
+ .containsOnly(
+ tuple("languages", IN, asList("java", "python", "<null>"), null));
+ }
+
+ @Test
+ public void parse_filter_having_value_containing_non_alphanumeric_characters() {
+ List<Criterion> criterion = FilterParser.parse("q = \"+*ç%&/()\"");
+
+ assertThat(criterion)
+ .extracting(Criterion::getKey, Criterion::getOperator, Criterion::getValue)
+ .containsOnly(
+ tuple("q", EQ, "+*ç%&/()"));
+ }
+
+ @Test
+ public void parse_filter_having_in_empty_list() {
+ List<Criterion> criterion = FilterParser.parse("languages IN ()");
+
+ assertThat(criterion)
+ .extracting(Criterion::getKey, Criterion::getOperator, Criterion::getValues, Criterion::getValue)
+ .containsOnly(
+ tuple("languages", IN, Collections.emptyList(), null));
+ }
+
@Test
public void parse_filter_having_only_key() {
List<Criterion> criterion = FilterParser.parse("isFavorite");