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