@@ -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 [result], using [startTime], [endTime] as metadata. This variant can be
4348 * used if the result is not read from a local file.
@@ -56,17 +61,24 @@ internal fun generateSummary(startTime: Instant, endTime: Instant, results: List
5661 }
5762
5863 MatchType .snippet -> {
59- val file = requireNotNull(details.file )
60- val lines = requireNotNull(details.lines)
61- val sourceLocations = convertLines(file, lines )
64+ val localFile = requireNotNull(result.filePath )
65+ val localLines = requireNotNull(details.lines)
66+ val sourceLocations = convertLines(localFile, localLines )
6267 val snippets = getSnippets(details)
6368
64- snippets.forEach { snippet ->
65- sourceLocations.forEach { sourceLocation ->
66- // TODO: Aggregate the snippet by source file location.
67- snippetFindings + = SnippetFinding (sourceLocation, setOf (snippet))
69+ // The number of snippets should match the number of source locations.
70+ if (sourceLocations.size != snippets.size) {
71+ logger.warn {
72+ " Unexpected mismatch in '$localFile ': " +
73+ " ${sourceLocations.size} source locations vs ${snippets.size} snippets. " +
74+ " This indicates a potential issue with line range conversion."
6875 }
6976 }
77+
78+ // Associate each source location with its corresponding snippet.
79+ sourceLocations.zip(snippets).forEach { (location, snippet) ->
80+ snippetFindings + = SnippetFinding (location, setOf (snippet))
81+ }
7082 }
7183
7284 MatchType .none -> {
@@ -134,32 +146,33 @@ private fun getCopyrightFindings(details: ScanFileDetails): List<CopyrightFindin
134146}
135147
136148/* *
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.
149+ * Get the snippet findings from the given [details]. If a snippet returned by ScanOSS contains several PURLs,
150+ * the function extracts the first PURL as the primary identifier while storing the remaining PURLs in additionalData
151+ * to preserve the complete information.
139152 */
140- private fun getSnippets (details : ScanFileDetails ): Set <Snippet > {
153+ private fun getSnippets (details : ScanFileDetails ): List <Snippet > {
141154 val matched = requireNotNull(details.matched)
142- val fileUrl = requireNotNull(details.fileUrl )
155+ val ossFile = requireNotNull(details.file )
143156 val ossLines = requireNotNull(details.ossLines)
144157 val url = requireNotNull(details.url)
145- val purls = requireNotNull(details.purls)
158+ val purls = requireNotNull(details.purls).toMutableList()
159+ val primaryPurl = purls.removeFirstOrNull().orEmpty()
146160
147161 val license = details.licenseDetails.orEmpty()
148162 .map { license -> SpdxExpression .parse(license.name) }
149163 .toExpression()?.sorted() ? : SpdxLicenseIdExpression (SpdxConstants .NOASSERTION )
150164
151165 val score = matched.substringBeforeLast(" %" ).toFloat()
152- val locations = convertLines(fileUrl , ossLines)
166+ val ossLocations = convertLines(ossFile , ossLines)
153167 // TODO: No resolved revision is available. Should a ArtifactProvenance be created instead ?
154168 val vcsInfo = VcsHost .parseUrl(url.takeUnless { it == " none" }.orEmpty())
155169 val provenance = RepositoryProvenance (vcsInfo, " ." )
156170
157- return buildSet {
158- purls.forEach { purl ->
159- locations.forEach { snippetLocation ->
160- add(Snippet (score, snippetLocation, provenance, purl, license))
161- }
162- }
171+ val additionalData = purls.associateWith { " " }
172+
173+ // Create one snippet per location, using the first PURL as the primary identifier.
174+ return ossLocations.map { snippetLocation ->
175+ Snippet (score, snippetLocation, provenance, primaryPurl, license, additionalData)
163176 }
164177}
165178
0 commit comments