// Copyright (C) 2018 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.googlesource.gerrit.plugins.deleteproject;

import static com.google.gerrit.entities.RefNames.REFS_HEADS;
import static com.google.gerrit.entities.RefNames.REFS_TAGS;
import static com.googlesource.gerrit.plugins.deleteproject.DeleteOwnProjectCapability.DELETE_OWN_PROJECT;
import static com.googlesource.gerrit.plugins.deleteproject.DeleteProjectCapability.DELETE_PROJECT;
import static java.util.stream.Collectors.toSet;

import com.google.common.collect.Iterables;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.api.access.PluginPermission;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.restapi.project.ListChildProjects;
import com.google.gerrit.server.submit.MergeOpRepoManager;
import com.google.gerrit.server.submit.SubmoduleConflictException;
import com.google.gerrit.server.submit.SubmoduleOp;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.googlesource.gerrit.plugins.deleteproject.DeleteProject.Input;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;

@Singleton
class DeletePreconditions {
  private final Configuration config;
  private final Provider<ListChildProjects> listChildProjectsProvider;
  private final Provider<MergeOpRepoManager> mergeOpProvider;
  private final String pluginName;
  private final Provider<InternalChangeQuery> queryProvider;
  private final GitRepositoryManager repoManager;
  private final SubmoduleOp.Factory subOpFactory;
  private final Provider<CurrentUser> userProvider;
  private final ProtectedProjects protectedProjects;
  private final PermissionBackend permissionBackend;

  @Inject
  public DeletePreconditions(
      Configuration config,
      Provider<ListChildProjects> listChildProjectsProvider,
      Provider<MergeOpRepoManager> mergeOpProvider,
      @PluginName String pluginName,
      Provider<InternalChangeQuery> queryProvider,
      GitRepositoryManager repoManager,
      SubmoduleOp.Factory subOpFactory,
      Provider<CurrentUser> userProvider,
      ProtectedProjects protectedProjects,
      PermissionBackend permissionBackend) {
    this.config = config;
    this.listChildProjectsProvider = listChildProjectsProvider;
    this.mergeOpProvider = mergeOpProvider;
    this.pluginName = pluginName;
    this.queryProvider = queryProvider;
    this.repoManager = repoManager;
    this.subOpFactory = subOpFactory;
    this.userProvider = userProvider;
    this.protectedProjects = protectedProjects;
    this.permissionBackend = permissionBackend;
  }

  void assertDeletePermission(ProjectResource rsrc) throws AuthException {
    if (!canDelete(rsrc)) {
      throw new AuthException("not allowed to delete project");
    }
  }

  protected boolean canDelete(ProjectResource rsrc) {
    PermissionBackend.WithUser userPermission = permissionBackend.user(userProvider.get());
    return userPermission.testOrFalse(GlobalPermission.ADMINISTRATE_SERVER)
        || userPermission.testOrFalse(new PluginPermission(pluginName, DELETE_PROJECT))
        || (userPermission.testOrFalse(new PluginPermission(pluginName, DELETE_OWN_PROJECT))
            && userPermission
                .project(rsrc.getNameKey())
                .testOrFalse(ProjectPermission.WRITE_CONFIG));
  }

  void assertCanBeDeleted(ProjectResource rsrc, Input input) throws ResourceConflictException {
    try {
      protectedProjects.assertIsNotProtected(rsrc);
      assertHasNoChildProjects(rsrc);
      Project.NameKey projectNameKey = rsrc.getNameKey();
      assertIsNotSubmodule(projectNameKey);
      assertDeleteWithTags(projectNameKey, input != null && input.preserve);
      assertHasOpenChanges(projectNameKey, input != null && input.force);
    } catch (CannotDeleteProjectException e) {
      throw new ResourceConflictException(e.getMessage());
    }
  }

  public void assertHasOpenChanges(Project.NameKey projectNameKey, boolean force)
      throws CannotDeleteProjectException {
    if (!force) {
      try {
        List<ChangeData> openChanges = queryProvider.get().byProjectOpen(projectNameKey);
        if (!openChanges.isEmpty()) {
          throw new CannotDeleteProjectException(
              String.format("Project '%s' has open changes.", projectNameKey.get()));
        }
      } catch (StorageException e) {
        throw new CannotDeleteProjectException(
            String.format("Unable to verify if '%s' has open changes.", projectNameKey.get()), e);
      }
    }
  }

  private void assertHasNoChildProjects(ProjectResource rsrc) throws CannotDeleteProjectException {
    List<ProjectInfo> children;
    try {
      children = listChildProjectsProvider.get().withLimit(1).apply(rsrc).value();
    } catch (Exception e) {
      throw new CannotDeleteProjectException(
          String.format("Unable to verify if '%s' has children projects.", rsrc.getName()), e);
    }
    if (!children.isEmpty()) {
      throw new CannotDeleteProjectException(
          "Cannot delete project because it has at least one child: "
              + Iterables.getOnlyElement(children).name);
    }
  }

  private void assertIsNotSubmodule(Project.NameKey projectNameKey)
      throws CannotDeleteProjectException {
    try (Repository repo = repoManager.openRepository(projectNameKey);
        MergeOpRepoManager mergeOp = mergeOpProvider.get()) {
      Set<BranchNameKey> branches =
          repo.getRefDatabase().getRefsByPrefix(REFS_HEADS).stream()
              .map(ref -> BranchNameKey.create(projectNameKey, ref.getName()))
              .collect(toSet());
      SubmoduleOp sub = subOpFactory.create(branches, mergeOp);
      for (BranchNameKey b : branches) {
        if (!sub.superProjectSubscriptionsForSubmoduleBranch(b).isEmpty()) {
          throw new CannotDeleteProjectException("Project is subscribed by other projects.");
        }
      }
    } catch (RepositoryNotFoundException e) {
      // we're trying to delete the repository,
      // so this exception should not stop us
    } catch (IOException | SubmoduleConflictException e) {
      throw new CannotDeleteProjectException("Project is subscribed by other projects.", e);
    }
  }

  private void assertDeleteWithTags(Project.NameKey projectNameKey, boolean preserveGitRepository)
      throws CannotDeleteProjectException {
    if (!preserveGitRepository && !config.deletionWithTagsAllowed()) {
      assertHasNoTags(projectNameKey);
    }
  }

  private void assertHasNoTags(Project.NameKey projectNameKey) throws CannotDeleteProjectException {
    try (Repository repo = repoManager.openRepository(projectNameKey)) {
      if (!repo.getRefDatabase().getRefsByPrefix(REFS_TAGS).isEmpty()) {
        throw new CannotDeleteProjectException(
            String.format("Project %s has tags", projectNameKey));
      }
    } catch (IOException e) {
      throw new CannotDeleteProjectException(
          String.format("Unable to verify if project %s has tags", projectNameKey), e);
    }
  }
}

@@ -7194,68 +7194,29 @@
 if (
-!
 sub.
-s
+hasS
 uper
-P
+p
 roject
-SubscriptionsForSubmoduleBranch(b).isEmpty
 (
+b
 )) %7B

Use hasSuperproject to check