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 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 // Try first to match violations on same rule with same line and with same checkum (but not necessarily with same message) 094 for (Violation newViolation : newViolations) { 095 mapViolation(newViolation, 096 findPastViolationWithSameLineAndChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 097 pastViolationsByRule, referenceViolationsMap); 098 } 099 100 // If each new violation matches an old one we can stop the matching mechanism 101 if (referenceViolationsMap.size() != newViolations.size()) { 102 // Try then to match violations on same rule with same message and with same checkum 103 for (Violation newViolation : newViolations) { 104 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) { 105 mapViolation(newViolation, 106 findPastViolationWithSameChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 107 pastViolationsByRule, referenceViolationsMap); 108 } 109 } 110 111 // Try then to match violations on same rule with same line and with same message 112 for (Violation newViolation : newViolations) { 113 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) { 114 mapViolation(newViolation, 115 findPastViolationWithSameLineAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 116 pastViolationsByRule, referenceViolationsMap); 117 } 118 } 119 120 // Last check: match violation if same rule and same checksum but different line and different message 121 // See https://jira.codehaus.org/browse/SONAR-2812 122 for (Violation newViolation : newViolations) { 123 if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) { 124 mapViolation(newViolation, 125 findPastViolationWithSameChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), 126 pastViolationsByRule, referenceViolationsMap); 127 } 128 } 129 } 130 return referenceViolationsMap; 131 } 132 133 private boolean isNotAlreadyMapped(Violation newViolation, Map<Violation, RuleFailureModel> violationMap) { 134 return !violationMap.containsKey(newViolation); 135 } 136 137 private RuleFailureModel findPastViolationWithSameChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 138 for (RuleFailureModel pastViolation : pastViolations) { 139 if (isSameChecksum(newViolation, pastViolation)) { 140 return pastViolation; 141 } 142 } 143 return null; 144 } 145 146 private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 147 for (RuleFailureModel pastViolation : pastViolations) { 148 if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) { 149 return pastViolation; 150 } 151 } 152 return null; 153 } 154 155 private RuleFailureModel findPastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 156 for (RuleFailureModel pastViolation : pastViolations) { 157 if (isSameChecksum(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) { 158 return pastViolation; 159 } 160 } 161 return null; 162 } 163 164 private RuleFailureModel findPastViolationWithSameLineAndChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) { 165 for (RuleFailureModel pastViolation : pastViolations) { 166 if (isSameLine(newViolation, pastViolation) && isSameChecksum(newViolation, pastViolation)) { 167 return pastViolation; 168 } 169 } 170 return null; 171 } 172 173 private boolean isSameChecksum(Violation newViolation, RuleFailureModel pastViolation) { 174 return StringUtils.equals(pastViolation.getChecksum(), newViolation.getChecksum()); 175 } 176 177 private boolean isSameLine(Violation newViolation, RuleFailureModel pastViolation) { 178 return ObjectUtils.equals(pastViolation.getLine(), newViolation.getLineId()); 179 } 180 181 private boolean isSameMessage(Violation newViolation, RuleFailureModel pastViolation) { 182 return StringUtils.equals(RuleFailureModel.abbreviateMessage(newViolation.getMessage()), pastViolation.getMessage()); 183 } 184 185 private void mapViolation(Violation newViolation, RuleFailureModel pastViolation, 186 Multimap<Integer, RuleFailureModel> pastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) { 187 if (pastViolation != null) { 188 newViolation.setCreatedAt(pastViolation.getCreatedAt()); 189 newViolation.setSwitchedOff(pastViolation.isSwitchedOff()); 190 newViolation.setNew(false); 191 pastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation); 192 violationMap.put(newViolation, pastViolation); 193 194 } else { 195 newViolation.setNew(true); 196 newViolation.setCreatedAt(project.getAnalysisDate()); 197 } 198 } 199 200 @Override 201 public String toString() { 202 return getClass().getSimpleName(); 203 } 204 205 }