]> source.dussan.org Git - sonarqube.git/blob
8f4c2405d8205546cb8256d82ce78f2a5c7919a5
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2022 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.ce.task.projectanalysis.issue;
21
22 import java.util.Collection;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.stream.Stream;
26 import org.sonar.ce.task.projectanalysis.component.Component;
27 import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
28 import org.sonar.ce.task.projectanalysis.component.FileStatuses;
29 import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids;
30 import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitorAdapter;
31 import org.sonar.ce.task.projectanalysis.util.cache.DiskCache.CacheAppender;
32 import org.sonar.core.issue.DefaultIssue;
33 import org.sonar.core.issue.tracking.Input;
34 import org.sonar.core.util.stream.MoreCollectors;
35
36 import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER;
37
38 public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
39
40   private final ProtoIssueCache protoIssueCache;
41   private final TrackerRawInputFactory rawInputFactory;
42   private final TrackerBaseInputFactory baseInputFactory;
43   private final IssueLifecycle issueLifecycle;
44   private final IssueVisitors issueVisitors;
45   private final IssueTrackingDelegator issueTracking;
46   private final SiblingsIssueMerger issueStatusCopier;
47   private final ReferenceBranchComponentUuids referenceBranchComponentUuids;
48   private final PullRequestSourceBranchMerger pullRequestSourceBranchMerger;
49   private final FileStatuses fileStatuses;
50
51   public IntegrateIssuesVisitor(
52     ProtoIssueCache protoIssueCache,
53     TrackerRawInputFactory rawInputFactory,
54     TrackerBaseInputFactory baseInputFactory,
55     IssueLifecycle issueLifecycle,
56     IssueVisitors issueVisitors,
57     IssueTrackingDelegator issueTracking,
58     SiblingsIssueMerger issueStatusCopier,
59     ReferenceBranchComponentUuids referenceBranchComponentUuids,
60     PullRequestSourceBranchMerger pullRequestSourceBranchMerger,
61     FileStatuses fileStatuses) {
62     super(CrawlerDepthLimit.FILE, POST_ORDER);
63     this.protoIssueCache = protoIssueCache;
64     this.rawInputFactory = rawInputFactory;
65     this.baseInputFactory = baseInputFactory;
66     this.issueLifecycle = issueLifecycle;
67     this.issueVisitors = issueVisitors;
68     this.issueTracking = issueTracking;
69     this.issueStatusCopier = issueStatusCopier;
70     this.referenceBranchComponentUuids = referenceBranchComponentUuids;
71     this.pullRequestSourceBranchMerger = pullRequestSourceBranchMerger;
72     this.fileStatuses = fileStatuses;
73   }
74
75   @Override
76   public void visitAny(Component component) {
77     try (CacheAppender<DefaultIssue> cacheAppender = protoIssueCache.newAppender()) {
78       issueVisitors.beforeComponent(component);
79
80       if (fileStatuses.isDataUnchanged(component)) {
81         // we assume there's a previous analysis of the same branch
82         Input<DefaultIssue> baseIssues = baseInputFactory.create(component);
83         useBaseIssues(component, baseIssues.getIssues(), cacheAppender);
84       } else {
85         Input<DefaultIssue> rawInput = rawInputFactory.create(component);
86         TrackingResult tracking = issueTracking.track(component, rawInput);
87         fillNewOpenIssues(component, tracking.newIssues(), rawInput, cacheAppender);
88         fillExistingOpenIssues(component, tracking.issuesToMerge(), cacheAppender);
89         closeIssues(component, tracking.issuesToClose(), cacheAppender);
90         copyIssues(component, tracking.issuesToCopy(), cacheAppender);
91       }
92       issueVisitors.afterComponent(component);
93     } catch (Exception e) {
94       throw new IllegalStateException(String.format("Fail to process issues of component '%s'", component.getDbKey()), e);
95     }
96   }
97
98   private void useBaseIssues(Component component, Collection<DefaultIssue> dbIssues, CacheAppender<DefaultIssue> cacheAppender) {
99     for (DefaultIssue issue : dbIssues) {
100       process(component, issue, cacheAppender);
101     }
102   }
103
104   private void fillNewOpenIssues(Component component, Stream<DefaultIssue> newIssues, Input<DefaultIssue> rawInput, CacheAppender<DefaultIssue> cacheAppender) {
105     List<DefaultIssue> newIssuesList = newIssues
106       .peek(issueLifecycle::initNewOpenIssue)
107       .collect(MoreCollectors.toList());
108
109     if (newIssuesList.isEmpty()) {
110       return;
111     }
112
113     pullRequestSourceBranchMerger.tryMergeIssuesFromSourceBranchOfPullRequest(component, newIssuesList, rawInput);
114     issueStatusCopier.tryMerge(component, newIssuesList);
115
116     for (DefaultIssue issue : newIssuesList) {
117       process(component, issue, cacheAppender);
118     }
119   }
120
121   private void copyIssues(Component component, Map<DefaultIssue, DefaultIssue> matched, CacheAppender<DefaultIssue> cacheAppender) {
122     for (Map.Entry<DefaultIssue, DefaultIssue> entry : matched.entrySet()) {
123       DefaultIssue raw = entry.getKey();
124       DefaultIssue base = entry.getValue();
125       issueLifecycle.copyExistingOpenIssueFromBranch(raw, base, referenceBranchComponentUuids.getReferenceBranchName());
126       process(component, raw, cacheAppender);
127     }
128   }
129
130   private void fillExistingOpenIssues(Component component, Map<DefaultIssue, DefaultIssue> matched, CacheAppender<DefaultIssue> cacheAppender) {
131     for (Map.Entry<DefaultIssue, DefaultIssue> entry : matched.entrySet()) {
132       DefaultIssue raw = entry.getKey();
133       DefaultIssue base = entry.getValue();
134       issueLifecycle.mergeExistingOpenIssue(raw, base);
135       process(component, raw, cacheAppender);
136     }
137   }
138
139   private void closeIssues(Component component, Stream<DefaultIssue> issues, CacheAppender<DefaultIssue> cacheAppender) {
140     issues.forEach(issue -> {
141       // TODO should replace flag "beingClosed" by express call to transition "automaticClose"
142       issue.setBeingClosed(true);
143       // TODO manual issues -> was updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);. Is it a problem ?
144       process(component, issue, cacheAppender);
145     });
146   }
147
148   private void process(Component component, DefaultIssue issue, CacheAppender<DefaultIssue> cacheAppender) {
149     issueLifecycle.doAutomaticTransition(issue);
150     issueVisitors.onIssue(component, issue);
151     if (issue.isNew() || issue.isChanged() || issue.isCopied() || issue.isNoLongerNewCodeReferenceIssue() || issue.isToBeMigratedAsNewCodeReferenceIssue()) {
152       cacheAppender.append(issue);
153     }
154   }
155
156 }