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.BARRIER)
49 /* temporary workaround - see NewViolationsDecorator */
50 public class ViolationPersisterDecorator implements Decorator {
52 public static final String BARRIER = "ViolationPersisterDecorator";
55 * Those chars would be ignored during generation of checksums.
57 private static final String SPACE_CHARS = "\t\n\r ";
59 private PastViolationsLoader pastViolationsLoader;
60 private ViolationPersister violationPersister;
62 List<String> checksums;
64 public ViolationPersisterDecorator(PastViolationsLoader pastViolationsLoader, ViolationPersister violationPersister) {
65 this.pastViolationsLoader = pastViolationsLoader;
66 this.violationPersister = violationPersister;
69 public boolean shouldExecuteOnProject(Project project) {
73 public void decorate(Resource resource, DecoratorContext context) {
74 if (context.getViolations().isEmpty()) {
77 // Load new violations
78 List<Violation> newViolations = context.getViolations();
80 // Load past violations
81 List<RuleFailureModel> pastViolations = pastViolationsLoader.getPastViolations(resource);
83 // Load current source code and calculate checksums for each line
84 checksums = getChecksums(pastViolationsLoader.getSource(resource));
86 // Map new violations with old ones
87 Map<Violation, RuleFailureModel> violationMap = mapViolations(newViolations, pastViolations);
89 for (Violation newViolation : newViolations) {
90 String checksum = getChecksumForLine(checksums, newViolation.getLineId());
91 violationPersister.saveViolation(context.getProject(), newViolation, violationMap.get(newViolation), checksum);
93 violationPersister.commit();
98 Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) {
99 Map<Violation, RuleFailureModel> violationMap = new IdentityHashMap<Violation, RuleFailureModel>();
101 Multimap<Integer, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create();
102 for (RuleFailureModel pastViolation : pastViolations) {
103 pastViolationsByRule.put(pastViolation.getRuleId(), pastViolation);
106 // Try first an exact matching : same rule, same message, same line and same checkum
107 for (Violation newViolation : newViolations) {
108 mapViolation(newViolation,
109 findPastViolationWithSameLineAndChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
110 pastViolationsByRule, violationMap);
113 // If each new violation matches an old one we can stop the matching mechanism
114 if (violationMap.size() != newViolations.size()) {
116 // Try then to match violations on same rule with same message and with same checkum
117 for (Violation newViolation : newViolations) {
118 if (isNotAlreadyMapped(newViolation, violationMap)) {
119 mapViolation(newViolation,
120 findPastViolationWithSameChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
121 pastViolationsByRule, violationMap);
125 // Try then to match violations on same rule with same line and with same message
126 for (Violation newViolation : newViolations) {
127 if (isNotAlreadyMapped(newViolation, violationMap)) {
128 mapViolation(newViolation,
129 findPastViolationWithSameLineAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
130 pastViolationsByRule, violationMap);
138 private final boolean isNotAlreadyMapped(Violation newViolation, Map<Violation, RuleFailureModel> violationMap) {
139 return violationMap.get(newViolation) == null;
142 private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
143 for (RuleFailureModel pastViolation : pastViolations) {
144 if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
145 return pastViolation;
151 private RuleFailureModel findPastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
152 for (RuleFailureModel pastViolation : pastViolations) {
153 if (isSameChecksum(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
154 return pastViolation;
160 private RuleFailureModel findPastViolationWithSameLineAndChecksumAndMessage(Violation newViolation,
161 Collection<RuleFailureModel> pastViolations) {
162 for (RuleFailureModel pastViolation : pastViolations) {
163 if (isSameLine(newViolation, pastViolation) && isSameChecksum(newViolation, pastViolation)
164 && isSameMessage(newViolation, pastViolation)) {
165 return pastViolation;
171 private final boolean isSameChecksum(Violation newViolation, RuleFailureModel pastViolation) {
172 return pastViolation.getChecksum().equals(getChecksumForLine(checksums, newViolation.getLineId()));
175 private final boolean isSameLine(Violation newViolation, RuleFailureModel pastViolation) {
176 return pastViolation.getLine() == newViolation.getLineId();
179 private final boolean isSameMessage(Violation newViolation, RuleFailureModel pastViolation) {
180 return StringUtils.equals(RuleFailureModel.abbreviateMessage(newViolation.getMessage()), pastViolation.getMessage());
183 private void mapViolation(Violation newViolation, RuleFailureModel pastViolation,
184 Multimap<Integer, RuleFailureModel> pastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) {
185 if (pastViolation != null) {
186 pastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation);
187 violationMap.put(newViolation, pastViolation);
192 * @return checksums, never null
194 private List<String> getChecksums(SnapshotSource source) {
195 return source == null || source.getData() == null ? Collections.<String> emptyList() : getChecksums(source.getData());
199 * @param data can't be null
201 static List<String> getChecksums(String data) {
202 String[] lines = data.split("\r?\n|\r", -1);
203 List<String> result = Lists.newArrayList();
204 for (String line : lines) {
205 result.add(getChecksum(line));
210 static String getChecksum(String line) {
211 String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
212 return DigestUtils.md5Hex(reducedLine);
216 * @return checksum or null if checksum not exists for line
218 private String getChecksumForLine(List<String> checksums, Integer line) {
219 if (line == null || line < 1 || line > checksums.size()) {
222 return checksums.get(line - 1);
226 public String toString() {
227 return getClass().getSimpleName();