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.StringUtils;
27 import org.sonar.api.batch.*;
28 import org.sonar.api.database.model.RuleFailureModel;
29 import org.sonar.api.database.model.SnapshotSource;
30 import org.sonar.api.resources.Project;
31 import org.sonar.api.resources.Resource;
32 import org.sonar.api.rules.Violation;
33 import org.sonar.batch.components.PastViolationsLoader;
34 import org.sonar.batch.index.ViolationPersister;
38 @DependsUpon({DecoratorBarriers.END_OF_VIOLATIONS_GENERATION, DecoratorBarriers.START_VIOLATION_TRACKING})
39 @DependedUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING)
40 public class ViolationPersisterDecorator implements Decorator {
43 * Those chars would be ignored during generation of checksums.
45 private static final String SPACE_CHARS = "\t\n\r ";
47 private PastViolationsLoader pastViolationsLoader;
48 private ViolationPersister violationPersister;
50 List<String> checksums;
52 public ViolationPersisterDecorator(PastViolationsLoader pastViolationsLoader, ViolationPersister violationPersister) {
53 this.pastViolationsLoader = pastViolationsLoader;
54 this.violationPersister = violationPersister;
57 public boolean shouldExecuteOnProject(Project project) {
61 public void decorate(Resource resource, DecoratorContext context) {
62 if (context.getViolations().isEmpty()) {
65 // Load new violations
66 List<Violation> newViolations = context.getViolations();
68 // Load past violations
69 List<RuleFailureModel> pastViolations = pastViolationsLoader.getPastViolations(resource);
71 // Load current source code and calculate checksums for each line
72 checksums = getChecksums(pastViolationsLoader.getSource(resource));
74 // Map new violations with old ones
75 Map<Violation, RuleFailureModel> violationMap = mapViolations(newViolations, pastViolations);
77 for (Violation newViolation : newViolations) {
78 String checksum = getChecksumForLine(checksums, newViolation.getLineId());
79 violationPersister.saveViolation(context.getProject(), newViolation, violationMap.get(newViolation), checksum);
81 violationPersister.commit();
86 Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) {
87 Map<Violation, RuleFailureModel> violationMap = new IdentityHashMap<Violation, RuleFailureModel>();
89 Multimap<Integer, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create();
90 for (RuleFailureModel pastViolation : pastViolations) {
91 pastViolationsByRule.put(pastViolation.getRuleId(), pastViolation);
94 // Try first an exact matching : same rule, same message, same line and same checkum
95 for (Violation newViolation : newViolations) {
96 mapViolation(newViolation,
97 findPastViolationWithSameLineAndChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
98 pastViolationsByRule, violationMap);
101 // If each new violation matches an old one we can stop the matching mechanism
102 if (violationMap.size() != newViolations.size()) {
104 // Try then to match violations on same rule with same message and with same checkum
105 for (Violation newViolation : newViolations) {
106 if (isNotAlreadyMapped(newViolation, violationMap)) {
107 mapViolation(newViolation,
108 findPastViolationWithSameChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
109 pastViolationsByRule, violationMap);
113 // Try then to match violations on same rule with same line and with same message
114 for (Violation newViolation : newViolations) {
115 if (isNotAlreadyMapped(newViolation, violationMap)) {
116 mapViolation(newViolation,
117 findPastViolationWithSameLineAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
118 pastViolationsByRule, violationMap);
126 private final boolean isNotAlreadyMapped(Violation newViolation, Map<Violation, RuleFailureModel> violationMap) {
127 return violationMap.get(newViolation) == null;
130 private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
131 for (RuleFailureModel pastViolation : pastViolations) {
132 if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
133 return pastViolation;
139 private RuleFailureModel findPastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
140 for (RuleFailureModel pastViolation : pastViolations) {
141 if (isSameChecksum(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
142 return pastViolation;
148 private RuleFailureModel findPastViolationWithSameLineAndChecksumAndMessage(Violation newViolation,
149 Collection<RuleFailureModel> pastViolations) {
150 for (RuleFailureModel pastViolation : pastViolations) {
151 if (isSameLine(newViolation, pastViolation) && isSameChecksum(newViolation, pastViolation)
152 && isSameMessage(newViolation, pastViolation)) {
153 return pastViolation;
159 private boolean isSameChecksum(Violation newViolation, RuleFailureModel pastViolation) {
160 return pastViolation.getChecksum()!=null && StringUtils.equals(pastViolation.getChecksum(), getChecksumForLine(checksums, newViolation.getLineId()));
163 private boolean isSameLine(Violation newViolation, RuleFailureModel pastViolation) {
164 return pastViolation.getLine() == newViolation.getLineId();
167 private boolean isSameMessage(Violation newViolation, RuleFailureModel pastViolation) {
168 return StringUtils.equals(RuleFailureModel.abbreviateMessage(newViolation.getMessage()), pastViolation.getMessage());
171 private void mapViolation(Violation newViolation, RuleFailureModel pastViolation,
172 Multimap<Integer, RuleFailureModel> pastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) {
173 if (pastViolation != null) {
174 pastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation);
175 violationMap.put(newViolation, pastViolation);
180 * @return checksums, never null
182 private List<String> getChecksums(SnapshotSource source) {
183 return source == null || source.getData() == null ? Collections.<String> emptyList() : getChecksums(source.getData());
187 * @param data can't be null
189 static List<String> getChecksums(String data) {
190 String[] lines = data.split("\r?\n|\r", -1);
191 List<String> result = Lists.newArrayList();
192 for (String line : lines) {
193 result.add(getChecksum(line));
198 static String getChecksum(String line) {
199 String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
200 return DigestUtils.md5Hex(reducedLine);
204 * @return checksum or null if checksum not exists for line
206 private String getChecksumForLine(List<String> checksums, Integer line) {
207 if (line == null || line < 1 || line > checksums.size()) {
210 return checksums.get(line - 1);
214 public String toString() {
215 return getClass().getSimpleName();