]> source.dussan.org Git - sonarqube.git/blob
202838755a3bb8443ac1516ac574ffba50d31b7d
[sonarqube.git] /
1 /*
2  * Sonar, open source software quality management tool.
3  * Copyright (C) 2008-2011 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 java.io.IOException;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.IdentityHashMap;
26 import java.util.List;
27 import java.util.Map;
28
29 import org.apache.commons.codec.digest.DigestUtils;
30 import org.apache.commons.io.IOUtils;
31 import org.apache.commons.lang.StringUtils;
32 import org.codehaus.plexus.util.StringInputStream;
33 import org.sonar.api.batch.Decorator;
34 import org.sonar.api.batch.DecoratorBarriers;
35 import org.sonar.api.batch.DecoratorContext;
36 import org.sonar.api.batch.DependedUpon;
37 import org.sonar.api.batch.DependsUpon;
38 import org.sonar.api.database.model.RuleFailureModel;
39 import org.sonar.api.database.model.SnapshotSource;
40 import org.sonar.api.resources.Project;
41 import org.sonar.api.resources.Resource;
42 import org.sonar.api.rules.Violation;
43 import org.sonar.api.utils.SonarException;
44 import org.sonar.batch.components.PastViolationsLoader;
45 import org.sonar.batch.index.ViolationPersister;
46
47 import com.google.common.collect.LinkedHashMultimap;
48 import com.google.common.collect.Lists;
49 import com.google.common.collect.Multimap;
50
51 @DependsUpon(DecoratorBarriers.END_OF_VIOLATIONS_GENERATION)
52 @DependedUpon("ViolationPersisterDecorator")
53 /* temporary workaround - see NewViolationsDecorator */
54 public class ViolationPersisterDecorator implements Decorator {
55
56   /**
57    * Those chars would be ignored during generation of checksums.
58    */
59   private static final String SPACE_CHARS = "\t\n\r ";
60
61   private PastViolationsLoader pastViolationsLoader;
62   private ViolationPersister violationPersister;
63
64   List<String> checksums;
65
66   public ViolationPersisterDecorator(PastViolationsLoader pastViolationsLoader, ViolationPersister violationPersister) {
67     this.pastViolationsLoader = pastViolationsLoader;
68     this.violationPersister = violationPersister;
69   }
70
71   public boolean shouldExecuteOnProject(Project project) {
72     return true;
73   }
74
75   public void decorate(Resource resource, DecoratorContext context) {
76     if (context.getViolations().isEmpty()) {
77       return;
78     }
79     // Load new violations
80     List<Violation> newViolations = context.getViolations();
81
82     // Load past violations
83     List<RuleFailureModel> pastViolations = pastViolationsLoader.getPastViolations(resource);
84
85     // Load current source code and calculate checksums for each line
86     checksums = getChecksums(pastViolationsLoader.getSource(resource));
87
88     // Map new violations with old ones
89     Map<Violation, RuleFailureModel> violationMap = mapViolations(newViolations, pastViolations);
90
91     for (Violation newViolation : newViolations) {
92       String checksum = getChecksumForLine(checksums, newViolation.getLineId());
93       violationPersister.saveViolation(context.getProject(), newViolation, violationMap.get(newViolation), checksum);
94     }
95     violationPersister.commit();
96     // Clear cache
97     checksums.clear();
98   }
99
100   Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) {
101     Map<Violation, RuleFailureModel> violationMap = new IdentityHashMap<Violation, RuleFailureModel>();
102
103     Multimap<Integer, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create();
104     for (RuleFailureModel pastViolation : pastViolations) {
105       pastViolationsByRule.put(pastViolation.getRuleId(), pastViolation);
106     }
107
108     // Try first an exact matching : same rule, same message, same line and same checkum
109     for (Violation newViolation : newViolations) {
110       mapViolation(newViolation,
111           findPastViolationWithSameLineAndChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
112           pastViolationsByRule, violationMap);
113     }
114
115     // If each new violation matches an old one we can stop the matching mechanism
116     if (violationMap.size() != newViolations.size()) {
117
118       // Try then to match violations on same rule with same message and with same checkum
119       for (Violation newViolation : newViolations) {
120         if (isNotAlreadyMapped(newViolation, violationMap)) {
121           mapViolation(newViolation,
122               findPastViolationWithSameChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
123               pastViolationsByRule, violationMap);
124         }
125       }
126
127       // Try then to match violations on same rule with same line and with same message
128       for (Violation newViolation : newViolations) {
129         if (isNotAlreadyMapped(newViolation, violationMap)) {
130           mapViolation(newViolation,
131               findPastViolationWithSameLineAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
132               pastViolationsByRule, violationMap);
133         }
134       }
135     }
136
137     return violationMap;
138   }
139
140   private final boolean isNotAlreadyMapped(Violation newViolation, Map<Violation, RuleFailureModel> violationMap) {
141     return violationMap.get(newViolation) == null;
142   }
143
144   private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
145     for (RuleFailureModel pastViolation : pastViolations) {
146       if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
147         return pastViolation;
148       }
149     }
150     return null;
151   }
152
153   private RuleFailureModel findPastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
154     for (RuleFailureModel pastViolation : pastViolations) {
155       if (isSameChecksum(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
156         return pastViolation;
157       }
158     }
159     return null;
160   }
161
162   private RuleFailureModel findPastViolationWithSameLineAndChecksumAndMessage(Violation newViolation,
163       Collection<RuleFailureModel> pastViolations) {
164     for (RuleFailureModel pastViolation : pastViolations) {
165       if (isSameLine(newViolation, pastViolation) && isSameChecksum(newViolation, pastViolation)
166           && isSameMessage(newViolation, pastViolation)) {
167         return pastViolation;
168       }
169     }
170     return null;
171   }
172
173   private final boolean isSameChecksum(Violation newViolation, RuleFailureModel pastViolation) {
174     return pastViolation.getChecksum().equals(getChecksumForLine(checksums, newViolation.getLineId()));
175   }
176
177   private final boolean isSameLine(Violation newViolation, RuleFailureModel pastViolation) {
178     return pastViolation.getLine() == newViolation.getLineId();
179   }
180
181   private final boolean isSameMessage(Violation newViolation, RuleFailureModel pastViolation) {
182     return StringUtils.equals(RuleFailureModel.abbreviateMessage(newViolation.getMessage()), pastViolation.getMessage());
183   }
184
185   private void mapViolation(Violation newViolation, RuleFailureModel pastViolation,
186       Multimap<Integer, RuleFailureModel> pastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) {
187     if (pastViolation != null) {
188       pastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation);
189       violationMap.put(newViolation, pastViolation);
190     }
191   }
192
193   /**
194    * @return checksums, never null
195    */
196   private List<String> getChecksums(SnapshotSource source) {
197     return source == null || source.getData() == null ? Collections.<String> emptyList() : getChecksums(source.getData());
198   }
199
200   static List<String> getChecksums(String data) {
201     List<String> result = Lists.newArrayList();
202     StringInputStream stream = new StringInputStream(data);
203     try {
204       List<String> lines = IOUtils.readLines(stream);
205       for (String line : lines) {
206         result.add(getChecksum(line));
207       }
208     } catch (IOException e) {
209       throw new SonarException("Unable to calculate checksums", e);
210
211     } finally {
212       IOUtils.closeQuietly(stream);
213     }
214     return result;
215   }
216
217   static String getChecksum(String line) {
218     String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
219     return DigestUtils.md5Hex(reducedLine);
220   }
221
222   /**
223    * @return checksum or null if checksum not exists for line
224    */
225   private String getChecksumForLine(List<String> checksums, Integer line) {
226     if (line == null || line < 1 || line > checksums.size()) {
227       return null;
228     }
229     return checksums.get(line - 1);
230   }
231
232   @Override
233   public String toString() {
234     return getClass().getSimpleName();
235   }
236
237 }