| // Copyright (C) 2010 The Android Open Source Project | |
| // | |
| // Licensed 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 com.google.gerrit.server.query.change; | |
| import com.google.common.collect.Lists; | |
| import com.google.gerrit.common.Nullable; | |
| import com.google.gerrit.entities.Account; | |
| import com.google.gerrit.entities.AccountGroup; | |
| import com.google.gerrit.index.query.OrPredicate; | |
| import com.google.gerrit.index.query.Predicate; | |
| import com.google.gerrit.index.query.RangeUtil; | |
| import com.google.gerrit.index.query.RangeUtil.Range; | |
| import com.google.gerrit.server.IdentifiedUser; | |
| import com.google.gerrit.server.account.AccountResolver; | |
| import com.google.gerrit.server.account.GroupBackend; | |
| import com.google.gerrit.server.permissions.PermissionBackend; | |
| import com.google.gerrit.server.project.ProjectCache; | |
| import com.google.gerrit.server.util.LabelVote; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import java.util.Set; | |
| import java.util.stream.IntStream; | |
| public class LabelPredicate extends OrPredicate<ChangeData> { | |
| protected static final int MAX_LABEL_VALUE = 4; | |
| protected static final int MAX_COUNT = 5; // inclusive | |
| protected static class Args { | |
| protected final AccountResolver accountResolver; | |
| protected final ProjectCache projectCache; | |
| protected final PermissionBackend permissionBackend; | |
| protected final IdentifiedUser.GenericFactory userFactory; | |
| protected final String value; | |
| protected final Set<Account.Id> accounts; | |
| protected final AccountGroup.UUID group; | |
| protected final Integer count; | |
| protected final PredicateArgs.Operator countOp; | |
| protected final GroupBackend groupBackend; | |
| protected Args( | |
| AccountResolver accountResolver, | |
| ProjectCache projectCache, | |
| PermissionBackend permissionBackend, | |
| IdentifiedUser.GenericFactory userFactory, | |
| String value, | |
| Set<Account.Id> accounts, | |
| AccountGroup.UUID group, | |
| @Nullable Integer count, | |
| @Nullable PredicateArgs.Operator countOp, | |
| GroupBackend groupBackend) { | |
| this.accountResolver = accountResolver; | |
| this.projectCache = projectCache; | |
| this.permissionBackend = permissionBackend; | |
| this.userFactory = userFactory; | |
| this.value = value; | |
| this.accounts = accounts; | |
| this.group = group; | |
| this.count = count; | |
| this.countOp = countOp; | |
| this.groupBackend = groupBackend; | |
| } | |
| } | |
| protected static class Parsed { | |
| protected final String label; | |
| protected final String test; | |
| protected final int numericValue; | |
| protected Parsed(String label, String test, int numericValue) { | |
| this.label = label; | |
| this.test = test; | |
| this.numericValue = numericValue; | |
| } | |
| } | |
| protected final String value; | |
| public LabelPredicate( | |
| ChangeQueryBuilder.Arguments a, | |
| String value, | |
| Set<Account.Id> accounts, | |
| AccountGroup.UUID group, | |
| @Nullable Integer count, | |
| @Nullable PredicateArgs.Operator countOp) { | |
| super( | |
| predicates( | |
| new Args( | |
| a.accountResolver, | |
| a.projectCache, | |
| a.permissionBackend, | |
| a.userFactory, | |
| value, | |
| accounts, | |
| group, | |
| count, | |
| countOp, | |
| a.groupBackend))); | |
| this.value = value; | |
| } | |
| protected static List<Predicate<ChangeData>> predicates(Args args) { | |
| String v = args.value; | |
| List<Integer> counts = getCounts(args.count, args.countOp); | |
| try { | |
| MagicLabelVote mlv = MagicLabelVote.parseWithEquals(v); | |
| List<Predicate<ChangeData>> result = Lists.newArrayListWithCapacity(counts.size()); | |
| if (counts.isEmpty()) { | |
| result.add(magicLabelPredicate(args, mlv, /* count= */ null)); | |
| } else { | |
| counts.forEach(count -> result.add(magicLabelPredicate(args, mlv, count))); | |
| } | |
| return result; | |
| } catch (IllegalArgumentException e) { | |
| // Try next format. | |
| } | |
| Parsed parsed = null; | |
| try { | |
| LabelVote lv = LabelVote.parse(v); | |
| parsed = new Parsed(lv.label(), "=", lv.value()); | |
| } catch (IllegalArgumentException e) { | |
| // Try next format. | |
| } | |
| try { | |
| LabelVote lv = LabelVote.parseWithEquals(v); | |
| parsed = new Parsed(lv.label(), "=", lv.value()); | |
| } catch (IllegalArgumentException e) { | |
| // Try next format. | |
| } | |
| Range range; | |
| if (parsed == null) { | |
| range = RangeUtil.getRange(v, -MAX_LABEL_VALUE, MAX_LABEL_VALUE); | |
| if (range == null) { | |
| range = new Range(v, 1, 1); | |
| } | |
| } else { | |
| range = | |
| RangeUtil.getRange( | |
| parsed.label, parsed.test, parsed.numericValue, -MAX_LABEL_VALUE, MAX_LABEL_VALUE); | |
| } | |
| String prefix = range.prefix; | |
| int min = range.min; | |
| int max = range.max; | |
| List<Predicate<ChangeData>> r = | |
| Lists.newArrayListWithCapacity((counts.isEmpty() ? 1 : counts.size()) * (max - min + 1)); | |
| for (int i = min; i <= max; i++) { | |
| if (counts.isEmpty()) { | |
| r.add(onePredicate(args, prefix, i, /* count= */ null)); | |
| } else { | |
| for (int count : counts) { | |
| r.add(onePredicate(args, prefix, i, count)); | |
| } | |
| } | |
| } | |
| return r; | |
| } | |
| protected static Predicate<ChangeData> onePredicate( | |
| Args args, String label, int expVal, @Nullable Integer count) { | |
| if (expVal != 0) { | |
| return equalsLabelPredicate(args, label, expVal, count); | |
| } | |
| return noLabelQuery(args, label); | |
| } | |
| protected static Predicate<ChangeData> noLabelQuery(Args args, String label) { | |
| List<Predicate<ChangeData>> r = Lists.newArrayListWithCapacity(2 * MAX_LABEL_VALUE); | |
| for (int i = 1; i <= MAX_LABEL_VALUE; i++) { | |
| r.add(equalsLabelPredicate(args, label, i, /* count= */ null)); | |
| r.add(equalsLabelPredicate(args, label, -i, /* count= */ null)); | |
| } | |
| return not(or(r)); | |
| } | |
| protected static Predicate<ChangeData> equalsLabelPredicate( | |
| Args args, String label, int expVal, @Nullable Integer count) { | |
| if (args.groupBackend.isOrContainsExternalGroup(args.group)) { | |
| // We can only get members of internal groups and negating an index search that doesn't | |
| // include the external group information leads to incorrect query results. Use a | |
| // PostFilterPredicate in this case instead. | |
| return new EqualsLabelPredicates.PostFilterEqualsLabelPredicate(args, label, expVal, count); | |
| } | |
| if (args.accounts == null || args.accounts.isEmpty()) { | |
| return new EqualsLabelPredicates.IndexEqualsLabelPredicate(args, label, expVal, count); | |
| } | |
| List<Predicate<ChangeData>> r = new ArrayList<>(); | |
| for (Account.Id a : args.accounts) { | |
| r.add(new EqualsLabelPredicates.IndexEqualsLabelPredicate(args, label, expVal, a, count)); | |
| } | |
| return or(r); | |
| } | |
| protected static Predicate<ChangeData> magicLabelPredicate( | |
| Args args, MagicLabelVote mlv, @Nullable Integer count) { | |
| if (args.groupBackend.isOrContainsExternalGroup(args.group)) { | |
| // We can only get members of internal groups and negating an index search that doesn't | |
| // include the external group information leads to incorrect query results. Use a | |
| // PostFilterPredicate in this case instead. | |
| return new MagicLabelPredicates.PostFilterMagicLabelPredicate(args, mlv, count); | |
| } | |
| if (args.accounts == null || args.accounts.isEmpty()) { | |
| return new MagicLabelPredicates.IndexMagicLabelPredicate(args, mlv, count); | |
| } | |
| List<Predicate<ChangeData>> r = new ArrayList<>(); | |
| for (Account.Id a : args.accounts) { | |
| r.add(new MagicLabelPredicates.IndexMagicLabelPredicate(args, mlv, a, count)); | |
| } | |
| return or(r); | |
| } | |
| private static List<Integer> getCounts( | |
| @Nullable Integer count, @Nullable PredicateArgs.Operator countOp) { | |
| List<Integer> result = new ArrayList<>(); | |
| if (count == null) { | |
| return result; | |
| } | |
| switch (countOp) { | |
| case EQUAL: | |
| case GREATER_EQUAL: | |
| case LESS_EQUAL: | |
| result.add(count); | |
| break; | |
| case GREATER: | |
| case LESS: | |
| default: | |
| break; | |
| } | |
| switch (countOp) { | |
| case GREATER: | |
| case GREATER_EQUAL: | |
| IntStream.range(count + 1, MAX_COUNT + 1).forEach(result::add); | |
| break; | |
| case LESS: | |
| case LESS_EQUAL: | |
| IntStream.range(0, count).forEach(result::add); | |
| break; | |
| case EQUAL: | |
| default: | |
| break; | |
| } | |
| return result; | |
| } | |
| @Override | |
| public String toString() { | |
| return ChangeQueryBuilder.FIELD_LABEL + ":" + value; | |
| } | |
| } |