Files
kst4contest/src/main/java/kst4contest/logic/FrequencyTextParser.java
T

197 lines
5.3 KiB
Java

package kst4contest.logic;
import kst4contest.model.Band;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Common parser for explicit amateur-radio frequencies embedded in text.
*
* <p>This parser deliberately handles only complete frequencies such as
* 144.300, 432.357, 10368.100 or their compact digit-only forms. Relative
* forms such as ".210" or ambiguous bare values such as "210" require
* additional message context and remain the responsibility of the
* chat-message parser.</p>
*/
public final class FrequencyTextParser {
/*
* Examples:
* 50.150
* 144.300
* 432,357
* 10368.100
* 144.300.03
* 144300
* 10368100
*
* At least two digits are required before a decimal separator. Compact
* values need at least five digits because their final three digits form
* the kHz part. This intentionally prevents "1.2" and bare values such as
* "210" from being interpreted as complete frequencies.
*/
private static final Pattern EXPLICIT_FREQUENCY_PATTERN = Pattern.compile(
"(?<![A-Z0-9])"
+ "(\\d{2,5}[.,]\\d{1,3}(?:[.,]\\d{1,3})?|\\d{5,8})"
+ "(?![A-Z0-9])",
Pattern.CASE_INSENSITIVE
);
private FrequencyTextParser() {
}
/**
* Finds all distinct explicit frequencies that fall into one of the bands
* supported by {@link Band}.
*/
public static List<DetectedFrequency> findExplicitFrequencies(String text) {
if (text == null || text.isBlank()) {
return List.of();
}
Matcher matcher = EXPLICIT_FREQUENCY_PATTERN.matcher(text);
Map<String, DetectedFrequency> uniqueFrequencies =
new LinkedHashMap<>();
while (matcher.find()) {
DetectedFrequency detected =
parseExplicitFrequency(matcher.group(1));
if (detected == null) {
continue;
}
String uniqueKey =
detected.getBand().name()
+ "|"
+ Double.toString(
detected.getFrequencyMHz()
);
uniqueFrequencies.putIfAbsent(
uniqueKey,
detected
);
}
return List.copyOf(
new ArrayList<>(uniqueFrequencies.values())
);
}
/**
* Parses one complete frequency.
*
* @return detected band/frequency or {@code null} when the value is invalid
* or outside the supported amateur bands
*/
public static DetectedFrequency parseExplicitFrequency(
String rawFrequency
) {
if (rawFrequency == null || rawFrequency.isBlank()) {
return null;
}
final String trimmedFrequency = rawFrequency.trim();
final String normalized = trimmedFrequency.matches("\\d{5,8}")
? trimmedFrequency.substring(0, trimmedFrequency.length() - 3)
+ "."
+ trimmedFrequency.substring(trimmedFrequency.length() - 3)
: normalizeFrequencyString(trimmedFrequency.replace(',', '.'));
try {
double frequencyMHz =
Double.parseDouble(normalized);
if (!Double.isFinite(frequencyMHz)) {
return null;
}
Band band =
Band.fromFrequency(frequencyMHz);
if (band == null) {
return null;
}
return new DetectedFrequency(
band,
frequencyMHz,
rawFrequency.trim()
);
} catch (NumberFormatException ignored) {
return null;
}
}
/**
* Normalizes KST-style frequency strings with an optional second decimal
* separator.
*
* Examples:
* 144.300.03 -> 144.30003
* 144.300 -> 144.300
*/
private static String normalizeFrequencyString(
String rawInput
) {
int firstDotIndex = rawInput.indexOf('.');
if (firstDotIndex < 0) {
return rawInput;
}
String decimalPart =
rawInput.substring(firstDotIndex + 1);
if (!decimalPart.contains(".")) {
return rawInput;
}
decimalPart =
decimalPart.replace(".", "");
return rawInput.substring(0, firstDotIndex + 1)
+ decimalPart;
}
/**
* Immutable result of one explicit-frequency detection.
*/
public static final class DetectedFrequency {
private final Band band;
private final double frequencyMHz;
private final String sourceText;
private DetectedFrequency(
Band band,
double frequencyMHz,
String sourceText
) {
this.band = band;
this.frequencyMHz = frequencyMHz;
this.sourceText = sourceText;
}
public Band getBand() {
return band;
}
public double getFrequencyMHz() {
return frequencyMHz;
}
public String getSourceText() {
return sourceText;
}
}
}