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.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;
39 import java.util.Collection;
40 import java.util.Collections;
41 import java.util.List;
43 @DependsUpon(DecoratorBarriers.END_OF_VIOLATIONS_GENERATION)
44 @DependedUpon("ViolationPersisterDecorator") /* temporary workaround - see NewViolationsDecorator */
45 public class ViolationPersisterDecorator implements Decorator {
48 * Those chars would be ignored during generation of checksums.
50 private static final String SPACE_CHARS = "\t\n\r ";
52 private RuleFinder ruleFinder;
53 private PastViolationsLoader pastViolationsLoader;
54 private ViolationPersister violationPersister;
56 List<String> checksums = Lists.newArrayList();
58 public ViolationPersisterDecorator(RuleFinder ruleFinder, PastViolationsLoader pastViolationsLoader, ViolationPersister violationPersister) {
59 this.ruleFinder = ruleFinder;
60 this.pastViolationsLoader = pastViolationsLoader;
61 this.violationPersister = violationPersister;
64 public boolean shouldExecuteOnProject(Project project) {
68 public void decorate(Resource resource, DecoratorContext context) {
69 if (context.getViolations().isEmpty()) {
72 // Load past violations
73 List<RuleFailureModel> pastViolations = pastViolationsLoader.getPastViolations(resource);
74 // Load current source and calculate checksums
75 checksums = getChecksums(pastViolationsLoader.getSource(resource));
77 compareWithPastViolations(context, pastViolations);
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);
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);
95 String checksum = getChecksumForLine(checksums, violation.getLineId());
96 violationPersister.saveViolation(context.getProject(), violation, pastViolation, checksum);
101 * @return checksums, never null
103 private List<String> getChecksums(SnapshotSource source) {
104 return source == null || source.getData() == null ? Collections.<String>emptyList() : getChecksums(source.getData());
108 * @param data can't be null
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));
119 static String getChecksum(String line) {
120 String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
121 return DigestUtils.md5Hex(reducedLine);
125 * @return checksum or null if checksum not exists for line
127 private String getChecksumForLine(List<String> checksums, Integer line) {
128 if (line == null || line < 1 || line > checksums.size()) {
131 return checksums.get(line - 1);
135 * Search for past violation.
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
143 String dbFormattedMessage = RuleFailureModel.abbreviateMessage(violation.getMessage());
144 RuleFailureModel found = selectPastViolationUsingLine(violation, dbFormattedMessage, pastViolations);
146 found = selectPastViolationUsingChecksum(violation, dbFormattedMessage, pastViolations);
152 * Search for past violation with same message and line.
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;
164 * Search for past violation with same message and checksum.
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) {
172 for (RuleFailureModel pastViolation : pastViolations) {
173 String pastChecksum = pastViolation.getChecksum();
174 if (StringUtils.equals(checksum, pastChecksum) && StringUtils.equals(dbFormattedMessage, pastViolation.getMessage())) {
175 return pastViolation;
182 public String toString() {
183 return getClass().getSimpleName();