]> source.dussan.org Git - sonarqube.git/blob
f3ce8bd0bbef0886b51490908658660341acc710
[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.StringUtils;
9 import org.codehaus.plexus.util.StringInputStream;
10 import org.sonar.api.batch.Decorator;
11 import org.sonar.api.batch.DecoratorBarriers;
12 import org.sonar.api.batch.DecoratorContext;
13 import org.sonar.api.batch.DependsUpon;
14 import org.sonar.api.database.model.RuleFailureModel;
15 import org.sonar.api.database.model.Snapshot;
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   List<String> pastChecksums = 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     Snapshot previousLastSnapshot = pastViolationsLoader.getPreviousLastSnapshot(resource);
57     // Load past violations
58     List<RuleFailureModel> pastViolations = pastViolationsLoader.getPastViolations(previousLastSnapshot);
59     // Load past source and calculate checksums
60     pastChecksums = getChecksums(pastViolationsLoader.getPastSource(previousLastSnapshot));
61     // Load current source and calculate checksums
62     checksums = getChecksums(pastViolationsLoader.getSource(resource));
63     // Save violations
64     compareWithPastViolations(context, pastViolations);
65     // Clear caches
66     checksums.clear();
67     pastChecksums.clear();
68   }
69
70   private void compareWithPastViolations(DecoratorContext context, List<RuleFailureModel> pastViolations) {
71     Multimap<Rule, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create();
72     for (RuleFailureModel pastViolation : pastViolations) {
73       Rule rule = ruleFinder.findById(pastViolation.getRuleId());
74       pastViolationsByRule.put(rule, pastViolation);
75     }
76     // for each violation, search equivalent past violation
77     for (Violation violation : context.getViolations()) {
78       RuleFailureModel pastViolation = selectPastViolation(violation, pastViolationsByRule);
79       if (pastViolation != null) {
80         // remove violation from past, since would be updated and shouldn't affect other violations anymore
81         pastViolationsByRule.remove(violation.getRule(), pastViolation);
82       }
83       violationPersister.saveOrUpdateViolation(context.getProject(), violation, pastViolation);
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         String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
100         result.add(DigestUtils.md5Hex(reducedLine));
101       }
102     } catch (IOException e) {
103       throw new SonarException("Unable to calculate checksums", e);
104     }
105     return result;
106   }
107
108   /**
109    * @return checksum or null if checksum not exists for line
110    */
111   private String getChecksumForLine(List<String> checksums, Integer line) {
112     if (line == null || line < 1 || line > checksums.size()) {
113       return null;
114     }
115     return checksums.get(line - 1);
116   }
117
118   /**
119    * Search for past violation.
120    */
121   RuleFailureModel selectPastViolation(Violation violation, Multimap<Rule, RuleFailureModel> pastViolationsByRule) {
122     // skip violation, if there is no past violations with same rule
123     if (!pastViolationsByRule.containsKey(violation.getRule())) {
124       return null;
125     }
126     Collection<RuleFailureModel> pastViolations = pastViolationsByRule.get(violation.getRule());
127     RuleFailureModel found = selectPastViolationUsingLine(violation, pastViolations);
128     if (found == null) {
129       found = selectPastViolationUsingChecksum(violation, pastViolations);
130     }
131     return found;
132   }
133
134   /**
135    * Search for past violation with same message and line.
136    */
137   private RuleFailureModel selectPastViolationUsingLine(Violation violation, Collection<RuleFailureModel> pastViolations) {
138     for (RuleFailureModel pastViolation : pastViolations) {
139       if (violation.getLineId() == pastViolation.getLine() && StringUtils.equals(violation.getMessage(), pastViolation.getMessage())) {
140         return pastViolation;
141       }
142     }
143     return null;
144   }
145
146   /**
147    * Search for past violation with same message and checksum.
148    */
149   private RuleFailureModel selectPastViolationUsingChecksum(Violation violation, Collection<RuleFailureModel> pastViolations) {
150     String checksum = getChecksumForLine(checksums, violation.getLineId());
151     // skip violation, which not attached to line
152     if (checksum == null) {
153       return null;
154     }
155     for (RuleFailureModel pastViolation : pastViolations) {
156       String pastChecksum = getChecksumForLine(pastChecksums, pastViolation.getLine());
157       if (StringUtils.equals(checksum, pastChecksum) && StringUtils.equals(violation.getMessage(), pastViolation.getMessage())) {
158         return pastViolation;
159       }
160     }
161     return null;
162   }
163
164   @Override
165   public String toString() {
166     return getClass().getSimpleName();
167   }
168
169 }