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

org.graalvm.tools.lsp.server.DelegateServers Maven / Gradle / Ivy

/*
 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package org.graalvm.tools.lsp.server;

import java.io.IOException;
import java.net.URI;
import java.time.Instant;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.logging.Level;

import org.graalvm.shadowed.org.json.JSONArray;
import org.graalvm.shadowed.org.json.JSONObject;
import org.graalvm.tools.lsp.server.types.CodeLensOptions;
import org.graalvm.tools.lsp.server.types.CompletionOptions;
import org.graalvm.tools.lsp.server.types.DocumentLinkOptions;
import org.graalvm.tools.lsp.server.types.ExecuteCommandOptions;
import org.graalvm.tools.lsp.server.types.LanguageServer.DelegateServer;
import org.graalvm.tools.lsp.server.types.LanguageServer.LoggerProxy;
import org.graalvm.tools.lsp.server.types.RenameOptions;
import org.graalvm.tools.lsp.server.types.ServerCapabilities;
import org.graalvm.tools.lsp.server.types.TextDocumentSyncKind;
import org.graalvm.tools.lsp.server.types.TextDocumentSyncOptions;

/**
 * Merge of communication with delegate language servers.
 */
public final class DelegateServers {

    private final TruffleAdapter truffleAdapter;
    private final DelegateServer[] delegateServers;
    private final LoggerProxy logger;

    public DelegateServers(TruffleAdapter truffleAdapter, List delegateServers, LoggerProxy logger) {
        this.truffleAdapter = truffleAdapter;
        this.delegateServers = delegateServers.toArray(new DelegateServer[delegateServers.size()]);
        this.logger = logger;
    }

    public void submitAll(ExecutorService executors) {
        for (DelegateServer ds : delegateServers) {
            executors.submit(ds);
        }
    }

    public void sendMessageToDelegates(byte[] buffer, Object id, String method, JSONObject params) {
        String language = findContextLanguage(params);
        for (DelegateServer ds : delegateServers) {
            if (languageMatch(language, ds.getLanguageId()) && supportsMethod(method, params, ds.getCapabilities())) {
                try {
                    ds.sendMessage(buffer, id, method);
                } catch (IOException e) {
                    logger.log(Level.WARNING, "Delegate server " + ds.getAddress() + ": " + e.getMessage(), e);
                }
            }
        }
    }

    private String findContextLanguage(JSONObject params) {
        JSONObject textDocument = params != null ? params.optJSONObject("textDocument") : null;
        Object uri = textDocument != null ? textDocument.opt("uri") : null;
        return uri instanceof String ? truffleAdapter.getLanguageId(URI.create((String) uri)) : null;
    }

    private static boolean languageMatch(String languageId1, String languageId2) {
        if (languageId1 == null || languageId2 == null) {
            return true;
        }
        return languageId1.equals(languageId2);
    }

    public Object mergeResults(Object id, Object result) {
        Object allResults = result;
        for (DelegateServer ds : delegateServers) {
            try {
                JSONObject message = ds.awaitMessage(id);
                if (message != null && logger.isLoggable(Level.FINER)) {
                    String format = "[Trace - %s] Received response from %s: %s";
                    logger.log(Level.FINER, String.format(format, Instant.now().toString(), ds.toString(), message.toString()));
                }
                allResults = mergeResults(allResults, message);
            } catch (InterruptedException iex) {
            }
        }
        return allResults;
    }

    public void close() {
        for (DelegateServer ds : delegateServers) {
            ds.close();
        }
    }

    private static Object mergeResults(Object allResults, JSONObject message2) {
        if (message2 != null && message2.has("result")) {
            Object result2 = message2.get("result");
            if (allResults == null) {
                return result2;
            } else {
                mergeJSONInto("result", allResults, result2);
            }
        }
        return allResults;
    }

    private static Object mergeJSONInto(String propertyName, Object j1, Object j2) {
        if (j1 instanceof JSONObject && j2 instanceof JSONObject) {
            JSONObject jo1 = (JSONObject) j1;
            JSONObject jo2 = (JSONObject) j2;
            for (String key2 : jo2.keySet()) {
                if (!jo1.has(key2)) {
                    jo1.put(key2, jo2.get(key2));
                } else {
                    jo1.put(key2, mergeJSONInto(key2, jo1.get(key2), jo2.get(key2)));
                }
            }
        } else if (j1 instanceof JSONArray) {
            JSONArray ja1 = (JSONArray) j1;
            if (j2 instanceof JSONArray) {
                JSONArray ja2 = (JSONArray) j2;
                if (ja1.isEmpty()) {
                    for (Object value : ja2) {
                        ja1.put(value);
                    }
                } else if (!ja2.isEmpty()) {
                    Set labels = getLabels(ja1);
                    for (Object value : ja2) {
                        addIfNoSuchLabel(ja1, labels, value);
                    }
                }
            } else if (!ja1.isEmpty()) {
                Set labels = getLabels(ja1);
                addIfNoSuchLabel(ja1, labels, j2);
            } else {
                ja1.put(j2);
            }
        } else if (j2 instanceof JSONArray) {
            JSONArray ja2 = (JSONArray) j2;
            if (!ja2.isEmpty()) {
                Set labels = getLabels(ja2);
                addIfNoSuchLabel(ja2, labels, j1);
            } else {
                ja2.put(j1);
            }
        } else {
            if (isTrue(j1) && isFalse(j2) || isFalse(j1) && isTrue(j2)) {
                // One says true, the other says false. We merge it into true:
                return Boolean.TRUE;
            }
            if (j1 instanceof Number && j2 instanceof Number) {
                if ("textDocumentSync".equals(propertyName)) {
                    int i1 = ((Number) j1).intValue();
                    int i2 = ((Number) j2).intValue();
                    if (i1 == 0 || i2 == 0) {
                        return 0;
                    }
                    if (i1 == 1 || i2 == 1) {
                        return 1;
                    }
                }
            }
        }
        return j1;
    }

    private static boolean isTrue(Object jsonObj) {
        return Boolean.TRUE.equals(jsonObj) || jsonObj instanceof String && "true".equalsIgnoreCase((String) jsonObj);
    }

    private static boolean isFalse(Object jsonObj) {
        return Boolean.FALSE.equals(jsonObj) || jsonObj instanceof String && "false".equalsIgnoreCase((String) jsonObj);
    }

    private static String getLabel(Object value) {
        if (value instanceof JSONObject) {
            JSONObject jv = (JSONObject) value;
            return jv.optString("label");
        } else {
            return null;
        }
    }

    private static Set getLabels(JSONArray ja) {
        Set labels = null;
        for (Object value : ja) {
            String label = getLabel(value);
            if (label != null) {
                if (labels == null) {
                    labels = new HashSet<>();
                }
                labels.add(label);
            }
        }
        return labels != null ? labels : Collections.emptySet();
    }

    private static void addIfNoSuchLabel(JSONArray ja, Set labels, Object value) {
        String label = getLabel(value);
        if (label == null || !labels.contains(label)) {
            ja.put(value);
        }
    }

    static boolean supportsMethod(String method, JSONObject params, ServerCapabilities capabilities) {
        Object capability;
        switch (method) {
            case "textDocument/codeAction":
                capability = capabilities.getCodeActionProvider();
                break;
            case "textDocument/codeLens":
                capability = capabilities.getCodeLensProvider();
                break;
            case "codeLens/resolve":
                CodeLensOptions clp = capabilities.getCodeLensProvider();
                capability = (clp != null) ? clp.getResolveProvider() : null;
                break;
            case "textDocument/colorPresentation":
                capability = capabilities.getColorProvider();
                break;
            case "textDocument/completion":
                capability = capabilities.getCompletionProvider();
                break;
            case "completionItem/resolve":
                CompletionOptions co = capabilities.getCompletionProvider();
                capability = (co != null) ? co.getResolveProvider() : null;
                break;
            case "textDocument/declaration":
                capability = capabilities.getDeclarationProvider();
                break;
            case "textDocument/definition":
                capability = capabilities.getDefinitionProvider();
                break;
            case "textDocument/formatting":
                capability = capabilities.getDocumentFormattingProvider();
                break;
            case "textDocument/documentHighlight":
                capability = capabilities.getDocumentHighlightProvider();
                break;
            case "textDocument/documentLink":
                capability = capabilities.getDocumentLinkProvider();
                break;
            case "documentLink/resolve":
                DocumentLinkOptions dlo = capabilities.getDocumentLinkProvider();
                capability = (dlo != null) ? dlo.getResolveProvider() : null;
                break;
            case "textDocument/onTypeFormatting":
                capability = capabilities.getDocumentOnTypeFormattingProvider();
                break;
            case "textDocument/rangeFormatting":
                capability = capabilities.getDocumentRangeFormattingProvider();
                break;
            case "textDocument/documentSymbol":
                capability = capabilities.getDocumentSymbolProvider();
                break;
            case "workspace/executeCommand":
                ExecuteCommandOptions eco = capabilities.getExecuteCommandProvider();
                return eco.getCommands().contains(params.getString("command"));
            case "textDocument/foldingRange":
                capability = capabilities.getFoldingRangeProvider();
                break;
            case "textDocument/hover":
                capability = capabilities.getHoverProvider();
                break;
            case "textDocument/implementation":
                capability = capabilities.getImplementationProvider();
                break;
            case "textDocument/references":
                capability = capabilities.getReferencesProvider();
                break;
            case "textDocument/rename":
                capability = capabilities.getRenameProvider();
                break;
            case "textDocument/prepareRename":
                Object renameProvider = capabilities.getRenameProvider();
                capability = (renameProvider != null) ? ((RenameOptions) renameProvider).getPrepareProvider() : null;
                break;
            case "textDocument/signatureHelp":
                capability = capabilities.getSignatureHelpProvider();
                break;
            case "textDocument/didChange":
                capability = capabilities.getTextDocumentSync();
                if (capability instanceof TextDocumentSyncOptions) {
                    capability = ((TextDocumentSyncOptions) capability).getChange();
                }
                capability = (capability == TextDocumentSyncKind.Full || capability == TextDocumentSyncKind.Incremental) ? true : null;
                break;
            case "textDocument/typeDefinition":
                capability = capabilities.getTypeDefinitionProvider();
                break;
            case "workspace/symbol":
                capability = capabilities.getWorkspaceSymbolProvider();
                break;
            default:
                return true;
        }
        return capability != null && !Boolean.FALSE.equals(capability);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy