LCOV - code coverage report
Current view: top level - server/query/change - DistinctVotersPredicate.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 36 41 87.8 %
Date: 2022-11-19 15:00:39 Functions: 5 7 71.4 %

          Line data    Source code
       1             : // Copyright (C) 2022 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.common.collect.ImmutableList;
      19             : import com.google.common.primitives.Ints;
      20             : import com.google.gerrit.entities.LabelType;
      21             : import com.google.gerrit.entities.PatchSetApproval;
      22             : import com.google.gerrit.index.query.QueryParseException;
      23             : import com.google.gerrit.server.project.ProjectCache;
      24             : import com.google.gerrit.server.project.ProjectState;
      25             : import com.google.inject.Inject;
      26             : import com.google.inject.assistedinject.Assisted;
      27             : import java.util.Optional;
      28             : import java.util.regex.Matcher;
      29             : import java.util.regex.Pattern;
      30             : 
      31             : /**
      32             :  * A submit requirement predicate that allows checking for distinct voters across labels.
      33             :  *
      34             :  * <p>Examples:
      35             :  *
      36             :  * <ul>
      37             :  *   <li>[Label-Name1,Label-Name2],value=MAX,count=2
      38             :  *   <li>[Label-Name1,Label-Name2,Label-Name3],count=5
      39             :  * </ul>
      40             :  */
      41             : public class DistinctVotersPredicate extends SubmitRequirementPredicate {
      42             :   public interface Factory {
      43             :     DistinctVotersPredicate create(String value) throws QueryParseException;
      44             :   }
      45             : 
      46           1 :   private static final Pattern PATTERN =
      47           1 :       Pattern.compile(
      48             :           "\\[(?<labels>[^\\]]+)\\](,value=(?<value>MAX|MIN|-?[0-9]+))?,count\\>(?<count>[0-9]+)");
      49             : 
      50             :   private final ProjectCache projectCache;
      51             :   private final ImmutableList<String> labelNames;
      52             :   private final boolean enforceMaxVote;
      53             :   private final boolean enforceMinVote;
      54             :   private final Optional<Integer> enforceIntegerVote;
      55             :   private final int numDistinctVotes;
      56             : 
      57             :   @Inject
      58             :   public DistinctVotersPredicate(ProjectCache projectCache, @Assisted String value)
      59             :       throws QueryParseException {
      60           1 :     super("distinctvoters", value);
      61           1 :     this.projectCache = projectCache;
      62           1 :     Matcher m = PATTERN.matcher(value);
      63           1 :     if (!m.matches()) {
      64           0 :       throw new QueryParseException("input " + value + " invalid");
      65             :     }
      66           1 :     labelNames = ImmutableList.copyOf(Splitter.on(',').split(m.group("labels")));
      67           1 :     Integer votes = Ints.tryParse(m.group("count"));
      68           1 :     if (votes == null) {
      69           0 :       throw new QueryParseException("unable to parse number of required votes");
      70             :     }
      71           1 :     numDistinctVotes = votes + 1; // Regex has > sign
      72           1 :     if (m.group("value") != null) {
      73           1 :       enforceMaxVote = "MAX".equals(m.group("value"));
      74           1 :       enforceMinVote = "MIN".equals(m.group("value"));
      75           1 :       enforceIntegerVote = Optional.ofNullable(Ints.tryParse(m.group("value")));
      76             :     } else {
      77           1 :       enforceMaxVote = false;
      78           1 :       enforceMinVote = false;
      79           1 :       enforceIntegerVote = Optional.empty();
      80             :     }
      81           1 :   }
      82             : 
      83             :   @Override
      84             :   public boolean match(ChangeData cd) {
      85           1 :     ProjectState projectState =
      86             :         projectCache
      87           1 :             .get(cd.project())
      88           1 :             .orElseThrow(() -> new IllegalStateException("project absent " + cd.project()));
      89           1 :     return cd.currentApprovals().stream()
      90           1 :             .filter(psa -> filterPatchSetApproval(psa, projectState))
      91           1 :             .map(PatchSetApproval::accountId)
      92           1 :             .distinct()
      93           1 :             .count()
      94             :         >= numDistinctVotes;
      95             :   }
      96             : 
      97             :   @Override
      98             :   public int getCost() {
      99           0 :     return 1;
     100             :   }
     101             : 
     102             :   private boolean filterPatchSetApproval(PatchSetApproval psa, ProjectState projectState) {
     103           1 :     if (!labelNames.contains(psa.label())) {
     104           0 :       return false;
     105             :     }
     106             : 
     107           1 :     Optional<LabelType> labelType = projectState.getLabelTypes().byLabel(psa.labelId());
     108           1 :     if (labelType.isEmpty()) {
     109             :       // Label is not configured in this project
     110           0 :       return false;
     111             :     }
     112             : 
     113           1 :     if (enforceMaxVote && psa.value() != labelType.get().getMaxPositive()) {
     114           1 :       return false;
     115             :     }
     116           1 :     if (enforceMinVote && psa.value() != labelType.get().getMaxNegative()) {
     117           1 :       return false;
     118             :     }
     119           1 :     if (enforceIntegerVote.isPresent() && psa.value() != enforceIntegerVote.get()) {
     120           1 :       return false;
     121             :     }
     122           1 :     return true;
     123             :   }
     124             : }

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