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 java.util.Collection;
23 import java.util.Collections;
24 import java.util.IdentityHashMap;
25 import java.util.List;
28 import org.apache.commons.codec.digest.DigestUtils;
29 import org.apache.commons.lang.StringUtils;
30 import org.sonar.api.batch.Decorator;
31 import org.sonar.api.batch.DecoratorBarriers;
32 import org.sonar.api.batch.DecoratorContext;
33 import org.sonar.api.batch.DependedUpon;
34 import org.sonar.api.batch.DependsUpon;
35 import org.sonar.api.database.model.RuleFailureModel;
36 import org.sonar.api.database.model.SnapshotSource;
37 import org.sonar.api.resources.Project;
38 import org.sonar.api.resources.Resource;
39 import org.sonar.api.rules.Violation;
40 import org.sonar.batch.components.PastViolationsLoader;
41 import org.sonar.batch.index.ViolationPersister;
43 import com.google.common.collect.LinkedHashMultimap;
44 import com.google.common.collect.Lists;
45 import com.google.common.collect.Multimap;
47 @DependsUpon(DecoratorBarriers.END_OF_VIOLATIONS_GENERATION)
48 @DependedUpon("ViolationPersisterDecorator")
49 /* temporary workaround - see NewViolationsDecorator */
50 public class ViolationPersisterDecorator implements Decorator {
53 * Those chars would be ignored during generation of checksums.
55 private static final String SPACE_CHARS = "\t\n\r ";
57 private PastViolationsLoader pastViolationsLoader;
58 private ViolationPersister violationPersister;
60 List<String> checksums;
62 public ViolationPersisterDecorator(PastViolationsLoader pastViolationsLoader, ViolationPersister violationPersister) {
63 this.pastViolationsLoader = pastViolationsLoader;
64 this.violationPersister = violationPersister;
67 public boolean shouldExecuteOnProject(Project project) {
71 public void decorate(Resource resource, DecoratorContext context) {
72 if (context.getViolations().isEmpty()) {
75 // Load new violations
76 List<Violation> newViolations = context.getViolations();
78 // Load past violations
79 List<RuleFailureModel> pastViolations = pastViolationsLoader.getPastViolations(resource);
81 // Load current source code and calculate checksums for each line
82 checksums = getChecksums(pastViolationsLoader.getSource(resource));
84 // Map new violations with old ones
85 Map<Violation, RuleFailureModel> violationMap = mapViolations(newViolations, pastViolations);
87 for (Violation newViolation : newViolations) {
88 String checksum = getChecksumForLine(checksums, newViolation.getLineId());
89 violationPersister.saveViolation(context.getProject(), newViolation, violationMap.get(newViolation), checksum);
91 violationPersister.commit();
96 Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) {
97 Map<Violation, RuleFailureModel> violationMap = new IdentityHashMap<Violation, RuleFailureModel>();
99 Multimap<Integer, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create();
100 for (RuleFailureModel pastViolation : pastViolations) {
101 pastViolationsByRule.put(pastViolation.getRuleId(), pastViolation);
104 // Try first an exact matching : same rule, same message, same line and same checkum
105 for (Violation newViolation : newViolations) {
106 mapViolation(newViolation,
107 findPastViolationWithSameLineAndChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
108 pastViolationsByRule, violationMap);
111 // If each new violation matches an old one we can stop the matching mechanism
112 if (violationMap.size() != newViolations.size()) {
114 // Try then to match violations on same rule with same message and with same checkum
115 for (Violation newViolation : newViolations) {
116 if (isNotAlreadyMapped(newViolation, violationMap)) {
117 mapViolation(newViolation,
118 findPastViolationWithSameChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
119 pastViolationsByRule, violationMap);
123 // Try then to match violations on same rule with same line and with same message
124 for (Violation newViolation : newViolations) {
125 if (isNotAlreadyMapped(newViolation, violationMap)) {
126 mapViolation(newViolation,
127 findPastViolationWithSameLineAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
128 pastViolationsByRule, violationMap);
136 private final boolean isNotAlreadyMapped(Violation newViolation, Map<Violation, RuleFailureModel> violationMap) {
137 return violationMap.get(newViolation) == null;
140 private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
141 for (RuleFailureModel pastViolation : pastViolations) {
142 if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
143 return pastViolation;
149 private RuleFailureModel findPastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
150 for (RuleFailureModel pastViolation : pastViolations) {
151 if (isSameChecksum(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
152 return pastViolation;
158 private RuleFailureModel findPastViolationWithSameLineAndChecksumAndMessage(Violation newViolation,
159 Collection<RuleFailureModel> pastViolations) {
160 for (RuleFailureModel pastViolation : pastViolations) {
161 if (isSameLine(newViolation, pastViolation) && isSameChecksum(newViolation, pastViolation)
162 && isSameMessage(newViolation, pastViolation)) {
163 return pastViolation;
169 private final boolean isSameChecksum(Violation newViolation, RuleFailureModel pastViolation) {
170 return pastViolation.getChecksum().equals(getChecksumForLine(checksums, newViolation.getLineId()));
173 private final boolean isSameLine(Violation newViolation, RuleFailureModel pastViolation) {
174 return pastViolation.getLine() == newViolation.getLineId();
177 private final boolean isSameMessage(Violation newViolation, RuleFailureModel pastViolation) {
178 return StringUtils.equals(RuleFailureModel.abbreviateMessage(newViolation.getMessage()), pastViolation.getMessage());
181 private void mapViolation(Violation newViolation, RuleFailureModel pastViolation,
182 Multimap<Integer, RuleFailureModel> pastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) {
183 if (pastViolation != null) {
184 pastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation);
185 violationMap.put(newViolation, pastViolation);
190 * @return checksums, never null
192 private List<String> getChecksums(SnapshotSource source) {
193 return source == null || source.getData() == null ? Collections.<String> emptyList() : getChecksums(source.getData());
200 static List<String> getChecksums(String data) {
201 String[] lines = data.split("\r?\n|\r", -1);
202 List<String> result = Lists.newArrayList();
203 for (String line : lines) {
204 result.add(getChecksum(line));
209 static String getChecksum(String line) {
210 String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
211 return DigestUtils.md5Hex(reducedLine);
215 * @return checksum or null if checksum not exists for line
217 private String getChecksumForLine(List<String> checksums, Integer line) {
218 if (line == null || line < 1 || line > checksums.size()) {
221 return checksums.get(line - 1);
225 public String toString() {
226 return getClass().getSimpleName();