]> source.dussan.org Git - sonarqube.git/blob
4935390e21d780d45c4b8fa3ad933f5090e6cbcc
[sonarqube.git] /
1 /*
2  * Sonar, open source software quality management tool.
3  * Copyright (C) 2008-2011 SonarSource
4  * mailto:contact AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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
19  */
20 package org.sonar.plugins.core.timemachine;
21
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;
37
38 import java.util.*;
39
40 @DependsUpon({DecoratorBarriers.END_OF_VIOLATIONS_GENERATION, DecoratorBarriers.START_VIOLATION_TRACKING})
41 @DependedUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING)
42 public class ViolationPersisterDecorator implements Decorator {
43
44   /**
45    * Those chars would be ignored during generation of checksums.
46    */
47   private static final String SPACE_CHARS = "\t\n\r ";
48
49   private PastViolationsLoader pastViolationsLoader;
50   private ViolationPersister violationPersister;
51
52   List<String> checksums;
53
54   public ViolationPersisterDecorator(PastViolationsLoader pastViolationsLoader, ViolationPersister violationPersister) {
55     this.pastViolationsLoader = pastViolationsLoader;
56     this.violationPersister = violationPersister;
57   }
58
59   public boolean shouldExecuteOnProject(Project project) {
60     return true;
61   }
62
63   public void decorate(Resource resource, DecoratorContext context) {
64     if (context.getViolations().isEmpty()) {
65       return;
66     }
67     // Load new violations
68     List<Violation> newViolations = context.getViolations();
69
70     // Load past violations
71     List<RuleFailureModel> pastViolations = pastViolationsLoader.getPastViolations(resource);
72
73     // Load current source code and calculate checksums for each line
74     checksums = getChecksums(pastViolationsLoader.getSource(resource));
75
76     // Map new violations with old ones
77     Map<Violation, RuleFailureModel> violationMap = mapViolations(newViolations, pastViolations);
78
79     for (Violation newViolation : newViolations) {
80       String checksum = getChecksumForLine(checksums, newViolation.getLineId());
81       violationPersister.saveViolation(context.getProject(), newViolation, violationMap.get(newViolation), checksum);
82     }
83     violationPersister.commit();
84     // Clear cache
85     checksums.clear();
86   }
87
88   Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) {
89     Map<Violation, RuleFailureModel> violationMap = new IdentityHashMap<Violation, RuleFailureModel>();
90
91     Multimap<Integer, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create();
92     for (RuleFailureModel pastViolation : pastViolations) {
93       pastViolationsByRule.put(pastViolation.getRuleId(), pastViolation);
94     }
95
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);
101     }
102
103     // If each new violation matches an old one we can stop the matching mechanism
104     if (violationMap.size() != newViolations.size()) {
105
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);
112         }
113       }
114
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);
121         }
122       }
123     }
124
125     return violationMap;
126   }
127
128   private boolean isNotAlreadyMapped(Violation newViolation, Map<Violation, RuleFailureModel> violationMap) {
129     return violationMap.get(newViolation) == null;
130   }
131
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;
136       }
137     }
138     return null;
139   }
140
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;
145       }
146     }
147     return null;
148   }
149
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;
156       }
157     }
158     return null;
159   }
160
161   private boolean isSameChecksum(Violation newViolation, RuleFailureModel pastViolation) {
162     return pastViolation.getChecksum()!=null && StringUtils.equals(pastViolation.getChecksum(), getChecksumForLine(checksums, newViolation.getLineId()));
163   }
164
165   private boolean isSameLine(Violation newViolation, RuleFailureModel pastViolation) {
166     if (pastViolation.getLine()==null && newViolation.getLineId()==null) {
167       return true;
168     }
169     return ObjectUtils.equals(pastViolation.getLine(), newViolation.getLineId());
170   }
171
172   private boolean isSameMessage(Violation newViolation, RuleFailureModel pastViolation) {
173     return StringUtils.equals(RuleFailureModel.abbreviateMessage(newViolation.getMessage()), pastViolation.getMessage());
174   }
175
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);
181     }
182   }
183
184   /**
185    * @return checksums, never null
186    */
187   private List<String> getChecksums(SnapshotSource source) {
188     return source == null || source.getData() == null ? Collections.<String> emptyList() : getChecksums(source.getData());
189   }
190
191   /**
192    * @param data can't be null
193    */
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));
199     }
200     return result;
201   }
202
203   static String getChecksum(String line) {
204     String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, "");
205     return DigestUtils.md5Hex(reducedLine);
206   }
207
208   /**
209    * @return checksum or null if checksum not exists for line
210    */
211   private String getChecksumForLine(List<String> checksums, Integer line) {
212     if (line == null || line < 1 || line > checksums.size()) {
213       return null;
214     }
215     return checksums.get(line - 1);
216   }
217
218   @Override
219   public String toString() {
220     return getClass().getSimpleName();
221   }
222
223 }