com.github._1c_syntax.bsl.languageserver.configuration.LanguageServerConfiguration Maven / Gradle / Ivy
Show all versions of bsl-language-server Show documentation
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2024
* Alexey Sosnoviy , Nikita Fedkin and contributors
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* BSL Language Server is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* BSL Language Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BSL Language Server.
*/
package com.github._1c_syntax.bsl.languageserver.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.github._1c_syntax.bsl.languageserver.configuration.codelens.CodeLensOptions;
import com.github._1c_syntax.bsl.languageserver.configuration.diagnostics.DiagnosticsOptions;
import com.github._1c_syntax.bsl.languageserver.configuration.documentlink.DocumentLinkOptions;
import com.github._1c_syntax.bsl.languageserver.configuration.formating.FormattingOptions;
import com.github._1c_syntax.bsl.languageserver.configuration.inlayhints.InlayHintOptions;
import com.github._1c_syntax.utils.Absolute;
import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.PostConstruct;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Role;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import static com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS;
/**
* Корневой класс конфигурации BSL Language Server.
*
* В обычном режиме работы провайдеры и прочие классы могут расчитывать на единственность объекта конфигурации
* и безопасно сохранять ссылку на конфигурацию или ее части.
*/
@Data
@Component
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@AllArgsConstructor(onConstructor = @__({@JsonCreator(mode = JsonCreator.Mode.DISABLED)}))
@NoArgsConstructor
@Slf4j
@JsonIgnoreProperties(ignoreUnknown = true)
public class LanguageServerConfiguration {
private static final Pattern searchConfiguration = Pattern.compile("Configuration\\.(xml|mdo)$");
private Language language = Language.DEFAULT_LANGUAGE;
@JsonProperty("diagnostics")
@Setter(value = AccessLevel.NONE)
private DiagnosticsOptions diagnosticsOptions = new DiagnosticsOptions();
@JsonProperty("codeLens")
@Setter(value = AccessLevel.NONE)
private CodeLensOptions codeLensOptions = new CodeLensOptions();
@JsonProperty("documentLink")
@Setter(value = AccessLevel.NONE)
private DocumentLinkOptions documentLinkOptions = new DocumentLinkOptions();
@JsonProperty("inlayHint")
@Setter(value = AccessLevel.NONE)
private InlayHintOptions inlayHintOptions = new InlayHintOptions();
@JsonProperty("formatting")
@Setter(value = AccessLevel.NONE)
private FormattingOptions formattingOptions = new FormattingOptions();
private String siteRoot = "https://1c-syntax.github.io/bsl-language-server";
private boolean useDevSite;
private SendErrorsMode sendErrors = SendErrorsMode.DEFAULT;
@Nullable
private File traceLog;
@Nullable
private Path configurationRoot;
@JsonIgnore
@Setter(value = AccessLevel.NONE)
private File configurationFile;
@Value("${app.configuration.path:.bsl-language-server.json}")
@JsonIgnore
private String configurationFilePath;
@Value(("${app.globalConfiguration.path:${user.home}/.bsl-language-server.json}"))
@JsonIgnore
private String globalConfigPath;
@PostConstruct
private void init() {
configurationFile = new File(configurationFilePath);
if (configurationFile.exists()) {
loadConfigurationFile(configurationFile);
return;
}
var configuration = new File(globalConfigPath);
if (configuration.exists()) {
loadConfigurationFile(configuration);
}
}
public void update(File configurationFile) {
loadConfigurationFile(configurationFile);
}
public void reset() {
copyPropertiesFrom(new LanguageServerConfiguration());
}
public static Path getCustomConfigurationRoot(LanguageServerConfiguration configuration, Path srcDir) {
Path rootPath = null;
var pathFromConfiguration = configuration.getConfigurationRoot();
if (pathFromConfiguration == null) {
rootPath = Absolute.path(srcDir);
} else {
// Проверим, что srcDir = pathFromConfiguration или что pathFromConfiguration находится внутри srcDir
var absoluteSrcDir = Absolute.path(srcDir);
var absolutePathFromConfiguration = Absolute.path(pathFromConfiguration);
if (absolutePathFromConfiguration.startsWith(absoluteSrcDir)) {
rootPath = absolutePathFromConfiguration;
}
}
if (rootPath != null) {
var fileConfiguration = getConfigurationFile(rootPath);
if (fileConfiguration != null) {
if (fileConfiguration.getAbsolutePath().endsWith(".mdo")) {
rootPath = Optional.of(fileConfiguration.toPath())
.map(Path::getParent)
.map(Path::getParent)
.map(Path::getParent)
.orElse(null);
} else {
rootPath = Optional.of(fileConfiguration.toPath())
.map(Path::getParent)
.orElse(null);
}
}
}
return rootPath;
}
@SuppressFBWarnings(
value = "RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE",
justification = "False positive"
)
private static File getConfigurationFile(Path rootPath) {
File configurationFile = null;
List listPath = new ArrayList<>();
try (Stream stream = Files.find(rootPath, 50, (path, basicFileAttributes) ->
basicFileAttributes.isRegularFile() && searchConfiguration.matcher(path.getFileName().toString()).find())) {
listPath = stream.toList();
} catch (IOException e) {
LOGGER.error("Error on read configuration file", e);
}
if (!listPath.isEmpty()) {
configurationFile = listPath.get(0).toFile();
}
return configurationFile;
}
private void loadConfigurationFile(File configurationFile) {
if (!configurationFile.exists()) {
return;
}
LanguageServerConfiguration configuration;
var mapper = JsonMapper.builder()
.enable(ACCEPT_CASE_INSENSITIVE_ENUMS)
.build();
try {
configuration = mapper.readValue(configurationFile, LanguageServerConfiguration.class);
} catch (IOException e) {
LOGGER.error("Can't deserialize configuration file", e);
return;
}
this.configurationFile = configurationFile;
copyPropertiesFrom(configuration);
}
@SneakyThrows
private void copyPropertiesFrom(LanguageServerConfiguration configuration) {
// todo: refactor
PropertyUtils.copyProperties(this, configuration);
PropertyUtils.copyProperties(this.inlayHintOptions, configuration.inlayHintOptions);
PropertyUtils.copyProperties(this.codeLensOptions, configuration.codeLensOptions);
PropertyUtils.copyProperties(this.diagnosticsOptions, configuration.diagnosticsOptions);
PropertyUtils.copyProperties(this.documentLinkOptions, configuration.documentLinkOptions);
PropertyUtils.copyProperties(this.formattingOptions, configuration.formattingOptions);
}
}