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