@@ -24,8 +24,11 @@ import com.scanoss.dto.ScanFileDetails
2424import com.scanoss.dto.ScanFileResult
2525import com.scanoss.dto.enums.MatchType
2626
27+ import java.lang.invoke.MethodHandles
2728import java.time.Instant
2829
30+ import org.apache.logging.log4j.kotlin.loggerOf
31+
2932import org.ossreviewtoolkit.downloader.VcsHost
3033import org.ossreviewtoolkit.model.CopyrightFinding
3134import org.ossreviewtoolkit.model.LicenseFinding
@@ -38,6 +41,8 @@ import org.ossreviewtoolkit.utils.spdx.SpdxConstants
3841import org.ossreviewtoolkit.utils.spdx.SpdxExpression
3942import org.ossreviewtoolkit.utils.spdx.SpdxLicenseIdExpression
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 -> {
@@ -135,34 +147,37 @@ private fun getCopyrightFindings(details: ScanFileDetails): List<CopyrightFindin
135147
136148/* *
137149 * 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.
150+ * the function uses the first PURL as the primary identifier while storing all 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)
145158 val purls = requireNotNull(details.purls)
146159
147160 val license = getUniqueLicenseExpression(details.licenseDetails.toList())
148161
149162 val score = matched.substringBeforeLast(" %" ).toFloat()
150- val locations = convertLines(fileUrl , ossLines)
163+ val ossLocations = convertLines(ossFile , ossLines)
151164 // TODO: No resolved revision is available. Should a ArtifactProvenance be created instead ?
152165 val vcsInfo = VcsHost .parseUrl(url.takeUnless { it == " none" }.orEmpty())
153166 val provenance = RepositoryProvenance (vcsInfo, " ." )
154167
155- return buildSet {
156- purls.forEach { purl ->
157- locations.forEach { snippetLocation ->
158- add(Snippet (score, snippetLocation, provenance, purl, license))
159- }
160- }
168+ // Store all PURLs in additionalData to preserve the complete information.
169+ val additionalData = mapOf (
170+ " all_purls" to purls.joinToString(" , " )
171+ )
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, purls.firstOrNull().orEmpty(), license, additionalData)
161176 }
162177}
163178
164179/* *
165- * Split [lineRanges] returned by ScanOSS such as "32-105,117-199" into [TextLocation]s for the given [file].
180+ * Split [lineRanges] returned by SCANOSS such as "32-105,117-199" into [TextLocation]s for the given [file].
166181 */
167182private fun convertLines (file : String , lineRanges : String ): List <TextLocation > =
168183 lineRanges.split(' ,' ).map { lineRange ->
0 commit comments