]> source.dussan.org Git - sonarqube.git/blob
a01d02f6a3e5e054928644bcd2803f7bc2f27754
[sonarqube.git] /
1 /*
2  * Sonar, open source software quality management tool.
3  * Copyright (C) 2008-2012 SonarSource
4  * mailto:contact AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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
19  */
20 package org.sonar.plugins.core.timemachine;
21
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;
32
33 import java.util.*;
34
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;
42
43   public ViolationTrackingDecorator(Project project, ReferenceAnalysis referenceAnalysis, SonarIndex index) {
44     this.referenceAnalysis = referenceAnalysis;
45     this.index = index;
46     this.project = project;
47   }
48
49   public boolean shouldExecuteOnProject(Project project) {
50     return true;
51   }
52
53   public void decorate(Resource resource, DecoratorContext context) {
54     referenceViolationsMap.clear();
55
56     ViolationQuery violationQuery = ViolationQuery.create().forResource(resource).setSwitchMode(ViolationQuery.SwitchMode.BOTH);
57     if (context.getViolations(violationQuery).isEmpty()) {
58       return;
59     }
60
61     String source = index.getSource(resource);
62     String referenceSource = referenceAnalysis.getSource(resource);
63
64     // Load new violations
65     List<Violation> newViolations = prepareNewViolations(context, source);
66
67     // Load reference violations
68     List<RuleFailureModel> referenceViolations = referenceAnalysis.getViolations(resource);
69
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);
74     }
75
76     // Map new violations with old ones
77     mapViolations(newViolations, referenceViolations, rec);
78   }
79
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);
86     }
87     return result;
88   }
89
90   public RuleFailureModel getReferenceViolation(Violation violation) {
91     return referenceViolationsMap.get(violation);
92   }
93
94   @VisibleForTesting
95   Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) {
96     return mapViolations(newViolations, pastViolations, null);
97   }
98
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);
103     }
104
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);
110     }
111
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);
118       }
119     }
120
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
124       if (rec != null) {
125         // SONAR-3072
126
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));
132           }
133         }
134         Collections.sort(possiblePairs, ViolationPair.COMPARATOR);
135
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);
143           }
144         }
145
146       }
147
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);
154         }
155       }
156
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);
163         }
164       }
165
166       // Last check: match violation if same rule and same checksum but different line and different message
167       // See SONAR-2812
168       for (Violation newViolation : newViolations) {
169         if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) {
170           mapViolation(newViolation,
171               findPastViolationWithSameChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
172               pastViolationsByRule, referenceViolationsMap);
173         }
174       }
175     }
176     return referenceViolationsMap;
177   }
178
179   private boolean isNotAlreadyMapped(Violation newViolation, Map<Violation, RuleFailureModel> violationMap) {
180     return !violationMap.containsKey(newViolation);
181   }
182
183   private RuleFailureModel findPastViolationWithSameChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
184     for (RuleFailureModel pastViolation : pastViolations) {
185       if (isSameChecksum(newViolation, pastViolation)) {
186         return pastViolation;
187       }
188     }
189     return null;
190   }
191
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;
196       }
197     }
198     return null;
199   }
200
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;
205       }
206     }
207     return null;
208   }
209
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;
214       }
215     }
216     return null;
217   }
218
219   private RuleFailureModel findPastViolationWithSamePermanentId(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
220     for (RuleFailureModel pastViolation : pastViolations) {
221       if (isSamePermanentId(newViolation, pastViolation)) {
222         return pastViolation;
223       }
224     }
225     return null;
226   }
227
228   private boolean isSameChecksum(Violation newViolation, RuleFailureModel pastViolation) {
229     return StringUtils.equals(pastViolation.getChecksum(), newViolation.getChecksum());
230   }
231
232   private boolean isSameLine(Violation newViolation, RuleFailureModel pastViolation) {
233     return ObjectUtils.equals(pastViolation.getLine(), newViolation.getLineId());
234   }
235
236   private boolean isSameMessage(Violation newViolation, RuleFailureModel pastViolation) {
237     return StringUtils.equals(RuleFailureModel.abbreviateMessage(newViolation.getMessage()), pastViolation.getMessage());
238   }
239
240   private boolean isSamePermanentId(Violation newViolation, RuleFailureModel pastViolation) {
241     return newViolation.getPermanentId() != null && newViolation.getPermanentId().equals(pastViolation.getPermanentId());
242   }
243
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);
254
255     } else {
256       newViolation.setNew(true);
257       newViolation.setCreatedAt(project.getAnalysisDate());
258     }
259   }
260
261   @Override
262   public String toString() {
263     return getClass().getSimpleName();
264   }
265
266 }