summaryrefslogtreecommitdiffstats
path: root/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
blob: 73cda7f282bef6e0a3cd3b6674c8fda3eeb3088a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Copyright (C) 2013 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.git;

import static com.google.common.base.Preconditions.checkArgument;

import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.LabelValue;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Collection;
import java.util.List;

/**
 * Normalizes votes on labels according to project config.
 *
 * <p>Votes are recorded in the database for a user based on the state of the project at that time:
 * what labels are defined for the project. The label definition can change between the time a vote
 * is originally made and a later point, for example when a change is submitted. This class
 * normalizes old votes against current project configuration.
 */
@Singleton
public class LabelNormalizer {
  @AutoValue
  public abstract static class Result {
    @VisibleForTesting
    static Result create(
        List<PatchSetApproval> unchanged,
        List<PatchSetApproval> updated,
        List<PatchSetApproval> deleted) {
      return new AutoValue_LabelNormalizer_Result(
          ImmutableList.copyOf(unchanged),
          ImmutableList.copyOf(updated),
          ImmutableList.copyOf(deleted));
    }

    public abstract ImmutableList<PatchSetApproval> unchanged();

    public abstract ImmutableList<PatchSetApproval> updated();

    public abstract ImmutableList<PatchSetApproval> deleted();

    public Iterable<PatchSetApproval> getNormalized() {
      return Iterables.concat(unchanged(), updated());
    }
  }

  private final IdentifiedUser.GenericFactory userFactory;
  private final ProjectCache projectCache;

  @Inject
  LabelNormalizer(IdentifiedUser.GenericFactory userFactory, ProjectCache projectCache) {
    this.userFactory = userFactory;
    this.projectCache = projectCache;
  }

  /**
   * @param notes change containing the given approvals.
   * @param approvals list of approvals.
   * @return copies of approvals normalized to the defined ranges for the label type. Approvals for
   *     unknown labels are not included in the output.
   * @throws OrmException
   */
  public Result normalize(ChangeNotes notes, Collection<PatchSetApproval> approvals)
      throws OrmException, IOException {
    IdentifiedUser user = userFactory.create(notes.getChange().getOwner());
    return normalize(notes, user, approvals);
  }

  /**
   * @param notes change notes containing the given approvals.
   * @param user current user.
   * @param approvals list of approvals.
   * @return copies of approvals normalized to the defined ranges for the label type. Approvals for
   *     unknown labels are not included in the output.
   */
  public Result normalize(
      ChangeNotes notes, CurrentUser user, Collection<PatchSetApproval> approvals)
      throws IOException {
    List<PatchSetApproval> unchanged = Lists.newArrayListWithCapacity(approvals.size());
    List<PatchSetApproval> updated = Lists.newArrayListWithCapacity(approvals.size());
    List<PatchSetApproval> deleted = Lists.newArrayListWithCapacity(approvals.size());
    LabelTypes labelTypes =
        projectCache.checkedGet(notes.getProjectName()).getLabelTypes(notes, user);
    for (PatchSetApproval psa : approvals) {
      Change.Id changeId = psa.getKey().getParentKey().getParentKey();
      checkArgument(
          changeId.equals(notes.getChangeId()),
          "Approval %s does not match change %s",
          psa.getKey(),
          notes.getChange().getKey());
      if (psa.isLegacySubmit()) {
        unchanged.add(psa);
        continue;
      }
      LabelType label = labelTypes.byLabel(psa.getLabelId());
      if (label == null) {
        deleted.add(psa);
        continue;
      }
      PatchSetApproval copy = copy(psa);
      applyTypeFloor(label, copy);
      if (copy.getValue() != psa.getValue()) {
        updated.add(copy);
      } else {
        unchanged.add(psa);
      }
    }
    return Result.create(unchanged, updated, deleted);
  }

  private PatchSetApproval copy(PatchSetApproval src) {
    return new PatchSetApproval(src.getPatchSetId(), src);
  }

  private void applyTypeFloor(LabelType lt, PatchSetApproval a) {
    LabelValue atMin = lt.getMin();
    if (atMin != null && a.getValue() < atMin.getValue()) {
      a.setValue(atMin.getValue());
    }
    LabelValue atMax = lt.getMax();
    if (atMax != null && a.getValue() > atMax.getValue()) {
      a.setValue(atMax.getValue());
    }
  }
}