From 66dbb297125d4f6cb51a31425771a70cc1c31c42 Mon Sep 17 00:00:00 2001 From: Gepeto Escalante Date: Thu, 26 Feb 2026 10:26:12 +0000 Subject: [PATCH 1/2] feat(settings):SP-384 Implement scan configuration parameters --- CHANGELOG.md | 8 +- pom.xml | 2 +- src/main/java/com/scanoss/Scanner.java | 12 +- src/main/java/com/scanoss/Winnowing.java | 75 +++++++- .../java/com/scanoss/cli/ScanCommandLine.java | 52 +++++- src/main/java/com/scanoss/rest/ScanApi.java | 28 +++ .../com/scanoss/settings/FileSnippet.java | 144 +++++++++++++++ .../com/scanoss/settings/ScanossSettings.java | 20 +++ src/test/java/com/scanoss/TestSettings.java | 168 ++++++++++++++++++ src/test/java/com/scanoss/TestWinnowing.java | 86 +++++++++ src/test/resources/scanoss.json | 9 + 11 files changed, 595 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/scanoss/settings/FileSnippet.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b3c4dbe..a1fe4a70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.13.0] - 2026-02-03 ### Added +- Added `file_snippet` scan configuration support in `scanoss.json` for engine tuning parameters (`min_snippet_hits`, `min_snippet_lines`, `honour_file_exts`, `ranking_enabled`, `ranking_threshold`, `skip_headers`, `skip_headers_limit`) +- Added CLI scan configuration options with resolution priority (file_snippet > CLI) +- Added `FileSnippet` class for scan configuration management and resolution -- Upcoming changes... ## [0.12.1] - 2026-01-08 ### Changed - Updated slf4j from 2.0.16 to 2.0.17 @@ -141,4 +144,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [0.10.1]: https://github.com/scanoss/scanoss.java/compare/v0.10.0...v0.10.1 [0.11.0]: https://github.com/scanoss/scanoss.java/compare/v0.10.1...v0.11.0 [0.12.0]: https://github.com/scanoss/scanoss.java/compare/v0.11.0...v0.12.0 -[0.12.1]: https://github.com/scanoss/scanoss.java/compare/v0.12.0...v0.12.1 \ No newline at end of file +[0.12.1]: https://github.com/scanoss/scanoss.java/compare/v0.12.0...v0.12.1 +[0.13.0]: https://github.com/scanoss/scanoss.java/compare/v0.12.1...v0.13.0 \ No newline at end of file diff --git a/pom.xml b/pom.xml index c646000e..dccba64c 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.scanoss scanoss - 0.12.1 + 0.13.0 jar scanoss.java https://github.com/scanoss/scanoss.java diff --git a/src/main/java/com/scanoss/Scanner.java b/src/main/java/com/scanoss/Scanner.java index 9f86e635..30c1075d 100644 --- a/src/main/java/com/scanoss/Scanner.java +++ b/src/main/java/com/scanoss/Scanner.java @@ -31,6 +31,7 @@ import com.scanoss.processor.*; import com.scanoss.rest.ScanApi; import com.scanoss.settings.Bom; +import com.scanoss.settings.FileSnippet; import com.scanoss.settings.ScanossSettings; import com.scanoss.utils.JsonUtils; import lombok.*; @@ -116,7 +117,8 @@ private Scanner(Boolean skipSnippets, Boolean allExtensions, Boolean obfuscate, Integer snippetLimit, String customCert, Proxy proxy, Winnowing winnowing, ScanApi scanApi, ScanFileProcessor scanFileProcessor, WfpFileProcessor wfpFileProcessor, - ScanossSettings settings, ScannerPostProcessor postProcessor, FilterConfig filterConfig, + ScanossSettings settings, + ScannerPostProcessor postProcessor, FilterConfig filterConfig, Predicate fileFilter, Predicate folderFilter ) { @@ -137,20 +139,24 @@ private Scanner(Boolean skipSnippets, Boolean allExtensions, Boolean obfuscate, this.snippetLimit = snippetLimit; this.customCert = customCert; this.proxy = proxy; + this.settings = Objects.requireNonNullElseGet(settings, () -> ScanossSettings.builder().build()); + + FileSnippet resolvedSnippet = this.settings.getSettings().getFileSnippet(); this.winnowing = Objects.requireNonNullElseGet(winnowing, () -> Winnowing.builder().skipSnippets(skipSnippets).allExtensions(allExtensions).obfuscate(obfuscate) .hpsm(hpsm).snippetLimit(snippetLimit) + .skipHeaders(resolvedSnippet != null && Boolean.TRUE.equals(resolvedSnippet.getSkipHeaders())) + .skipHeadersLimit(resolvedSnippet != null && resolvedSnippet.getSkipHeadersLimit() != null ? resolvedSnippet.getSkipHeadersLimit() : 0) .build()); this.scanApi = Objects.requireNonNullElseGet(scanApi, () -> ScanApi.builder().url(url).apiKey(apiKey).timeout(timeout).retryLimit(retryLimit).flags(scanFlags) - .sbomType(sbomType).sbom(sbom).customCert(customCert).proxy(proxy).settings(settings) + .sbomType(sbomType).sbom(sbom).customCert(customCert).proxy(proxy).settings(this.settings) .build()); this.scanFileProcessor = Objects.requireNonNullElseGet(scanFileProcessor, () -> ScanFileProcessor.builder().winnowing(this.winnowing).scanApi(this.scanApi).build()); this.wfpFileProcessor = Objects.requireNonNullElseGet(wfpFileProcessor, () -> WfpFileProcessor.builder() .winnowing(this.winnowing) .build()); - this.settings = Objects.requireNonNullElseGet(settings, () -> ScanossSettings.builder().build()); this.postProcessor = Objects.requireNonNullElseGet(postProcessor, () -> ScannerPostProcessor.builder().build()); diff --git a/src/main/java/com/scanoss/Winnowing.java b/src/main/java/com/scanoss/Winnowing.java index 8ae39540..034bf243 100644 --- a/src/main/java/com/scanoss/Winnowing.java +++ b/src/main/java/com/scanoss/Winnowing.java @@ -80,6 +80,10 @@ public class Winnowing { @Builder.Default private int snippetLimit = MAX_LONG_LINE_CHARS; // Enable limiting of size of a single line of snippet generation @Builder.Default + private boolean skipHeaders = false; // Skip license headers, comments and imports at the beginning of files + @Builder.Default + private int skipHeadersLimit = 0; // Maximum number of header lines to skip (0 = auto-detect) + @Builder.Default private Map obfuscationMap = new ConcurrentHashMap<>(); /** @@ -168,6 +172,12 @@ public String wfpForContents(@NonNull String filename, Boolean binFile, byte[] c wfpBuilder.append(String.format("hpsm=%s\n", Hpsm.calcHpsm(contents))); } + int skipLines = 0; + if (this.skipHeaders) { + skipLines = detectHeaderLines(fileContents, this.skipHeadersLimit); + log.trace("Skipping {} header lines for snippet generation: {}", skipLines, filename); + } + String gram = ""; List window = new ArrayList<>(); char normalized; @@ -183,7 +193,7 @@ public String wfpForContents(@NonNull String filename, Boolean binFile, byte[] c } else { normalized = WinnowingUtils.normalize(c); } - if (normalized > 0) { + if (normalized > 0 && line > skipLines) { gram += normalized; if (gram.length() >= ScanossConstants.GRAM) { Long gramCRC32 = crc32c(gram); @@ -312,6 +322,69 @@ private Boolean skipSnippets(@NonNull String filename, char[] contents) { return false; } + /** + * Detect the number of header lines at the beginning of a file. + * Header lines include license comment blocks, single-line comments, + * blank lines, and import/package statements. + * + * @param contents file contents as char array + * @param maxLines maximum number of header lines to detect (0 = no limit) + * @return number of header lines detected + */ + int detectHeaderLines(char[] contents, int maxLines) { + int headerLines = 0; + boolean inBlockComment = false; + int lineStart = 0; + + for (int i = 0; i <= contents.length; i++) { + if (i == contents.length || contents[i] == '\n') { + String line = new String(contents, lineStart, i - lineStart).trim(); + + if (inBlockComment) { + headerLines++; + if (line.contains("*/")) { + inBlockComment = false; + } + } else if (line.isEmpty()) { + headerLines++; + } else if (line.startsWith("//") || line.startsWith("#!") || line.startsWith("# ")) { + headerLines++; + } else if (line.startsWith("/*")) { + headerLines++; + if (!line.contains("*/")) { + inBlockComment = true; + } + } else if (line.startsWith("*") || line.startsWith("* ")) { + headerLines++; + } else if (isImportOrPackageLine(line)) { + headerLines++; + } else { + break; // Non-header line found + } + + if (maxLines > 0 && headerLines >= maxLines) { + break; + } + + lineStart = i + 1; + } + } + + return headerLines; + } + + /** + * Check if a line is an import or package declaration. + * + * @param line trimmed source line + * @return true if the line is an import/package/include statement + */ + private boolean isImportOrPackageLine(String line) { + return line.startsWith("import ") || line.startsWith("package ") || + line.startsWith("from ") || line.startsWith("#include ") || + line.startsWith("using ") || line.startsWith("require "); + } + /** * Try to detect if this is a text file or not * diff --git a/src/main/java/com/scanoss/cli/ScanCommandLine.java b/src/main/java/com/scanoss/cli/ScanCommandLine.java index d3e6c535..7854cccf 100644 --- a/src/main/java/com/scanoss/cli/ScanCommandLine.java +++ b/src/main/java/com/scanoss/cli/ScanCommandLine.java @@ -25,6 +25,7 @@ import com.scanoss.Scanner; import com.scanoss.exceptions.ScannerException; import com.scanoss.exceptions.WinnowingException; +import com.scanoss.settings.FileSnippet; import com.scanoss.settings.ScanossSettings; import com.scanoss.utils.JsonUtils; import com.scanoss.utils.ProxyUtils; @@ -105,6 +106,27 @@ class ScanCommandLine implements Runnable { @picocli.CommandLine.Option(names = {"-H", "--hpsm"}, description = "Use High Precision Snippet Matching algorithm") private boolean enableHpsm = false; + @picocli.CommandLine.Option(names = {"--min-snippet-hits"}, description = "Minimum snippet hits required (0 = unset, uses server config)") + private int minSnippetHits = 0; + + @picocli.CommandLine.Option(names = {"--min-snippet-lines"}, description = "Minimum snippet lines required (0 = unset, uses server config)") + private int minSnippetLines = 0; + + @picocli.CommandLine.Option(names = {"--honour-file-exts"}, description = "Honour file extensions (true|false|unset)", arity = "1") + private String honourFileExts = null; + + @picocli.CommandLine.Option(names = {"--ranking"}, description = "Enable/disable ranking (true|false|unset)", arity = "1") + private String ranking = null; + + @picocli.CommandLine.Option(names = {"--ranking-threshold"}, description = "Ranking threshold value (-1 = unset, uses server config)") + private int rankingThreshold = -1; + + @picocli.CommandLine.Option(names = {"--skip-headers"}, description = "Skip license headers, comments and imports at the beginning of files (applies locally)") + private boolean skipHeaders = false; + + @picocli.CommandLine.Option(names = {"--skip-headers-limit"}, description = "Skip limit for license headers (0 = unset, applies locally)") + private int skipHeadersLimit = 0; + @picocli.CommandLine.Parameters(arity = "1", description = "file/folder to scan") private String fileFolder; @@ -133,11 +155,25 @@ public void run() { } } + // Load settings from scanoss.json if provided, otherwise use defaults + ScanossSettings settings = new ScanossSettings(); if(settingsPath != null && !settingsPath.isEmpty()) { settings = ScanossSettings.createFromPath(Paths.get(settingsPath)); if (settings == null) throw new RuntimeException("Error: Failed to read settings file"); printMsg(err, String.format("Settings file read %s", settings)); } + // Build file_snippet config from CLI arguments (lowest priority) + FileSnippet fileSnippetCLI = FileSnippet.builder() + .minSnippetHits(minSnippetHits) + .minSnippetLines(minSnippetLines) + .rankingThreshold(rankingThreshold) + .skipHeaders(skipHeaders) + .skipHeadersLimit(skipHeadersLimit) + .rankingEnabled(parseTriStateBoolean(ranking)) + .honourFileExts(parseTriStateBoolean(honourFileExts)) + .build(); + // Resolve: file_snippet from scanoss.json (highest priority) overrides CLI values + settings.getSettings().setFileSnippet(FileSnippet.resolve(fileSnippetCLI, settings.getSettings().getFileSnippet())); if (com.scanoss.cli.CommandLine.debug) { @@ -166,7 +202,6 @@ public void run() { .snippetLimit(snippetLimit).customCert(caCertPem).proxy(proxy).hpsm(enableHpsm) .settings(settings).obfuscate(obfuscate) .build(); - File f = new File(fileFolder); if (!f.exists()) { throw new RuntimeException(String.format("Error: File or folder does not exist: %s\n", fileFolder)); @@ -198,6 +233,19 @@ private String loadFileToString(@NonNull String filename) { } } + /** + * Parse a tri-state boolean string value. + * + * @param value the string value ("true", "false", "unset", or null) + * @return Boolean.TRUE, Boolean.FALSE, or null for unset + */ + private static Boolean parseTriStateBoolean(String value) { + if (value == null || value.equalsIgnoreCase("unset")) { + return null; + } + return Boolean.parseBoolean(value); + } + /** * Scan the specified file and output the results * @@ -245,7 +293,7 @@ private void scanFolder(String folder) { if (CommandLine.debug) { e.printStackTrace(err); } - throw new RuntimeException(String.format("Something went wrong while scanning %s.", folder)); + throw new RuntimeException(String.format("Something went wrong while scanning %s.", folder), e); } } } diff --git a/src/main/java/com/scanoss/rest/ScanApi.java b/src/main/java/com/scanoss/rest/ScanApi.java index 8526b362..495d41be 100644 --- a/src/main/java/com/scanoss/rest/ScanApi.java +++ b/src/main/java/com/scanoss/rest/ScanApi.java @@ -25,6 +25,7 @@ import com.scanoss.dto.SbomLegacy; import com.scanoss.exceptions.ScanApiException; import com.scanoss.settings.Rule; +import com.scanoss.settings.FileSnippet; import com.scanoss.settings.ScanossSettings; import com.scanoss.utils.JsonUtils; import com.scanoss.utils.PackageDetails; @@ -36,6 +37,7 @@ import okhttp3.tls.HandshakeCertificates; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.io.InterruptedIOException; import java.net.Proxy; import java.time.Duration; @@ -178,6 +180,32 @@ public String scan(String wfp, String context, int scanID) throws ScanApiExcepti data.put("type", "identify"); } + // Add scan configuration parameters as base64-encoded JSON in scanoss-settings key + if (this.settings != null && this.settings.getSettings().getFileSnippet() != null) { + FileSnippet fileSnippet = this.settings.getSettings().getFileSnippet(); + Map settingsMap = new LinkedHashMap<>(); + if (fileSnippet.isMinSnippetHitsSet()) { + settingsMap.put("min_snippet_hits", fileSnippet.getMinSnippetHits()); + } + if (fileSnippet.isMinSnippetLinesSet()) { + settingsMap.put("min_snippet_lines", fileSnippet.getMinSnippetLines()); + } + if (fileSnippet.isHonourFileExtsSet()) { + settingsMap.put("honour_file_exts", fileSnippet.getHonourFileExts()); + } + if (fileSnippet.isRankingEnabledSet()) { + settingsMap.put("ranking_enabled", fileSnippet.getRankingEnabled()); + } + if (fileSnippet.isRankingThresholdSet()) { + settingsMap.put("ranking_threshold", fileSnippet.getRankingThreshold()); + } + if (!settingsMap.isEmpty()) { + String json = JsonUtils.toJson(settingsMap); + log.debug("scanoss settings:" + json); + String encoded = Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8)); + headers.put("scanoss-settings", encoded); + } + } Request request; // Create multipart request try { diff --git a/src/main/java/com/scanoss/settings/FileSnippet.java b/src/main/java/com/scanoss/settings/FileSnippet.java new file mode 100644 index 00000000..28b7ca0f --- /dev/null +++ b/src/main/java/com/scanoss/settings/FileSnippet.java @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (c) 2026, SCANOSS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.scanoss.settings; + +import com.google.gson.annotations.SerializedName; +import lombok.*; + +/** + * Scan configuration parameters for engine tuning. + *

+ * Holds parameters that control how the SCANOSS scanning engine processes files. + * Used both for JSON deserialization from scanoss.json and for resolved CLI configuration. + *

+ * Resolution priority (highest to lowest): + *
    + *
  1. settings.file_snippet section in scanoss.json
  2. + *
  3. CLI arguments
  4. + *
+ */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FileSnippet { + + @Builder.Default + @SerializedName("min_snippet_hits") + private Integer minSnippetHits = 0; + + @Builder.Default + @SerializedName("min_snippet_lines") + private Integer minSnippetLines = 0; + + @SerializedName("honour_file_exts") + private Boolean honourFileExts; + + @SerializedName("ranking_enabled") + private Boolean rankingEnabled; + + @Builder.Default + @SerializedName("ranking_threshold") + private Integer rankingThreshold = -1; + + @Builder.Default + @SerializedName("skip_headers") + private Boolean skipHeaders = false; + + @Builder.Default + @SerializedName("skip_headers_limit") + private Integer skipHeadersLimit = 0; + + public boolean isMinSnippetHitsSet() { + return minSnippetHits != null && minSnippetHits > 0; + } + + public boolean isMinSnippetLinesSet() { + return minSnippetLines != null && minSnippetLines > 0; + } + + public boolean isHonourFileExtsSet() { + return honourFileExts != null; + } + + public boolean isRankingEnabledSet() { + return rankingEnabled != null; + } + + public boolean isRankingThresholdSet() { + return rankingThreshold != null && rankingThreshold >= 0; + } + + public boolean isSkipHeadersLimitSet() { + return skipHeadersLimit != null && skipHeadersLimit > 0; + } + + /** + * Resolves scan configuration by merging two priority layers. + * Priority: fileSnippet (highest) > cli (lowest). + *

+ * When a setting is "unset" at a given level, it is not applied, allowing + * lower-priority levels to provide the value. + *

+ * + * @param cli CLI-provided config (lowest priority) + * @param fileSnippet File snippet config from scanoss.json (highest priority) + * @return Resolved ScanConfig with highest-priority non-unset values + */ + public static FileSnippet resolve(FileSnippet cli, FileSnippet fileSnippet) { + FileSnippet resolved = FileSnippet.builder().build(); + + if (cli != null) { + applyNonDefault(cli, resolved); + } + if (fileSnippet != null) { + applyNonDefault(fileSnippet, resolved); + } + + return resolved; + } + + private static void applyNonDefault(FileSnippet source, FileSnippet target) { + if (source.isMinSnippetHitsSet()) { + target.setMinSnippetHits(source.getMinSnippetHits()); + } + if (source.isMinSnippetLinesSet()) { + target.setMinSnippetLines(source.getMinSnippetLines()); + } + if (source.isHonourFileExtsSet()) { + target.setHonourFileExts(source.getHonourFileExts()); + } + if (source.isRankingEnabledSet()) { + target.setRankingEnabled(source.getRankingEnabled()); + } + if (source.isRankingThresholdSet()) { + target.setRankingThreshold(source.getRankingThreshold()); + } + if (source.getSkipHeaders() != null) { + target.setSkipHeaders(source.getSkipHeaders()); + } + if (source.isSkipHeadersLimitSet()) { + target.setSkipHeadersLimit(source.getSkipHeadersLimit()); + } + } +} diff --git a/src/main/java/com/scanoss/settings/ScanossSettings.java b/src/main/java/com/scanoss/settings/ScanossSettings.java index ff975b94..733dc044 100644 --- a/src/main/java/com/scanoss/settings/ScanossSettings.java +++ b/src/main/java/com/scanoss/settings/ScanossSettings.java @@ -23,6 +23,7 @@ package com.scanoss.settings; import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; import com.scanoss.dto.SbomLegacy; import lombok.*; import lombok.extern.slf4j.Slf4j; @@ -59,6 +60,9 @@ public class ScanossSettings { @AllArgsConstructor public static class Settings { private final @Builder.Default Skip skip = Skip.builder().build(); + + @SerializedName("file_snippet") + private FileSnippet fileSnippet; } @Data @@ -147,5 +151,21 @@ public List getScanningIgnorePattern() { return this.settings.getSkip().getPatterns().getScanning(); } + /** + * Resolves scan configuration by merging CLI arguments with settings from scanoss.json. + * Priority (highest to lowest): + *
    + *
  1. settings.file_snippet section in scanoss.json
  2. + *
  3. settings section in scanoss.json
  4. + *
  5. CLI arguments (fallback)
  6. + *
+ * + * @param cliConfig Configuration from CLI arguments + * @return Resolved ScanConfig with highest-priority non-unset values + */ + public FileSnippet getResolvedScanConfig(FileSnippet cliConfig) { + FileSnippet fileSnippet = (settings != null) ? settings.getFileSnippet() : null; + return FileSnippet.resolve(cliConfig, fileSnippet); + } } diff --git a/src/test/java/com/scanoss/TestSettings.java b/src/test/java/com/scanoss/TestSettings.java index fa711324..1a895579 100644 --- a/src/test/java/com/scanoss/TestSettings.java +++ b/src/test/java/com/scanoss/TestSettings.java @@ -22,6 +22,7 @@ */ package com.scanoss; +import com.scanoss.settings.FileSnippet; import com.scanoss.settings.ScanossSettings; import com.scanoss.utils.JsonUtils; import lombok.extern.slf4j.Slf4j; @@ -145,4 +146,171 @@ public void testSkip() { log.info("Finished {} -->", methodName); } + @Test + public void testScanConfigFromJson() { + String methodName = new Object() {}.getClass().getEnclosingMethod().getName(); + log.info("<-- Starting {}", methodName); + + assertNotNull("Scan config settings path should exist", existingSettingsPath); + ScanossSettings settings = ScanossSettings.createFromPath(existingSettingsPath); + assertNotNull("Settings should not be null", settings); + + // Verify settings-level scan config fields + assertEquals(Integer.valueOf(3), settings.getSettings().getFileSnippet().getMinSnippetHits()); + assertEquals(Integer.valueOf(10), settings.getSettings().getFileSnippet().getMinSnippetLines()); + assertEquals(Boolean.TRUE, settings.getSettings().getFileSnippet().getHonourFileExts()); + assertEquals(Boolean.TRUE, settings.getSettings().getFileSnippet().getRankingEnabled()); + assertEquals(Integer.valueOf(5), settings.getSettings().getFileSnippet().getRankingThreshold()); + assertEquals(Boolean.FALSE, settings.getSettings().getFileSnippet().getSkipHeaders()); + assertEquals(Integer.valueOf(0), settings.getSettings().getFileSnippet().getSkipHeadersLimit()); + + // Verify file_snippet section + assertNotNull("file_snippet should not be null", settings.getSettings().getFileSnippet()); + assertEquals(Integer.valueOf(3), settings.getSettings().getFileSnippet().getMinSnippetHits()); + assertEquals(Integer.valueOf(5), settings.getSettings().getFileSnippet().getRankingThreshold()); + + log.info("Finished {} -->", methodName); + } + + @Test + public void testScanConfigResolution() { + String methodName = new Object() {}.getClass().getEnclosingMethod().getName(); + log.info("<-- Starting {}", methodName); + + // CLI config (lowest priority) + FileSnippet cliConfig = FileSnippet.builder() + .minSnippetHits(2) + .minSnippetLines(5) + .rankingEnabled(false) + .rankingThreshold(3) + .build(); + + // File-snippet config (highest priority) + FileSnippet fileSnippetConfig = FileSnippet.builder() + .minSnippetHits(5) + .rankingThreshold(8) + .build(); + + FileSnippet resolved = FileSnippet.resolve(cliConfig, fileSnippetConfig); + + // file_snippet has min_snippet_hits=5 (highest priority) + assertEquals(Integer.valueOf(5), resolved.getMinSnippetHits()); + // settings has ranking_enabled=true (file_snippet doesn't override) + assertEquals(Boolean.FALSE, resolved.getRankingEnabled()); + // file_snippet has ranking_threshold=8 (highest priority) + assertEquals(Integer.valueOf(8), resolved.getRankingThreshold()); + // CLI has min_snippet_lines=5 (only CLI sets it) + assertEquals(Integer.valueOf(5), resolved.getMinSnippetLines()); + + log.info("Finished {} -->", methodName); + } + + @Test + public void testScanConfigResolutionFromSettingsFile() { + String methodName = new Object() {}.getClass().getEnclosingMethod().getName(); + log.info("<-- Starting {}", methodName); + + assertNotNull("Scan config settings path should exist", existingSettingsPath); + ScanossSettings settings = ScanossSettings.createFromPath(existingSettingsPath); + assertNotNull("Settings should not be null", settings); + + // CLI provides some values + FileSnippet cliConfig = FileSnippet.builder() + .minSnippetHits(2) + .minSnippetLines(9) + .rankingThreshold(4) + .build(); + + FileSnippet resolved = settings.getResolvedScanConfig(cliConfig); + + // file_snippet provides min_snippet_hits=3 (overrides cli: 2) + assertEquals(Integer.valueOf(3), resolved.getMinSnippetHits()); + // file_snippet provides ranking_threshold=5 (overrides cli: 4) + assertEquals(Integer.valueOf(5), resolved.getRankingThreshold()); + // file_snippet provides ranking_enabled=true (CLI doesn't set it) + assertEquals(Boolean.TRUE, resolved.getRankingEnabled()); + // file_snippet provides min_snippet_lines=10 (overrides cli: 9) + assertEquals(Integer.valueOf(10), resolved.getMinSnippetLines()); + // file_snippet provides honour_file_exts=true (CLI doesn't set it) + assertEquals(Boolean.TRUE, resolved.getHonourFileExts()); + + log.info("Finished {} -->", methodName); + } + + @Test + public void testScanConfigUnsetValues() { + String methodName = new Object() {}.getClass().getEnclosingMethod().getName(); + log.info("<-- Starting {}", methodName); + + // Default config - all values unset + FileSnippet defaultConfig = FileSnippet.builder().build(); + + assertFalse("min_snippet_hits should be unset", defaultConfig.isMinSnippetHitsSet()); + assertFalse("min_snippet_lines should be unset", defaultConfig.isMinSnippetLinesSet()); + assertFalse("honour_file_exts should be unset", defaultConfig.isHonourFileExtsSet()); + assertFalse("ranking_enabled should be unset", defaultConfig.isRankingEnabledSet()); + assertFalse("ranking_threshold should be unset", defaultConfig.isRankingThresholdSet()); + assertFalse("skip_headers_limit should be unset", defaultConfig.isSkipHeadersLimitSet()); + + // Config with values set + FileSnippet setConfig = FileSnippet.builder() + .minSnippetHits(3) + .minSnippetLines(10) + .honourFileExts(true) + .rankingEnabled(false) + .rankingThreshold(5) + .skipHeadersLimit(20) + .build(); + + assertTrue("min_snippet_hits should be set", setConfig.isMinSnippetHitsSet()); + assertTrue("min_snippet_lines should be set", setConfig.isMinSnippetLinesSet()); + assertTrue("honour_file_exts should be set", setConfig.isHonourFileExtsSet()); + assertTrue("ranking_enabled should be set", setConfig.isRankingEnabledSet()); + assertTrue("ranking_threshold should be set", setConfig.isRankingThresholdSet()); + assertTrue("skip_headers_limit should be set", setConfig.isSkipHeadersLimitSet()); + + log.info("Finished {} -->", methodName); + } + + @Test + public void testScanConfigResolutionWithNulls() { + String methodName = new Object() {}.getClass().getEnclosingMethod().getName(); + log.info("<-- Starting {}", methodName); + + // Test resolution with null layers + FileSnippet cliConfig = FileSnippet.builder() + .minSnippetHits(2) + .build(); + + FileSnippet resolved = FileSnippet.resolve(cliConfig, null); + assertEquals(Integer.valueOf(2), resolved.getMinSnippetHits()); + assertNull("ranking_enabled should remain unset", resolved.getRankingEnabled()); + + // Test with all nulls + FileSnippet allNullResolved = FileSnippet.resolve(null, null); + assertFalse("All values should be unset", allNullResolved.isMinSnippetHitsSet()); + assertNull("ranking_enabled should be null", allNullResolved.getRankingEnabled()); + + log.info("Finished {} -->", methodName); + } + + @Test + public void testExistingSettingsWithoutScanConfig() { + String methodName = new Object() {}.getClass().getEnclosingMethod().getName(); + log.info("<-- Starting {}", methodName); + + // Test that existing settings file without scan config fields still works + ScanossSettings settings = ScanossSettings.builder().build(); + + // Scan config fields should be null when not in JSON + assertNull("file snippet should be null", settings.getSettings().getFileSnippet()); + + + // Resolution should still work with defaults + FileSnippet cliConfig = FileSnippet.builder().minSnippetHits(2).build(); + FileSnippet resolved = settings.getResolvedScanConfig(cliConfig); + assertEquals(Integer.valueOf(2), resolved.getMinSnippetHits()); + + log.info("Finished {} -->", methodName); + } } diff --git a/src/test/java/com/scanoss/TestWinnowing.java b/src/test/java/com/scanoss/TestWinnowing.java index 449a324e..58d0bd92 100644 --- a/src/test/java/com/scanoss/TestWinnowing.java +++ b/src/test/java/com/scanoss/TestWinnowing.java @@ -334,5 +334,91 @@ public void TestDeobfuscateFilePathInvalid() { log.info("Finished {} -->", methodName); } + + @Test + public void TestDetectHeaderLines() { + String methodName = new Object() { + }.getClass().getEnclosingMethod().getName(); + log.info("<-- Starting {}", methodName); + + Winnowing winnowing = Winnowing.builder().build(); + + // Test with a typical Java file header + String javaHeader = "// SPDX-License-Identifier: MIT\n" + + "/*\n" + + " * Copyright (c) 2024, SCANOSS\n" + + " */\n" + + "package com.scanoss;\n" + + "\n" + + "import java.util.List;\n" + + "import java.io.File;\n" + + "\n" + + "public class MyClass {\n" + + " int x = 1;\n" + + "}\n"; + char[] contents = javaHeader.toCharArray(); + int headerLines = winnowing.detectHeaderLines(contents, 0); + assertEquals("Should detect 9 header lines", 9, headerLines); + + // Test with limit + int limitedHeaderLines = winnowing.detectHeaderLines(contents, 5); + assertEquals("Should detect at most 5 header lines with limit", 5, limitedHeaderLines); + + // Test with no header + String noHeader = "public class MyClass {\n int x = 1;\n}\n"; + int noHeaderLines = winnowing.detectHeaderLines(noHeader.toCharArray(), 0); + assertEquals("Should detect 0 header lines", 0, noHeaderLines); + + // Test with block comment + String blockComment = "/*\n * License block\n * More license\n */\n\nclass Foo {}\n"; + int blockLines = winnowing.detectHeaderLines(blockComment.toCharArray(), 0); + assertEquals("Should detect 5 header lines (block comment + blank)", 5, blockLines); + + log.info("Finished {} -->", methodName); + } + + @Test + public void TestWinnowingSkipHeaders() { + String methodName = new Object() { + }.getClass().getEnclosingMethod().getName(); + log.info("<-- Starting {}", methodName); + + // Build content with a license header + code + String content = "// License header line 1\n" + + "// License header line 2\n" + + "\n" + + "import java.util.List;\n" + + "\n" + + "sample c code with lots of code that we should analyse\n" + + "And even more code to get connected.\n" + + "And we need to get this as long as possible, in order to trigger snippet matching.\n" + + "Here comes more code to help get this working.\n" + + "Please help get this across the line. We need all the help we can get.\n"; + + // Without skip headers - should generate snippet hashes + Winnowing winnowingNoSkip = Winnowing.builder().skipHeaders(false).build(); + String wfpNoSkip = winnowingNoSkip.wfpForContents("test.c", false, content.getBytes()); + assertNotNull(wfpNoSkip); + assertTrue("Should have snippets", snippetPat.matcher(wfpNoSkip).find()); + + // With skip headers - should still generate snippet hashes (just starting later) + Winnowing winnowingSkip = Winnowing.builder().skipHeaders(true).build(); + String wfpSkip = winnowingSkip.wfpForContents("test.c", false, content.getBytes()); + assertNotNull(wfpSkip); + + // The file= line should be the same (full file hash) + assertTrue("Both should start with file= line", wfpNoSkip.startsWith("file=")); + assertTrue("Both should start with file= line", wfpSkip.startsWith("file=")); + + // The file hash should be identical (skip_headers doesn't affect the file hash) + String noSkipFirstLine = wfpNoSkip.split("\n")[0]; + String skipFirstLine = wfpSkip.split("\n")[0]; + assertEquals("File hash should be the same regardless of skip_headers", noSkipFirstLine, skipFirstLine); + + log.info("WFP without skip: {}", wfpNoSkip); + log.info("WFP with skip: {}", wfpSkip); + + log.info("Finished {} -->", methodName); + } } diff --git a/src/test/resources/scanoss.json b/src/test/resources/scanoss.json index 618e0d9e..0447079b 100644 --- a/src/test/resources/scanoss.json +++ b/src/test/resources/scanoss.json @@ -9,6 +9,15 @@ ] }, "settings": { + "file_snippet": { + "min_snippet_hits": 3, + "min_snippet_lines": 10, + "honour_file_exts": true, + "ranking_enabled": true, + "ranking_threshold": 5, + "skip_headers": false, + "skip_headers_limit": 0 + }, "skip": { "patterns": { "scanning": [ From 9d5f72cce3177c51ccbf860c188430a1ee7f3096 Mon Sep 17 00:00:00 2001 From: Agustin Groh Date: Tue, 3 Mar 2026 15:23:07 -0300 Subject: [PATCH 2/2] chore(doc):SP-4117 add missing Javadoc documentation --- CHANGELOG.md | 3 ++ config/checkstyle.xml | 21 ++++++++ .../java/com/scanoss/ScanossConstants.java | 15 ++++-- .../java/com/scanoss/filters/BaseFilter.java | 4 ++ .../java/com/scanoss/filters/FileFilter.java | 6 ++- .../com/scanoss/filters/FolderFilter.java | 5 ++ .../com/scanoss/filters/GitIgnoreFilter.java | 5 ++ .../filters/factories/FileFilterFactory.java | 3 ++ .../factories/FolderFilterFactory.java | 3 ++ src/main/java/com/scanoss/settings/Bom.java | 11 +++- .../com/scanoss/settings/FileSnippet.java | 35 +++++++++++- .../java/com/scanoss/settings/RemoveRule.java | 7 +++ .../com/scanoss/settings/ReplaceRule.java | 7 +++ src/main/java/com/scanoss/settings/Rule.java | 6 +++ .../com/scanoss/settings/RuleComparator.java | 3 ++ .../com/scanoss/settings/ScanossSettings.java | 54 ++++++++++++++++--- src/main/java/com/scanoss/utils/Hpsm.java | 4 ++ .../java/com/scanoss/utils/JsonUtils.java | 4 ++ .../com/scanoss/utils/LineRangeUtils.java | 5 ++ .../com/scanoss/utils/PackageDetails.java | 5 ++ .../java/com/scanoss/utils/ProxyUtils.java | 4 ++ src/main/java/com/scanoss/utils/Purl2Url.java | 4 ++ .../com/scanoss/utils/WinnowingUtils.java | 4 ++ 23 files changed, 204 insertions(+), 14 deletions(-) create mode 100644 config/checkstyle.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index a1fe4a70..c495ad9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `file_snippet` scan configuration support in `scanoss.json` for engine tuning parameters (`min_snippet_hits`, `min_snippet_lines`, `honour_file_exts`, `ranking_enabled`, `ranking_threshold`, `skip_headers`, `skip_headers_limit`) - Added CLI scan configuration options with resolution priority (file_snippet > CLI) - Added `FileSnippet` class for scan configuration management and resolution +- Scan configuration parameters are now sent as base64-encoded JSON in the `scanoss-settings` HTTP header +### Fixed +- Fixed Javadoc warnings across the codebase ## [0.12.1] - 2026-01-08 ### Changed diff --git a/config/checkstyle.xml b/config/checkstyle.xml new file mode 100644 index 00000000..12c9a727 --- /dev/null +++ b/config/checkstyle.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/scanoss/ScanossConstants.java b/src/main/java/com/scanoss/ScanossConstants.java index 87af55c7..7529f949 100644 --- a/src/main/java/com/scanoss/ScanossConstants.java +++ b/src/main/java/com/scanoss/ScanossConstants.java @@ -33,6 +33,11 @@ */ @SuppressWarnings("SpellCheckingInspection") public class ScanossConstants { + + /** Private constructor to prevent instantiation. */ + private ScanossConstants() { + } + /** * Default timeout for HTTP communication */ @@ -52,7 +57,7 @@ public class ScanossConstants { static final int MIN_FILE_SIZE = 256; // Minimum size for a file to be considered for snippet generation static final int MAX_LONG_LINE_CHARS = 1000; // Maximum length of a single source line to be considered source code - // File extensions to ignore snippets for + /** File extensions to ignore snippets for. */ public static final List SKIP_SNIPPET_EXT = Arrays.asList( ".exe", ".zip", ".tar", ".tgz", ".gz", ".7z", ".rar", ".jar", ".war", ".ear", ".class", ".pyc", ".o", ".a", ".so", ".obj", ".dll", ".lib", ".out", ".app", ".bin", @@ -61,17 +66,17 @@ public class ScanossConstants { ".pdf", ".min.js", ".mf", ".sum", ".woff", ".woff2", ".xsd", ".pom", ".whl" ); - // Folders to skip + /** Folders to skip during scanning. */ public static final List FILTERED_DIRS = Arrays.asList( "nbproject", "nbbuild", "nbdist", "__pycache__", "venv", "_yardoc", "eggs", "wheels", "htmlcov", "__pypackages__", "target" ); - // Folder endings to skip + /** Folder suffixes to skip during scanning. */ public static final List FILTERED_DIR_EXT = List.of(".egg-info"); - // File extensions to skip + /** File extensions to skip during scanning. */ public static final List FILTERED_EXTENSIONS = Arrays.asList( ".1", ".2", ".3", ".4", ".5", ".6", ".7", ".8", ".9", ".ac", ".adoc", ".am", ".asciidoc", ".bmp", ".build", ".cfg", ".chm", ".class", ".cmake", ".cnf", @@ -94,7 +99,7 @@ public class ScanossConstants { "readme", "swiftdoc", "texidoc", "todo", "version", "ignore", "manifest", "sqlite", "sqlite3" ); - // Files to skip + /** Files to skip during scanning. */ public static final List FILTERED_FILES = Arrays.asList( "gradlew", "gradlew.bat", "mvnw", "mvnw.cmd", "gradle-wrapper.jar", "maven-wrapper.jar", "thumbs.db", "babel.config.js", "license.txt", "license.md", "copying.lib", "makefile" diff --git a/src/main/java/com/scanoss/filters/BaseFilter.java b/src/main/java/com/scanoss/filters/BaseFilter.java index e1bcb6d4..bf017b1c 100644 --- a/src/main/java/com/scanoss/filters/BaseFilter.java +++ b/src/main/java/com/scanoss/filters/BaseFilter.java @@ -32,9 +32,13 @@ */ @Slf4j public abstract class BaseFilter { + /** The filter configuration. */ protected final FilterConfig config; + /** The Git ignore pattern filter. */ protected final GitIgnoreFilter gitIgnoreFilter; + /** The Ant pattern filter. */ protected final AntFilter antFilter; + /** The combined base skip filter predicate. */ protected final Predicate baseSkipFilter; /** diff --git a/src/main/java/com/scanoss/filters/FileFilter.java b/src/main/java/com/scanoss/filters/FileFilter.java index 6bec8642..cc087071 100644 --- a/src/main/java/com/scanoss/filters/FileFilter.java +++ b/src/main/java/com/scanoss/filters/FileFilter.java @@ -39,7 +39,11 @@ public class FileFilter extends BaseFilter { @Builder.Default // Tells Lombok this field has a default initialization private final FilterConfig filterConfig = FilterConfig.builder().build(); - // Use @Builder constructor annotation to tell Lombok to use this constructor + /** + * Constructs a FileFilter with the specified configuration. + * + * @param filterConfig the filter configuration + */ protected FileFilter(FilterConfig filterConfig) { super(filterConfig); this.filterConfig = filterConfig; diff --git a/src/main/java/com/scanoss/filters/FolderFilter.java b/src/main/java/com/scanoss/filters/FolderFilter.java index e723635c..881da8df 100644 --- a/src/main/java/com/scanoss/filters/FolderFilter.java +++ b/src/main/java/com/scanoss/filters/FolderFilter.java @@ -36,6 +36,11 @@ */ public class FolderFilter extends BaseFilter { + /** + * Constructs a FolderFilter with the specified configuration. + * + * @param filterConfig the filter configuration + */ public FolderFilter(FilterConfig filterConfig) { super(filterConfig); } diff --git a/src/main/java/com/scanoss/filters/GitIgnoreFilter.java b/src/main/java/com/scanoss/filters/GitIgnoreFilter.java index c024616b..7dfed15a 100644 --- a/src/main/java/com/scanoss/filters/GitIgnoreFilter.java +++ b/src/main/java/com/scanoss/filters/GitIgnoreFilter.java @@ -44,6 +44,11 @@ public class GitIgnoreFilter { @Setter(AccessLevel.PRIVATE) private List rules; + /** + * Constructs a GitIgnoreFilter with the specified patterns. + * + * @param patterns the list of Git ignore patterns + */ @Builder public GitIgnoreFilter(@NonNull List patterns) { this.rules = new ArrayList<>(); diff --git a/src/main/java/com/scanoss/filters/factories/FileFilterFactory.java b/src/main/java/com/scanoss/filters/factories/FileFilterFactory.java index f2b162d7..ab02fc11 100644 --- a/src/main/java/com/scanoss/filters/factories/FileFilterFactory.java +++ b/src/main/java/com/scanoss/filters/factories/FileFilterFactory.java @@ -31,6 +31,9 @@ A factory class that provides a static method for creating file filtering predicates. */ public class FileFilterFactory { + /** Private constructor to prevent instantiation. */ + private FileFilterFactory() { + } /** * Creates a file filtering predicate based on the provided configuration. * diff --git a/src/main/java/com/scanoss/filters/factories/FolderFilterFactory.java b/src/main/java/com/scanoss/filters/factories/FolderFilterFactory.java index f6c85097..27169c6b 100644 --- a/src/main/java/com/scanoss/filters/factories/FolderFilterFactory.java +++ b/src/main/java/com/scanoss/filters/factories/FolderFilterFactory.java @@ -31,6 +31,9 @@ A factory class that provides a static method for creating folder filtering predicates. */ public class FolderFilterFactory { + /** Private constructor to prevent instantiation. */ + private FolderFilterFactory() { + } /** * Creates a folder filtering predicate based on the provided configuration. * diff --git a/src/main/java/com/scanoss/settings/Bom.java b/src/main/java/com/scanoss/settings/Bom.java index 2897f68d..716790bc 100644 --- a/src/main/java/com/scanoss/settings/Bom.java +++ b/src/main/java/com/scanoss/settings/Bom.java @@ -36,9 +36,18 @@ @Data @Builder @AllArgsConstructor -@NoArgsConstructor public class Bom { + /** Default constructor. */ + public Bom() { + this.include = new ArrayList<>(); + this.ignore = new ArrayList<>(); + this.remove = new ArrayList<>(); + this.replace = new ArrayList<>(); + this.sortedReplace = new ArrayList<>(); + } + + /** * List of include rules for adding context when scanning. * These rules are sent to the SCANOSS API and have a higher chance of being diff --git a/src/main/java/com/scanoss/settings/FileSnippet.java b/src/main/java/com/scanoss/settings/FileSnippet.java index 28b7ca0f..c2abb5f7 100644 --- a/src/main/java/com/scanoss/settings/FileSnippet.java +++ b/src/main/java/com/scanoss/settings/FileSnippet.java @@ -39,10 +39,13 @@ */ @Data @Builder -@NoArgsConstructor @AllArgsConstructor public class FileSnippet { + /** Default constructor. */ + public FileSnippet() {} + + @Builder.Default @SerializedName("min_snippet_hits") private Integer minSnippetHits = 0; @@ -69,26 +72,56 @@ public class FileSnippet { @SerializedName("skip_headers_limit") private Integer skipHeadersLimit = 0; + /** + * Returns {@code true} if minSnippetHits has been explicitly set to a positive value. + * + * @return whether minSnippetHits is set + */ public boolean isMinSnippetHitsSet() { return minSnippetHits != null && minSnippetHits > 0; } + /** + * Returns {@code true} if minSnippetLines has been explicitly set to a positive value. + * + * @return whether minSnippetLines is set + */ public boolean isMinSnippetLinesSet() { return minSnippetLines != null && minSnippetLines > 0; } + /** + * Returns {@code true} if honourFileExts has been explicitly set. + * + * @return whether honourFileExts is set + */ public boolean isHonourFileExtsSet() { return honourFileExts != null; } + /** + * Returns {@code true} if rankingEnabled has been explicitly set. + * + * @return whether rankingEnabled is set + */ public boolean isRankingEnabledSet() { return rankingEnabled != null; } + /** + * Returns {@code true} if rankingThreshold has been explicitly set to a non-negative value. + * + * @return whether rankingThreshold is set + */ public boolean isRankingThresholdSet() { return rankingThreshold != null && rankingThreshold >= 0; } + /** + * Returns {@code true} if skipHeadersLimit has been explicitly set to a positive value. + * + * @return whether skipHeadersLimit is set + */ public boolean isSkipHeadersLimitSet() { return skipHeadersLimit != null && skipHeadersLimit > 0; } diff --git a/src/main/java/com/scanoss/settings/RemoveRule.java b/src/main/java/com/scanoss/settings/RemoveRule.java index 3e351882..7a293ef5 100644 --- a/src/main/java/com/scanoss/settings/RemoveRule.java +++ b/src/main/java/com/scanoss/settings/RemoveRule.java @@ -38,4 +38,11 @@ public class RemoveRule extends Rule { private final Integer startLine; @SerializedName("end_line") private final Integer endLine; + + /** Default constructor. */ + protected RemoveRule() { + super(); + this.startLine = null; + this.endLine = null; + } } \ No newline at end of file diff --git a/src/main/java/com/scanoss/settings/ReplaceRule.java b/src/main/java/com/scanoss/settings/ReplaceRule.java index 1e882656..0c3ea92f 100644 --- a/src/main/java/com/scanoss/settings/ReplaceRule.java +++ b/src/main/java/com/scanoss/settings/ReplaceRule.java @@ -37,4 +37,11 @@ public class ReplaceRule extends Rule { @SerializedName("replace_with") private final String replaceWith; private final String license; + + /** Default constructor. */ + protected ReplaceRule() { + super(); + this.replaceWith = null; + this.license = null; + } } \ No newline at end of file diff --git a/src/main/java/com/scanoss/settings/Rule.java b/src/main/java/com/scanoss/settings/Rule.java index 0b18942e..255e25cc 100644 --- a/src/main/java/com/scanoss/settings/Rule.java +++ b/src/main/java/com/scanoss/settings/Rule.java @@ -41,4 +41,10 @@ public class Rule { private final String path; private final String purl; + + /** Default constructor. */ + protected Rule() { + this.path = null; + this.purl = null; + } } \ No newline at end of file diff --git a/src/main/java/com/scanoss/settings/RuleComparator.java b/src/main/java/com/scanoss/settings/RuleComparator.java index a45cb9b1..eb76bfe2 100644 --- a/src/main/java/com/scanoss/settings/RuleComparator.java +++ b/src/main/java/com/scanoss/settings/RuleComparator.java @@ -47,6 +47,9 @@ */ public class RuleComparator implements Comparator { + /** Default constructor. */ + public RuleComparator() {} + /** * Compares two Rule objects based on their priority scores and path lengths. * diff --git a/src/main/java/com/scanoss/settings/ScanossSettings.java b/src/main/java/com/scanoss/settings/ScanossSettings.java index 733dc044..ab61d013 100644 --- a/src/main/java/com/scanoss/settings/ScanossSettings.java +++ b/src/main/java/com/scanoss/settings/ScanossSettings.java @@ -43,9 +43,15 @@ @Slf4j @Data @Builder -@NoArgsConstructor @AllArgsConstructor public class ScanossSettings { + + /** Default constructor. */ + public ScanossSettings() { + this.bom = Bom.builder().build(); + this.settings = Settings.builder().build(); + } + /** * The Bill of Materials (BOM) configuration containing rules for component handling. * Includes rules for including, ignoring, removing, and replacing components @@ -54,49 +60,80 @@ public class ScanossSettings { private final @Builder.Default Bom bom = Bom.builder().build(); private final @Builder.Default Settings settings = Settings.builder().build(); + /** General scan settings including skip patterns and file snippet configuration. */ @Data @Builder - @NoArgsConstructor @AllArgsConstructor public static class Settings { + + /** Default constructor. */ + public Settings() { + this.skip = Skip.builder().build(); + } + private final @Builder.Default Skip skip = Skip.builder().build(); @SerializedName("file_snippet") private FileSnippet fileSnippet; } + /** Configuration for skipping files during scanning based on patterns and sizes. */ @Data @Builder - @NoArgsConstructor @AllArgsConstructor public static class Skip { + + /** Default constructor. */ + public Skip() { + this.patterns = Patterns.builder().build(); + this.sizes = Sizes.builder().build(); + } + private final @Builder.Default Patterns patterns = Patterns.builder().build(); private final @Builder.Default Sizes sizes = Sizes.builder().build(); } + /** Glob patterns for skipping files during scanning and fingerprinting. */ @Data @Builder - @NoArgsConstructor @AllArgsConstructor public static class Patterns { + + /** Default constructor. */ + public Patterns() { + this.scanning = new ArrayList<>(); + this.fingerprinting = new ArrayList<>(); + } + private final @Builder.Default List scanning = new ArrayList<>(); private final @Builder.Default List fingerprinting = new ArrayList<>(); } + /** Size-based rules for skipping files during scanning and fingerprinting. */ @Data @Builder - @NoArgsConstructor @AllArgsConstructor public static class Sizes { + + /** Default constructor. */ + public Sizes() { + this.scanning = new ArrayList<>(); + this.fingerprinting = new ArrayList<>(); + } + private final @Builder.Default List scanning = new ArrayList<>(); private final @Builder.Default List fingerprinting = new ArrayList<>(); } + /** A size rule matching file patterns with minimum and maximum byte thresholds. */ @Data @Builder - @NoArgsConstructor @AllArgsConstructor public static class SizeRule { + + /** Default constructor. */ + public SizeRule() {} + private List patterns; private long min; private long max; @@ -147,6 +184,11 @@ public static ScanossSettings createFromPath(@NonNull Path path) { } + /** + * Returns the list of scanning ignore patterns from the skip settings. + * + * @return list of scanning ignore patterns + */ public List getScanningIgnorePattern() { return this.settings.getSkip().getPatterns().getScanning(); } diff --git a/src/main/java/com/scanoss/utils/Hpsm.java b/src/main/java/com/scanoss/utils/Hpsm.java index 8346c1d2..b057347a 100644 --- a/src/main/java/com/scanoss/utils/Hpsm.java +++ b/src/main/java/com/scanoss/utils/Hpsm.java @@ -34,6 +34,10 @@ */ public class Hpsm { + /** Private constructor to prevent instantiation. */ + private Hpsm() { + } + // CRC8 table, Polynomial, initial CRC and post CRC XOR value. private static final int CRC8_MAXIM_DOW_TABLE_SIZE = 0x100; private static final int CRC8_MAXIM_DOW_POLYNOMIAL = 0x8C; // 0x31 reflected diff --git a/src/main/java/com/scanoss/utils/JsonUtils.java b/src/main/java/com/scanoss/utils/JsonUtils.java index ce05ab70..274e4f45 100644 --- a/src/main/java/com/scanoss/utils/JsonUtils.java +++ b/src/main/java/com/scanoss/utils/JsonUtils.java @@ -47,6 +47,10 @@ @Slf4j public class JsonUtils { + /** Private constructor to prevent instantiation. */ + private JsonUtils() { + } + // Custom list type for decoding Scan Results private static final Type scanDetailslistType = new TypeToken>() { }.getType(); diff --git a/src/main/java/com/scanoss/utils/LineRangeUtils.java b/src/main/java/com/scanoss/utils/LineRangeUtils.java index da2d40e4..e5701bb4 100644 --- a/src/main/java/com/scanoss/utils/LineRangeUtils.java +++ b/src/main/java/com/scanoss/utils/LineRangeUtils.java @@ -34,6 +34,11 @@ */ @Slf4j public class LineRangeUtils { + + /** Private constructor to prevent instantiation. */ + private LineRangeUtils() { + } + /** * Parses a line range string into a list of intervals * diff --git a/src/main/java/com/scanoss/utils/PackageDetails.java b/src/main/java/com/scanoss/utils/PackageDetails.java index 39973c9e..41c3d613 100644 --- a/src/main/java/com/scanoss/utils/PackageDetails.java +++ b/src/main/java/com/scanoss/utils/PackageDetails.java @@ -30,6 +30,11 @@ * Lookup information about the current package */ public class PackageDetails { + + /** Private constructor to prevent instantiation. */ + private PackageDetails() { + } + private static String version; // Package version if we can /** diff --git a/src/main/java/com/scanoss/utils/ProxyUtils.java b/src/main/java/com/scanoss/utils/ProxyUtils.java index 08033bb9..99f24dfb 100644 --- a/src/main/java/com/scanoss/utils/ProxyUtils.java +++ b/src/main/java/com/scanoss/utils/ProxyUtils.java @@ -36,6 +36,10 @@ @Slf4j public class ProxyUtils { + /** Private constructor to prevent instantiation. */ + private ProxyUtils() { + } + /** * Create basic proxy from the supplied HTTP Proxy strting * diff --git a/src/main/java/com/scanoss/utils/Purl2Url.java b/src/main/java/com/scanoss/utils/Purl2Url.java index aa522092..c7cbc0fb 100644 --- a/src/main/java/com/scanoss/utils/Purl2Url.java +++ b/src/main/java/com/scanoss/utils/Purl2Url.java @@ -35,6 +35,10 @@ @Slf4j public class Purl2Url { + /** Private constructor to prevent instantiation. */ + private Purl2Url() { + } + /** * PURL type to URL enum */ diff --git a/src/main/java/com/scanoss/utils/WinnowingUtils.java b/src/main/java/com/scanoss/utils/WinnowingUtils.java index abc15b0e..63975037 100644 --- a/src/main/java/com/scanoss/utils/WinnowingUtils.java +++ b/src/main/java/com/scanoss/utils/WinnowingUtils.java @@ -41,6 +41,10 @@ */ public class WinnowingUtils { + /** Private constructor to prevent instantiation. */ + private WinnowingUtils() { + } + /** * Inner class to hold line ending detection results. */