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