@@ -23,8 +23,11 @@ import com.scanoss.dto.ScanFileDetails
2323import com.scanoss.dto.ScanFileResult
2424import com.scanoss.dto.enums.MatchType
2525
26+ import java.lang.invoke.MethodHandles
2627import java.time.Instant
2728
29+ import org.apache.logging.log4j.kotlin.loggerOf
30+
2831import org.ossreviewtoolkit.downloader.VcsHost
2932import org.ossreviewtoolkit.model.CopyrightFinding
3033import org.ossreviewtoolkit.model.LicenseFinding
@@ -38,6 +41,8 @@ import org.ossreviewtoolkit.utils.spdx.SpdxExpression
3841import org.ossreviewtoolkit.utils.spdx.SpdxLicenseIdExpression
3942import org.ossreviewtoolkit.utils.spdx.toExpression
4043
44+ private val logger = loggerOf(MethodHandles .lookup().lookupClass())
45+
4146/* *
4247 * Generate a summary from the given SCANOSS [results], using [startTime], [endTime] as metadata. This variant can be
4348 * used if the result is not read from a local file.
@@ -62,12 +67,19 @@ internal fun generateSummary(startTime: Instant, endTime: Instant, results: List
6267 val sourceLocations = convertLines(localFile, localLines)
6368 val snippets = getSnippets(details)
6469
65- snippets.forEach { snippet ->
66- sourceLocations.forEach { sourceLocation ->
67- // TODO: Aggregate the snippet by source file location.
68- snippetFindings + = SnippetFinding (sourceLocation, setOf (snippet))
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 '$localFile ': " +
74+ " ${sourceLocations.size} source locations vs ${snippets.size} snippets. " +
75+ " This indicates a potential issue with line range conversion."
6976 }
7077 }
78+
79+ // Associate each source location with its corresponding snippet.
80+ sourceLocations.zip(snippets).forEach { (location, snippet) ->
81+ snippetFindings + = SnippetFinding (location, setOf (snippet))
82+ }
7183 }
7284
7385 MatchType .none -> {
@@ -132,15 +144,17 @@ private fun getCopyrightFindings(details: ScanFileDetails, path: String): List<C
132144}
133145
134146/* *
135- * Get the snippet findings from the given [details]. If a snippet returned by ScanOSS contains several Purls,
136- * several snippets are created in ORT each containing a single Purl.
147+ * Get the snippet findings from the given [details]. If a snippet returned by ScanOSS contains several PURLs,
148+ * the function extracts the first PURL as the primary identifier while storing the remaining PURLs in additionalData
149+ * to preserve the complete information.
137150 */
138- private fun getSnippets (details : ScanFileDetails ): Set <Snippet > {
151+ private fun getSnippets (details : ScanFileDetails ): List <Snippet > {
139152 val matched = requireNotNull(details.matched)
140153 val ossFile = requireNotNull(details.file)
141154 val ossLines = requireNotNull(details.ossLines)
142155 val url = requireNotNull(details.url)
143- val purls = requireNotNull(details.purls)
156+ val purls = requireNotNull(details.purls).toMutableList()
157+ val primaryPurl = purls.removeFirstOrNull().orEmpty()
144158
145159 val license = details.licenseDetails.orEmpty()
146160 .map { license -> SpdxExpression .parse(license.name) }
@@ -152,12 +166,11 @@ private fun getSnippets(details: ScanFileDetails): Set<Snippet> {
152166 val vcsInfo = VcsHost .parseUrl(url.takeUnless { it == " none" }.orEmpty())
153167 val provenance = RepositoryProvenance (vcsInfo, " ." )
154168
155- return buildSet {
156- purls.forEach { purl ->
157- ossLocations.forEach { snippetLocation ->
158- add(Snippet (score, snippetLocation, provenance, purl, license))
159- }
160- }
169+ val additionalData = purls.associateWith { " " }
170+
171+ // Create one snippet per location, using the first PURL as the primary identifier.
172+ return ossLocations.map { snippetLocation ->
173+ Snippet (score, snippetLocation, provenance, primaryPurl, license, additionalData)
161174 }
162175}
163176
0 commit comments