All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github._1c_syntax.bsl.languageserver.context.ServerContext Maven / Gradle / Ivy

Go to download

Language Server Protocol implementation for 1C (BSL) - 1C:Enterprise 8 and OneScript languages.

The newest version!
/*
 * 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.context;

import com.github._1c_syntax.bsl.languageserver.WorkDoneProgressHelper;
import com.github._1c_syntax.bsl.languageserver.configuration.LanguageServerConfiguration;
import com.github._1c_syntax.bsl.languageserver.utils.MdoRefBuilder;
import com.github._1c_syntax.bsl.languageserver.utils.NamedForkJoinWorkerThreadFactory;
import com.github._1c_syntax.bsl.languageserver.utils.Resources;
import com.github._1c_syntax.bsl.mdclasses.CF;
import com.github._1c_syntax.bsl.mdclasses.MDClasses;
import com.github._1c_syntax.bsl.types.ModuleType;
import com.github._1c_syntax.utils.Absolute;
import com.github._1c_syntax.utils.Lazy;
import edu.umd.cs.findbugs.annotations.Nullable;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;

import java.io.File;
import java.net.URI;
import java.nio.file.Path;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

@Slf4j
@Component
@RequiredArgsConstructor
public class ServerContext {
  private final ObjectProvider documentContextProvider;
  private final WorkDoneProgressHelper workDoneProgressHelper;
  private final LanguageServerConfiguration languageServerConfiguration;

  private final Map documents = Collections.synchronizedMap(new HashMap<>());
  private final Lazy configurationMetadata = new Lazy<>(this::computeConfigurationMetadata);
  @Nullable
  @Setter
  private Path configurationRoot;
  private final Map mdoRefs = Collections.synchronizedMap(new HashMap<>());
  private final Map> documentsByMDORef
    = Collections.synchronizedMap(new HashMap<>());
  private final ReadWriteLock contextLock = new ReentrantReadWriteLock();

  private final Map states = new ConcurrentHashMap<>();
  private final Set openedDocuments = ConcurrentHashMap.newKeySet();

  public void populateContext() {
    if (configurationRoot == null) {
      LOGGER.info("Can't populate server context. Configuration root is not defined.");
      return;
    }

    var workDoneProgressReporter = workDoneProgressHelper.createProgress(0, "");
    workDoneProgressReporter.beginProgress(getMessage("populateFindFiles"));

    LOGGER.debug("Finding files to populate context...");
    var files = (List) FileUtils.listFiles(
      configurationRoot.toFile(),
      new String[]{"bsl", "os"},
      true
    );
    workDoneProgressReporter.endProgress("");
    populateContext(files);
  }

  public void populateContext(List files) {
    var workDoneProgressReporter = workDoneProgressHelper.createProgress(
      files.size(),
      getMessage("populateFilesPostfix")
    );
    workDoneProgressReporter.beginProgress(getMessage("populatePopulatingContext"));

    LOGGER.debug("Populating context...");
    contextLock.writeLock().lock();

    try {

      files.parallelStream().forEach((File file) -> {

        workDoneProgressReporter.tick();

        var uri = file.toURI();
        var documentContext = getDocument(uri);
        if (documentContext == null) {
          documentContext = createDocumentContext(uri);
          rebuildDocument(documentContext);
          documentContext.freezeComputedData();
          tryClearDocument(documentContext);
        }
      });

    } finally {
      contextLock.writeLock().unlock();
    }

    workDoneProgressReporter.endProgress(getMessage("populateContextPopulated"));
    LOGGER.debug("Context populated.");
  }

  public Map getDocuments() {
    return Collections.unmodifiableMap(documents);
  }

  @Nullable
  public DocumentContext getDocument(String uri) {
    return getDocument(URI.create(uri));
  }

  public Optional getDocument(String mdoRef, ModuleType moduleType) {
    var documentsGroup = documentsByMDORef.get(mdoRef);
    if (documentsGroup != null) {
      return Optional.ofNullable(documentsGroup.get(moduleType));
    }
    return Optional.empty();
  }

  @Nullable
  public DocumentContext getDocument(URI uri) {
    return documents.get(Absolute.uri(uri));
  }

  public Map getDocuments(String mdoRef) {
    return documentsByMDORef.getOrDefault(mdoRef, Collections.emptyMap());
  }

  public DocumentContext addDocument(URI uri) {
    contextLock.readLock().lock();

    var documentContext = getDocument(uri);
    if (documentContext == null) {
      documentContext = createDocumentContext(uri);
    }

    contextLock.readLock().unlock();
    return documentContext;
  }

  public void removeDocument(URI uri) {
    var absoluteURI = Absolute.uri(uri);
    var documentContext = documents.get(absoluteURI);
    if (openedDocuments.contains(documentContext)) {
      throw new IllegalStateException(String.format("Document %s is opened", absoluteURI));
    }

    removeDocumentMdoRefByUri(absoluteURI);
    states.remove(documentContext);
    documents.remove(absoluteURI);
  }

  public void clear() {
    documents.clear();
    openedDocuments.clear();
    states.clear();
    documentsByMDORef.clear();
    mdoRefs.clear();
    configurationMetadata.clear();
  }

  /**
   * Помечает документ как открытый и перестраивает его содержимое
   * 

* Документы, помеченные как открытые, не будут удаляться из контекста сервера при вызове {@link #removeDocument(URI)}, * а так же не будут очищаться при вызове {@link #tryClearDocument(DocumentContext)}. *

* Если вспомогательные данные документа был в "замороженном" состоянии, то перед перестроением документа * они будут разморожены. * * @param documentContext документ, который необходимо открыть. * @param content новое содержимое документа. * @param version версия документа. */ public void openDocument(DocumentContext documentContext, String content, Integer version) { openedDocuments.add(documentContext); documentContext.unfreezeComputedData(); rebuildDocument(documentContext, content, version); } /** * Перестроить документ. В качестве содержимого будут использоваться данные, * прочитанные из файла, с которым связан документ. * * @param documentContext документ, который необходимо перестроить. */ public void rebuildDocument(DocumentContext documentContext) { if (states.get(documentContext) == State.WITH_CONTENT) { return; } documentContext.rebuild(); states.put(documentContext, State.WITH_CONTENT); } /** * Перестроить документ, используя новое содержимое. * * @param documentContext документ, который необходимо перестроить. * @param content новое содержимое документа. * @param version версия документа. */ public void rebuildDocument(DocumentContext documentContext, String content, Integer version) { documentContext.rebuild(content, version); states.put(documentContext, State.WITH_CONTENT); } /** * Попытаться очистить документ, если он не открыт. * * @param documentContext документ, который необходимо попытаться закрыть. */ public void tryClearDocument(DocumentContext documentContext) { if (openedDocuments.contains(documentContext)) { return; } states.put(documentContext, State.WITHOUT_CONTENT); documentContext.clearSecondaryData(); } /** * Закрыть документ и очистить его содержимое. * * @param documentContext документ, который необходимо закрыть. */ public void closeDocument(DocumentContext documentContext) { openedDocuments.remove(documentContext); states.put(documentContext, State.WITHOUT_CONTENT); documentContext.clearSecondaryData(); } public CF getConfiguration() { return configurationMetadata.getOrCompute(); } private DocumentContext createDocumentContext(URI uri) { var absoluteURI = Absolute.uri(uri); var documentContext = documentContextProvider.getObject(absoluteURI); documents.put(absoluteURI, documentContext); addMdoRefByUri(absoluteURI, documentContext); return documentContext; } private CF computeConfigurationMetadata() { if (configurationRoot == null) { return (CF) MDClasses.createConfiguration(); } var progress = workDoneProgressHelper.createProgress(0, ""); progress.beginProgress(getMessage("computeConfigurationMetadata")); var factory = new NamedForkJoinWorkerThreadFactory("compute-configuration-"); var executorService = new ForkJoinPool(ForkJoinPool.getCommonPoolParallelism(), factory, null, true); CF configuration; try { configuration = (CF) executorService.submit(() -> MDClasses.createConfiguration(configurationRoot)).get(); } catch (ExecutionException e) { LOGGER.error("Can't parse configuration metadata. Execution exception.", e); configuration = (CF) MDClasses.createConfiguration(); } catch (InterruptedException e) { LOGGER.error("Can't parse configuration metadata. Interrupted exception.", e); configuration = (CF) MDClasses.createConfiguration(); Thread.currentThread().interrupt(); } finally { executorService.shutdown(); } progress.endProgress(getMessage("computeConfigurationMetadataDone")); return configuration; } private void addMdoRefByUri(URI uri, DocumentContext documentContext) { String mdoRef = MdoRefBuilder.getMdoRef(documentContext); mdoRefs.put(uri, mdoRef); documentsByMDORef.computeIfAbsent( mdoRef, k -> new EnumMap<>(ModuleType.class) ).put(documentContext.getModuleType(), documentContext); } private void removeDocumentMdoRefByUri(URI uri) { var mdoRef = mdoRefs.get(uri); if (mdoRef != null) { var documentsGroup = documentsByMDORef.get(mdoRef); if (documentsGroup != null) { documentsGroup.remove(documents.get(uri).getModuleType()); if (documentsGroup.isEmpty()) { documentsByMDORef.remove(mdoRef); } } mdoRefs.remove(uri); } } private String getMessage(String key) { return Resources.getResourceString(languageServerConfiguration.getLanguage(), getClass(), key); } /** * Состояние документа в контексте. */ private enum State { /** * В документе отсутствует контент или он был очищен. */ WITHOUT_CONTENT, /** * В документе присутствует контент. */ WITH_CONTENT } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy