/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.languageserver.registry;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.languageserver.exception.LanguageServerException;
import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher;
import org.eclipse.che.api.languageserver.shared.ProjectExtensionKey;
import org.eclipse.che.api.languageserver.shared.model.LanguageDescription;
import org.eclipse.che.api.project.server.FolderEntry;
import org.eclipse.che.api.project.server.ProjectManager;
import org.eclipse.che.api.project.server.VirtualFileEntry;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.services.LanguageServer;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import static com.google.common.io.Files.getFileExtension;
import static org.eclipse.che.api.languageserver.shared.ProjectExtensionKey.createProjectKey;
@Singleton
public class LanguageServerRegistryImpl implements LanguageServerRegistry, ServerInitializerObserver {
public final static String PROJECT_FOLDER_PATH = "/projects";
/**
* Available {@link LanguageServerLauncher} by extension.
*/
private final ConcurrentHashMap<String, List<LanguageServerLauncher>> extensionToLauncher;
/**
* Started {@link LanguageServer} by project.
*/
private final ConcurrentHashMap<ProjectExtensionKey, LanguageServer> projectToServer;
private final Provider<ProjectManager> projectManagerProvider;
private final ServerInitializer initializer;
@Inject
public LanguageServerRegistryImpl(Set<LanguageServerLauncher> languageServerLaunchers,
Provider<ProjectManager> projectManagerProvider,
ServerInitializer initializer) {
this.projectManagerProvider = projectManagerProvider;
this.initializer = initializer;
this.extensionToLauncher = new ConcurrentHashMap<>();
this.projectToServer = new ConcurrentHashMap<>();
this.initializer.addObserver(this);
for (LanguageServerLauncher launcher : languageServerLaunchers) {
for (String extension : launcher.getLanguageDescription().getFileExtensions()) {
extensionToLauncher.putIfAbsent(extension, new ArrayList<>());
extensionToLauncher.get(extension).add(launcher);
}
}
}
@Override
public LanguageServer findServer(String fileUri) throws LanguageServerException {
String path = URI.create(fileUri).getPath();
String extension = getFileExtension(path);
String projectPath = extractProjectPath(path);
return findServer(extension, projectPath);
}
@Nullable
protected LanguageServer findServer(String extension, String projectPath) throws LanguageServerException {
ProjectExtensionKey projectKey = createProjectKey(projectPath, extension);
for (LanguageServerLauncher launcher : extensionToLauncher.get(extension)) {
if (!projectToServer.containsKey(projectKey)) {
synchronized (launcher) {
if (!projectToServer.containsKey(projectKey)) {
LanguageServer server = initializer.initialize(launcher, projectPath);
projectToServer.put(projectKey, server);
}
}
}
return projectToServer.get(projectKey);
}
return null;
}
@Override
public List<LanguageDescription> getSupportedLanguages() {
return extensionToLauncher.values()
.stream()
.flatMap(Collection::stream)
.filter(LanguageServerLauncher::isAbleToLaunch)
.map(LanguageServerLauncher::getLanguageDescription)
.collect(Collectors.toList());
}
@Override
public Map<ProjectExtensionKey, LanguageServerDescription> getInitializedLanguages() {
Map<LanguageServer, LanguageServerDescription> initializedServers = initializer.getInitializedServers();
return projectToServer.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> initializedServers.get(e.getValue())));
}
protected String extractProjectPath(String filePath) throws LanguageServerException {
FolderEntry root;
try {
root = projectManagerProvider.get().getProjectsRoot();
} catch (ServerException e) {
throw new LanguageServerException("Project not found for " + filePath, e);
}
if (!filePath.startsWith(PROJECT_FOLDER_PATH)) {
throw new LanguageServerException("Project not found for " + filePath);
}
VirtualFileEntry fileEntry;
try {
fileEntry = root.getChild(filePath.substring(PROJECT_FOLDER_PATH.length() + 1));
} catch (ServerException e) {
throw new LanguageServerException("Project not found for " + filePath, e);
}
if (fileEntry == null) {
throw new LanguageServerException("Project not found for " + filePath);
}
return PROJECT_FOLDER_PATH + fileEntry.getProject();
}
@Override
public void onServerInitialized(LanguageServer server,
ServerCapabilities capabilities,
LanguageDescription languageDescription,
String projectPath) {
for (String ext : languageDescription.getFileExtensions()) {
projectToServer.put(createProjectKey(projectPath, ext), server);
}
}
}