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.sensors; 021 022 import com.google.common.collect.Maps; 023 import org.sonar.api.batch.Decorator; 024 import org.sonar.api.batch.DecoratorContext; 025 import org.sonar.api.batch.DependedUpon; 026 import org.sonar.api.batch.DependsUpon; 027 import org.sonar.api.measures.CoreMetrics; 028 import org.sonar.api.measures.Measure; 029 import org.sonar.api.measures.MeasureUtils; 030 import org.sonar.api.measures.Metric; 031 import org.sonar.api.resources.Project; 032 import org.sonar.api.resources.Resource; 033 import org.sonar.api.resources.ResourceUtils; 034 import org.sonar.api.rules.Violation; 035 import org.sonar.batch.components.PastSnapshot; 036 import org.sonar.batch.components.TimeMachineConfiguration; 037 import org.sonar.core.review.ReviewDao; 038 import org.sonar.core.review.ReviewDto; 039 import org.sonar.core.review.ReviewPredicates; 040 041 import java.util.*; 042 043 /** 044 * Decorator that creates measures related to reviews. 045 * 046 * @since 2.14 047 */ 048 @DependsUpon(ReviewWorkflowDecorator.END_OF_REVIEWS_UPDATES) 049 public class ReviewsMeasuresDecorator implements Decorator { 050 051 private ReviewDao reviewDao; 052 private TimeMachineConfiguration timeMachineConfiguration; 053 054 public ReviewsMeasuresDecorator(ReviewDao reviewDao, TimeMachineConfiguration timeMachineConfiguration) { 055 this.reviewDao = reviewDao; 056 this.timeMachineConfiguration = timeMachineConfiguration; 057 } 058 059 public boolean shouldExecuteOnProject(Project project) { 060 return project.isLatestAnalysis(); 061 } 062 063 @DependedUpon 064 public Collection<Metric> generatesMetrics() { 065 return Arrays.asList(CoreMetrics.ACTIVE_REVIEWS, CoreMetrics.UNASSIGNED_REVIEWS, CoreMetrics.UNPLANNED_REVIEWS, CoreMetrics.FALSE_POSITIVE_REVIEWS, 066 CoreMetrics.UNREVIEWED_VIOLATIONS, CoreMetrics.NEW_UNREVIEWED_VIOLATIONS); 067 } 068 069 @SuppressWarnings({"rawtypes"}) 070 public void decorate(Resource resource, DecoratorContext context) { 071 if (!ResourceUtils.isPersistable(resource) || ResourceUtils.isUnitTestClass(resource) || resource.getId()==null) { 072 return; 073 } 074 075 // Load open reviews (used for counting and also for tracking new violations without a review) 076 Collection<ReviewDto> openReviews = reviewDao.selectOpenByResourceId(resource.getId(), 077 ReviewPredicates.status(ReviewDto.STATUS_OPEN, ReviewDto.STATUS_REOPENED)); 078 079 Map<Integer, ReviewDto> openReviewsByViolationPermanentId = Maps.newHashMap(); 080 int countUnassigned = 0; 081 int unplanned = 0; 082 for (ReviewDto openReview : openReviews) { 083 openReviewsByViolationPermanentId.put(openReview.getViolationPermanentId(), openReview); 084 if (openReview.getAssigneeId() == null) { 085 countUnassigned++; 086 } 087 if (openReview.getActionPlanId() == null) { 088 unplanned++; 089 } 090 } 091 092 int totalOpenReviews = openReviews.size() + sumChildren(resource, context, CoreMetrics.ACTIVE_REVIEWS); 093 context.saveMeasure(CoreMetrics.ACTIVE_REVIEWS, (double) totalOpenReviews); 094 context.saveMeasure(CoreMetrics.UNASSIGNED_REVIEWS, (double) (countUnassigned + sumChildren(resource, context, CoreMetrics.UNASSIGNED_REVIEWS))); 095 context.saveMeasure(CoreMetrics.UNPLANNED_REVIEWS, (double) (unplanned + sumChildren(resource, context, CoreMetrics.UNPLANNED_REVIEWS))); 096 097 Collection<ReviewDto> falsePositives = reviewDao.selectOpenByResourceId(resource.getId(), 098 ReviewPredicates.resolution(ReviewDto.RESOLUTION_FALSE_POSITIVE)); 099 100 context.saveMeasure(CoreMetrics.FALSE_POSITIVE_REVIEWS, (double) (falsePositives.size() + sumChildren(resource, context, CoreMetrics.FALSE_POSITIVE_REVIEWS))); 101 102 Double violationsCount = MeasureUtils.getValue(context.getMeasure(CoreMetrics.VIOLATIONS), 0.0); 103 context.saveMeasure(CoreMetrics.UNREVIEWED_VIOLATIONS, violationsCount - totalOpenReviews); 104 105 trackNewViolationsWithoutReview(context, openReviewsByViolationPermanentId); 106 } 107 108 protected void trackNewViolationsWithoutReview(DecoratorContext context, Map<Integer, ReviewDto> openReviewsByViolationPermanentIds) { 109 List<Violation> violations = context.getViolations(); 110 Measure measure = new Measure(CoreMetrics.NEW_UNREVIEWED_VIOLATIONS); 111 for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) { 112 int newUnreviewedViolations = countNewUnreviewedViolationsForSnapshot(pastSnapshot, violations, openReviewsByViolationPermanentIds); 113 int variationIndex = pastSnapshot.getIndex(); 114 Collection<Measure> children = context.getChildrenMeasures(CoreMetrics.NEW_UNREVIEWED_VIOLATIONS); 115 double sumNewUnreviewedViolations = MeasureUtils.sumOnVariation(true, variationIndex, children) + newUnreviewedViolations; 116 measure.setVariation(variationIndex, sumNewUnreviewedViolations); 117 } 118 context.saveMeasure(measure); 119 } 120 121 protected int countNewUnreviewedViolationsForSnapshot(PastSnapshot pastSnapshot, List<Violation> violations, 122 Map<Integer, ReviewDto> openReviewsByViolationPermanentIds) { 123 Date targetDate = pastSnapshot.getTargetDate(); 124 int newViolationCount = 0; 125 int newReviewedViolationCount = 0; 126 for (Violation violation : violations) { 127 if (isAfter(violation, targetDate)) { 128 newViolationCount += 1; 129 if (openReviewsByViolationPermanentIds.get(violation.getPermanentId()) != null) { 130 newReviewedViolationCount += 1; 131 } 132 } 133 } 134 return newViolationCount - newReviewedViolationCount; 135 } 136 137 private int sumChildren(Resource<?> resource, DecoratorContext context, Metric metric) { 138 int sum = 0; 139 if (!ResourceUtils.isFile(resource)) { 140 sum = MeasureUtils.sum(true, context.getChildrenMeasures(metric)).intValue(); 141 } 142 return sum; 143 } 144 145 private boolean isAfter(Violation violation, Date date) { 146 if (date == null) { 147 return true; 148 } 149 return violation.getCreatedAt() != null && violation.getCreatedAt().after(date); 150 } 151 152 }