]> source.dussan.org Git - sonarqube.git/blob
75a964a0e405dbc76a6c174a8bd30e68487f2050
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2017 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.server.computation.task.projectanalysis.component;
21
22 import com.google.common.annotations.VisibleForTesting;
23 import com.google.common.base.MoreObjects;
24 import com.google.common.base.Optional;
25 import com.google.common.base.Supplier;
26 import java.util.function.Function;
27 import javax.annotation.CheckForNull;
28 import javax.annotation.Nullable;
29 import org.sonar.db.component.ComponentDto;
30 import org.sonar.db.component.SnapshotDto;
31 import org.sonar.scanner.protocol.output.ScannerReport;
32
33 import static com.google.common.base.Preconditions.checkArgument;
34 import static com.google.common.collect.Iterables.toArray;
35 import static java.lang.String.format;
36 import static org.apache.commons.lang.StringUtils.trimToNull;
37 import static org.sonar.core.component.ComponentKeys.createEffectiveKey;
38 import static org.sonar.core.component.ComponentKeys.createKey;
39 import static org.sonar.core.util.stream.MoreCollectors.toList;
40
41 public class ComponentRootBuilder {
42   private static final String DEFAULT_PROJECT_VERSION = "not provided";
43
44   /**
45    * Will supply the UUID for any component in the tree, given it's key.
46    * <p>
47    * The String argument of the {@link Function#apply(Object)} method is the component's key.
48    * </p>
49    */
50   private final Function<String, String> uuidSupplier;
51   /**
52    * Will supply the {@link ScannerReport.Component} of all the components in the component tree as we crawl it from the
53    * root.
54    * <p>
55    * The Integer argument of the {@link Function#apply(Object)} method is the component's ref.
56    * </p>
57    */
58   private final Function<Integer, ScannerReport.Component> scannerComponentSupplier;
59   /**
60    * Will supply the ComponentDto of the project (if it exists) if we need it to get the name of the project
61    *
62    * @see #nameOfProject(ScannerReport.Component, String, Supplier)
63    */
64   private final Supplier<Optional<ComponentDto>> projectDtoSupplier;
65   /**
66    * Will supply the SnapshotDto of the base analysis of the project (if it exists) if we need it to get the version
67    * of the project.
68    * <p>
69    * The String argument of the {@link Function#apply(Object)} method is the project's UUID.
70    * </p>
71    *
72    * @see #createProjectVersion(ScannerReport.Component, String, Function)
73    */
74   private final Function<String, Optional<SnapshotDto>> analysisSupplier;
75   @CheckForNull
76   private final String branch;
77
78   public ComponentRootBuilder(@Nullable String branch,
79     Function<String, String> uuidSupplier,
80     Function<Integer, ScannerReport.Component> scannerComponentSupplier,
81     Supplier<Optional<ComponentDto>> projectDtoSupplier,
82     Function<String, Optional<SnapshotDto>> analysisSupplier) {
83     this.uuidSupplier = uuidSupplier;
84     this.scannerComponentSupplier = scannerComponentSupplier;
85     this.projectDtoSupplier = projectDtoSupplier;
86     this.branch = branch;
87     this.analysisSupplier = analysisSupplier;
88   }
89
90   public Component build(ScannerReport.Component reportProject, String projectKey) {
91     return buildComponent(reportProject, projectKey);
92   }
93
94   private ComponentImpl buildComponent(ScannerReport.Component reportComponent, String latestModuleKey) {
95     switch (reportComponent.getType()) {
96       case PROJECT:
97         return buildProjectComponent(reportComponent, latestModuleKey);
98       case MODULE:
99         String moduleKey = createKey(reportComponent.getKey(), branch);
100         return buildOtherComponent(reportComponent, moduleKey, moduleKey);
101       case DIRECTORY:
102       case FILE:
103         return buildOtherComponent(reportComponent, createEffectiveKey(latestModuleKey, reportComponent.getPath()), latestModuleKey);
104       default:
105         throw new IllegalArgumentException(format("Unsupported component type '%s'", reportComponent.getType()));
106     }
107   }
108
109   private ComponentImpl buildProjectComponent(ScannerReport.Component reportComponent, String latestModuleKey) {
110     ComponentImpl.Builder builder = createCommonBuilder(reportComponent, latestModuleKey, latestModuleKey);
111     return builder
112       .setName(nameOfProject(reportComponent, latestModuleKey, projectDtoSupplier))
113       .setReportAttributes(createProjectReportAttributes(reportComponent, builder.getUuid(), analysisSupplier))
114       .build();
115   }
116
117   private ComponentImpl buildOtherComponent(ScannerReport.Component reportComponent, String componentKey, String latestModuleKey) {
118     return createCommonBuilder(reportComponent, componentKey, latestModuleKey)
119       .setName(nameOfOthers(reportComponent, componentKey))
120       .setReportAttributes(createOtherReportAttributes(reportComponent))
121       .build();
122   }
123
124   private ComponentImpl.Builder createCommonBuilder(ScannerReport.Component reportComponent, String componentKey, String latestModuleKey) {
125     return ComponentImpl.builder(convertType(reportComponent.getType()))
126       .setUuid(uuidSupplier.apply(componentKey))
127       .setKey(componentKey)
128       .setStatus(convertStatus(reportComponent.getStatus()))
129       .setDescription(trimToNull(reportComponent.getDescription()))
130       .setFileAttributes(createFileAttributes(reportComponent))
131       .addChildren(toArray(buildChildren(reportComponent, latestModuleKey), Component.class));
132   }
133
134   private Iterable<Component> buildChildren(ScannerReport.Component component, String latestModuleKey) {
135     return component.getChildRefList()
136       .stream()
137       .map(componentRef -> buildComponent(scannerComponentSupplier.apply(componentRef), latestModuleKey))
138       .collect(toList(component.getChildRefList().size()));
139   }
140
141   private static String nameOfProject(ScannerReport.Component project, String projectKey, Supplier<Optional<ComponentDto>> projectDtoSupplier) {
142     String name = trimToNull(project.getName());
143     if (name == null) {
144       return projectDtoSupplier.get().transform(ComponentDto::name).or(projectKey);
145     }
146     return name;
147   }
148
149   private static String nameOfOthers(ScannerReport.Component reportComponent, String componentKey) {
150     String name = trimToNull(reportComponent.getName());
151     return name == null ? componentKey : name;
152   }
153
154   @VisibleForTesting
155   static ReportAttributes createProjectReportAttributes(ScannerReport.Component component,
156     String projectUuid, Function<String, Optional<SnapshotDto>> analysisSupplier) {
157     return createCommonBuilder(component)
158       .setVersion(createProjectVersion(component, projectUuid, analysisSupplier))
159       .build();
160   }
161
162   private static String createProjectVersion(ScannerReport.Component component,
163     String projectUuid, Function<String, Optional<SnapshotDto>> analysisSupplier) {
164     String version = trimToNull(component.getVersion());
165     if (version != null) {
166       return version;
167     }
168     Optional<SnapshotDto> snapshotDto = analysisSupplier.apply(projectUuid);
169     if (snapshotDto.isPresent()) {
170       return MoreObjects.firstNonNull(snapshotDto.get().getVersion(), DEFAULT_PROJECT_VERSION);
171     }
172     return DEFAULT_PROJECT_VERSION;
173   }
174
175   @VisibleForTesting
176   static ReportAttributes createOtherReportAttributes(ScannerReport.Component component) {
177     return createCommonBuilder(component)
178       .setVersion(trimToNull(component.getVersion()))
179       .build();
180   }
181
182   private static ReportAttributes.Builder createCommonBuilder(ScannerReport.Component component) {
183     return ReportAttributes.newBuilder(component.getRef())
184       .setPath(trimToNull(component.getPath()));
185   }
186
187   @VisibleForTesting
188   @CheckForNull
189   static FileAttributes createFileAttributes(ScannerReport.Component component) {
190     if (component.getType() != ScannerReport.Component.ComponentType.FILE) {
191       return null;
192     }
193
194     checkArgument(component.getLines() > 0, "File '%s' has no line", component.getPath());
195     return new FileAttributes(
196       component.getIsTest(),
197       trimToNull(component.getLanguage()),
198       component.getLines());
199   }
200
201   static Component.Status convertStatus(ScannerReport.Component.FileStatus status) {
202     switch(status) {
203       case ADDED:
204         return Component.Status.ADDED;
205       case SAME:
206         return Component.Status.SAME;
207       case CHANGED:
208         return Component.Status.CHANGED;
209       case UNAVAILABLE:
210         return Component.Status.UNAVAILABLE;
211       case UNRECOGNIZED:
212       default:
213         throw new IllegalArgumentException("Unsupported ComponentType value " + status);
214     }
215   }
216
217   @VisibleForTesting
218   static Component.Type convertType(ScannerReport.Component.ComponentType type) {
219     switch (type) {
220       case PROJECT:
221         return Component.Type.PROJECT;
222       case MODULE:
223         return Component.Type.MODULE;
224       case DIRECTORY:
225         return Component.Type.DIRECTORY;
226       case FILE:
227         return Component.Type.FILE;
228       default:
229         throw new IllegalArgumentException("Unsupported ComponentType value " + type);
230     }
231   }
232 }