18
18
*/
19
19
20
20
package org.ossreviewtoolkit.plugins.scanners.scanoss
21
-
21
+ import com.scanoss.dto.LicenseDetails
22
22
import com.scanoss.dto.ScanFileDetails
23
23
import com.scanoss.dto.ScanFileResult
24
24
import com.scanoss.dto.enums.MatchType
25
+ import com.scanoss.dto.enums.StatusType
25
26
27
+ import java.lang.invoke.MethodHandles
26
28
import java.time.Instant
27
29
30
+ import org.apache.logging.log4j.kotlin.loggerOf
31
+
28
32
import org.ossreviewtoolkit.downloader.VcsHost
29
33
import org.ossreviewtoolkit.model.CopyrightFinding
30
34
import org.ossreviewtoolkit.model.LicenseFinding
@@ -36,7 +40,8 @@ import org.ossreviewtoolkit.model.TextLocation
36
40
import org.ossreviewtoolkit.utils.spdx.SpdxConstants
37
41
import org.ossreviewtoolkit.utils.spdx.SpdxExpression
38
42
import org.ossreviewtoolkit.utils.spdx.SpdxLicenseIdExpression
39
- import org.ossreviewtoolkit.utils.spdx.toExpression
43
+
44
+ private val logger = loggerOf(MethodHandles .lookup().lookupClass())
40
45
41
46
/* *
42
47
* Generate a summary from the given SCANOSS [result], using [startTime], [endTime] as metadata. This variant can be
@@ -56,16 +61,29 @@ internal fun generateSummary(startTime: Instant, endTime: Instant, results: List
56
61
}
57
62
58
63
MatchType .snippet -> {
59
- val file = requireNotNull(details.file)
60
- val lines = requireNotNull(details.lines)
61
- val sourceLocations = convertLines(file, lines)
62
- val snippets = getSnippets(details)
63
-
64
- snippets.forEach { snippet ->
65
- sourceLocations.forEach { sourceLocation ->
66
- // TODO: Aggregate the snippet by source file location.
67
- snippetFindings + = SnippetFinding (sourceLocation, setOf (snippet))
64
+ val file = requireNotNull(result.filePath)
65
+ if (details.status == StatusType .pending) {
66
+ val lines = requireNotNull(details.lines)
67
+ val sourceLocations = convertLines(file, lines)
68
+ val snippets = getSnippets(details)
69
+
70
+ // The number of snippets should match the number of source locations.
71
+ if (sourceLocations.size != snippets.size) {
72
+ logger.warn {
73
+ " Unexpected mismatch in '$file ': " +
74
+ " ${sourceLocations.size} source locations vs ${snippets.size} snippets. " +
75
+ " This indicates a potential issue with line range conversion."
76
+ }
68
77
}
78
+
79
+ // Associate each source location with its corresponding snippet.
80
+ sourceLocations.zip(snippets).forEach { (location, snippet) ->
81
+ snippetFindings + = SnippetFinding (location, setOf (snippet))
82
+ }
83
+ } else {
84
+ logger.warn { " File '$file ' is identified, not including on snippet findings" }
85
+ licenseFindings + = getLicenseFindings(details)
86
+ copyrightFindings + = getCopyrightFindings(details)
69
87
}
70
88
}
71
89
@@ -134,36 +152,34 @@ private fun getCopyrightFindings(details: ScanFileDetails): List<CopyrightFindin
134
152
}
135
153
136
154
/* *
137
- * Get the snippet findings from the given [details]. If a snippet returned by ScanOSS contains several Purls,
138
- * several snippets are created in ORT each containing a single Purl.
155
+ * Get the snippet findings from the given [details]. If a snippet returned by SCANOSS contains several Purls,
156
+ * the function uses the first PURL as the primary identifier while storing all PURLs in additionalData
157
+ * to preserve the complete information.
139
158
*/
140
- private fun getSnippets (details : ScanFileDetails ): Set <Snippet > {
159
+ private fun getSnippets (details : ScanFileDetails ): List <Snippet > {
141
160
val matched = requireNotNull(details.matched)
142
161
val fileUrl = requireNotNull(details.fileUrl)
143
162
val ossLines = requireNotNull(details.ossLines)
144
163
val url = requireNotNull(details.url)
145
164
val purls = requireNotNull(details.purls)
146
165
147
- val licenses = details.licenseDetails.orEmpty().mapTo(mutableSetOf ()) { license ->
148
- SpdxExpression .parse(license.name)
149
- }
166
+ val license = getUniqueLicenseExpression(details.licenseDetails.toList())
150
167
151
168
val score = matched.substringBeforeLast(" %" ).toFloat()
152
169
val locations = convertLines(fileUrl, ossLines)
153
170
// TODO: No resolved revision is available. Should a ArtifactProvenance be created instead ?
154
171
val vcsInfo = VcsHost .parseUrl(url.takeUnless { it == " none" }.orEmpty())
155
172
val provenance = RepositoryProvenance (vcsInfo, " ." )
156
173
157
- val additionalData = mapOf (" release_date" to details.releaseDate)
158
-
159
- return buildSet {
160
- purls.forEach { purl ->
161
- locations.forEach { snippetLocation ->
162
- val license = licenses.toExpression()?.sorted() ? : SpdxLicenseIdExpression (SpdxConstants .NOASSERTION )
174
+ // Store all PURLs in additionalData to preserve the complete information.
175
+ val additionalData = mapOf (
176
+ " release_date" to details.releaseDate,
177
+ " all_purls" to purls.joinToString(" " )
178
+ )
163
179
164
- add( Snippet (score, snippetLocation, provenance, purl, license, additionalData))
165
- }
166
- }
180
+ // Create one snippet per location, using the first PURL as the primary identifier.
181
+ return locations.map { snippetLocation ->
182
+ Snippet (score, snippetLocation, provenance, purls.firstOrNull().orEmpty(), license, additionalData)
167
183
}
168
184
}
169
185
@@ -180,3 +196,14 @@ private fun convertLines(file: String, lineRanges: String): List<TextLocation> =
180
196
else -> throw IllegalArgumentException (" Unsupported line range '$lineRange '." )
181
197
}
182
198
}
199
+
200
+ fun getUniqueLicenseExpression (licensesDetails : List <LicenseDetails >): SpdxExpression {
201
+ if (licensesDetails.isEmpty()) {
202
+ return SpdxLicenseIdExpression (SpdxConstants .NOASSERTION )
203
+ }
204
+
205
+ return licensesDetails
206
+ .map { license -> SpdxExpression .parse(license.name) }
207
+ .reduce { acc, expr -> acc and expr }
208
+ .simplify()
209
+ }
0 commit comments