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.apache.commons.lang.math.NumberUtils;
29 import org.sonar.api.batch.*;
30 import org.sonar.api.database.model.RuleFailureModel;
31 import org.sonar.api.database.model.SnapshotSource;
32 import org.sonar.api.resources.Project;
33 import org.sonar.api.resources.Resource;
34 import org.sonar.api.rules.Violation;
35 import org.sonar.batch.components.PastViolationsLoader;
36 import org.sonar.batch.index.ViolationPersister;
40 @DependsUpon({DecoratorBarriers.END_OF_VIOLATIONS_GENERATION, DecoratorBarriers.START_VIOLATION_TRACKING})
41 @DependedUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING)
42 public class ViolationPersisterDecorator implements Decorator {
45 * Those chars would be ignored during generation of checksums.
47 private static final String SPACE_CHARS = "\t\n\r ";
49 private PastViolationsLoader pastViolationsLoader;
50 private ViolationPersister violationPersister;
52 List<String> checksums;
54 public ViolationPersisterDecorator(PastViolationsLoader pastViolationsLoader, ViolationPersister violationPersister) {
55 this.pastViolationsLoader = pastViolationsLoader;
56 this.violationPersister = violationPersister;
59 public boolean shouldExecuteOnProject(Project project) {
63 public void decorate(Resource resource, DecoratorContext context) {
64 if (context.getViolations().isEmpty()) {
67 // Load new violations
68 List<Violation> newViolations = context.getViolations();
70 // Load past violations
71 List<RuleFailureModel> pastViolations = pastViolationsLoader.getPastViolations(resource);
73 // Load current source code and calculate checksums for each line
74 checksums = getChecksums(pastViolationsLoader.getSource(resource));
76 // Map new violations with old ones
77 Map<Violation, RuleFailureModel> violationMap = mapViolations(newViolations, pastViolations);
79 for (Violation newViolation : newViolations) {
80 String checksum = getChecksumForLine(checksums, newViolation.getLineId());
81 violationPersister.saveViolation(context.getProject(), newViolation, violationMap.get(newViolation), checksum);
83 violationPersister.commit();
88 Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) {
89 Map<Violation, RuleFailureModel> violationMap = new IdentityHashMap<Violation, RuleFailureModel>();
91 Multimap<Integer, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create();
92 for (RuleFailureModel pastViolation : pastViolations) {
93 pastViolationsByRule.put(pastViolation.getRuleId(), pastViolation);
96 // Try first an exact matching : same rule, same message, same line and same checkum
97 for (Violation newViolation : newViolations) {
98 mapViolation(newViolation,
99 findPastViolationWithSameLineAndChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
100 pastViolationsByRule, violationMap);
103 // If each new violation matches an old one we can stop the matching mechanism
104 if (violationMap.size() != newViolations.size()) {
106 // Try then to match violations on same rule with same message and with same checkum
107 for (Violation newViolation : newViolations) {
108 if (isNotAlreadyMapped(newViolation, violationMap)) {
109 mapViolation(newViolation,
110 findPastViolationWithSameChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
111 pastViolationsByRule, violationMap);
115 // Try then to match violations on same rule with same line and with same message
116 for (Violation newViolation : newViolations) {
117 if (isNotAlreadyMapped(newViolation, violationMap)) {
118 mapViolation(newViolation,
119 findPastViolationWithSameLineAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())),
120 pastViolationsByRule, violationMap);
128 private boolean isNotAlreadyMapped(Violation newViolation, Map<Violation, RuleFailureModel> violationMap) {
129 return violationMap.get(newViolation) == null;
132 private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
133 for (RuleFailureModel pastViolation : pastViolations) {
134 if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
135 return pastViolation;
141 private RuleFailureModel findPastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) {
142 for (RuleFailureModel pastViolation : pastViolations) {
143 if (isSameChecksum(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) {
144 return pastViolation;
150 private RuleFailureModel findPastViolationWithSameLineAndChecksumAndMessage(Violation newViolation,
151 Collection<RuleFailureModel> pastViolations) {
152 for (RuleFailureModel pastViolation : pastViolations) {
153 if (isSameLine(newViolation, pastViolation) && isSameChecksum(newViolation, pastViolation)
154 && isSameMessage(newViolation, pastViolation)) {
155 return pastViolation;
161 private boolean isSameChecksum(Violation newViolation, RuleFailureModel pastViolation) {
162 return pastViolation.getChecksum()!=null && StringUtils.equals(pastViolation.getChecksum(), getChecksumForLine(checksums, newViolation.getLineId()));
165 private boolean isSameLine(Violation newViolation, RuleFailureModel pastViolation) {
166 if (pastViolation.getLine()==null && newViolation.getLineId()==null) {
169 return ObjectUtils.equals(pastViolation.getLine(), newViolation.getLineId());
172 private boolean isSameMessage(Violation newViolation, RuleFailureModel pastViolation) {
173 return StringUtils.equals(RuleFailureModel.abbreviateMessage(newViolation.getMessage()), pastViolation.getMessage());
176 private void mapViolation(Violation newViolation, RuleFailureModel pastViolation,
177 Multimap<Integer, RuleFailureModel> pastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) {
178 if (pastViolation != null) {
179 pastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation);
180 violationMap.put(newViolation, pastViolation);
185 * @return checksums, never null
187 private List<String> getChecksums(SnapshotSource source) {
188 return source == null || source.getData() == null ? Collections.<String> emptyList() : getChecksums(source.getData());
192 * @param data can't be null
194 static List<String> getChecksums(String data) {
195 String[] lines = data.split("\r?\n|\r", -1);
196 List<String> result = Lists.newArrayList();
197 for (String line : lines) {
198 result.add(getChecksum(line));
203 static String getChecksum(String line) {
204 String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
205 return DigestUtils.md5Hex(reducedLine);
209 * @return checksum or null if checksum not exists for line
211 private String getChecksumForLine(List<String> checksums, Integer line) {
212 if (line == null || line < 1 || line > checksums.size()) {
215 return checksums.get(line - 1);
219 public String toString() {
220 return getClass().getSimpleName();