2 * Sonar, open source software quality management tool.
3 * Copyright (C) 2008-2012 SonarSource
4 * mailto:contact AT sonarsource DOT com
6 * Sonar is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * Sonar is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with Sonar; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
20 package org.sonar.plugins.core.timemachine;
22 import com.google.common.annotations.VisibleForTesting;
23 import com.google.common.collect.*;
24 import org.apache.commons.lang.ObjectUtils;
25 import org.apache.commons.lang.StringUtils;
26 import org.sonar.api.batch.*;
27 import org.sonar.api.database.model.RuleFailureModel;
28 import org.sonar.api.resources.Project;
29 import org.sonar.api.resources.Resource;
30 import org.sonar.api.rules.Violation;
31 import org.sonar.api.violations.ViolationQuery;
35 @DependsUpon({DecoratorBarriers.END_OF_VIOLATIONS_GENERATION, DecoratorBarriers.START_VIOLATION_TRACKING})
36 @DependedUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING)
37 public class ViolationTrackingDecorator implements Decorator {
38 private ReferenceAnalysis referenceAnalysis;
39 private Map<Violation, RuleFailureModel> referenceViolationsMap = Maps.newIdentityHashMap();
40 private SonarIndex index;
41 private Project project;
43 public ViolationTrackingDecorator(Project project, ReferenceAnalysis referenceAnalysis, SonarIndex index) {
44 this.referenceAnalysis = referenceAnalysis;
46 this.project = project;
49 public boolean shouldExecuteOnProject(Project project) {
53 public void decorate(Resource resource, DecoratorContext context) {
54 referenceViolationsMap.clear();
56 ViolationQuery violationQuery = ViolationQuery.create().forResource(resource).setSwitchMode(ViolationQuery.SwitchMode.BOTH);
57 if (context.getViolations(violationQuery).isEmpty()) {
61 String source = index.getSource(resource);
62 String referenceSource = referenceAnalysis.getSource(resource);
64 // Load new violations
65 List<Violation> newViolations = prepareNewViolations(context, source);
67 // Load reference violations
68 List<RuleFailureModel> referenceViolations = referenceAnalysis.getViolations(resource);
70 // SONAR-3072 Construct blocks recognizer based on reference source
71 ViolationTrackingBlocksRecognizer rec = null;
72 if (source != null && referenceSource != null) {
73 rec = new ViolationTrackingBlocksRecognizer(referenceSource, source);
76 // Map new violations with old ones
77 mapViolations(newViolations, referenceViolations, rec);
80 private List<Violation> prepareNewViolations(DecoratorContext context, String source) {
81 List<Violation> result = Lists.newArrayList();
82 List<String> checksums = SourceChecksum.lineChecksumsOfFile(source);
83 for (Violation violation : context.getViolations()) {
84 violation.setChecksum(SourceChecksum.getChecksumForLine(checksums, violation.getLineId()));
85 result.add(violation);
90 public RuleFailureModel getReferenceViolation(Violation violation) {
91 return referenceViolationsMap.get(violation);
95 Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) {
96 return mapViolations(newViolations, pastViolations, null);
99 private Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations, ViolationTrackingBlocksRecognizer rec) {
100 Multimap<Integer, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create();
101 for (RuleFailureModel pastViolation : pastViolations) {
102 pastViolationsByRule.put(pastViolation.getRuleId(), pastViolation);
105 // Match the permanent id of the violation. This id is for example set explicitly when injecting manual violations
106 for (Violation newViolation : newViolations) {
107 mapViolation(newViolation,
108 findPastViolationWithSamePermanentId(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
109 pastViolationsByRule, referenceViolationsMap);
112 // Try first to match violations on same rule with same line and with same checkum (but not necessarily with same message)
113 for (Violation newViolation : newViolations) {
114 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) {
115 mapViolation(newViolation,
116 findPastViolationWithSameLineAndChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
117 pastViolationsByRule, referenceViolationsMap);
121 // If each new violation matches an old one we can stop the matching mechanism
122 if (referenceViolationsMap.size() != newViolations.size()) {
123 // FIXME Godin: this condition just in order to bypass test
127 List<ViolationPair> possiblePairs = Lists.newArrayList();
128 for (Violation newViolation : newViolations) {
129 for (RuleFailureModel pastViolation : pastViolationsByRule.get(newViolation.getRule().getId())) {
130 int weight = rec.computeLengthOfMaximalBlock(pastViolation.getLine() - 1, newViolation.getLineId() - 1);
131 possiblePairs.add(new ViolationPair(pastViolation, newViolation, weight));
134 Collections.sort(possiblePairs, ViolationPair.COMPARATOR);
136 Set<RuleFailureModel> pp = Sets.newHashSet(pastViolations);
137 for (ViolationPair pair : possiblePairs) {
138 Violation newViolation = pair.getNewViolation();
139 RuleFailureModel pastViolation = pair.getPastViolation();
140 if (isNotAlreadyMapped(newViolation, referenceViolationsMap) && pp.contains(pastViolation)) {
141 pp.remove(pastViolation);
142 mapViolation(newViolation, pastViolation, pastViolationsByRule, referenceViolationsMap);
148 // Try then to match violations on same rule with same message and with same checksum
149 for (Violation newViolation : newViolations) {
150 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) {
151 mapViolation(newViolation,
152 findPastViolationWithSameChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
153 pastViolationsByRule, referenceViolationsMap);
157 // Try then to match violations on same rule with same line and with same message
158 for (Violation newViolation : newViolations) {
159 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) {
160 mapViolation(newViolation,
161 findPastViolationWithSameLineAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
162 pastViolationsByRule, referenceViolationsMap);
166 // Last check: match violation if same rule and same checksum but different line and different message
168 for (Violation newViolation : newViolations) {
169 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) {
170 mapViolation(newViolation,
171 findPastViolationWithSameChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
172 pastViolationsByRule, referenceViolationsMap);
176 return referenceViolationsMap;
179 private boolean isNotAlreadyMapped(Violation newViolation, Map<Violation, RuleFailureModel> violationMap) {
180 return !violationMap.containsKey(newViolation);
183 private RuleFailureModel findPastViolationWithSameChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
184 for (RuleFailureModel pastViolation : pastViolations) {
185 if (isSameChecksum(newViolation, pastViolation)) {
186 return pastViolation;
192 private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
193 for (RuleFailureModel pastViolation : pastViolations) {
194 if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
195 return pastViolation;
201 private RuleFailureModel findPastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
202 for (RuleFailureModel pastViolation : pastViolations) {
203 if (isSameChecksum(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
204 return pastViolation;
210 private RuleFailureModel findPastViolationWithSameLineAndChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
211 for (RuleFailureModel pastViolation : pastViolations) {
212 if (isSameLine(newViolation, pastViolation) && isSameChecksum(newViolation, pastViolation)) {
213 return pastViolation;
219 private RuleFailureModel findPastViolationWithSamePermanentId(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
220 for (RuleFailureModel pastViolation : pastViolations) {
221 if (isSamePermanentId(newViolation, pastViolation)) {
222 return pastViolation;
228 private boolean isSameChecksum(Violation newViolation, RuleFailureModel pastViolation) {
229 return StringUtils.equals(pastViolation.getChecksum(), newViolation.getChecksum());
232 private boolean isSameLine(Violation newViolation, RuleFailureModel pastViolation) {
233 return ObjectUtils.equals(pastViolation.getLine(), newViolation.getLineId());
236 private boolean isSameMessage(Violation newViolation, RuleFailureModel pastViolation) {
237 return StringUtils.equals(RuleFailureModel.abbreviateMessage(newViolation.getMessage()), pastViolation.getMessage());
240 private boolean isSamePermanentId(Violation newViolation, RuleFailureModel pastViolation) {
241 return newViolation.getPermanentId() != null && newViolation.getPermanentId().equals(pastViolation.getPermanentId());
244 private void mapViolation(Violation newViolation, RuleFailureModel pastViolation,
245 Multimap<Integer, RuleFailureModel> pastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) {
246 if (pastViolation != null) {
247 newViolation.setCreatedAt(pastViolation.getCreatedAt());
248 newViolation.setPermanentId(pastViolation.getPermanentId());
249 newViolation.setSwitchedOff(pastViolation.isSwitchedOff());
250 newViolation.setPersonId(pastViolation.getPersonId());
251 newViolation.setNew(false);
252 pastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation);
253 violationMap.put(newViolation, pastViolation);
256 newViolation.setNew(true);
257 newViolation.setCreatedAt(project.getAnalysisDate());
262 public String toString() {
263 return getClass().getSimpleName();