]> source.dussan.org Git - sonarqube.git/blob
5290bd6b02f2af15d0f2c1552501edd6e05d786a
[sonarqube.git] /
1 package org.sonar.plugins.core.timemachine;
2
3 import com.google.common.collect.LinkedHashMultimap;
4 import com.google.common.collect.Lists;
5 import com.google.common.collect.Multimap;
6 import org.apache.commons.codec.digest.DigestUtils;
7 import org.apache.commons.io.IOUtils;
8 import org.apache.commons.lang.ObjectUtils;
9 import org.apache.commons.lang.StringUtils;
10 import org.codehaus.plexus.util.StringInputStream;
11 import org.sonar.api.batch.Decorator;
12 import org.sonar.api.batch.DecoratorBarriers;
13 import org.sonar.api.batch.DecoratorContext;
14 import org.sonar.api.batch.DependsUpon;
15 import org.sonar.api.database.model.RuleFailureModel;
16 import org.sonar.api.database.model.SnapshotSource;
17 import org.sonar.api.resources.Project;
18 import org.sonar.api.resources.Resource;
19 import org.sonar.api.rules.Rule;
20 import org.sonar.api.rules.RuleFinder;
21 import org.sonar.api.rules.Violation;
22 import org.sonar.api.utils.SonarException;
23 import org.sonar.batch.index.ViolationPersister;
24
25 import java.io.IOException;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.List;
29
30 @DependsUpon(DecoratorBarriers.END_OF_VIOLATIONS_GENERATION)
31 public class ViolationPersisterDecorator implements Decorator {
32
33   /**
34    * Those chars would be ignored during generation of checksums.
35    */
36   private static final String SPACE_CHARS = "\t\n\r ";
37
38   private RuleFinder ruleFinder;
39   private PastViolationsLoader pastViolationsLoader;
40   private ViolationPersister violationPersister;
41
42   List<String> checksums = Lists.newArrayList();
43
44   public ViolationPersisterDecorator(RuleFinder ruleFinder, PastViolationsLoader pastViolationsLoader, ViolationPersister violationPersister) {
45     this.ruleFinder = ruleFinder;
46     this.pastViolationsLoader = pastViolationsLoader;
47     this.violationPersister = violationPersister;
48   }
49
50   public boolean shouldExecuteOnProject(Project project) {
51     return true;
52   }
53
54   public void decorate(Resource resource, DecoratorContext context) {
55     if (context.getViolations().isEmpty()) {
56       return;
57     }
58     // Load past violations
59     List<RuleFailureModel> pastViolations = pastViolationsLoader.getPastViolations(resource);
60     // Load current source and calculate checksums
61     checksums = getChecksums(pastViolationsLoader.getSource(resource));
62     // Save violations
63     compareWithPastViolations(context, pastViolations);
64     // Clear cache
65     checksums.clear();
66   }
67
68   private void compareWithPastViolations(DecoratorContext context, List<RuleFailureModel> pastViolations) {
69     Multimap<Rule, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create();
70     for (RuleFailureModel pastViolation : pastViolations) {
71       Rule rule = ruleFinder.findById(pastViolation.getRuleId());
72       pastViolationsByRule.put(rule, pastViolation);
73     }
74     // for each violation, search equivalent past violation
75     for (Violation violation : context.getViolations()) {
76       RuleFailureModel pastViolation = selectPastViolation(violation, pastViolationsByRule);
77       if (pastViolation != null) {
78         // remove violation from past, since would be updated and shouldn't affect other violations anymore
79         pastViolationsByRule.remove(violation.getRule(), pastViolation);
80       }
81       String checksum = getChecksumForLine(checksums, violation.getLineId());
82       violationPersister.saveOrUpdateViolation(context.getProject(), violation, pastViolation, checksum);
83     }
84   }
85
86   /**
87    * @return checksums, never null
88    */
89   private List<String> getChecksums(SnapshotSource source) {
90     return source == null || source.getData() == null ? Collections.<String> emptyList() : getChecksums(source.getData());
91   }
92
93   static List<String> getChecksums(String data) {
94     List<String> result = Lists.newArrayList();
95     try {
96       List<String> lines = IOUtils.readLines(new StringInputStream(data));
97       for (String line : lines) {
98         result.add(getChecksum(line));
99       }
100     } catch (IOException e) {
101       throw new SonarException("Unable to calculate checksums", e);
102     }
103     return result;
104   }
105
106   static String getChecksum(String line) {
107     String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
108     return DigestUtils.md5Hex(reducedLine);
109   }
110
111   /**
112    * @return checksum or null if checksum not exists for line
113    */
114   private String getChecksumForLine(List<String> checksums, Integer line) {
115     if (line == null || line < 1 || line > checksums.size()) {
116       return null;
117     }
118     return checksums.get(line - 1);
119   }
120
121   /**
122    * Search for past violation.
123    */
124   RuleFailureModel selectPastViolation(Violation violation, Multimap<Rule, RuleFailureModel> pastViolationsByRule) {
125     // skip violation, if there is no past violations with same rule
126     if (!pastViolationsByRule.containsKey(violation.getRule())) {
127       return null;
128     }
129     Collection<RuleFailureModel> pastViolations = pastViolationsByRule.get(violation.getRule());
130     RuleFailureModel found = selectPastViolationUsingLine(violation, pastViolations);
131     if (found == null) {
132       found = selectPastViolationUsingChecksum(violation, pastViolations);
133     }
134     return found;
135   }
136
137   /**
138    * Search for past violation with same message and line.
139    */
140   private RuleFailureModel selectPastViolationUsingLine(Violation violation, Collection<RuleFailureModel> pastViolations) {
141     for (RuleFailureModel pastViolation : pastViolations) {
142       if (ObjectUtils.equals(violation.getLineId(), pastViolation.getLine()) && StringUtils.equals(violation.getMessage(), pastViolation.getMessage())) {
143         return pastViolation;
144       }
145     }
146     return null;
147   }
148
149   /**
150    * Search for past violation with same message and checksum.
151    */
152   private RuleFailureModel selectPastViolationUsingChecksum(Violation violation, Collection<RuleFailureModel> pastViolations) {
153     String checksum = getChecksumForLine(checksums, violation.getLineId());
154     // skip violation, which not attached to line
155     if (checksum == null) {
156       return null;
157     }
158     for (RuleFailureModel pastViolation : pastViolations) {
159       String pastChecksum = pastViolation.getChecksum();
160       if (StringUtils.equals(checksum, pastChecksum) && StringUtils.equals(violation.getMessage(), pastViolation.getMessage())) {
161         return pastViolation;
162       }
163     }
164     return null;
165   }
166
167   @Override
168   public String toString() {
169     return getClass().getSimpleName();
170   }
171
172 }