blob: d7e449485f815e0eb87026bd284d02b1857ca2b6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.java.lsp.server.protocol;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.sun.source.util.TreePath;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.DidChangeConfigurationParams;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ShowDocumentParams;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.WorkspaceSymbol;
import org.eclipse.lsp4j.WorkspaceSymbolLocation;
import org.eclipse.lsp4j.WorkspaceSymbolParams;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.eclipse.lsp4j.services.WorkspaceService;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.debugger.ActionsManager;
import org.netbeans.api.debugger.DebuggerManager;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.queries.SourceForBinaryQuery;
import org.netbeans.api.java.queries.UnitTestForSourceQuery;
import org.netbeans.api.java.source.ClassIndex;
import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.java.source.ui.ElementOpen;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectInformation;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.ui.OpenProjects;
import org.netbeans.modules.csl.api.IndexSearcher;
import org.netbeans.modules.editor.indent.spi.CodeStylePreferences;
import org.netbeans.modules.gsf.testrunner.ui.api.TestMethodController;
import org.netbeans.modules.gsf.testrunner.ui.api.TestMethodFinder;
import org.netbeans.modules.java.lsp.server.LspServerState;
import org.netbeans.modules.java.lsp.server.Utils;
import org.netbeans.modules.java.lsp.server.debugging.attach.AttachConfigurations;
import org.netbeans.modules.java.lsp.server.debugging.attach.AttachNativeConfigurations;
import org.netbeans.modules.java.lsp.server.project.LspProjectInfo;
import org.netbeans.modules.java.source.ElementHandleAccessor;
import org.netbeans.modules.java.source.ui.JavaSymbolProvider;
import org.netbeans.modules.java.source.ui.JavaTypeProvider;
import org.netbeans.modules.java.source.usages.ClassIndexImpl;
import org.netbeans.modules.parsing.lucene.support.Queries;
import org.netbeans.spi.jumpto.type.SearchType;
import org.netbeans.spi.project.ActionProgress;
import org.netbeans.spi.project.ActionProvider;
import org.netbeans.spi.project.ProjectConfiguration;
import org.netbeans.spi.project.ProjectConfigurationProvider;
import org.netbeans.spi.project.ui.ProjectProblemsProvider;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.URLMapper;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbPreferences;
import org.openide.util.Pair;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.Lookups;
/**
*
* @author lahvac
*/
public final class WorkspaceServiceImpl implements WorkspaceService, LanguageClientAware {
private static final Logger LOG = Logger.getLogger(WorkspaceServiceImpl.class.getName());
private static final RequestProcessor WORKER = new RequestProcessor(WorkspaceServiceImpl.class.getName(), 1, false, false);
private static final RequestProcessor PROJECT_WORKER = new RequestProcessor(WorkspaceServiceImpl.class.getName(), 5, false, false);
private final Gson gson = new Gson();
private final LspServerState server;
private NbCodeLanguageClient client;
WorkspaceServiceImpl(LspServerState server) {
this.server = server;
}
@Override
public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
String command = params.getCommand();
switch (command) {
case Server.GRAALVM_PAUSE_SCRIPT:
ActionsManager am = DebuggerManager.getDebuggerManager().getCurrentEngine().getActionsManager();
am.doAction("pauseInGraalScript");
return CompletableFuture.completedFuture(true);
case Server.JAVA_NEW_FROM_TEMPLATE:
return LspTemplateUI.createFromTemplate("Templates", client, params);
case Server.JAVA_NEW_PROJECT:
return LspTemplateUI.createProject("Templates/Project", client, params);
case Server.NBLS_BUILD_WORKSPACE: {
final CommandProgress progressOfCompilation = new CommandProgress();
final Lookup ctx = Lookups.singleton(progressOfCompilation);
for (Project prj : server.openedProjects().getNow(OpenProjects.getDefault().getOpenProjects())) {
ActionProvider ap = prj.getLookup().lookup(ActionProvider.class);
if (ap != null && ap.isActionEnabled(ActionProvider.COMMAND_BUILD, Lookup.EMPTY)) {
ap.invokeAction(ActionProvider.COMMAND_REBUILD, ctx);
}
}
progressOfCompilation.checkStatus();
return progressOfCompilation.getFinishFuture();
}
case Server.JAVA_RUN_PROJECT_ACTION: {
// TODO: maybe a structure would be better for future compatibility / extensions, i.e. what to place in the action's context Lookup.
List<FileObject> targets = new ArrayList<>();
ProjectActionParams actionParams = gson.fromJson(gson.toJson(params.getArguments().get(0)), ProjectActionParams.class);
String actionName = actionParams.getAction();
String configName = actionParams.getConfiguration();
boolean acceptDefault = actionParams.getFallbackDefault() == Boolean.TRUE;
for (int i = 1; i < params.getArguments().size(); i++) {
JsonElement item = gson.fromJson(gson.toJson(params.getArguments().get(i)), JsonElement.class);
if (item.isJsonPrimitive()) {
String uri = item.getAsString();
FileObject file;
try {
file = URLMapper.findFileObject(new URL(uri));
} catch (MalformedURLException ex) {
// TODO: report an invalid parameter or ignore ?
continue;
}
targets.add(file);
}
}
// also forms invokeAction off the main LSP thread.
return server.asyncOpenSelectedProjects(targets, false).thenCompose((Project[] owners) -> {
Map<Project, List<FileObject>> items = new LinkedHashMap<>();
for (int i = 0; i < owners.length; i++) {
items.computeIfAbsent(owners[i], (p) -> new ArrayList<>()).add(targets.get(i));
}
final CommandProgress progressOfCompilation = new CommandProgress();
boolean someStarted = false;
boolean configNotFound = false;
for (Project prj : items.keySet()) {
List<Object> ctxObjects = new ArrayList<>();
ctxObjects.add(progressOfCompilation);
ctxObjects.addAll(items.get(prj));
if (!StringUtils.isBlank(configName)) {
ProjectConfigurationProvider<ProjectConfiguration> pcp = prj.getLookup().lookup(ProjectConfigurationProvider.class);
if (pcp != null) {
Optional<ProjectConfiguration> cfg = pcp.getConfigurations().stream().filter(c -> c.getDisplayName().equals(configName)).findAny();
if (cfg.isPresent()) {
ctxObjects.add(cfg);
} else if (!acceptDefault) {
// TODO: report ? Fail the action ? Fallback to default config ?
configNotFound = true;
continue;
}
}
}
// TBD: possibly include project configuration ?
final Lookup ctx = Lookups.fixed(ctxObjects.toArray(new Object[ctxObjects.size()]));
ActionProvider ap = prj.getLookup().lookup(ActionProvider.class);
if (ap != null && ap.isActionEnabled(actionName, ctx)) {
ap.invokeAction(actionName, ctx);
someStarted = true;
}
}
if (!configNotFound || !someStarted) {
// TODO: print a message like 'nothing to do' in the status bar ?
return CompletableFuture.completedFuture(false);
}
final boolean cfgNotFound = configNotFound;
progressOfCompilation.checkStatus();
return progressOfCompilation.getFinishFuture().thenApply(b -> (b == Boolean.TRUE) && cfgNotFound);
});
}
case Server.NBLS_CLEAN_WORKSPACE: {
final CommandProgress progressOfCompilation = new CommandProgress();
final Lookup ctx = Lookups.singleton(progressOfCompilation);
for (Project prj : server.openedProjects().getNow(OpenProjects.getDefault().getOpenProjects())) {
ActionProvider ap = prj.getLookup().lookup(ActionProvider.class);
if (ap != null && ap.isActionEnabled(ActionProvider.COMMAND_CLEAN, Lookup.EMPTY)) {
ap.invokeAction(ActionProvider.COMMAND_CLEAN, ctx);
}
}
progressOfCompilation.checkStatus();
return progressOfCompilation.getFinishFuture();
}
case Server.JAVA_GET_PROJECT_SOURCE_ROOTS: {
String uri = ((JsonPrimitive) params.getArguments().get(0)).getAsString();
String type = params.getArguments().size() > 1 ? ((JsonPrimitive) params.getArguments().get(1)).getAsString() : JavaProjectConstants.SOURCES_TYPE_JAVA;
return getSourceRoots(uri, type).thenApply(roots -> {
return roots.stream().map(root -> Utils.toUri(root)).collect(Collectors.toList());
});
}
case Server.JAVA_GET_PROJECT_CLASSPATH: {
String uri = ((JsonPrimitive) params.getArguments().get(0)).getAsString();
ClasspathInfo.PathKind kind = params.getArguments().size() > 1 ? ClasspathInfo.PathKind.valueOf(((JsonPrimitive) params.getArguments().get(1)).getAsString()) : ClasspathInfo.PathKind.COMPILE;
boolean preferSources = params.getArguments().size() > 2 ? ((JsonPrimitive) params.getArguments().get(2)).getAsBoolean() : false;
return getSourceRoots(uri, JavaProjectConstants.SOURCES_TYPE_JAVA).thenApply(roots -> {
HashSet<FileObject> cpRoots = new HashSet<>();
for(FileObject root : roots) {
for (FileObject cpRoot : ClasspathInfo.create(root).getClassPath(kind).getRoots()) {
FileObject[] srcRoots = preferSources ? SourceForBinaryQuery.findSourceRoots(cpRoot.toURL()).getRoots() : null;
if (srcRoots != null && srcRoots.length > 0) {
for (FileObject srcRoot : srcRoots) {
cpRoots.add(srcRoot);
}
} else {
cpRoots.add(cpRoot);
}
}
}
return cpRoots.stream().map(fo -> Utils.toUri(fo)).collect(Collectors.toList());
});
}
case Server.JAVA_GET_PROJECT_PACKAGES: {
String uri = ((JsonPrimitive) params.getArguments().get(0)).getAsString();
boolean srcOnly = params.getArguments().size() > 1 ? ((JsonPrimitive) params.getArguments().get(1)).getAsBoolean() : false;
return getSourceRoots(uri, JavaProjectConstants.SOURCES_TYPE_JAVA).thenCompose(roots -> {
CompletableFuture<Object> future = new CompletableFuture<>();
JavaSource js = JavaSource.create(ClasspathInfo.create(ClassPath.EMPTY, ClassPath.EMPTY, ClassPath.EMPTY));
try {
js.runWhenScanFinished(controller -> {
HashSet<String> packages = new HashSet<>();
EnumSet<ClassIndex.SearchScope> scope = srcOnly ? EnumSet.of(ClassIndex.SearchScope.SOURCE) : EnumSet.allOf(ClassIndex.SearchScope.class);
for(FileObject root : roots) {
packages.addAll(ClasspathInfo.create(root).getClassIndex().getPackageNames("", false, scope));
}
ArrayList<String> ret = new ArrayList<>(packages);
Collections.sort(ret);
future.complete(ret);
}, true);
} catch (IOException ex) {
future.completeExceptionally(ex);
}
return future;
});
}
case Server.JAVA_LOAD_WORKSPACE_TESTS: {
String uri = ((JsonPrimitive) params.getArguments().get(0)).getAsString();
FileObject file;
try {
file = URLMapper.findFileObject(new URL(uri));
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
return CompletableFuture.completedFuture(Collections.emptyList());
}
if (file == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
return server.asyncOpenFileOwner(file).thenCompose(this::getTestRoots).thenCompose(testRoots -> {
BiFunction<FileObject, Collection<TestMethodController.TestMethod>, Collection<TestSuiteInfo>> f = (fo, methods) -> {
String url = Utils.toUri(fo);
Map<String, TestSuiteInfo> suite2infos = new LinkedHashMap<>();
for (TestMethodController.TestMethod testMethod : methods) {
TestSuiteInfo suite = suite2infos.computeIfAbsent(testMethod.getTestClassName(), name -> {
Position pos = testMethod.getTestClassPosition() != null ? Utils.createPosition(fo, testMethod.getTestClassPosition().getOffset()) : null;
return new TestSuiteInfo(name, url, pos != null ? new Range(pos, pos) : null, TestSuiteInfo.State.Loaded, new ArrayList<>());
});
String id = testMethod.getTestClassName() + ':' + testMethod.method().getMethodName();
Position startPos = testMethod.start() != null ? Utils.createPosition(fo, testMethod.start().getOffset()) : null;
Position endPos = testMethod.end() != null ? Utils.createPosition(fo, testMethod.end().getOffset()) : startPos;
Range range = startPos != null ? new Range(startPos, endPos) : null;
suite.getTests().add(new TestSuiteInfo.TestCaseInfo(id, testMethod.method().getMethodName(), url, range, TestSuiteInfo.State.Loaded, null));
}
return suite2infos.values();
};
testMethodsListener.compareAndSet(null, (fo, methods) -> {
try {
for (TestSuiteInfo testSuiteInfo : f.apply(fo, methods)) {
client.notifyTestProgress(new TestProgressParams(Utils.toUri(fo), testSuiteInfo));
}
} catch (Exception e) {
Logger.getLogger(WorkspaceServiceImpl.class.getName()).severe(e.getMessage());
Exceptions.printStackTrace(e);
testMethodsListener.set(null);
}
});
if (openProjectsListener.compareAndSet(null, (evt) -> {
if ("openProjects".equals(evt.getPropertyName())) {
JavaSource js = JavaSource.create(ClasspathInfo.create(ClassPath.EMPTY, ClassPath.EMPTY, ClassPath.EMPTY));
try {
js.runWhenScanFinished(controller -> {
List<Project> old = Arrays.asList((Project[]) evt.getOldValue());
for (Project p : (Project[])evt.getNewValue()) {
if (!old.contains(p)) {
getTestRoots(p).thenAccept(tr -> {
for (Entry<FileObject, Collection<TestMethodController.TestMethod>> entry : TestMethodFinder.findTestMethods(tr, testMethodsListener.get()).entrySet()) {
for (TestSuiteInfo tsi : f.apply(entry.getKey(), entry.getValue())) {
client.notifyTestProgress(new TestProgressParams(Utils.toUri(entry.getKey()), tsi));
}
}
});
}
}
}, true);
} catch (IOException ex) {
}
}
})) {
OpenProjects.getDefault().addPropertyChangeListener(WeakListeners.propertyChange(openProjectsListener.get(), OpenProjects.getDefault()));
}
CompletableFuture<Object> future = new CompletableFuture<>();
JavaSource js = JavaSource.create(ClasspathInfo.create(ClassPath.EMPTY, ClassPath.EMPTY, ClassPath.EMPTY));
try {
js.runWhenScanFinished(controller -> {
Map<FileObject, Collection<TestMethodController.TestMethod>> testMethods = TestMethodFinder.findTestMethods(testRoots, testMethodsListener.get());
Collection<TestSuiteInfo> suites = new ArrayList<>(testMethods.size());
for (Entry<FileObject, Collection<TestMethodController.TestMethod>> entry : testMethods.entrySet()) {
suites.addAll(f.apply(entry.getKey(), entry.getValue()));
}
future.complete(suites);
}, true);
} catch (IOException ex) {
future.completeExceptionally(ex);
}
return future;
});
}
case Server.JAVA_SUPER_IMPLEMENTATION:
String uri = ((JsonPrimitive) params.getArguments().get(0)).getAsString();
Position pos = gson.fromJson(gson.toJson(params.getArguments().get(1)), Position.class);
return (CompletableFuture)((TextDocumentServiceImpl)server.getTextDocumentService()).superImplementations(uri, pos);
case Server.JAVA_SOURCE_FOR: {
CompletableFuture<Object> result = new CompletableFuture<>();
try {
String sourceUri = ((JsonPrimitive) params.getArguments().get(0)).getAsString();
if (sourceUri == null || !sourceUri.startsWith(SOURCE_FOR)) {
throw new IllegalArgumentException("Invalid uri: " + sourceUri);
}
sourceUri = URLDecoder.decode(sourceUri.substring(SOURCE_FOR.length()), StandardCharsets.UTF_8.toString());
int qIdx = sourceUri.lastIndexOf('?');
int hIdx = sourceUri.lastIndexOf('#');
if (qIdx < 0 || hIdx < 0 || hIdx <= qIdx) {
throw new IllegalArgumentException("Invalid uri: " + sourceUri);
}
String rootUri = sourceUri.substring(0, qIdx);
FileObject root = URLMapper.findFileObject(URI.create(rootUri).toURL());
if (root == null) {
throw new IllegalStateException("Unable to find root: " + rootUri);
}
ElementHandle typeHandle = ElementHandleAccessor.getInstance().create(ElementKind.valueOf(sourceUri.substring(qIdx + 1, hIdx)), sourceUri.substring(hIdx + 1));
CompletableFuture<ElementOpen.Location> location = ElementOpen.getLocation(ClasspathInfo.create(root), typeHandle, typeHandle.getQualifiedName().replace('.', '/') + ".class");
location.exceptionally(ex -> {
result.completeExceptionally(ex);
return null;
}).thenAccept(loc -> {
if (loc != null) {
ShowDocumentParams sdp = new ShowDocumentParams(Utils.toUri(loc.getFileObject()));
Position position = Utils.createPosition(loc.getFileObject(), loc.getStartOffset());
sdp.setSelection(new Range(position, position));
client.showDocument(sdp).thenAccept(res -> {
if (res.isSuccess()) {
result.complete(null);
} else {
result.completeExceptionally(new IllegalStateException("Cannot open source for: " + typeHandle.getQualifiedName()));
}
});
} else if (!result.isCompletedExceptionally()) {
result.completeExceptionally(new IllegalStateException("Cannot find source for: " + typeHandle.getQualifiedName()));
}
});
} catch (Throwable t) {
result.completeExceptionally(t);
}
return result;
}
case Server.JAVA_FIND_PROJECT_CONFIGURATIONS: {
String fileUri = ((JsonPrimitive) params.getArguments().get(0)).getAsString();
FileObject file;
try {
file = URLMapper.findFileObject(new URL(fileUri));
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
return CompletableFuture.completedFuture(Collections.emptyList());
}
return findProjectConfigurations(file);
}
case Server.JAVA_FIND_DEBUG_ATTACH_CONFIGURATIONS: {
return AttachConfigurations.findConnectors();
}
case Server.JAVA_FIND_DEBUG_PROCESS_TO_ATTACH: {
return AttachConfigurations.findProcessAttachTo(client);
}
case Server.NATIVE_IMAGE_FIND_DEBUG_PROCESS_TO_ATTACH: {
return AttachNativeConfigurations.findProcessAttachTo(client);
}
case Server.JAVA_PROJECT_CONFIGURATION_COMPLETION: {
// We expect one, two or three arguments.
// The first argument is always the URI of the launch.json file.
// When not more arguments are provided, all available configurations ought to be provided.
// When only a second argument is present, it's a map of the current attributes in a configuration,
// and additional attributes valid in that particular configuration ought to be provided.
// When a third argument is present, it's an attribute name whose possible values ought to be provided.
List<Object> arguments = params.getArguments();
Collection<? extends LaunchConfigurationCompletion> configurations = Lookup.getDefault().lookupAll(LaunchConfigurationCompletion.class);
List<CompletableFuture<List<CompletionItem>>> completionFutures;
String configUri = ((JsonPrimitive) arguments.get(0)).getAsString();
Supplier<CompletableFuture<Project>> projectSupplier = () -> {
FileObject file;
try {
file = URLMapper.findFileObject(new URL(configUri));
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
return CompletableFuture.completedFuture(null);
}
return server.asyncOpenFileOwner(file);
};
switch (arguments.size()) {
case 1:
completionFutures = configurations.stream().map(c -> c.configurations(projectSupplier)).collect(Collectors.toList());
break;
case 2:
Map<String, Object> attributes = attributesMap((JsonObject) arguments.get(1));
completionFutures = configurations.stream().map(c -> c.attributes(projectSupplier, attributes)).collect(Collectors.toList());
break;
case 3:
attributes = attributesMap((JsonObject) arguments.get(1));
String attribute = ((JsonPrimitive) arguments.get(2)).getAsString();
completionFutures = configurations.stream().map(c -> c.attributeValues(projectSupplier, attributes, attribute)).collect(Collectors.toList());
break;
default:
StringBuilder classes = new StringBuilder();
for (int i = 0; i < arguments.size(); i++) {
classes.append(arguments.get(i).getClass().toString());
}
throw new IllegalStateException("Wrong arguments("+arguments.size()+"): " + arguments + ", classes = " + classes); // NOI18N
}
CompletableFuture<List<CompletionItem>> joinedFuture = CompletableFuture.allOf(completionFutures.toArray(new CompletableFuture[0]))
.thenApply(avoid -> completionFutures.stream().flatMap(c -> c.join().stream()).collect(Collectors.toList()));
return (CompletableFuture<Object>) (CompletableFuture<?>) joinedFuture;
}
case Server.JAVA_PROJECT_RESOLVE_PROJECT_PROBLEMS: {
final CompletableFuture<Object> result = new CompletableFuture<>();
List<Object> arguments = params.getArguments();
if (!arguments.isEmpty()) {
String fileStr = ((JsonPrimitive) arguments.get(0)).getAsString();
FileObject file;
try {
file = URLMapper.findFileObject(URI.create(fileStr).toURL());
} catch (MalformedURLException ex) {
result.completeExceptionally(ex);
return result;
}
Project project = FileOwnerQuery.getOwner(file);
if (project != null) {
ProjectProblemsProvider ppp = project.getLookup().lookup(ProjectProblemsProvider.class);
if (ppp != null) {
Collection<? extends ProjectProblemsProvider.ProjectProblem> problems = ppp.getProblems();
if (!problems.isEmpty()) {
WORKER.post(() -> {
List<Pair<ProjectProblemsProvider.ProjectProblem, Future<ProjectProblemsProvider.Result>>> resolvers = new LinkedList<>();
for (ProjectProblemsProvider.ProjectProblem problem : ppp.getProblems()) {
if (problem.isResolvable()) {
resolvers.add(Pair.of(problem, problem.resolve()));
} else {
DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message(problem.getDescription(), NotifyDescriptor.Message.ERROR_MESSAGE));
}
}
if (!resolvers.isEmpty()) {
for (Pair<ProjectProblemsProvider.ProjectProblem, Future<ProjectProblemsProvider.Result>> resolver : resolvers) {
try {
if (!resolver.second().get().isResolved()) {
String message = resolver.second().get().getMessage();
if (message != null) {
DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message(message, NotifyDescriptor.Message.ERROR_MESSAGE));
}
}
} catch (ExecutionException ex) {
result.completeExceptionally(ex.getCause());
} catch (InterruptedException ex) {
result.complete(false);
break;
}
}
}
if (!result.isDone()) {
result.complete(true);
}
});
}
}
}
} else {
result.completeExceptionally(new IllegalStateException("Expecting file URL as an argument to " + command));
}
return result;
}
case Server.JAVA_CLEAR_PROJECT_CACHES: {
// politely clear project manager's cache of "no project" answers
ProjectManager.getDefault().clearNonProjectCache();
// impolitely clean the project-based traversal's cache, so any affiliation of intermediate folders will disappear
ClassLoader loader = Lookup.getDefault().lookup(ClassLoader.class);
CompletableFuture<Boolean> result = new CompletableFuture<>();
try {
Class queryImpl = Class.forName("org.netbeans.modules.projectapi.SimpleFileOwnerQueryImplementation", true, loader); // NOI18N
Method resetMethod = queryImpl.getMethod("reset"); // NOI18N
resetMethod.invoke(null);
result.complete(true);
} catch (ReflectiveOperationException ex) {
result.completeExceptionally(ex);
}
// and finally, let's refresh everything we had opened:
for (FileObject f : server.getAcceptedWorkspaceFolders()) {
f.refresh();
}
for (Project p : OpenProjects.getDefault().getOpenProjects()) {
p.getProjectDirectory().refresh();
}
return (CompletableFuture<Object>) (CompletableFuture<?>)result;
}
case Server.JAVA_PROJECT_INFO: {
final CompletableFuture<Object> result = new CompletableFuture<>();
List<Object> arguments = params.getArguments();
if (arguments.size() < 1) {
result.completeExceptionally(new IllegalArgumentException("Expecting URL or URL[] as an argument to " + command));
return result;
}
Object o = arguments.get(0);
URL[] locations = null;
if (o instanceof JsonArray) {
List<URL> locs = new ArrayList<>();
JsonArray a = (JsonArray)o;
a.forEach((e) -> {
if (e instanceof JsonPrimitive) {
String s = ((JsonPrimitive)e).getAsString();
try {
locs.add(new URL(s));
} catch (MalformedURLException ex) {
throw new IllegalArgumentException("Illegal location: " + s);
}
}
});
locations = locs.toArray(new URL[0]);
} else if (o instanceof JsonPrimitive) {
String s = ((JsonPrimitive)o).getAsString();
try {
locations = new URL[] { new URL(s) };
} catch (MalformedURLException ex) {
throw new IllegalArgumentException("Illegal location: " + s);
}
}
if (locations == null || locations.length == 0) {
result.completeExceptionally(new IllegalArgumentException("Expecting URL or URL[] as an argument to " + command));
return result;
}
boolean projectStructure = false;
boolean actions = false;
boolean recursive = false;
if (arguments.size() > 1) {
Object a2 = arguments.get(1);
if (a2 instanceof JsonObject) {
JsonObject options = (JsonObject)a2;
projectStructure = getOption(options, "projectStructure", false); // NOI18N
actions = getOption(options, "actions", false); // NOI18N
recursive = getOption(options, "recursive", false); // NOI18N
}
}
return (CompletableFuture<Object>)(CompletableFuture<?>)new ProjectInfoWorker(locations, projectStructure, recursive, actions).process();
}
default:
for (CodeActionsProvider codeActionsProvider : Lookup.getDefault().lookupAll(CodeActionsProvider.class)) {
if (codeActionsProvider.getCommands().contains(command)) {
return codeActionsProvider.processCommand(client, command, params.getArguments());
}
}
}
throw new UnsupportedOperationException("Command not supported: " + params.getCommand());
}
private class ProjectInfoWorker {
final URL[] locations;
final boolean projectStructure;
final boolean recursive;
final boolean actions;
Map<FileObject, LspProjectInfo> infos = new HashMap<>();
Set<Project> toOpen = new HashSet<>();
public ProjectInfoWorker(URL[] locations, boolean projectStructure, boolean recursive, boolean actions) {
this.locations = locations;
this.projectStructure = projectStructure;
this.recursive = recursive;
this.actions = actions;
}
public CompletableFuture<LspProjectInfo[]> process() {
List<FileObject> files = new ArrayList();
for (URL u : locations) {
FileObject f = URLMapper.findFileObject(u);
if (f != null) {
files.add(f);
}
}
return server.asyncOpenSelectedProjects(files, false).thenCompose(this::processProjects);
}
LspProjectInfo fillProjectInfo(Project p) {
LspProjectInfo info = infos.get(p.getProjectDirectory());
if (info != null) {
return info;
}
info = new LspProjectInfo();
ProjectInformation pi = ProjectUtils.getInformation(p);
URL projectURL = URLMapper.findURL(p.getProjectDirectory(), URLMapper.EXTERNAL);
if (projectURL != null) {
try {
info.projectDirectory = projectURL.toURI();
} catch (URISyntaxException ex) {
// should not happen
}
}
info.name = pi.getName();
info.displayName = pi.getDisplayName();
// attempt to determine the project type
ProjectManager.Result r = ProjectManager.getDefault().isProject2(p.getProjectDirectory());
info.projectType = r.getProjectType();
if (actions) {
ActionProvider ap = p.getLookup().lookup(ActionProvider.class);
if (ap != null) {
info.projectActionNames = ap.getSupportedActions();
}
}
if (projectStructure) {
Set<Project> children = ProjectUtils.getContainedProjects(p, false);
List<URI> subprojectDirs = new ArrayList<>();
for (Project c : children) {
try {
subprojectDirs.add(URLMapper.findURL(c.getProjectDirectory(), URLMapper.EXTERNAL).toURI());
} catch (URISyntaxException ex) {
// should not happen
}
}
info.subprojects = subprojectDirs.toArray(new URI[subprojectDirs.size()]);
Project root = ProjectUtils.rootOf(p);
if (root != null) {
try {
info.rootProject = URLMapper.findURL(root.getProjectDirectory(), URLMapper.EXTERNAL).toURI();
} catch (URISyntaxException ex) {
// should not happen
}
}
if (recursive) {
toOpen.addAll(children);
}
}
infos.put(p.getProjectDirectory(), info);
return info;
}
CompletableFuture<LspProjectInfo[]> processProjects(Project[] prjs) {
for (Project p : prjs) {
fillProjectInfo(p);
}
if (toOpen.isEmpty()) {
return finalizeInfos();
}
List<FileObject> dirs = new ArrayList<>(toOpen.size());
for (Project p : toOpen) {
dirs.add(p.getProjectDirectory());
}
toOpen.clear();
return server.asyncOpenSelectedProjects(dirs).thenCompose(this::processProjects);
}
CompletableFuture<LspProjectInfo[]> finalizeInfos() {
List<LspProjectInfo> list = new ArrayList();
for (URL u : locations) {
FileObject f = URLMapper.findFileObject(u);
Project owner = FileOwnerQuery.getOwner(f);
if (owner != null) {
list.add(infos.remove(owner.getProjectDirectory()));
} else {
list.add(null);
}
}
list.addAll(infos.values());
LspProjectInfo[] toArray = list.toArray(new LspProjectInfo[list.size()]);
return CompletableFuture.completedFuture(toArray);
}
}
private static boolean getOption(JsonObject opts, String member, boolean def) {
if (!opts.has(member)) {
return def;
}
Object o = opts.get(member);
if (!(o instanceof JsonPrimitive)) {
return false;
}
return ((JsonPrimitive)o).getAsBoolean();
}
private final AtomicReference<BiConsumer<FileObject, Collection<TestMethodController.TestMethod>>> testMethodsListener = new AtomicReference<>();
private final AtomicReference<PropertyChangeListener> openProjectsListener = new AtomicReference<>();
private static Map<String, Object> attributesMap(JsonObject json) {
Map<String, Object> map = new LinkedHashMap<>();
for (Entry<String, JsonElement> entry : json.entrySet()) {
JsonPrimitive jp = (JsonPrimitive) entry.getValue();
Object value = jp.isBoolean() ? jp.getAsBoolean() : jp.isNumber() ? jp.getAsNumber() : jp.getAsString();
map.put(entry.getKey(), value);
}
return map;
}
private CompletableFuture<Object> findProjectConfigurations(FileObject ownedFile) {
return server.asyncOpenFileOwner(ownedFile).thenApply(p -> {
if (p == null) {
return Collections.emptyList();
}
ProjectConfigurationProvider<ProjectConfiguration> provider = p.getLookup().lookup(ProjectConfigurationProvider.class);
List<String> configDispNames = new ArrayList<>();
if (provider != null) {
for (ProjectConfiguration c : provider.getConfigurations()) {
configDispNames.add(c.getDisplayName());
}
}
return configDispNames;
});
}
private CompletableFuture<List<FileObject>> getSourceRoots(String uri, String type) {
FileObject file;
try {
file = URLMapper.findFileObject(new URL(uri));
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
return CompletableFuture.completedFuture(Collections.emptyList());
}
if (file == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
return server.asyncOpenFileOwner(file).thenApply(project -> {
if (project != null) {
List<FileObject> roots = new ArrayList<>();
for(SourceGroup sourceGroup : ProjectUtils.getSources(project).getSourceGroups(type)) {
roots.add(sourceGroup.getRootFolder());
}
return roots;
}
return Collections.emptyList();
});
}
private static final String[] SOURCE_TYPES = {"java", "groovy"};
private CompletableFuture<Set<FileObject>> getTestRoots(Project prj) {
final Set<FileObject> testRoots = new HashSet<>();
List<FileObject> contained = null;
if (prj != null) {
for (String sourceType : SOURCE_TYPES) {
for (SourceGroup sg : ProjectUtils.getSources(prj).getSourceGroups(sourceType)) {
if (isTestGroup(sg)) {
testRoots.add(sg.getRootFolder());
}
}
}
Set<Project> containedProjects = ProjectUtils.getContainedProjects(prj, true);
if (containedProjects != null) {
contained = containedProjects.stream().map(p -> p.getProjectDirectory()).collect(Collectors.toList());
}
}
return server.asyncOpenSelectedProjects(contained).thenApply(projects -> {
for (Project project : projects) {
for (String sourceType : SOURCE_TYPES) {
for (SourceGroup sg : ProjectUtils.getSources(project).getSourceGroups(sourceType)) {
if (isTestGroup(sg)) {
testRoots.add(sg.getRootFolder());
}
}
}
}
return testRoots;
});
}
private boolean isTestGroup(SourceGroup sg) {
return UnitTestForSourceQuery.findSources(sg.getRootFolder()).length > 0;
}
private static final Position NO_POS = new Position(0, 0);
private static final Range NO_RANGE = new Range(NO_POS, NO_POS);
private static final String SOURCE_FOR = "sourceFor:";
@Override
public CompletableFuture<Either<List<? extends SymbolInformation>, List<? extends WorkspaceSymbol>>> symbol(WorkspaceSymbolParams params) {
// shortcut: if the projects are not yet initialized, return empty:
if (server.openedProjects().getNow(null) == null) {
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
String query = params.getQuery();
if (query.isEmpty()) {
//cannot query "all":
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
System.err.println("query=" + query);
boolean exact = false;
if (query.endsWith(" ")) {
query = query.substring(0, query.length() - 1);
exact = true;
}
String queryFin = query;
boolean exactFin = exact;
AtomicBoolean cancel = new AtomicBoolean();
CompletableFuture<Either<List<? extends SymbolInformation>, List<? extends WorkspaceSymbol>>> result = new CompletableFuture<Either<List<? extends SymbolInformation>, List<? extends WorkspaceSymbol>>>() {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
cancel.set(mayInterruptIfRunning);
return super.cancel(mayInterruptIfRunning);
}
};
WORKER.post(() -> {
try {
List<WorkspaceSymbol> symbols = new ArrayList<>();
if (client.getNbCodeCapabilities().wantsJavaSupport()) {
SearchType searchType = getSearchType(queryFin, exactFin, false, null, null);
// CSL Part
Collection<? extends IndexSearcher> providers = Lookup.getDefault().lookupAll(IndexSearcher.class);
Set<? extends IndexSearcher.Descriptor> descriptors;
if (!providers.isEmpty()) {
for (IndexSearcher provider : providers) {
descriptors = provider.getSymbols(null, queryFin, Utils.searchType2QueryKind(searchType), null);
for (IndexSearcher.Descriptor desc : descriptors) {
FileObject fo = desc.getFileObject();
org.netbeans.modules.csl.api.ElementHandle element = desc.getElement();
if (fo != null) {
Position startPos = Utils.createPosition(fo, desc.getOffset());
Position endPos = Utils.createPosition(fo, desc.getOffset() + desc.getSimpleName().length());
WorkspaceSymbol symbol = new WorkspaceSymbol(
desc.getSimpleName(),
Utils.cslElementKind2SymbolKind(element.getKind()),
Either.forLeft(new Location(Utils.toUri(fo), new Range(startPos, endPos))),
desc.getContextName());
symbols.add(symbol);
}
}
}
}
// Java part
JavaSymbolProvider.ResultHandler symbolHandler = new JavaSymbolProvider.ResultHandler() {
@Override
public void setHighlightText(String text) {
}
private Map<ElementHandle<TypeElement>, List<String>> type2Idents;
@Override
public void runRoot(FileObject root, ClassIndexImpl ci, Exec exec) throws IOException, InterruptedException {
ClasspathInfo cpInfo = ClasspathInfo.create(root);
try {
type2Idents = new HashMap<>();
exec.run();
Map<FileObject, Map<ElementHandle<TypeElement>, List<String>>> sources = new HashMap<>();
for (Entry<ElementHandle<TypeElement>, List<String>> e : type2Idents.entrySet()) {
FileObject sourceFile = SourceUtils.getFile(e.getKey(), cpInfo);
sources.computeIfAbsent(sourceFile, s -> new HashMap<>())
.put(e.getKey(), e.getValue());
}
if (!sources.isEmpty()) {
JavaSource.create(cpInfo, sources.keySet())
.runUserActionTask(cc -> {
if (Phase.ELEMENTS_RESOLVED.compareTo(cc.toPhase(Phase.ELEMENTS_RESOLVED))> 0) {
return ;
}
for (Entry<ElementHandle<TypeElement>, List<String>> e : sources.get(cc.getFileObject()).entrySet()) {
TypeElement te = e.getKey().resolve(cc);
if (te == null) {
//cannot resolve
continue;
}
for (String ident : e.getValue()) {
if (ident.equals(getSimpleName(te, null, false))) {
TreePath path = cc.getTrees().getPath(te);
if (path != null) {
final String symbolName = te.getSimpleName().toString();
final ElementKind kind = te.getKind();
if (!kind.isClass() && !kind.isInterface()) {
WorkspaceSymbol symbol = new WorkspaceSymbol(symbolName, Utils.elementKind2SymbolKind(kind), Either.forLeft(tree2Location(cc, path)), te.getQualifiedName().toString());
symbols.add(symbol);
}
}
}
for (Element ne : te.getEnclosedElements()) {
if (ident.equals(getSimpleName(ne, te, false))) {
TreePath path = cc.getTrees().getPath(ne);
if (path != null) {
final Pair<String,String> name = JavaSymbolProvider.getDisplayName(ne, te);
final String symbolName = name.first() + (name.second() != null ? name.second() : "");
final ElementKind kind = ne.getKind();
if (!kind.isClass() && !kind.isInterface()) {
WorkspaceSymbol symbol = new WorkspaceSymbol(symbolName, Utils.elementKind2SymbolKind(kind), Either.forLeft(tree2Location(cc, path)), te.getQualifiedName().toString());
symbols.add(symbol);
}
}
}
}
}
}
}, true);
}
//TODO: handle exceptions
} finally {
type2Idents = null;
}
}
@Override
public void handleResult(ElementHandle<TypeElement> owner, String ident, boolean caseSensitive) {
type2Idents.computeIfAbsent(owner, s -> new ArrayList<>()).add(ident);
}
};
JavaSymbolProvider.doComputeSymbols(searchType, queryFin, symbolHandler, true, cancel);
List<Pair<ElementHandle<TypeElement>, FileObject>> pairs = new ArrayList<>();
JavaTypeProvider.ResultHandler<Pair<ElementHandle<TypeElement>, FileObject>> typeHandler = new JavaTypeProvider.ResultHandler<Pair<ElementHandle<TypeElement>, FileObject>>() {
private FileObject root;
@Override
public void setMessage(String msg) {
}
@Override
public void setHighlightText(String text) {
}
@Override
public void pendingResult() {
}
@Override
public void runRoot(FileObject root, JavaTypeProvider.ResultHandler.Exec exec) throws IOException, InterruptedException {
this.root = root;
try {
exec.run();
} finally {
this.root = null;
}
}
@Override
public Pair<ElementHandle<TypeElement>, FileObject> create(JavaTypeProvider.CacheItem cacheItem, ElementHandle<TypeElement> handle, String simpleName, String relativePath) {
return Pair.of(handle, this.root);
}
@Override
public void addResult(List<? extends Pair<ElementHandle<TypeElement>, FileObject>> types) {
pairs.addAll(types);
}
};
JavaTypeProvider.doComputeTypes(searchType, queryFin, typeHandler, cancel);
for (Pair<ElementHandle<TypeElement>, FileObject> pair : pairs) {
ElementHandle<TypeElement> handle = pair.first();
String fqn = handle.getQualifiedName();
int idx = fqn.lastIndexOf('.');
String simpleName = idx < 0 ? fqn : fqn.substring(idx + 1);
String contextName = idx < 0 ? null : fqn.substring(0, idx);
String uri = URLEncoder.encode(pair.second().toURI().toString() + '?' + handle.getKind().name() + '#' + handle.getBinaryName(), StandardCharsets.UTF_8.toString());
WorkspaceSymbol symbol = new WorkspaceSymbol(simpleName, Utils.elementKind2SymbolKind(handle.getKind()), Either.forRight(new WorkspaceSymbolLocation(SOURCE_FOR + uri)), contextName);
symbols.add(symbol);
}
}
result.complete(Either.forRight(symbols));
} catch (Throwable t) {
result.completeExceptionally(t);
}
});
return result;
}
private Location tree2Location(CompilationInfo info, TreePath path) {
return new Location(Utils.toUri(info.getFileObject()),
Utils.treeRange(info, path.getLeaf()));
}
//from jumpto.Utils:
public static int containsWildCard( String text ) {
for( int i = 0; i < text.length(); i++ ) {
if ( text.charAt( i ) == '?' || text.charAt( i ) == '*' ) { // NOI18N
return i;
}
}
return -1;
}
public static boolean isAllUpper( String text ) {
for( int i = 0; i < text.length(); i++ ) {
if ( !Character.isUpperCase( text.charAt( i ) ) ) {
return false;
}
}
return true;
}
public static SearchType getSearchType(
@NonNull final String text,
final boolean exact,
final boolean isCaseSensitive,
@NullAllowed final String camelCaseSeparator,
@NullAllowed final String camelCasePart) {
int wildcard = containsWildCard(text);
if (exact) {
//nameKind = isCaseSensitive ? SearchType.EXACT_NAME : SearchType.CASE_INSENSITIVE_EXACT_NAME;
return SearchType.EXACT_NAME;
} else if (wildcard != -1) {
return isCaseSensitive ? SearchType.REGEXP : SearchType.CASE_INSENSITIVE_REGEXP;
} else if ((isAllUpper(text) && text.length() > 1) || Queries.isCamelCase(text, camelCaseSeparator, camelCasePart)) {
return isCaseSensitive ? SearchType.CAMEL_CASE : SearchType.CASE_INSENSITIVE_CAMEL_CASE;
} else {
return isCaseSensitive ? SearchType.PREFIX : SearchType.CASE_INSENSITIVE_PREFIX;
}
}
//TODO: from AsyncJavaSymbolDescriptor:
private static final String INIT = "<init>"; //NOI18N
@NonNull
private static String getSimpleName (
@NonNull final Element element,
@NullAllowed final Element enclosingElement,
final boolean caseSensitive) {
String result = element.getSimpleName().toString();
if (enclosingElement != null && INIT.equals(result)) {
result = enclosingElement.getSimpleName().toString();
}
if (!caseSensitive) {
result = result.toLowerCase();
}
return result;
}
@Override
public void didChangeConfiguration(DidChangeConfigurationParams params) {
server.openedProjects().thenAccept(projects -> {
if (projects != null && projects.length > 0) {
updateJavaFormatPreferences(projects[0].getProjectDirectory(), ((JsonObject) params.getSettings()).getAsJsonObject("netbeans").getAsJsonObject("format"));
updateJavaImportPreferences(projects[0].getProjectDirectory(), ((JsonObject) params.getSettings()).getAsJsonObject("netbeans").getAsJsonObject("java").getAsJsonObject("imports"));
}
});
}
void updateJavaFormatPreferences(FileObject fo, JsonObject configuration) {
if (configuration != null && client.getNbCodeCapabilities().wantsJavaSupport()) {
NbPreferences.Provider provider = Lookup.getDefault().lookup(NbPreferences.Provider.class);
Preferences prefs = provider != null ? provider.preferencesRoot().node("de/funfried/netbeans/plugins/externalcodeformatter") : null;
JsonPrimitive formatterPrimitive = configuration.getAsJsonPrimitive("codeFormatter");
String formatter = formatterPrimitive != null ? formatterPrimitive.getAsString() : null;
JsonPrimitive pathPrimitive = configuration.getAsJsonPrimitive("settingsPath");
String path = pathPrimitive != null ? pathPrimitive.getAsString() : null;
if (formatter == null || "NetBeans".equals(formatter)) {
if (prefs != null) {
prefs.put("enabledFormatter.JAVA", "netbeans-formatter");
}
Path p = path != null ? Paths.get(path) : null;
File file = p != null ? p.toFile() : null;
try {
if (file != null && file.exists() && file.canRead() && file.getName().endsWith(".zip")) {
OptionsExportModel.get().doImport(file);
} else {
OptionsExportModel.get().clean();
}
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
} else if (prefs != null) {
prefs.put("enabledFormatter.JAVA", formatter.toLowerCase(Locale.ENGLISH).concat("-java-formatter"));
if (path != null) {
prefs.put(formatter.toLowerCase(Locale.ENGLISH).concat("FormatterLocation"), path);
}
}
}
}
void updateJavaImportPreferences(FileObject fo, JsonObject configuration) {
Preferences prefs = CodeStylePreferences.get(fo, "text/x-java").getPreferences();
if (prefs != null && configuration != null) {
prefs.put("importGroupsOrder", String.join(";", gson.fromJson(configuration.get("groups"), String[].class)));
prefs.putBoolean("allowConvertToStarImport", true);
prefs.putInt("countForUsingStarImport", configuration.getAsJsonPrimitive("countForUsingStarImport").getAsInt());
prefs.putBoolean("allowConvertToStaticStarImport", true);
prefs.putInt("countForUsingStaticStarImport", configuration.getAsJsonPrimitive("countForUsingStaticStarImport").getAsInt());
}
}
@Override
public void didChangeWatchedFiles(DidChangeWatchedFilesParams arg0) {
//TODO: not watching files for now
}
@Override
public void connect(LanguageClient client) {
this.client = (NbCodeLanguageClient)client;
}
private static final class CommandProgress extends ActionProgress {
private final CompletableFuture<Object> commandFinished = new CompletableFuture<>();;
private int running;
private int success;
private int failure;
@Override
protected synchronized void started() {
running++;
notify();
}
@Override
public synchronized void finished(boolean ok) {
if (ok) {
success++;
} else {
failure++;
}
checkStatus();
}
final synchronized void checkStatus() {
if (running == 0) {
try {
wait(100);
} catch (InterruptedException ex) {
}
}
if (running <= success + failure) {
commandFinished.complete(failure == 0);
}
}
CompletableFuture<Object> getFinishFuture() {
return commandFinished;
}
}
}