tests fixes and sprints work

This commit is contained in:
master
2026-01-22 19:08:46 +02:00
parent c32fff8f86
commit 726d70dc7f
881 changed files with 134434 additions and 6228 deletions

View File

@@ -0,0 +1,316 @@
// -----------------------------------------------------------------------------
// JavaLicenseDetector.cs
// Sprint: SPRINT_20260119_024_Scanner_license_detection_enhancements
// Task: TASK-024-006 - Upgrade Java license detector
// Description: Enhanced Java license detection returning LicenseDetectionResult
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
using StellaOps.Scanner.Analyzers.Lang.Core.Licensing;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.BuildMetadata;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.License;
/// <summary>
/// Enhanced Java license detector that returns full LicenseDetectionResult.
/// </summary>
internal sealed class JavaLicenseDetector
{
private readonly ILicenseCategorizationService _categorizationService;
private readonly ILicenseTextExtractor _textExtractor;
private readonly ICopyrightExtractor _copyrightExtractor;
private readonly SpdxLicenseNormalizer _normalizer;
/// <summary>
/// Creates a new Java license detector with the specified services.
/// </summary>
public JavaLicenseDetector(
ILicenseCategorizationService categorizationService,
ILicenseTextExtractor textExtractor,
ICopyrightExtractor copyrightExtractor)
{
_categorizationService = categorizationService;
_textExtractor = textExtractor;
_copyrightExtractor = copyrightExtractor;
_normalizer = SpdxLicenseNormalizer.Instance;
}
/// <summary>
/// Creates a new Java license detector with default services.
/// </summary>
public JavaLicenseDetector()
{
_categorizationService = new LicenseCategorizationService();
_textExtractor = new LicenseTextExtractor();
_copyrightExtractor = new CopyrightExtractor();
_normalizer = SpdxLicenseNormalizer.Instance;
}
/// <summary>
/// Detects license information from Java license info.
/// </summary>
/// <param name="licenseInfo">The license info from project metadata.</param>
/// <param name="projectDirectory">Project directory for license file extraction.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The full license detection result.</returns>
public async Task<LicenseDetectionResult?> DetectAsync(
JavaLicenseInfo licenseInfo,
string? projectDirectory = null,
CancellationToken ct = default)
{
if (licenseInfo.Name is null && licenseInfo.Url is null)
{
return null;
}
// Use existing normalizer
var normalized = _normalizer.Normalize(licenseInfo.Name, licenseInfo.Url);
var spdxId = normalized.SpdxId ?? BuildLicenseRef(licenseInfo.Name);
// Determine confidence
var confidence = MapConfidence(normalized.SpdxConfidence);
// Extract license text if project directory is available
LicenseTextExtractionResult? licenseTextResult = null;
string? noticeContent = null;
if (!string.IsNullOrWhiteSpace(projectDirectory))
{
// Extract LICENSE file
var licenseFiles = await _textExtractor.ExtractFromDirectoryAsync(projectDirectory, ct);
licenseTextResult = licenseFiles.FirstOrDefault();
// Look for NOTICE file (common in Apache projects)
noticeContent = await TryReadNoticeFileAsync(projectDirectory, ct);
}
// Get copyright notices from LICENSE and NOTICE files
var copyrightNotices = new List<CopyrightNotice>();
if (licenseTextResult?.CopyrightNotices.Length > 0)
{
copyrightNotices.AddRange(licenseTextResult.CopyrightNotices);
}
if (!string.IsNullOrWhiteSpace(noticeContent))
{
copyrightNotices.AddRange(_copyrightExtractor.Extract(noticeContent));
}
var primaryCopyright = copyrightNotices.Count > 0
? copyrightNotices[0].FullText
: null;
var result = new LicenseDetectionResult
{
SpdxId = spdxId,
OriginalText = FormatOriginalText(licenseInfo),
LicenseUrl = licenseInfo.Url,
Confidence = confidence,
Method = DetermineDetectionMethod(licenseInfo),
SourceFile = "pom.xml",
Category = LicenseCategory.Unknown,
Obligations = [],
LicenseText = licenseTextResult?.FullText,
LicenseTextHash = licenseTextResult?.TextHash,
CopyrightNotice = primaryCopyright,
IsExpression = false,
ExpressionComponents = []
};
return _categorizationService.Enrich(result);
}
/// <summary>
/// Detects licenses from multiple license declarations (pom.xml can have multiple).
/// </summary>
public async Task<IReadOnlyList<LicenseDetectionResult>> DetectMultipleAsync(
IEnumerable<JavaLicenseInfo> licenses,
string? projectDirectory = null,
CancellationToken ct = default)
{
var results = new List<LicenseDetectionResult>();
foreach (var license in licenses)
{
var result = await DetectAsync(license, projectDirectory, ct);
if (result is not null)
{
results.Add(result);
}
}
return results;
}
/// <summary>
/// Creates a combined expression from multiple license results.
/// </summary>
public LicenseDetectionResult? CombineAsExpression(IReadOnlyList<LicenseDetectionResult> results)
{
if (results.Count == 0)
{
return null;
}
if (results.Count == 1)
{
return results[0];
}
// Multiple licenses - create OR expression (dual licensing is common in Java)
var spdxIds = results
.Select(r => r.SpdxId)
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(s => s, StringComparer.Ordinal)
.ToList();
var expression = string.Join(" OR ", spdxIds);
// Use the first result as base and update
var first = results[0];
return first with
{
SpdxId = expression,
IsExpression = true,
ExpressionComponents = [.. spdxIds],
OriginalText = string.Join("; ", results.Select(r => r.OriginalText).Where(t => t is not null))
};
}
/// <summary>
/// Detects license from LICENSE file content.
/// </summary>
public LicenseDetectionResult? DetectFromLicenseFile(string licenseText, string? sourceFile = null)
{
if (string.IsNullOrWhiteSpace(licenseText))
{
return null;
}
var textResult = _textExtractor.Extract(licenseText, sourceFile);
var spdxId = textResult.DetectedLicenseId ?? "LicenseRef-Unknown";
var confidence = textResult.DetectedLicenseId is not null
? textResult.Confidence
: LicenseDetectionConfidence.Low;
var result = new LicenseDetectionResult
{
SpdxId = spdxId,
Confidence = confidence,
Method = LicenseDetectionMethod.LicenseFile,
SourceFile = sourceFile ?? "LICENSE",
Category = LicenseCategory.Unknown,
Obligations = [],
LicenseText = licenseText,
LicenseTextHash = textResult.TextHash,
CopyrightNotice = textResult.CopyrightNotices.Length > 0
? textResult.CopyrightNotices[0].FullText
: null
};
return _categorizationService.Enrich(result);
}
/// <summary>
/// Detects license synchronously without file extraction.
/// </summary>
public LicenseDetectionResult? Detect(JavaLicenseInfo licenseInfo)
{
if (licenseInfo.Name is null && licenseInfo.Url is null)
{
return null;
}
var normalized = _normalizer.Normalize(licenseInfo.Name, licenseInfo.Url);
var spdxId = normalized.SpdxId ?? BuildLicenseRef(licenseInfo.Name);
var confidence = MapConfidence(normalized.SpdxConfidence);
var result = new LicenseDetectionResult
{
SpdxId = spdxId,
OriginalText = FormatOriginalText(licenseInfo),
LicenseUrl = licenseInfo.Url,
Confidence = confidence,
Method = DetermineDetectionMethod(licenseInfo),
SourceFile = "pom.xml",
Category = LicenseCategory.Unknown,
Obligations = []
};
return _categorizationService.Enrich(result);
}
private static async Task<string?> TryReadNoticeFileAsync(string directory, CancellationToken ct)
{
var noticeFiles = new[] { "NOTICE", "NOTICE.txt", "NOTICE.md" };
foreach (var noticeFile in noticeFiles)
{
var path = Path.Combine(directory, noticeFile);
if (File.Exists(path))
{
try
{
return await File.ReadAllTextAsync(path, ct);
}
catch
{
// Ignore file read errors
}
}
}
return null;
}
private static LicenseDetectionConfidence MapConfidence(SpdxConfidence spdxConfidence)
{
return spdxConfidence switch
{
SpdxConfidence.High => LicenseDetectionConfidence.High,
SpdxConfidence.Medium => LicenseDetectionConfidence.Medium,
SpdxConfidence.Low => LicenseDetectionConfidence.Low,
_ => LicenseDetectionConfidence.None
};
}
private static LicenseDetectionMethod DetermineDetectionMethod(JavaLicenseInfo licenseInfo)
{
if (!string.IsNullOrWhiteSpace(licenseInfo.Url))
{
return LicenseDetectionMethod.UrlMatching;
}
return LicenseDetectionMethod.PackageMetadata;
}
private static string? FormatOriginalText(JavaLicenseInfo licenseInfo)
{
if (licenseInfo.Name is not null && licenseInfo.Url is not null)
{
return $"{licenseInfo.Name} ({licenseInfo.Url})";
}
return licenseInfo.Name ?? licenseInfo.Url;
}
private static string BuildLicenseRef(string? name)
{
if (string.IsNullOrWhiteSpace(name))
{
return "LicenseRef-Unknown";
}
// Sanitize for SPDX LicenseRef
var sanitized = new char[Math.Min(name.Length, 50)];
for (var i = 0; i < sanitized.Length; i++)
{
var c = name[i];
sanitized[i] = char.IsLetterOrDigit(c) || c == '.' || c == '-'
? c
: '-';
}
return $"LicenseRef-{new string(sanitized).Trim('-')}";
}
}