LCOV - code coverage report
Current view: top level - server/query/change - SubmitRequirementChangeQueryBuilder.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 51 52 98.1 %
Date: 2022-11-19 15:00:39 Functions: 9 9 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2021 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.query.change;
      16             : 
      17             : import com.google.common.base.Splitter;
      18             : import com.google.gerrit.index.SchemaFieldDefs.SchemaField;
      19             : import com.google.gerrit.index.query.Predicate;
      20             : import com.google.gerrit.index.query.QueryBuilder;
      21             : import com.google.gerrit.index.query.QueryParseException;
      22             : import com.google.gerrit.server.query.FileEditsPredicate;
      23             : import com.google.gerrit.server.query.FileEditsPredicate.FileEditsArgs;
      24             : import com.google.inject.Inject;
      25             : import java.util.List;
      26             : import java.util.Locale;
      27             : import java.util.regex.Matcher;
      28             : import java.util.regex.Pattern;
      29             : import java.util.regex.PatternSyntaxException;
      30             : 
      31             : /**
      32             :  * A query builder for submit requirement expressions that includes all {@link ChangeQueryBuilder}
      33             :  * operators, in addition to extra operators contributed by this class.
      34             :  *
      35             :  * <p>Operators defined in this class cannot be used in change queries.
      36             :  */
      37             : public class SubmitRequirementChangeQueryBuilder extends ChangeQueryBuilder {
      38             : 
      39           5 :   private static final QueryBuilder.Definition<ChangeData, ChangeQueryBuilder> def =
      40             :       new QueryBuilder.Definition<>(SubmitRequirementChangeQueryBuilder.class);
      41             : 
      42             :   private final DistinctVotersPredicate.Factory distinctVotersPredicateFactory;
      43             :   private final HasSubmoduleUpdatePredicate.Factory hasSubmoduleUpdateFactory;
      44             : 
      45             :   /**
      46             :    * Regular expression for the {@link #file(String)} operator. Field value is of the form:
      47             :    *
      48             :    * <p>'$fileRegex',withDiffContaining='$contentRegex'
      49             :    *
      50             :    * <p>Both $fileRegex and $contentRegex may contain escaped single or double quotes.
      51             :    */
      52           5 :   private static final Pattern FILE_EDITS_PATTERN =
      53           5 :       Pattern.compile("'((?:(?:\\\\')|(?:[^']))*)',withDiffContaining='((?:(?:\\\\')|(?:[^']))*)'");
      54             : 
      55             :   public static final String SUBMODULE_UPDATE_HAS_ARG = "submodule-update";
      56           5 :   private static final Splitter SUBMODULE_UPDATE_SPLITTER = Splitter.on(",");
      57             : 
      58             :   private final FileEditsPredicate.Factory fileEditsPredicateFactory;
      59             : 
      60             :   @Inject
      61             :   SubmitRequirementChangeQueryBuilder(
      62             :       Arguments args,
      63             :       DistinctVotersPredicate.Factory distinctVotersPredicateFactory,
      64             :       FileEditsPredicate.Factory fileEditsPredicateFactory,
      65             :       HasSubmoduleUpdatePredicate.Factory hasSubmoduleUpdateFactory) {
      66           5 :     super(def, args);
      67           5 :     this.distinctVotersPredicateFactory = distinctVotersPredicateFactory;
      68           5 :     this.fileEditsPredicateFactory = fileEditsPredicateFactory;
      69           5 :     this.hasSubmoduleUpdateFactory = hasSubmoduleUpdateFactory;
      70           5 :   }
      71             : 
      72             :   @Override
      73             :   protected void checkFieldAvailable(SchemaField<ChangeData, ?> field, String operator) {
      74             :     // Submit requirements don't rely on the index, so they can be used regardless of index schema
      75             :     // version.
      76           1 :   }
      77             : 
      78             :   @Override
      79             :   public Predicate<ChangeData> is(String value) throws QueryParseException {
      80           2 :     if ("submittable".equalsIgnoreCase(value)) {
      81           1 :       throw new QueryParseException(
      82           1 :           String.format(
      83             :               "Operator 'is:submittable' cannot be used in submit requirement expressions."));
      84             :     }
      85           2 :     if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
      86           2 :       return new ConstantPredicate(value);
      87             :     }
      88           1 :     return super.is(value);
      89             :   }
      90             : 
      91             :   @Override
      92             :   public Predicate<ChangeData> has(String value) throws QueryParseException {
      93           2 :     if (value.toLowerCase(Locale.US).startsWith(SUBMODULE_UPDATE_HAS_ARG)) {
      94           1 :       List<String> args = SUBMODULE_UPDATE_SPLITTER.splitToList(value);
      95           1 :       if (args.size() > 2) {
      96           1 :         throw error(
      97           1 :             String.format(
      98             :                 "wrong number of arguments for the has:%s operator", SUBMODULE_UPDATE_HAS_ARG));
      99           1 :       } else if (args.size() == 2) {
     100           1 :         List<String> baseValue = Splitter.on("=").splitToList(args.get(1));
     101           1 :         if (baseValue.size() != 2) {
     102           1 :           throw error("unexpected base value format");
     103             :         }
     104           1 :         if (!baseValue.get(0).toLowerCase(Locale.US).equals("base")) {
     105           0 :           throw error("unexpected base value format");
     106             :         }
     107             :         try {
     108           1 :           int base = Integer.parseInt(baseValue.get(1));
     109           1 :           return hasSubmoduleUpdateFactory.create(base);
     110           1 :         } catch (NumberFormatException e) {
     111           1 :           throw error(
     112           1 :               String.format(
     113           1 :                   "failed to parse the parent number %s: %s", baseValue.get(1), e.getMessage()));
     114             :         }
     115             :       } else {
     116           1 :         return hasSubmoduleUpdateFactory.create(0);
     117             :       }
     118             :     }
     119           1 :     return super.has(value);
     120             :   }
     121             : 
     122             :   @Operator
     123             :   public Predicate<ChangeData> authoremail(String who) throws QueryParseException {
     124           1 :     return new RegexAuthorEmailPredicate(who);
     125             :   }
     126             : 
     127             :   @Operator
     128             :   public Predicate<ChangeData> distinctvoters(String value) throws QueryParseException {
     129           1 :     return distinctVotersPredicateFactory.create(value);
     130             :   }
     131             : 
     132             :   /**
     133             :    * A SR operator that can match with file path and content pattern. The value should be of the
     134             :    * form:
     135             :    *
     136             :    * <p>file:"'$filePattern',withDiffContaining='$contentPattern'"
     137             :    *
     138             :    * <p>The operator matches with changes that have their latest PS vs. base diff containing a file
     139             :    * path matching the {@code filePattern} with an edit (added, deleted, modified) matching the
     140             :    * {@code contentPattern}. {@code filePattern} and {@code contentPattern} can start with "^" to
     141             :    * use regular expression matching.
     142             :    *
     143             :    * <p>If the specified value does not match this form, we fall back to the operator's
     144             :    * implementation in {@link ChangeQueryBuilder}.
     145             :    */
     146             :   @Override
     147             :   public Predicate<ChangeData> file(String value) throws QueryParseException {
     148           1 :     Matcher matcher = FILE_EDITS_PATTERN.matcher(value);
     149           1 :     if (!matcher.find()) {
     150           1 :       return super.file(value);
     151             :     }
     152           1 :     String filePattern = matcher.group(1);
     153           1 :     String contentPattern = matcher.group(2);
     154           1 :     if (filePattern.startsWith("^")) {
     155           1 :       validateRegularExpression(filePattern, "Invalid file pattern.");
     156             :     }
     157           1 :     if (contentPattern.startsWith("^")) {
     158           1 :       validateRegularExpression(contentPattern, "Invalid content pattern.");
     159             :     }
     160           1 :     return fileEditsPredicateFactory.create(FileEditsArgs.create(filePattern, contentPattern));
     161             :   }
     162             : 
     163             :   private static void validateRegularExpression(String pattern, String errorMessage)
     164             :       throws QueryParseException {
     165             :     try {
     166           1 :       Pattern.compile(pattern);
     167           1 :     } catch (PatternSyntaxException e) {
     168           1 :       throw new QueryParseException(errorMessage, e);
     169           1 :     }
     170           1 :   }
     171             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750