| /* |
| * 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; |
| } |
| } |
| } |