// Copyright (C) 2012 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.change;

import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.QueryChanges;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;

import org.eclipse.jgit.lib.Constants;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.List;

public class ChangesCollection implements
    RestCollection<TopLevelResource, ChangeResource> {
  private final Provider<ReviewDb> db;
  private final ChangeControl.Factory changeControlFactory;
  private final Provider<QueryChanges> queryFactory;
  private final DynamicMap<RestView<ChangeResource>> views;

  @Inject
  ChangesCollection(
      Provider<ReviewDb> dbProvider,
      ChangeControl.Factory changeControlFactory,
      Provider<QueryChanges> queryFactory,
      DynamicMap<RestView<ChangeResource>> views) {
    this.db = dbProvider;
    this.changeControlFactory = changeControlFactory;
    this.queryFactory = queryFactory;
    this.views = views;
  }

  @Override
  public RestView<TopLevelResource> list() {
    return queryFactory.get();
  }

  @Override
  public DynamicMap<RestView<ChangeResource>> views() {
    return views;
  }

  @Override
  public ChangeResource parse(TopLevelResource root, String id)
      throws ResourceNotFoundException, OrmException,
      UnsupportedEncodingException {
    ParsedId p = new ParsedId(id);
    List<Change> changes = findChanges(p);
    if (changes.size() != 1) {
      throw new ResourceNotFoundException(id);
    }

    ChangeControl control;
    try {
      control = changeControlFactory.validateFor(changes.get(0));
    } catch (NoSuchChangeException e) {
      throw new ResourceNotFoundException(id);
    }
    return new ChangeResource(control);
  }

  private List<Change> findChanges(ParsedId k) throws OrmException {
    if (k.legacyId != null) {
      Change c = db.get().changes().get(k.legacyId);
      if (c != null) {
        return ImmutableList.of(c);
      }
      return Collections.emptyList();
    } else if (k.project == null && k.branch == null && k.changeId != null) {
      return db.get().changes().byKey(new Change.Key(k.changeId)).toList();
    }
    return db.get().changes().byBranchKey(
        k.branch(),
        new Change.Key(k.changeId)).toList();
  }

  private static class ParsedId {
    Change.Id legacyId;
    String project;
    String branch;
    String changeId;

    ParsedId(String id) throws ResourceNotFoundException,
        UnsupportedEncodingException {
      if (id.matches("^[0-9]+$")) {
        legacyId = Change.Id.parse(id);
        return;
      }

      int t2 = id.lastIndexOf('~');
      int t1 = id.lastIndexOf('~', t2 - 1);
      if (t1 < 0 || t2 < 0) {
        if (!id.matches("^I[0-9a-z]{40}$")) {
          throw new ResourceNotFoundException(id);
        }
        changeId = id;
        return;
      }

      project = URLDecoder.decode(id.substring(0, t1), "UTF-8");
      branch = URLDecoder.decode(id.substring(t1 + 1, t2), "UTF-8");
      changeId = URLDecoder.decode(id.substring(t2 + 1), "UTF-8");

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

    Branch.NameKey branch() {
      return new Branch.NameKey(new Project.NameKey(project), branch);
    }
  }
}

@@ -3428,65 +3428,214 @@
     
-return db.get().changes().byKey(new Change.Key(k.changeId
+Change.Key id = new Change.Key(k.changeId);%0A      if (id.get().length() == 41) %7B%0A        return db.get().changes().byKey(id).toList();%0A      %7D else %7B%0A        return db.get().changes().byKeyRange(id, id.max(
 )).t
@@ -3639,24 +3639,32 @@
 ).toList();%0A
+      %7D%0A
     %7D%0A    re
@@ -4015,13 +4015,18 @@
 (%22%5E%5B
+1-9%5D%5B
 0-9%5D
-+
+*
 $%22))
@@ -4199,24 +4199,24 @@
 %7C t2 %3C 0) %7B%0A
-
         if (
@@ -4239,16 +4239,18 @@
 0-9a-z%5D%7B
+4,
 40%7D$%22)) 

Allow abbreviated Change-Id strings in /changes/ URLs