// 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.httpd.rpc.project;

import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.NoSuchGroupException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.NoSuchRefException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;

import java.util.Collections;

import javax.annotation.Nullable;

class AddRefRight extends Handler<ProjectDetail> {
  interface Factory {
    AddRefRight create(@Assisted Project.NameKey projectName,
        @Assisted ApprovalCategory.Id categoryId,
        @Assisted("groupName") String groupName,
        @Nullable @Assisted("refPattern") String refPattern,
        @Assisted("min") short min, @Assisted("max") short max);
  }

  private final ProjectDetailFactory.Factory projectDetailFactory;
  private final ProjectControl.Factory projectControlFactory;
  private final ProjectCache projectCache;
  private final GroupCache groupCache;
  private final ReviewDb db;
  private final ApprovalTypes approvalTypes;

  private final Project.NameKey projectName;
  private final ApprovalCategory.Id categoryId;
  private final AccountGroup.NameKey groupName;
  private final String refPattern;
  private final short min;
  private final short max;

  @Inject
  AddRefRight(final ProjectDetailFactory.Factory projectDetailFactory,
      final ProjectControl.Factory projectControlFactory,
      final ProjectCache projectCache, final GroupCache groupCache,
      final ReviewDb db, final ApprovalTypes approvalTypes,

      @Assisted final Project.NameKey projectName,
      @Assisted final ApprovalCategory.Id categoryId,
      @Assisted("groupName") final String groupName,
      @Nullable @Assisted("refPattern") final String refPattern,
      @Assisted("min") final short min, @Assisted("max") final short max) {
    this.projectDetailFactory = projectDetailFactory;
    this.projectControlFactory = projectControlFactory;
    this.projectCache = projectCache;
    this.groupCache = groupCache;
    this.approvalTypes = approvalTypes;
    this.db = db;

    this.projectName = projectName;
    this.categoryId = categoryId;
    this.groupName = new AccountGroup.NameKey(groupName);
    this.refPattern = refPattern != null ? refPattern.trim() : null;

    if (min <= max) {
      this.min = min;
      this.max = max;
    } else {
      this.min = max;
      this.max = min;
    }
  }

  @Override
  public ProjectDetail call() throws NoSuchProjectException, OrmException,
      NoSuchGroupException, InvalidNameException, NoSuchRefException {
    final ProjectControl projectControl =
        projectControlFactory.controlFor(projectName);

    final ApprovalType at = approvalTypes.getApprovalType(categoryId);
    if (at == null || at.getValue(min) == null || at.getValue(max) == null) {
      throw new IllegalArgumentException("Invalid category " + categoryId
          + " or range " + min + ".." + max);
    }

    String refPattern = this.refPattern;
    if (refPattern == null || refPattern.isEmpty()) {
      if (categoryId.equals(ApprovalCategory.SUBMIT)
          || categoryId.equals(ApprovalCategory.PUSH_HEAD)) {
        // Explicitly related to a branch head.
        refPattern = Constants.R_HEADS + "*";

      } else if (!at.getCategory().isAction()) {
        // Non actions are approval votes on a change, assume these apply
        // to branch heads only.
        refPattern = Constants.R_HEADS + "*";

      } else if (categoryId.equals(ApprovalCategory.PUSH_TAG)) {
        // Explicitly related to the tag namespace.
        refPattern = Constants.R_TAGS + "*";

      } else if (categoryId.equals(ApprovalCategory.READ)
          || categoryId.equals(ApprovalCategory.OWN)) {
        // Currently these are project-wide rights, so apply that way.
        refPattern = RefRight.ALL;

      } else {
        // Assume project wide for the default.
        refPattern = RefRight.ALL;
      }
    }

    boolean exclusive = refPattern.startsWith("-");
    if (exclusive) {
      refPattern = refPattern.substring(1);
    }

    while (refPattern.startsWith("/")) {
      refPattern = refPattern.substring(1);
    }

    if (refPattern.startsWith(RefRight.REGEX_PREFIX)) {
      String example = RefControl.shortestExample(refPattern);

      if (!example.startsWith(Constants.R_REFS)) {
        refPattern = RefRight.REGEX_PREFIX + Constants.R_HEADS
                + refPattern.substring(RefRight.REGEX_PREFIX.length());
        example = RefControl.shortestExample(refPattern);
      }

      if (!Repository.isValidRefName(example)) {
        throw new InvalidNameException();
      }

    } else {
      if (!refPattern.startsWith(Constants.R_REFS)) {
        refPattern = Constants.R_HEADS + refPattern;
      }

      if (refPattern.endsWith("/*")) {
        final String prefix = refPattern.substring(0, refPattern.length() - 2);
        if (!"refs".equals(prefix) && !Repository.isValidRefName(prefix)) {
          throw new InvalidNameException();
        }
      } else {
        if (!Repository.isValidRefName(refPattern)) {
          throw new InvalidNameException();
        }
      }
    }

    if (exclusive) {
      refPattern = "-" + refPattern;
    }

    if (!projectControl.controlForRef(refPattern).isOwner()) {
      throw new NoSuchRefException(refPattern);
    }

    final AccountGroup group = groupCache.get(groupName);
    if (group == null) {
      throw new NoSuchGroupException(groupName);
    }
    final RefRight.Key key =
        new RefRight.Key(projectName, new RefRight.RefPattern(refPattern),
            categoryId, group.getId());
    RefRight rr = db.refRights().get(key);
    if (rr == null) {
      rr = new RefRight(key);
      rr.setMinValue(min);
      rr.setMaxValue(max);
      db.refRights().insert(Collections.singleton(rr));
    } else {
      rr.setMinValue(min);
      rr.setMaxValue(max);
      db.refRights().update(Collections.singleton(rr));
    }
    projectCache.evictAll();
    return projectDetailFactory.create(projectName).call();
  }
}

@@ -6583,73 +6583,8 @@
  %7D%0A%0A
-    if (exclusive) %7B%0A      refPattern = %22-%22 + refPattern;%0A    %7D%0A%0A
     
@@ -6693,24 +6693,89 @@
 rn);%0A    %7D%0A%0A
+    if (exclusive) %7B%0A      refPattern = %22-%22 + refPattern;%0A    %7D%0A%0A
     final Ac

Fix branch owner adding exclusive ACL