001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2011 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.collect.LinkedHashMultimap; 023 import com.google.common.collect.Lists; 024 import com.google.common.collect.Maps; 025 import com.google.common.collect.Multimap; 026 import org.apache.commons.lang.ObjectUtils; 027 import org.apache.commons.lang.StringUtils; 028 import org.sonar.api.batch.*; 029 import org.sonar.api.database.model.RuleFailureModel; 030 import org.sonar.api.resources.Project; 031 import org.sonar.api.resources.Resource; 032 import org.sonar.api.rules.Violation; 033 import org.sonar.api.violations.ViolationQuery; 034 035 import java.util.Collection; 036 import java.util.List; 037 import java.util.Map; 038 039 @DependsUpon({DecoratorBarriers.END_OF_VIOLATIONS_GENERATION, DecoratorBarriers.START_VIOLATION_TRACKING}) 040 @DependedUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING) 041 public class ViolationTrackingDecorator implements Decorator { 042 private ReferenceAnalysis referenceAnalysis; 043 private Map<Violation, RuleFailureModel> referenceViolationsMap = Maps.newIdentityHashMap(); 044 private SonarIndex index; 045 private Project project; 046 047 public ViolationTrackingDecorator(Project project, ReferenceAnalysis referenceAnalysis, SonarIndex index) { 048 this.referenceAnalysis = referenceAnalysis; 049 this.index = index; 050 this.project = project; 051 } 052 053 public boolean shouldExecuteOnProject(Project project) { 054 return true; 055 } 056 057 public void decorate(Resource resource, DecoratorContext context) { 058 referenceViolationsMap.clear(); 059 060 ViolationQuery violationQuery = ViolationQuery.create().forResource(resource).setSwitchMode(ViolationQuery.SwitchMode.BOTH); 061 if (!context.getViolations(violationQuery).isEmpty()) { 062 // Load new violations 063 List<Violation> newViolations = prepareNewViolations(context); 064 065 // Load reference violations 066 List<RuleFailureModel> referenceViolations = referenceAnalysis.getViolations(resource); 067 068 // Map new violations with old ones 069 mapViolations(newViolations, referenceViolations); 070 } 071 } 072 073 private List<Violation> prepareNewViolations(DecoratorContext context) { 074 List<Violation> result = Lists.newArrayList(); 075 List<String> checksums = SourceChecksum.lineChecksumsOfFile(index.getSource(context.getResource())); 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 Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) { 088 Multimap<Integer, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create(); 089 for (RuleFailureModel pastViolation : pastViolations) { 090 pastViolationsByRule.put(pastViolation.getRuleId(), pastViolation); 091 } 092 093 // Match the permanent id of the violation. This id is for example set explicitly when injecting manual violations 094 for (Violation newViolation : newViolations) { 095 mapViolation(newViolation, 096 findPastViolationWithSamePermanentId(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 097 pastViolationsByRule, referenceViolationsMap); 098 } 099 100 101 // Try first to match violations on same rule with same line and with same checkum (but not necessarily with same message) 102 for (Violation newViolation : newViolations) { 103 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) { 104 mapViolation(newViolation, 105 findPastViolationWithSameLineAndChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 106 pastViolationsByRule, referenceViolationsMap); 107 } 108 } 109 110 // If each new violation matches an old one we can stop the matching mechanism 111 if (referenceViolationsMap.size() != newViolations.size()) { 112 // Try then to match violations on same rule with same message and with same checkum 113 for (Violation newViolation : newViolations) { 114 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) { 115 mapViolation(newViolation, 116 findPastViolationWithSameChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 117 pastViolationsByRule, referenceViolationsMap); 118 } 119 } 120 121 // Try then to match violations on same rule with same line and with same message 122 for (Violation newViolation : newViolations) { 123 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) { 124 mapViolation(newViolation, 125 findPastViolationWithSameLineAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 126 pastViolationsByRule, referenceViolationsMap); 127 } 128 } 129 130 // Last check: match violation if same rule and same checksum but different line and different message 131 // See https://jira.codehaus.org/browse/SONAR-2812 132 for (Violation newViolation : newViolations) { 133 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) { 134 mapViolation(newViolation, 135 findPastViolationWithSameChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 136 pastViolationsByRule, referenceViolationsMap); 137 } 138 } 139 } 140 return referenceViolationsMap; 141 } 142 143 private boolean isNotAlreadyMapped(Violation newViolation, Map<Violation, RuleFailureModel> violationMap) { 144 return !violationMap.containsKey(newViolation); 145 } 146 147 private RuleFailureModel findPastViolationWithSameChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 148 for (RuleFailureModel pastViolation : pastViolations) { 149 if (isSameChecksum(newViolation, pastViolation)) { 150 return pastViolation; 151 } 152 } 153 return null; 154 } 155 156 private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 157 for (RuleFailureModel pastViolation : pastViolations) { 158 if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) { 159 return pastViolation; 160 } 161 } 162 return null; 163 } 164 165 private RuleFailureModel findPastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 166 for (RuleFailureModel pastViolation : pastViolations) { 167 if (isSameChecksum(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) { 168 return pastViolation; 169 } 170 } 171 return null; 172 } 173 174 private RuleFailureModel findPastViolationWithSameLineAndChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 175 for (RuleFailureModel pastViolation : pastViolations) { 176 if (isSameLine(newViolation, pastViolation) && isSameChecksum(newViolation, pastViolation)) { 177 return pastViolation; 178 } 179 } 180 return null; 181 } 182 183 private RuleFailureModel findPastViolationWithSamePermanentId(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 184 for (RuleFailureModel pastViolation : pastViolations) { 185 if (isSamePermanentId(newViolation, pastViolation)) { 186 return pastViolation; 187 } 188 } 189 return null; 190 } 191 192 private boolean isSameChecksum(Violation newViolation, RuleFailureModel pastViolation) { 193 return StringUtils.equals(pastViolation.getChecksum(), newViolation.getChecksum()); 194 } 195 196 private boolean isSameLine(Violation newViolation, RuleFailureModel pastViolation) { 197 return ObjectUtils.equals(pastViolation.getLine(), newViolation.getLineId()); 198 } 199 200 private boolean isSameMessage(Violation newViolation, RuleFailureModel pastViolation) { 201 return StringUtils.equals(RuleFailureModel.abbreviateMessage(newViolation.getMessage()), pastViolation.getMessage()); 202 } 203 204 private boolean isSamePermanentId(Violation newViolation, RuleFailureModel pastViolation) { 205 return newViolation.getPermanentId() != null && newViolation.getPermanentId().equals(pastViolation.getPermanentId()); 206 } 207 208 private void mapViolation(Violation newViolation, RuleFailureModel pastViolation, 209 Multimap<Integer, RuleFailureModel> pastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) { 210 if (pastViolation != null) { 211 newViolation.setCreatedAt(pastViolation.getCreatedAt()); 212 newViolation.setPermanentId(pastViolation.getPermanentId()); 213 newViolation.setSwitchedOff(pastViolation.isSwitchedOff()); 214 newViolation.setCommitter(pastViolation.getCommitter()); 215 newViolation.setNew(false); 216 pastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation); 217 violationMap.put(newViolation, pastViolation); 218 219 } else { 220 newViolation.setNew(true); 221 newViolation.setCreatedAt(project.getAnalysisDate()); 222 } 223 } 224 225 @Override 226 public String toString() { 227 return getClass().getSimpleName(); 228 } 229 230 }