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