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    }