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 */
020package org.sonar.plugins.core.sensors;
021
022import com.google.common.collect.Maps;
023import org.sonar.api.batch.Decorator;
024import org.sonar.api.batch.DecoratorContext;
025import org.sonar.api.batch.DependedUpon;
026import org.sonar.api.batch.DependsUpon;
027import org.sonar.api.measures.CoreMetrics;
028import org.sonar.api.measures.Measure;
029import org.sonar.api.measures.MeasureUtils;
030import org.sonar.api.measures.Metric;
031import org.sonar.api.resources.Project;
032import org.sonar.api.resources.Resource;
033import org.sonar.api.resources.ResourceUtils;
034import org.sonar.api.rules.Violation;
035import org.sonar.batch.components.PastSnapshot;
036import org.sonar.batch.components.TimeMachineConfiguration;
037import org.sonar.core.review.ReviewDao;
038import org.sonar.core.review.ReviewDto;
039import org.sonar.core.review.ReviewPredicates;
040
041import 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)
049public 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}