3 * Copyright (C) 2009-2017 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.server.computation.task.projectanalysis.component;
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;
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;
41 public class ComponentRootBuilder {
42 private static final String DEFAULT_PROJECT_VERSION = "not provided";
45 * Will supply the UUID for any component in the tree, given it's key.
47 * The String argument of the {@link Function#apply(Object)} method is the component's key.
50 private final Function<String, String> uuidSupplier;
52 * Will supply the {@link ScannerReport.Component} of all the components in the component tree as we crawl it from the
55 * The Integer argument of the {@link Function#apply(Object)} method is the component's ref.
58 private final Function<Integer, ScannerReport.Component> scannerComponentSupplier;
60 * Will supply the ComponentDto of the project (if it exists) if we need it to get the name of the project
62 * @see #nameOfProject(ScannerReport.Component, String, Supplier)
64 private final Supplier<Optional<ComponentDto>> projectDtoSupplier;
66 * Will supply the SnapshotDto of the base analysis of the project (if it exists) if we need it to get the version
69 * The String argument of the {@link Function#apply(Object)} method is the project's UUID.
72 * @see #createProjectVersion(ScannerReport.Component, String, Function)
74 private final Function<String, Optional<SnapshotDto>> analysisSupplier;
76 private final String branch;
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;
87 this.analysisSupplier = analysisSupplier;
90 public Component build(ScannerReport.Component reportProject, String projectKey) {
91 return buildComponent(reportProject, projectKey);
94 private ComponentImpl buildComponent(ScannerReport.Component reportComponent, String latestModuleKey) {
95 switch (reportComponent.getType()) {
97 return buildProjectComponent(reportComponent, latestModuleKey);
99 String moduleKey = createKey(reportComponent.getKey(), branch);
100 return buildOtherComponent(reportComponent, moduleKey, moduleKey);
103 return buildOtherComponent(reportComponent, createEffectiveKey(latestModuleKey, reportComponent.getPath()), latestModuleKey);
105 throw new IllegalArgumentException(format("Unsupported component type '%s'", reportComponent.getType()));
109 private ComponentImpl buildProjectComponent(ScannerReport.Component reportComponent, String latestModuleKey) {
110 ComponentImpl.Builder builder = createCommonBuilder(reportComponent, latestModuleKey, latestModuleKey);
112 .setName(nameOfProject(reportComponent, latestModuleKey, projectDtoSupplier))
113 .setReportAttributes(createProjectReportAttributes(reportComponent, builder.getUuid(), analysisSupplier))
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))
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));
134 private Iterable<Component> buildChildren(ScannerReport.Component component, String latestModuleKey) {
135 return component.getChildRefList()
137 .map(componentRef -> buildComponent(scannerComponentSupplier.apply(componentRef), latestModuleKey))
138 .collect(toList(component.getChildRefList().size()));
141 private static String nameOfProject(ScannerReport.Component project, String projectKey, Supplier<Optional<ComponentDto>> projectDtoSupplier) {
142 String name = trimToNull(project.getName());
144 return projectDtoSupplier.get().transform(ComponentDto::name).or(projectKey);
149 private static String nameOfOthers(ScannerReport.Component reportComponent, String componentKey) {
150 String name = trimToNull(reportComponent.getName());
151 return name == null ? componentKey : name;
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))
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) {
168 Optional<SnapshotDto> snapshotDto = analysisSupplier.apply(projectUuid);
169 if (snapshotDto.isPresent()) {
170 return MoreObjects.firstNonNull(snapshotDto.get().getVersion(), DEFAULT_PROJECT_VERSION);
172 return DEFAULT_PROJECT_VERSION;
176 static ReportAttributes createOtherReportAttributes(ScannerReport.Component component) {
177 return createCommonBuilder(component)
178 .setVersion(trimToNull(component.getVersion()))
182 private static ReportAttributes.Builder createCommonBuilder(ScannerReport.Component component) {
183 return ReportAttributes.newBuilder(component.getRef())
184 .setPath(trimToNull(component.getPath()));
189 static FileAttributes createFileAttributes(ScannerReport.Component component) {
190 if (component.getType() != ScannerReport.Component.ComponentType.FILE) {
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());
201 static Component.Status convertStatus(ScannerReport.Component.FileStatus status) {
204 return Component.Status.ADDED;
206 return Component.Status.SAME;
208 return Component.Status.CHANGED;
210 return Component.Status.UNAVAILABLE;
213 throw new IllegalArgumentException("Unsupported ComponentType value " + status);
218 static Component.Type convertType(ScannerReport.Component.ComponentType type) {
221 return Component.Type.PROJECT;
223 return Component.Type.MODULE;
225 return Component.Type.DIRECTORY;
227 return Component.Type.FILE;
229 throw new IllegalArgumentException("Unsupported ComponentType value " + type);