<commit_before>// Copyright (C) 2021 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.project;

import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.gerrit.server.project.ProjectCache.illegalState;

import com.google.common.collect.ImmutableMap;
import com.google.gerrit.entities.SubmitRequirement;
import com.google.gerrit.entities.SubmitRequirementExpression;
import com.google.gerrit.entities.SubmitRequirementExpressionResult;
import com.google.gerrit.entities.SubmitRequirementExpressionResult.PredicateResult;
import com.google.gerrit.entities.SubmitRequirementResult;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.SubmitRequirementChangeQueryBuilder;
import com.google.gerrit.server.util.ManualRequestContext;
import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Scopes;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

/** Evaluates submit requirements for different change data. */
public class SubmitRequirementsEvaluatorImpl implements SubmitRequirementsEvaluator {

  private final Provider<SubmitRequirementChangeQueryBuilder> queryBuilder;
  private final ProjectCache projectCache;
  private final PluginSetContext<SubmitRequirement> globalSubmitRequirements;
  private final OneOffRequestContext requestContext;

  public static Module module() {
    return new AbstractModule() {
      @Override
      protected void configure() {
        bind(SubmitRequirementsEvaluator.class)
            .to(SubmitRequirementsEvaluatorImpl.class)
            .in(Scopes.SINGLETON);
      }
    };
  }

  @Inject
  private SubmitRequirementsEvaluatorImpl(
      Provider<SubmitRequirementChangeQueryBuilder> queryBuilder,
      ProjectCache projectCache,
      PluginSetContext<SubmitRequirement> globalSubmitRequirements,
      OneOffRequestContext requestContext) {
    this.queryBuilder = queryBuilder;
    this.projectCache = projectCache;
    this.globalSubmitRequirements = globalSubmitRequirements;
    this.requestContext = requestContext;
  }

  @Override
  public void validateExpression(SubmitRequirementExpression expression)
      throws QueryParseException {
    queryBuilder.get().parse(expression.expressionString());
  }

  @Override
  public ImmutableMap<SubmitRequirement, SubmitRequirementResult> evaluateAllRequirements(
      ChangeData cd) {
    return getRequirements(cd);
  }

  @Override
  public SubmitRequirementResult evaluateRequirement(SubmitRequirement sr, ChangeData cd) {
    try (ManualRequestContext ignored = requestContext.open()) {
      // Use a request context to execute predicates as an internal user with expanded visibility.
      // This is so that the evaluation does not depend on who is running the current request (e.g.
      // a "ownerin" predicate with group that is not visible to the person making this request).

      Optional<SubmitRequirementExpressionResult> applicabilityResult =
          sr.applicabilityExpression().isPresent()
              ? Optional.of(evaluateExpression(sr.applicabilityExpression().get(), cd))
              : Optional.empty();
      Optional<SubmitRequirementExpressionResult> submittabilityResult =
          Optional.of(
              SubmitRequirementExpressionResult.notEvaluated(sr.submittabilityExpression()));
      Optional<SubmitRequirementExpressionResult> overrideResult =
          sr.overrideExpression().isPresent()
              ? Optional.of(
                  SubmitRequirementExpressionResult.notEvaluated(sr.overrideExpression().get()))
              : Optional.empty();
      if (!sr.applicabilityExpression().isPresent()
          || SubmitRequirementResult.assertPass(applicabilityResult)) {
        submittabilityResult = Optional.of(evaluateExpression(sr.submittabilityExpression(), cd));
        overrideResult =
            sr.overrideExpression().isPresent()
                ? Optional.of(evaluateExpression(sr.overrideExpression().get(), cd))
                : Optional.empty();
      }

      return SubmitRequirementResult.builder()
          .legacy(Optional.of(false))
          .submitRequirement(sr)
          .patchSetCommitId(cd.currentPatchSet().commitId())
          .submittabilityExpressionResult(submittabilityResult)
          .applicabilityExpressionResult(applicabilityResult)
          .overrideExpressionResult(overrideResult)
          .build();
    }
  }

  @Override
  public SubmitRequirementExpressionResult evaluateExpression(
      SubmitRequirementExpression expression, ChangeData changeData) {
    try {
      Predicate<ChangeData> predicate = queryBuilder.get().parse(expression.expressionString());
      PredicateResult predicateResult = evaluatePredicateTree(predicate, changeData);
      return SubmitRequirementExpressionResult.create(expression, predicateResult);
    } catch (QueryParseException | SubmitRequirementEvaluationException e) {
      return SubmitRequirementExpressionResult.error(expression, e.getMessage());
    }
  }

  /**
   * Evaluate and return all {@link SubmitRequirement}s.
   *
   * <p>This includes all globally bound {@link SubmitRequirement}s, as well as requirements stored
   * in this project's config and its parents.
   *<commit_msg>Log pass/fail atoms of submit-requirements expression results<commit_after>// Copyright (C) 2021 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.project;

import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.gerrit.server.project.ProjectCache.illegalState;

import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.SubmitRequirement;
import com.google.gerrit.entities.SubmitRequirementExpression;
import com.google.gerrit.entities.SubmitRequirementExpressionResult;
import com.google.gerrit.entities.SubmitRequirementExpressionResult.PredicateResult;
import com.google.gerrit.entities.SubmitRequirementResult;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.SubmitRequirementChangeQueryBuilder;
import com.google.gerrit.server.util.ManualRequestContext;
import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Scopes;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

/** Evaluates submit requirements for different change data. */
public class SubmitRequirementsEvaluatorImpl implements SubmitRequirementsEvaluator {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  private final Provider<SubmitRequirementChangeQueryBuilder> queryBuilder;
  private final ProjectCache projectCache;
  private final PluginSetContext<SubmitRequirement> globalSubmitRequirements;
  private final OneOffRequestContext requestContext;

  public static Module module() {
    return new AbstractModule() {
      @Override
      protected void configure() {
        bind(SubmitRequirementsEvaluator.class)
            .to(SubmitRequirementsEvaluatorImpl.class)
            .in(Scopes.SINGLETON);
      }
    };
  }

  @Inject
  private SubmitRequirementsEvaluatorImpl(
      Provider<SubmitRequirementChangeQueryBuilder> queryBuilder,
      ProjectCache projectCache,
      PluginSetContext<SubmitRequirement> globalSubmitRequirements,
      OneOffRequestContext requestContext) {
    this.queryBuilder = queryBuilder;
    this.projectCache = projectCache;
    this.globalSubmitRequirements = globalSubmitRequirements;
    this.requestContext = requestContext;
  }

  @Override
  public void validateExpression(SubmitRequirementExpression expression)
      throws QueryParseException {
    queryBuilder.get().parse(expression.expressionString());
  }

  @Override
  public ImmutableMap<SubmitRequirement, SubmitRequirementResult> evaluateAllRequirements(
      ChangeData cd) {
    return getRequirements(cd);
  }

  @Override
  public SubmitRequirementResult evaluateRequirement(SubmitRequirement sr, ChangeData cd) {
    try (ManualRequestContext ignored = requestContext.open()) {
      // Use a request context to execute predicates as an internal user with expanded visibility.
      // This is so that the evaluation does not depend on who is running the current request (e.g.
      // a "ownerin" predicate with group that is not visible to the person making this request).

      Optional<SubmitRequirementExpressionResult> applicabilityResult =
          sr.applicabilityExpression().isPresent()
              ? Optional.of(evaluateExpression(sr.applicabilityExpression().get(), cd))
              : Optional.empty();
      Optional<SubmitRequirementExpressionResult> submittabilityResult =
          Optional.of(
              SubmitRequirementExpressionResult.notEvaluated(sr.submittabilityExpression()));
      Optional<SubmitRequirementExpressionResult> overrideResult =
          sr.overrideExpression().isPresent()
              ? Optional.of(
                  SubmitRequirementExpressionResult.notEvaluated(sr.overrideExpression().get()))
              : Optional.empty();
      if (!sr.applicabilityExpression().isPresent()
          || SubmitRequirementResult.assertPass(applicabilityResult)) {
        submittabilityResult = Optional.of(evaluateExpression(sr.submittabilityExpression(), cd));
        overrideResult =
            sr.overrideExpression().isPresent()
                ? Optional.of(evaluateExpression(sr.overrideExpression().get(), cd))
                : Optional.empty();
      }

      if (applicabilityResult.isPresent()) {
        logger.atFine().log(
            "Applicability expression result for SR name '%s':"
                + " passing atoms: %s, failing atoms: %s",
            sr.name(),
            applicabilityResult.get().passingAtoms(),
            applicabilityResult.get().failingAtoms());
      }
      if (submittabilityResult.isPresent()) {
        logger.atFine().log(
            "Submittability expression result for SR name '%s':"
                + " passing atoms: %s, failing atoms: %s",
            sr.name(),
            submittabilityResult.get().passingAtoms(),
            submittabilityResult.get().failingAtoms());
      }
      if (overrideResult.isPresent()) {
        logger.atFine().log(
            "Override expression result for SR name '%s':"
                + " passing atoms: %s, failing atoms: %s",
            sr.name(), overrideResult.get().passingAtoms(), overrideResult.get().failingAtoms());
      }

      return SubmitRequirementResult.builder()
          .legacy(Optional.of(false))
          .submitRequirement(sr)
          .patchSetCommitId(cd.currentPatchSet().commitId())
          .submittabilityExpressionResult(submittabilityResult)
          .applicabilityExpressionResult(applicabilityResult)
          .overrideExpressionResult(overrideResult)
          .build();
    }
  }

  @Override
  public SubmitRequirementExpressionResult evaluateExpression(
      SubmitRequirementExpression expression, ChangeData changeData) {
    try {
      Predicate<ChangeData> predicate = queryBuilder.get().parse(expression.expressionString());
      PredicateResult predicateResult = evaluatePredicateTree(predicate, changeData);
      return SubmitRequirementExpressionResult.create(expression, predicateResult);
    } catch (QueryParseException | SubmitRequirementEvaluationException e) {
      return SubmitRequirementExpressionResult.error(expression, e.getMessage());
    }
  }

  /**
   * Evaluate and return all {@link SubmitRequirement}s.
   *
   * <p>This includes all globally bound {@link SubmitRequirement}s, as well as requirements stored
   * in this project's config and its parents.
   *
{'fraction_non_alphanumeric': 0.08757938266134987, 'fraction_numerical': 0.0011815093782306897, 'mean_word_length': 3.9517367458866546, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '984453', 'n_tokens_mistral': 3476, 'n_tokens_neox': 3361, 'n_words': 793}