fix: sonarQube замечания

This commit is contained in:
Максименко Никита Владимирович
2026-09-23 09:55:48 +03:00
parent 1cee7d0f0f
commit f0e9c6c60c
7 changed files with 46 additions and 112 deletions
@@ -32,9 +32,6 @@ public class SharedIndex {
private static final Logger LOG = LoggerFactory.getLogger(SharedIndex.class);
private static final String KEY_BY_ID = "pdg:id:";
private static final String KEY_BY_MASK = "pdg:mask:";
/** Сколько подряд неудач размыкает предохранитель. */
private static final int FAILURES_TO_OPEN = 3;
@@ -51,6 +51,9 @@ public final class NameDictionary {
private static final Path EXTERNAL_FILE = Path.of("config/well-known.txt");
/** Разделитель слов: любая последовательность не-буквенных символов. */
private static final String WORD_SPLIT = "\\P{L}+";
/** Порядковые числительные в имени правителя: «Пётр Первый», «Екатерина Вторая». */
private static final String REGNAL_ORDINALS =
"перв|втор|трет|четв[её]рт|пят|шест|седьм|восьм|девят|десят";
@@ -111,7 +114,7 @@ public final class NameDictionary {
* приходится не больше четырёх обращений к хеш-таблице.
*/
public static boolean containsGivenName(String value) {
for (String word : value.split("\\P{L}+")) {
for (String word : value.split(WORD_SPLIT)) {
String lower = word.toLowerCase(Locale.ROOT);
// Точное совпадение с основой сильнее всего: «Яков» оканчивается на «ов»,
// но это имя, а не фамилия.
@@ -138,7 +141,7 @@ public final class NameDictionary {
* не подсказывает, что перед нами имя.
*/
public static boolean containsNamePart(String value) {
for (String word : value.split("\\P{L}+")) {
for (String word : value.split(WORD_SPLIT)) {
String lower = word.toLowerCase(Locale.ROOT);
if (GIVEN_NAMES.contains(lower)) {
return true;
@@ -155,9 +158,19 @@ public final class NameDictionary {
return lower.matches(".*(?:ович|евич|овна|евна|ична|ичн)$");
}
/** Фамилия по словообразованию: Иванов, Петрова, Троицкий, Шевченко. */
/** Окончания, по которым слово похоже на фамилию: Иванов, Петрова, Троицкий, Шевченко. */
private static final Set<String> SURNAME_ENDINGS = Set.of(
"ов", "ев", "ёв", "ин", "ын", "ский", "ская", "ского", "ской", "ском",
"цкий", "цкая", "енко", "ко", "ук", "юк", "ян", "швили", "дзе");
/** Фамилия по словообразованию. Набор окончаний вместо regex: проще и без CANON_EQ. */
private static boolean isSurname(String lower) {
return lower.matches(".*(?:ов|ев|ёв|ин|ын|ск(?:ий|ая|ого|ой|ом)|цк(?:ий|ая)|енко|ко|ук|юк|ян|швили|дзе)$");
for (String ending : SURNAME_ENDINGS) {
if (lower.endsWith(ending)) {
return true;
}
}
return false;
}
/**
@@ -174,7 +187,7 @@ public final class NameDictionary {
return true;
}
Set<String> stems = currentWellKnownStems();
for (String word : value.split("\\P{L}+")) {
for (String word : value.split(WORD_SPLIT)) {
String lower = word.toLowerCase(Locale.ROOT);
for (int length = lower.length(); length > 0; length--) {
if (stems.contains(lower.substring(0, length))) {
@@ -235,7 +248,6 @@ public final class NameDictionary {
}
}
return Set.copyOf(merged);
},
BUNDLED_WELL_KNOWN_STEMS);
});
}
}
@@ -67,10 +67,9 @@ final class ResourceLoader {
* @param path путь к файлу
* @param state состояние проверки (время последней проверки и mtime файла)
* @param reader как превратить строки файла в итоговое значение
* @param fallback значение, если файл недоступен
*/
static <T> T refreshIfChanged(Path path, FileWatchState<T> state,
Function<List<String>, T> reader, T fallback) {
Function<List<String>, T> reader) {
long now = System.currentTimeMillis();
if (now - state.lastCheck < state.recheckMillis) {
return state.current;
@@ -33,6 +33,9 @@ public class RuleRegistry {
*/
private static final String CAPITALISED = "\\p{Lu}[\\p{Lu}\\p{Ll}]+";
/** Якорное слово-основа: держатель карты, держателем и т.п. */
private static final String HOLDER_STEM = "держател";
/**
* Название улицы: от одного до трёх слов с заглавной буквы либо чисел —
* «Тверская», «Малая Никитская», «8 Марта». Ограничение по форме обязательно:
@@ -285,7 +288,7 @@ public class RuleRegistry {
Rule.of(PdTypes.CARDHOLDER, "(?iu:держател\\w*(?:\\s+карты)?|cardholder|на\\s+имя)"
+ "\\W{0,10}([A-Z]{2,20}\\s+[A-Z]{2,20})\\b", 86)
.groups(1)
.anchoredBy("держател", "cardholder", "на имя"),
.anchoredBy(HOLDER_STEM, "cardholder", "на имя"),
// --- ФИО ---
@@ -322,7 +325,7 @@ public class RuleRegistry {
.anchoredBy("клиент", "заказчик", "пациент", "сотрудник", "владел", "плательщик",
"получател", "абонент", "в лице", "представител", "поручител", "заёмщик",
"заемщик", "заявител", "доверител", "вкладчик", "ответственн",
"контактное лицо", "исполнител", "держател"),
"контактное лицо", "исполнител", HOLDER_STEM),
// «клиент иван иванов», «поручитель петрович» — строчные имена после
// ролевого слова. Регистр снимает требование к заглавной букве, а словарь
@@ -339,7 +342,7 @@ public class RuleRegistry {
.anchoredBy("клиент", "заказчик", "пациент", "сотрудник", "владел", "плательщик",
"получател", "абонент", "в лице", "представител", "поручител", "заёмщик",
"заемщик", "заявител", "доверител", "вкладчик", "ответственн",
"контактное лицо", "исполнител", "держател", "отправител", "бенефициар",
"контактное лицо", "исполнител", HOLDER_STEM, "отправител", "бенефициар",
"доверенное лицо", "наследник", "созаемщик"),
// Фамилия рядом с личным именем из словаря: без словаря правило ловило бы
@@ -138,13 +138,16 @@ public final class Validators {
}
String[] parts = value.split("[.\\-/\\s]+");
if (parts.length == 2) {
int a = Integer.parseInt(parts[0]);
int b = Integer.parseInt(parts[1]);
return (a >= 1 && a <= 31 && b >= 1 && b <= 12) || (b >= 1 && b <= 31 && a >= 1 && a <= 12);
return dayAndMonth(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));
}
if (parts.length != 3) {
return false;
}
return threePartDate(parts);
}
/** {@code 12.05.1985}, {@code 1985-05-12}, {@code 15 03 90} — дата из трёх чисел. */
private static boolean threePartDate(String[] parts) {
int[] n = new int[3];
for (int i = 0; i < 3; i++) {
if (parts[i].isEmpty() || parts[i].length() > 4) {