ScanStatistics.java
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright 2026 Nikolas Charalambidis
*/
package dev.nichar.facepalm.engine;
import jakarta.annotation.Nonnull;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;
import lombok.Data;
/**
* Thread-safe metrics container for real-time scan monitoring.
* Tracks file discovery, scan progress, and exclusion reasons across parallel threads.
*/
@Data
public class ScanStatistics {
private final long startTimeMillis = System.currentTimeMillis();
/**
* Files discovered during project traversal.
*/
private final LongAdder filesDiscovered = new LongAdder();
/**
* Files that passed filters and were successfully analyzed.
*/
private final LongAdder filesScanned = new LongAdder();
/**
* Distribution of scanned files by extension.
*/
private final Map<String, LongAdder> suffixCounts = new ConcurrentHashMap<>();
/**
* Frequency of file exclusions grouped by reason.
*/
private final Map<ExclusionReason, LongAdder> exclusionBreakdown = new ConcurrentHashMap<>();
/**
* Increments the total file discovery count.
*/
public void recordDiscovery() {
filesDiscovered.increment();
}
/**
* Logs a successful file scan and tracks its extension for reporting.
*/
public void recordScan(@Nonnull final Path path) {
filesScanned.increment();
final var fileName = path.getFileName().toString();
final var lastDot = fileName.lastIndexOf('.');
final var suffix = (lastDot == -1) ? "no-extension" : fileName.substring(lastDot).toLowerCase(Locale.ROOT);
suffixCounts.computeIfAbsent(suffix, k -> new LongAdder()).increment();
}
/**
* Logs a file exclusion and the associated reason.
*/
public void recordExclusion(@Nonnull final ExclusionReason reason) {
exclusionBreakdown.computeIfAbsent(reason, k -> new LongAdder()).increment();
}
/**
* Returns the total execution time in milliseconds.
*/
public long getDuration() {
return System.currentTimeMillis() - startTimeMillis;
}
}