Line data Source code
1 : // Copyright (C) 2013 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.change; 16 : 17 : import static com.google.common.base.Preconditions.checkArgument; 18 : import static com.google.common.collect.ImmutableSet.toImmutableSet; 19 : import static com.google.gerrit.server.project.ProjectCache.illegalState; 20 : 21 : import com.google.auto.value.AutoValue; 22 : import com.google.common.annotations.VisibleForTesting; 23 : import com.google.common.collect.ImmutableSet; 24 : import com.google.common.collect.Streams; 25 : import com.google.gerrit.entities.Change; 26 : import com.google.gerrit.entities.LabelType; 27 : import com.google.gerrit.entities.LabelTypes; 28 : import com.google.gerrit.entities.LabelValue; 29 : import com.google.gerrit.entities.PatchSetApproval; 30 : import com.google.gerrit.server.notedb.ChangeNotes; 31 : import com.google.gerrit.server.project.ProjectCache; 32 : import com.google.inject.Inject; 33 : import com.google.inject.Singleton; 34 : import java.util.Collection; 35 : import java.util.HashSet; 36 : import java.util.Optional; 37 : import java.util.Set; 38 : 39 : /** 40 : * Normalizes votes on labels according to project config. 41 : * 42 : * <p>Votes are recorded in the database for a user based on the state of the project at that time: 43 : * what labels are defined for the project. The label definition can change between the time a vote 44 : * is originally made and a later point, for example when a change is submitted. This class 45 : * normalizes old votes against current project configuration. 46 : */ 47 : @Singleton 48 : public class LabelNormalizer { 49 : @AutoValue 50 103 : public abstract static class Result { 51 : @VisibleForTesting 52 : static Result create( 53 : Set<PatchSetApproval> unchanged, 54 : Set<PatchSetApproval> updated, 55 : Set<PatchSetApproval> deleted) { 56 103 : return new AutoValue_LabelNormalizer_Result( 57 103 : ImmutableSet.copyOf(unchanged), 58 103 : ImmutableSet.copyOf(updated), 59 103 : ImmutableSet.copyOf(deleted)); 60 : } 61 : 62 : public abstract ImmutableSet<PatchSetApproval> unchanged(); 63 : 64 : public abstract ImmutableSet<PatchSetApproval> updated(); 65 : 66 : public abstract ImmutableSet<PatchSetApproval> deleted(); 67 : 68 : public ImmutableSet<PatchSetApproval> getNormalized() { 69 103 : return Streams.concat(unchanged().stream(), updated().stream()) 70 103 : .distinct() 71 103 : .collect(toImmutableSet()); 72 : } 73 : } 74 : 75 : private final ProjectCache projectCache; 76 : 77 : @Inject 78 146 : LabelNormalizer(ProjectCache projectCache) { 79 146 : this.projectCache = projectCache; 80 146 : } 81 : 82 : /** 83 : * Returns copies of approvals normalized to the defined ranges for the label type. Approvals for 84 : * unknown labels are not included in the output 85 : * 86 : * @param notes change notes containing the given approvals. 87 : * @param approvals list of approvals. 88 : */ 89 : public Result normalize(ChangeNotes notes, Collection<PatchSetApproval> approvals) { 90 103 : Set<PatchSetApproval> unchanged = new HashSet<>(approvals.size()); 91 103 : Set<PatchSetApproval> updated = new HashSet<>(approvals.size()); 92 103 : Set<PatchSetApproval> deleted = new HashSet<>(approvals.size()); 93 103 : LabelTypes labelTypes = 94 : projectCache 95 103 : .get(notes.getProjectName()) 96 103 : .orElseThrow(illegalState(notes.getProjectName())) 97 103 : .getLabelTypes(notes); 98 103 : for (PatchSetApproval psa : approvals) { 99 68 : Change.Id changeId = psa.key().patchSetId().changeId(); 100 68 : checkArgument( 101 68 : changeId.equals(notes.getChangeId()), 102 : "Approval %s does not match change %s", 103 68 : psa.key(), 104 68 : notes.getChange().getKey()); 105 68 : if (psa.isLegacySubmit()) { 106 55 : unchanged.add(psa); 107 55 : continue; 108 : } 109 59 : Optional<LabelType> label = labelTypes.byLabel(psa.labelId()); 110 59 : if (!label.isPresent()) { 111 4 : deleted.add(psa); 112 4 : continue; 113 : } 114 59 : PatchSetApproval copy = applyTypeFloor(label.get(), psa); 115 59 : if (copy.value() != psa.value()) { 116 1 : updated.add(copy); 117 : } else { 118 59 : unchanged.add(psa); 119 : } 120 59 : } 121 103 : return Result.create(unchanged, updated, deleted); 122 : } 123 : 124 : private PatchSetApproval applyTypeFloor(LabelType lt, PatchSetApproval a) { 125 59 : PatchSetApproval.Builder b = a.toBuilder(); 126 59 : LabelValue atMin = lt.getMin(); 127 59 : if (atMin != null && a.value() < atMin.getValue()) { 128 0 : b.value(atMin.getValue()); 129 : } 130 59 : LabelValue atMax = lt.getMax(); 131 59 : if (atMax != null && a.value() > atMax.getValue()) { 132 1 : b.value(atMax.getValue()); 133 : } 134 59 : return b.build(); 135 : } 136 : }