value = lines.getOrCompute(); // вычислится один раз и закэшируется
+lines.clear(); // сбросить кэш
+```
+
+Интернирование:
+
+```java
+StringInterner interner = new StringInterner();
+String canonical = interner.intern(name); // равные строки делят один экземпляр
+```
+
+Регистронезависимый поиск с учётом Unicode:
+
+```java
+Pattern pattern = CaseInsensitivePattern.compile("Процедура");
+boolean matches = pattern.matcher("процедура").matches(); // true
+```
+
+Скачивание BSL Language Server:
+
+```java
+var client = new GitHubReleaseClient(githubToken); // токен может быть null
+var downloader = new BslLanguageServerDownloader(installDir, client, HttpClient.newHttpClient());
+Path binary = downloader.downloadIfNeeded(BslLanguageServerReleaseChannel.STABLE);
+```
+
+## Сборка
+
+Используйте wrapper Gradle:
+
+```bash
+./gradlew build # сборка, проверки и тесты
+./gradlew check # то, что гоняет CI (test + jacoco + javadoc + проверка лицензий)
+```
+
+Заголовки лицензии проверяются плагином license; при необходимости их можно проставить командой
+`./gradlew licenseFormat`.
+
+## Лицензия
+
+[GNU LGPL 3.0 или новее](LICENSE.md) (`SPDX-License-Identifier: LGPL-3.0-or-later`).
diff --git a/src/main/java/com/github/_1c_syntax/utils/Absolute.java b/src/main/java/com/github/_1c_syntax/utils/Absolute.java
index 4146e2c..98dc0f7 100644
--- a/src/main/java/com/github/_1c_syntax/utils/Absolute.java
+++ b/src/main/java/com/github/_1c_syntax/utils/Absolute.java
@@ -34,16 +34,29 @@
import java.nio.file.Path;
/**
- * Методы получения абсолютного пути файла с учетом различных особенностей
+ * Приведение файловых путей и URI к каноническому абсолютному виду.
+ *
+ * Утилита сглаживает различия в записи одного и того же файла, из-за которых он иначе выглядел
+ * бы как разные ресурсы: относительные пути разворачиваются в абсолютные, символические ссылки и
+ * сегменты {@code .}/{@code ..} схлопываются через каноникализацию файла, регистр и разделители
+ * приводятся к виду файловой системы. Для {@code file:}-URI дополнительно нормализуется
+ * процентное кодирование спецсимволов и восстанавливается authority, чтобы форма URI совпадала с
+ * той, что отдаёт JDK для канонического файла.
+ *
+ *
Благодаря этому URI/путь можно использовать как стабильный ключ (например, документа в
+ * рабочей области), не опасаясь, что тот же файл придёт в другой записи.
*/
@UtilityClass
public final class Absolute {
/**
- * Получение URI из строки
+ * Разбирает строковый URI и приводит его к каноническому абсолютному виду.
+ *
+ *
Если строка не является корректным URL, она интерпретируется как {@link URI} и обрабатывается
+ * через {@link #uri(URI)}.
*
- * @param uri - строковое представление URI
- * @return - полученное значение
+ * @param uri строковое представление URI
+ * @return канонический абсолютный URI
*/
public static URI uri(String uri) {
try {
@@ -66,10 +79,11 @@ public static URI uri(String uri) {
}
/**
- * Получение абсолютного URI из URI с валидацией
+ * Приводит URI к каноническому абсолютному виду, нормализуя процентное кодирование пути и,
+ * для {@code file:}-URI без authority, восстанавливая её через каноникализацию файла.
*
- * @param uri - исходный URI
- * @return - полученное значение
+ * @param uri исходный URI
+ * @return канонический абсолютный URI
*/
public static URI uri(URI uri) {
var decodedUri = URI.create(uri.getScheme() + ":" + encodePath(uri.getSchemeSpecificPart()));
@@ -78,50 +92,51 @@ public static URI uri(URI uri) {
}
/**
- * Получение URI файла
+ * Возвращает канонический абсолютный URI файла.
*
- * @param file - исходный файл
- * @return - полученное значение
+ * @param file исходный файл
+ * @return канонический абсолютный {@code file:}-URI
*/
public static URI uri(File file) {
return uri(path(file).toUri());
}
/**
- * Получение пути (path) из строки
+ * Возвращает канонический абсолютный путь по его строковому представлению.
*
- * @param path - строковое представление пути
- * @return - полученное значение
+ * @param path строковое представление пути
+ * @return канонический абсолютный путь
*/
public static Path path(String path) {
return path(Path.of(path));
}
/**
- * Получение пути (path) из URI
+ * Возвращает канонический абсолютный путь к файлу, на который указывает URI.
*
- * @param uri - исходное значение URI
- * @return - полученное значение
+ * @param uri исходный URI
+ * @return канонический абсолютный путь
*/
public static Path path(URI uri) {
return path(Path.of(uri(uri)));
}
/**
- * Получение абсолютного пути (path) из Path
+ * Возвращает канонический абсолютный путь для переданного {@link Path}.
*
- * @param path - исходное значение пути
- * @return - полученное значение
+ * @param path исходный путь
+ * @return канонический абсолютный путь
*/
public static Path path(Path path) {
return path(path.toFile());
}
/**
- * Получение пути файла
+ * Возвращает канонический абсолютный путь файла: разворачивает символические ссылки и
+ * сегменты {@code .}/{@code ..}, приводит запись к виду файловой системы.
*
- * @param file - исходный файл
- * @return - полученное значение
+ * @param file исходный файл
+ * @return канонический абсолютный путь
*/
@SneakyThrows
public static Path path(File file) {
diff --git a/src/main/java/com/github/_1c_syntax/utils/CaseInsensitivePattern.java b/src/main/java/com/github/_1c_syntax/utils/CaseInsensitivePattern.java
index 1953192..cf290e3 100644
--- a/src/main/java/com/github/_1c_syntax/utils/CaseInsensitivePattern.java
+++ b/src/main/java/com/github/_1c_syntax/utils/CaseInsensitivePattern.java
@@ -27,7 +27,12 @@
import java.util.regex.PatternSyntaxException;
/**
- * Pattern helper
+ * Компиляция регулярных выражений, нечувствительных к регистру, включая Unicode.
+ *
+ *
Обёртка над {@link Pattern#compile(String, int)} с флагами
+ * {@link Pattern#CASE_INSENSITIVE} и {@link Pattern#UNICODE_CASE}. Второй флаг обязателен для
+ * корректного сравнения без учёта регистра в неlatin-алфавитах (в частности, в кириллице —
+ * основном алфавите кода 1С), где одного {@code CASE_INSENSITIVE} недостаточно.
*/
@UtilityClass
public class CaseInsensitivePattern {
diff --git a/src/main/java/com/github/_1c_syntax/utils/GenericInterner.java b/src/main/java/com/github/_1c_syntax/utils/GenericInterner.java
index f2fe75d..2d4fcbf 100644
--- a/src/main/java/com/github/_1c_syntax/utils/GenericInterner.java
+++ b/src/main/java/com/github/_1c_syntax/utils/GenericInterner.java
@@ -25,17 +25,37 @@
import java.util.concurrent.ConcurrentHashMap;
/**
- * Реализация универсального интернера
+ * Потокобезопасный интернер значений произвольного типа.
+ *
+ *
Хранит по одному каноническому экземпляру для каждого класса эквивалентности
+ * (по {@code equals}/{@code hashCode}) и возвращает его для всех равных значений. Позволяет
+ * заменить множество равных, но разных по ссылке объектов на один и тем самым сократить
+ * потребление памяти, а для потребителей — сравнивать значения по ссылке ({@code ==}).
+ *
+ *
Кэш не имеет ограничения по размеру и не вытесняет записи автоматически: интернированные
+ * значения удерживаются до явного {@link #clear()}. Реализация основана на
+ * {@link ConcurrentHashMap} и безопасна для конкурентного использования.
+ *
+ * @param тип интернируемых значений
*/
public class GenericInterner {
private final Map map = new ConcurrentHashMap<>();
/**
- * Метод интернирования значения
+ * Создаёт пустой интернер.
+ */
+ public GenericInterner() {
+ // no state to initialize beyond the backing map
+ }
+
+ /**
+ * Возвращает канонический экземпляр, равный переданному значению. Если равное значение ещё не
+ * интернировано, каноническим становится переданный объект.
*
- * @param object Интернируемый объект
- * @return значение из кеша
+ * @param object интернируемое значение
+ * @return ранее сохранённый экземпляр, равный {@code object}, либо сам {@code object},
+ * если равного ещё не было
*/
public T intern(T object) {
T exist = map.putIfAbsent(object, object);
diff --git a/src/main/java/com/github/_1c_syntax/utils/Lazy.java b/src/main/java/com/github/_1c_syntax/utils/Lazy.java
index 613bba1..f34f6bc 100644
--- a/src/main/java/com/github/_1c_syntax/utils/Lazy.java
+++ b/src/main/java/com/github/_1c_syntax/utils/Lazy.java
@@ -29,7 +29,19 @@
import static java.util.Objects.requireNonNull;
/**
- * Реализация хранения данных с ленивым чтением
+ * Хранилище значения с ленивым однократным вычислением и потокобезопасным доступом.
+ *
+ * Значение вычисляется не в момент создания, а при первом обращении через
+ * {@link #getOrCompute()} / {@link #getOrCompute(Supplier)} и кэшируется. Вычисление защищено
+ * блокировкой и выполняется по схеме double-checked locking: конкурентные потоки, попавшие на
+ * невычисленное значение, ждут единственного вычисления, а не запускают его повторно. Уже
+ * вычисленное значение читается без блокировки через {@code volatile}-поле.
+ *
+ *
Вычисленное значение не может быть {@code null}: если {@link Supplier} вернёт {@code null},
+ * будет брошено {@link NullPointerException}. Кэш можно сбросить через {@link #clear()},
+ * после чего следующее обращение вычислит значение заново.
+ *
+ * @param тип хранимого значения
*/
public final class Lazy {
@@ -37,21 +49,48 @@ public final class Lazy {
private final ReentrantLock lock;
private volatile @Nullable T value;
+ /**
+ * Создаёт хранилище с собственной блокировкой.
+ *
+ * @param supplier поставщик значения по умолчанию, используемый {@link #getOrCompute()}
+ */
public Lazy(Supplier supplier) {
this(supplier, new ReentrantLock());
}
+ /**
+ * Создаёт хранилище с внешней блокировкой. Общий {@link ReentrantLock} позволяет нескольким
+ * экземплярам сериализовать свои вычисления на одном мониторе.
+ *
+ * @param supplier поставщик значения по умолчанию, используемый {@link #getOrCompute()}
+ * @param lock блокировка, под которой выполняется вычисление значения
+ */
public Lazy(Supplier supplier, ReentrantLock lock) {
// no need to initialize lazy-value
this.supplier = supplier;
this.lock = lock;
}
+ /**
+ * Возвращает уже вычисленное значение, не запуская вычисление.
+ *
+ * @return закэшированное значение или {@code null}, если оно ещё не вычислено либо сброшено
+ * через {@link #clear()}
+ */
@Nullable
public T get() {
return value;
}
+ /**
+ * Возвращает закэшированное значение, а при его отсутствии вычисляет его переданным поставщиком
+ * и кэширует. Вычисление выполняется под блокировкой не более одного раза при конкурентном
+ * доступе.
+ *
+ * @param supplier поставщик значения для этого вызова; должен вернуть не {@code null}
+ * @return вычисленное (или ранее закэшированное) значение
+ * @throws NullPointerException если поставщик вернул {@code null}
+ */
public T getOrCompute(Supplier supplier) {
final T result = value; // Just one volatile read
if (result == null) {
@@ -65,15 +104,32 @@ public T getOrCompute(Supplier supplier) {
return result;
}
+ /**
+ * Возвращает закэшированное значение, а при его отсутствии вычисляет его поставщиком, переданным
+ * в конструктор, и кэширует.
+ *
+ * @return вычисленное (или ранее закэшированное) значение
+ * @throws NullPointerException если поставщик вернул {@code null}
+ * @see #getOrCompute(Supplier)
+ */
public T getOrCompute() {
return getOrCompute(supplier);
}
+ /**
+ * Проверяет, вычислено ли значение.
+ *
+ * @return {@code true}, если значение уже вычислено и закэшировано
+ */
public boolean isPresent() {
final T result = value;
return result != null;
}
+ /**
+ * Сбрасывает закэшированное значение. Следующее обращение через {@code getOrCompute} вычислит
+ * его заново.
+ */
public void clear() {
value = null;
}
diff --git a/src/main/java/com/github/_1c_syntax/utils/StringInterner.java b/src/main/java/com/github/_1c_syntax/utils/StringInterner.java
index 4e28cd8..7f2a5af 100644
--- a/src/main/java/com/github/_1c_syntax/utils/StringInterner.java
+++ b/src/main/java/com/github/_1c_syntax/utils/StringInterner.java
@@ -24,9 +24,28 @@
import org.jspecify.annotations.Nullable;
/**
- * Реализация интернера для строк
+ * Интернер строк — {@link GenericInterner} для {@link String}, дополнительно принимающий
+ * {@code null}.
+ *
+ * В отличие от базового интернера, {@code null} не сохраняется, а нормализуется в пустую
+ * строку, поэтому метод {@link #intern(String)} никогда не возвращает {@code null}. Для непустых
+ * строк поведение совпадает с {@link GenericInterner}.
*/
public class StringInterner extends GenericInterner {
+
+ /**
+ * Создаёт пустой интернер строк.
+ */
+ public StringInterner() {
+ // no additional state
+ }
+
+ /**
+ * Возвращает канонический экземпляр переданной строки; для {@code null} возвращает пустую строку.
+ *
+ * @param object интернируемая строка либо {@code null}
+ * @return канонический экземпляр строки, либо {@code ""}, если передан {@code null}
+ */
@Override
public String intern(@Nullable String object) {
if (object == null) {
diff --git a/src/main/java/com/github/_1c_syntax/utils/downloader/BslLanguageServerDownloader.java b/src/main/java/com/github/_1c_syntax/utils/downloader/BslLanguageServerDownloader.java
index eaa7843..335638a 100644
--- a/src/main/java/com/github/_1c_syntax/utils/downloader/BslLanguageServerDownloader.java
+++ b/src/main/java/com/github/_1c_syntax/utils/downloader/BslLanguageServerDownloader.java
@@ -84,6 +84,8 @@ public class BslLanguageServerDownloader {
private final HttpClient httpClient;
/**
+ * Создаёт загрузчик поверх указанного каталога установки и клиентов доступа к GitHub.
+ *
* @param installDir каталог установки сервера; в нём создаются подпапки с версиями
* и файл {@code SERVER-INFO}
* @param releaseClient источник сведений о последнем релизе
diff --git a/src/main/java/com/github/_1c_syntax/utils/downloader/DownloadProgressListener.java b/src/main/java/com/github/_1c_syntax/utils/downloader/DownloadProgressListener.java
index a4fdf3c..638106b 100644
--- a/src/main/java/com/github/_1c_syntax/utils/downloader/DownloadProgressListener.java
+++ b/src/main/java/com/github/_1c_syntax/utils/downloader/DownloadProgressListener.java
@@ -42,6 +42,8 @@ public interface DownloadProgressListener {
};
/**
+ * Вызывается при поступлении очередной порции скачанных байт.
+ *
* @param bytesRead сколько байт ассета уже скачано
* @param totalBytes полный размер ассета в байтах или {@code -1}, если сервер его не сообщил
*/
diff --git a/src/main/java/com/github/_1c_syntax/utils/downloader/GitHubReleaseClient.java b/src/main/java/com/github/_1c_syntax/utils/downloader/GitHubReleaseClient.java
index 58bce06..c6e5022 100644
--- a/src/main/java/com/github/_1c_syntax/utils/downloader/GitHubReleaseClient.java
+++ b/src/main/java/com/github/_1c_syntax/utils/downloader/GitHubReleaseClient.java
@@ -71,6 +71,9 @@ public class GitHubReleaseClient {
private final HttpClient httpClient;
/**
+ * Создаёт клиент с {@link HttpClient} по умолчанию (таймаут соединения и следование редиректам
+ * настроены под GitHub API).
+ *
* @param token GitHub OAuth-токен для обхода лимитов анонимного API; может быть {@code null}
*/
public GitHubReleaseClient(@Nullable String token) {
@@ -81,6 +84,8 @@ public GitHubReleaseClient(@Nullable String token) {
}
/**
+ * Создаёт клиент с переданным {@link HttpClient}.
+ *
* @param token GitHub OAuth-токен для обхода лимитов анонимного API; может быть {@code null}
* @param httpClient клиент для запросов к GitHub API — например, с настроенным прокси
*/
diff --git a/src/main/java/com/github/_1c_syntax/utils/package-info.java b/src/main/java/com/github/_1c_syntax/utils/package-info.java
index 1812918..78ba718 100644
--- a/src/main/java/com/github/_1c_syntax/utils/package-info.java
+++ b/src/main/java/com/github/_1c_syntax/utils/package-info.java
@@ -19,6 +19,19 @@
* You should have received a copy of the GNU Lesser General Public
* License along with 1c-syntax utils.
*/
+/**
+ * Общие утилиты java-проектов команды 1c-syntax.
+ *
+ * Небольшие независимые помощники, переиспользуемые в BSL Language Server и смежных проектах:
+ * каноникализация путей/URI ({@link com.github._1c_syntax.utils.Absolute}), ленивое вычисление
+ * ({@link com.github._1c_syntax.utils.Lazy}), интернирование значений
+ * ({@link com.github._1c_syntax.utils.GenericInterner},
+ * {@link com.github._1c_syntax.utils.StringInterner}) и компиляция регистронезависимых
+ * регулярных выражений ({@link com.github._1c_syntax.utils.CaseInsensitivePattern}).
+ *
+ *
Пакет помечен {@link org.jspecify.annotations.NullMarked}: типы считаются non-null, если
+ * явно не аннотированы {@link org.jspecify.annotations.Nullable}.
+ */
@NullMarked
package com.github._1c_syntax.utils;