2 * Sonar, open source software quality management tool.
3 * Copyright (C) 2008-2011 SonarSource
4 * mailto:contact AT sonarsource DOT com
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.
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.
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
20 package org.sonar.plugins.core.timemachine;
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;
42 import java.io.IOException;
43 import java.util.Collection;
44 import java.util.Collections;
45 import java.util.List;
47 @DependsUpon(DecoratorBarriers.END_OF_VIOLATIONS_GENERATION)
48 @DependedUpon("ViolationPersisterDecorator") /* temporary workaround - see NewViolationsDecorator */
49 public class ViolationPersisterDecorator implements Decorator {
52 * Those chars would be ignored during generation of checksums.
54 private static final String SPACE_CHARS = "\t\n\r ";
56 private RuleFinder ruleFinder;
57 private PastViolationsLoader pastViolationsLoader;
58 private ViolationPersister violationPersister;
60 List<String> checksums = Lists.newArrayList();
62 public ViolationPersisterDecorator(RuleFinder ruleFinder, PastViolationsLoader pastViolationsLoader, ViolationPersister violationPersister) {
63 this.ruleFinder = ruleFinder;
64 this.pastViolationsLoader = pastViolationsLoader;
65 this.violationPersister = violationPersister;
68 public boolean shouldExecuteOnProject(Project project) {
72 public void decorate(Resource resource, DecoratorContext context) {
73 if (context.getViolations().isEmpty()) {
76 // Load past violations
77 List<RuleFailureModel> pastViolations = pastViolationsLoader.getPastViolations(resource);
78 // Load current source and calculate checksums
79 checksums = getChecksums(pastViolationsLoader.getSource(resource));
81 compareWithPastViolations(context, pastViolations);
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);
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);
99 String checksum = getChecksumForLine(checksums, violation.getLineId());
100 violationPersister.saveViolation(context.getProject(), violation, pastViolation, checksum);
105 * @return checksums, never null
107 private List<String> getChecksums(SnapshotSource source) {
108 return source == null || source.getData() == null ? Collections.<String>emptyList() : getChecksums(source.getData());
111 static List<String> getChecksums(String data) {
112 List<String> result = Lists.newArrayList();
113 StringInputStream stream = new StringInputStream(data);
115 List<String> lines = IOUtils.readLines(stream);
116 for (String line : lines) {
117 result.add(getChecksum(line));
119 } catch (IOException e) {
120 throw new SonarException("Unable to calculate checksums", e);
123 IOUtils.closeQuietly(stream);
128 static String getChecksum(String line) {
129 String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
130 return DigestUtils.md5Hex(reducedLine);
134 * @return checksum or null if checksum not exists for line
136 private String getChecksumForLine(List<String> checksums, Integer line) {
137 if (line == null || line < 1 || line > checksums.size()) {
140 return checksums.get(line - 1);
144 * Search for past violation.
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
152 String dbFormattedMessage = RuleFailureModel.abbreviateMessage(violation.getMessage());
153 RuleFailureModel found = selectPastViolationUsingLine(violation, dbFormattedMessage, pastViolations);
155 found = selectPastViolationUsingChecksum(violation, dbFormattedMessage, pastViolations);
161 * Search for past violation with same message and line.
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;
173 * Search for past violation with same message and checksum.
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) {
181 for (RuleFailureModel pastViolation : pastViolations) {
182 String pastChecksum = pastViolation.getChecksum();
183 if (StringUtils.equals(checksum, pastChecksum) && StringUtils.equals(dbFormattedMessage, pastViolation.getMessage())) {
184 return pastViolation;
191 public String toString() {
192 return getClass().getSimpleName();