001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2012 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020 package org.sonar.plugins.core.timemachine; 021 022 import com.google.common.annotations.VisibleForTesting; 023 import com.google.common.collect.*; 024 import org.apache.commons.lang.ObjectUtils; 025 import org.apache.commons.lang.StringUtils; 026 import org.sonar.api.batch.*; 027 import org.sonar.api.database.model.RuleFailureModel; 028 import org.sonar.api.resources.Project; 029 import org.sonar.api.resources.Resource; 030 import org.sonar.api.rules.Violation; 031 import org.sonar.api.violations.ViolationQuery; 032 033 import java.util.*; 034 035 @DependsUpon({DecoratorBarriers.END_OF_VIOLATIONS_GENERATION, DecoratorBarriers.START_VIOLATION_TRACKING}) 036 @DependedUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING) 037 public class ViolationTrackingDecorator implements Decorator { 038 private ReferenceAnalysis referenceAnalysis; 039 private Map<Violation, RuleFailureModel> referenceViolationsMap = Maps.newIdentityHashMap(); 040 private SonarIndex index; 041 private Project project; 042 043 public ViolationTrackingDecorator(Project project, ReferenceAnalysis referenceAnalysis, SonarIndex index) { 044 this.referenceAnalysis = referenceAnalysis; 045 this.index = index; 046 this.project = project; 047 } 048 049 public boolean shouldExecuteOnProject(Project project) { 050 return true; 051 } 052 053 public void decorate(Resource resource, DecoratorContext context) { 054 referenceViolationsMap.clear(); 055 056 ViolationQuery violationQuery = ViolationQuery.create().forResource(resource).setSwitchMode(ViolationQuery.SwitchMode.BOTH); 057 if (context.getViolations(violationQuery).isEmpty()) { 058 return; 059 } 060 061 String source = index.getSource(resource); 062 063 // Load new violations 064 List<Violation> newViolations = prepareNewViolations(context, source); 065 066 // Load reference violations 067 List<RuleFailureModel> referenceViolations = referenceAnalysis.getViolations(resource); 068 069 // Map new violations with old ones 070 mapViolations(newViolations, referenceViolations, source, resource); 071 } 072 073 private List<Violation> prepareNewViolations(DecoratorContext context, String source) { 074 List<Violation> result = Lists.newArrayList(); 075 List<String> checksums = SourceChecksum.lineChecksumsOfFile(source); 076 for (Violation violation : context.getViolations()) { 077 violation.setChecksum(SourceChecksum.getChecksumForLine(checksums, violation.getLineId())); 078 result.add(violation); 079 } 080 return result; 081 } 082 083 public RuleFailureModel getReferenceViolation(Violation violation) { 084 return referenceViolationsMap.get(violation); 085 } 086 087 @VisibleForTesting 088 Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) { 089 return mapViolations(newViolations, pastViolations, null, null); 090 } 091 092 @VisibleForTesting 093 Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations, String source, Resource resource) { 094 Multimap<Integer, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create(); 095 for (RuleFailureModel pastViolation : pastViolations) { 096 pastViolationsByRule.put(pastViolation.getRuleId(), pastViolation); 097 } 098 099 // Match the permanent id of the violation. This id is for example set explicitly when injecting manual violations 100 for (Violation newViolation : newViolations) { 101 mapViolation(newViolation, 102 findPastViolationWithSamePermanentId(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 103 pastViolationsByRule, referenceViolationsMap); 104 } 105 106 // Try first to match violations on same rule with same line and with same checksum (but not necessarily with same message) 107 for (Violation newViolation : newViolations) { 108 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) { 109 mapViolation(newViolation, 110 findPastViolationWithSameLineAndChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 111 pastViolationsByRule, referenceViolationsMap); 112 } 113 } 114 115 // If each new violation matches an old one we can stop the matching mechanism 116 if (referenceViolationsMap.size() != newViolations.size()) { 117 118 // SONAR-3072 119 ViolationTrackingBlocksRecognizer rec = null; 120 if (source != null && resource != null) { 121 String referenceSource = referenceAnalysis.getSource(resource); 122 if (referenceSource != null) { 123 rec = new ViolationTrackingBlocksRecognizer(referenceSource, source); 124 125 List<ViolationPair> possiblePairs = Lists.newArrayList(); 126 for (Violation newViolation : newViolations) { 127 if (newViolation.getLineId() != null) { 128 for (RuleFailureModel pastViolation : pastViolationsByRule.get(newViolation.getRule().getId())) { 129 if (pastViolation.getLine() != null) { 130 int weight = rec.computeLengthOfMaximalBlock(pastViolation.getLine() - 1, newViolation.getLineId() - 1); 131 possiblePairs.add(new ViolationPair(pastViolation, newViolation, weight)); 132 } 133 } 134 } 135 } 136 Collections.sort(possiblePairs, ViolationPair.COMPARATOR); 137 138 Set<RuleFailureModel> pp = Sets.newHashSet(pastViolations); 139 for (ViolationPair pair : possiblePairs) { 140 Violation newViolation = pair.getNewViolation(); 141 RuleFailureModel pastViolation = pair.getPastViolation(); 142 if (isNotAlreadyMapped(newViolation, referenceViolationsMap) && pp.contains(pastViolation)) { 143 pp.remove(pastViolation); 144 mapViolation(newViolation, pastViolation, pastViolationsByRule, referenceViolationsMap); 145 } 146 } 147 } 148 } 149 150 // Try then to match violations on same rule with same message and with same checksum 151 for (Violation newViolation : newViolations) { 152 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) { 153 mapViolation(newViolation, 154 findPastViolationWithSameChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 155 pastViolationsByRule, referenceViolationsMap); 156 } 157 } 158 159 // Try then to match violations on same rule with same line and with same message 160 for (Violation newViolation : newViolations) { 161 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) { 162 mapViolation(newViolation, 163 findPastViolationWithSameLineAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 164 pastViolationsByRule, referenceViolationsMap); 165 } 166 } 167 168 // Last check: match violation if same rule and same checksum but different line and different message 169 // See SONAR-2812 170 for (Violation newViolation : newViolations) { 171 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) { 172 mapViolation(newViolation, 173 findPastViolationWithSameChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 174 pastViolationsByRule, referenceViolationsMap); 175 } 176 } 177 } 178 return referenceViolationsMap; 179 } 180 181 private boolean isNotAlreadyMapped(Violation newViolation, Map<Violation, RuleFailureModel> violationMap) { 182 return !violationMap.containsKey(newViolation); 183 } 184 185 private RuleFailureModel findPastViolationWithSameChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 186 for (RuleFailureModel pastViolation : pastViolations) { 187 if (isSameChecksum(newViolation, pastViolation)) { 188 return pastViolation; 189 } 190 } 191 return null; 192 } 193 194 private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 195 for (RuleFailureModel pastViolation : pastViolations) { 196 if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) { 197 return pastViolation; 198 } 199 } 200 return null; 201 } 202 203 private RuleFailureModel findPastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 204 for (RuleFailureModel pastViolation : pastViolations) { 205 if (isSameChecksum(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) { 206 return pastViolation; 207 } 208 } 209 return null; 210 } 211 212 private RuleFailureModel findPastViolationWithSameLineAndChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 213 for (RuleFailureModel pastViolation : pastViolations) { 214 if (isSameLine(newViolation, pastViolation) && isSameChecksum(newViolation, pastViolation)) { 215 return pastViolation; 216 } 217 } 218 return null; 219 } 220 221 private RuleFailureModel findPastViolationWithSamePermanentId(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 222 for (RuleFailureModel pastViolation : pastViolations) { 223 if (isSamePermanentId(newViolation, pastViolation)) { 224 return pastViolation; 225 } 226 } 227 return null; 228 } 229 230 private boolean isSameChecksum(Violation newViolation, RuleFailureModel pastViolation) { 231 return StringUtils.equals(pastViolation.getChecksum(), newViolation.getChecksum()); 232 } 233 234 private boolean isSameLine(Violation newViolation, RuleFailureModel pastViolation) { 235 return ObjectUtils.equals(pastViolation.getLine(), newViolation.getLineId()); 236 } 237 238 private boolean isSameMessage(Violation newViolation, RuleFailureModel pastViolation) { 239 return StringUtils.equals(RuleFailureModel.abbreviateMessage(newViolation.getMessage()), pastViolation.getMessage()); 240 } 241 242 private boolean isSamePermanentId(Violation newViolation, RuleFailureModel pastViolation) { 243 return newViolation.getPermanentId() != null && newViolation.getPermanentId().equals(pastViolation.getPermanentId()); 244 } 245 246 private void mapViolation(Violation newViolation, RuleFailureModel pastViolation, 247 Multimap<Integer, RuleFailureModel> pastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) { 248 if (pastViolation != null) { 249 newViolation.setCreatedAt(pastViolation.getCreatedAt()); 250 newViolation.setPermanentId(pastViolation.getPermanentId()); 251 newViolation.setSwitchedOff(pastViolation.isSwitchedOff()); 252 newViolation.setPersonId(pastViolation.getPersonId()); 253 newViolation.setNew(false); 254 pastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation); 255 violationMap.put(newViolation, pastViolation); 256 257 } else { 258 newViolation.setNew(true); 259 newViolation.setCreatedAt(project.getAnalysisDate()); 260 } 261 } 262 263 @Override 264 public String toString() { 265 return getClass().getSimpleName(); 266 } 267 268 }