This is an automated email from the ASF dual-hosted git repository.

dbalek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git


The following commit(s) were added to refs/heads/master by this push:
     new 16a48da  LSP: Find tests on initial scan. (#3093)
16a48da is described below

commit 16a48da097a251638483fbc33699bea0f73d8da2
Author: Dusan Balek <[email protected]>
AuthorDate: Thu Aug 26 09:39:27 2021 +0200

    LSP: Find tests on initial scan. (#3093)
---
 .../groovy/editor/api/parser/GroovyParser.java     |   5 +-
 .../editor/compiler/ParsingCompilerCustomizer.java |   9 +-
 ide/gsf.testrunner.ui/apichanges.xml               |  12 ++
 ide/gsf.testrunner.ui/manifest.mf                  |   2 -
 ide/gsf.testrunner.ui/nbproject/project.properties |   1 +
 ide/gsf.testrunner.ui/nbproject/project.xml        |   2 +-
 .../gsf/testrunner/ui/TestMethodFinderImpl.java    | 154 +++++++++++++++++++++
 .../gsf/testrunner/ui/api/TestMethodFinder.java    | 118 ++++++++++++++++
 .../parsing/impl/indexing/RepositoryUpdater.java   |  19 ++-
 java/java.lsp.server/nbproject/project.xml         |   2 +-
 .../server/protocol/TextDocumentServiceImpl.java   |  39 +-----
 .../lsp/server/protocol/WorkspaceServiceImpl.java  | 110 +++++++--------
 12 files changed, 370 insertions(+), 103 deletions(-)

diff --git 
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/parser/GroovyParser.java
 
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/parser/GroovyParser.java
index fee5c7f..40860f8 100644
--- 
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/parser/GroovyParser.java
+++ 
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/parser/GroovyParser.java
@@ -829,7 +829,10 @@ public class GroovyParser extends Parser {
         }
         
         void setIndexingTask(Task t) {
-            this.parserTask = parserTask;
+            this.parserTask = t;
+            if (customizerCtx != null) {
+                customizerCtx.setConsumerTask(t);
+            }
         }
 
         @Override
diff --git 
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/compiler/ParsingCompilerCustomizer.java
 
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/compiler/ParsingCompilerCustomizer.java
index 046272f..0e3d405 100644
--- 
a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/compiler/ParsingCompilerCustomizer.java
+++ 
b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/compiler/ParsingCompilerCustomizer.java
@@ -61,7 +61,7 @@ public interface ParsingCompilerCustomizer {
      */
     public final class Context {
         private final Snapshot snapshot;
-        private final Task consumerTask;
+        private Task consumerTask;
 
         /**
          * Constructs the context. 
@@ -90,5 +90,12 @@ public interface ParsingCompilerCustomizer {
         public Task getConsumerTask() {
             return consumerTask;
         }
+
+        /**
+         * Sets the task that is about to consume the parser's results.
+         */
+        public void setConsumerTask(Task consumerTask) {
+            this.consumerTask = consumerTask;
+        }
     }
 }
diff --git a/ide/gsf.testrunner.ui/apichanges.xml 
b/ide/gsf.testrunner.ui/apichanges.xml
index 2d3d18c..19ba911 100644
--- a/ide/gsf.testrunner.ui/apichanges.xml
+++ b/ide/gsf.testrunner.ui/apichanges.xml
@@ -52,6 +52,18 @@
 <!-- ACTUAL CHANGES BEGIN HERE: -->
 
 <changes>
+    <change id="TestMethodsFinder">
+        <api name="CommonTestrunnerUIAPI"/>
+        <summary>Added FindTestMethods API</summary>
+        <version major="1" minor="27"/>
+        <date day="25" month="8" year="2021"/>
+        <author login="dbalek"/>
+        <compatibility addition="yes"/>
+        <description>
+            Added API to provide a list of TestMethods found in sources under 
particutlar test roots.
+        </description>
+        <class package="org.netbeans.modules.gsf.testrunner.ui.api" 
name="TestMethodFinder"/>
+    </change>
     <change id="ComputeTestMethodsAndTestMethodGetTestClassPosition">
         <api name="CommonTestrunnerUIAPI"/>
         <summary>Added ComputeTestMethods SPI and getTestClassPosition() to 
TestMethod</summary>
diff --git a/ide/gsf.testrunner.ui/manifest.mf 
b/ide/gsf.testrunner.ui/manifest.mf
index 741896d..6df6ce7 100644
--- a/ide/gsf.testrunner.ui/manifest.mf
+++ b/ide/gsf.testrunner.ui/manifest.mf
@@ -2,5 +2,3 @@ Manifest-Version: 1.0
 OpenIDE-Module: org.netbeans.modules.gsf.testrunner.ui
 OpenIDE-Module-Layer: org/netbeans/modules/gsf/testrunner/ui/layer.xml
 OpenIDE-Module-Localizing-Bundle: 
org/netbeans/modules/gsf/testrunner/ui/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.26
-
diff --git a/ide/gsf.testrunner.ui/nbproject/project.properties 
b/ide/gsf.testrunner.ui/nbproject/project.properties
index 9c1cd1c..d664921 100644
--- a/ide/gsf.testrunner.ui/nbproject/project.properties
+++ b/ide/gsf.testrunner.ui/nbproject/project.properties
@@ -18,3 +18,4 @@ is.eager=true
 javac.source=1.8
 javac.compilerargs=-Xlint -Xlint:-serial
 javadoc.apichanges=${basedir}/apichanges.xml
+spec.version.base=1.27
diff --git a/ide/gsf.testrunner.ui/nbproject/project.xml 
b/ide/gsf.testrunner.ui/nbproject/project.xml
index e7ccc9f..63ffee1 100644
--- a/ide/gsf.testrunner.ui/nbproject/project.xml
+++ b/ide/gsf.testrunner.ui/nbproject/project.xml
@@ -136,7 +136,7 @@
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>9.0</specification-version>
+                        <implementation-version/>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git 
a/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodFinderImpl.java
 
b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodFinderImpl.java
new file mode 100644
index 0000000..6588969
--- /dev/null
+++ 
b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodFinderImpl.java
@@ -0,0 +1,154 @@
+/*
+ * 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.gsf.testrunner.ui;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
+import org.netbeans.api.editor.mimelookup.MimeLookup;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.modules.gsf.testrunner.ui.api.TestMethodController;
+import org.netbeans.modules.gsf.testrunner.ui.spi.ComputeTestMethods;
+import org.netbeans.modules.parsing.api.Snapshot;
+import org.netbeans.modules.parsing.spi.Parser;
+import org.netbeans.modules.parsing.spi.indexing.Context;
+import org.netbeans.modules.parsing.spi.indexing.EmbeddingIndexer;
+import org.netbeans.modules.parsing.spi.indexing.EmbeddingIndexerFactory;
+import org.netbeans.modules.parsing.spi.indexing.Indexable;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.Exceptions;
+import org.openide.util.WeakSet;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+public final class TestMethodFinderImpl extends EmbeddingIndexer {
+
+    public static final String NAME = "tests"; // NOI18N
+    public static final int VERSION = 1;
+    public static TestMethodFinderImpl INSTANCE = null;
+
+    private final WeakSet<BiConsumer<FileObject, 
Collection<TestMethodController.TestMethod>>> listeners = new WeakSet<>();
+
+    @Override
+    protected void index(Indexable indexable, Parser.Result parserResult, 
Context context) {
+        List<TestMethodController.TestMethod> testMethods = new ArrayList<>();
+        for (ComputeTestMethods ctm : 
MimeLookup.getLookup(indexable.getMimeType()).lookupAll(ComputeTestMethods.class))
 {
+            testMethods.addAll(ctm.computeTestMethods(parserResult, new 
AtomicBoolean()));
+        }
+        FileObject fo = parserResult.getSnapshot().getSource().getFileObject();
+        store(context.getIndexFolder(), indexable.getURL(), 
indexable.getRelativePath(), testMethods);
+        if (!context.isAllFilesIndexing()) {
+            synchronized (listeners) {
+                for (BiConsumer<FileObject, 
Collection<TestMethodController.TestMethod>> listener : listeners) {
+                    listener.accept(fo, testMethods);
+                }
+            }
+        }
+    }
+
+    public void addListener(BiConsumer<FileObject, 
Collection<TestMethodController.TestMethod>> listener) {
+        synchronized(listeners) {
+            listeners.putIfAbsent(listener);
+        }
+    }
+
+    private void store(FileObject indexFolder, URL url, String resourceName, 
List<TestMethodController.TestMethod> methods) {
+        File cacheRoot = FileUtil.toFile(indexFolder);
+        File output = new File(cacheRoot, resourceName + ".tests"); //NOI18N
+        if (methods.isEmpty()) {
+            if (output.exists()) {
+                output.delete();
+            }
+        } else {
+            output.getParentFile().mkdirs();
+            boolean printHeader = true;
+            try (PrintWriter pw = new PrintWriter(new OutputStreamWriter(new 
FileOutputStream(output), "UTF-8"))) {
+                for (TestMethodController.TestMethod method : methods) {
+                    if (printHeader) {
+                        pw.print("url: "); //NOI18N
+                        pw.println(url.toString());
+                        pw.print("class: "); //NOI18N
+                        pw.print(method.getTestClassName());
+                        if (method.getTestClassPosition() != null) {
+                            pw.print(':'); //NOI18N
+                            
pw.println(method.getTestClassPosition().getOffset());
+                        } else {
+                            pw.println();
+                        }
+                        printHeader = false;
+                    }
+                    pw.print("method: "); //NOI18N
+                    pw.print(method.method().getMethodName());
+                    pw.print(':'); //NOI18N
+                    pw.println(method.start().getOffset());
+                }
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+    }
+
+    @MimeRegistration(mimeType="", service=EmbeddingIndexerFactory.class) 
//NOI18N
+    public static class Factory extends EmbeddingIndexerFactory {
+
+        @Override
+        public EmbeddingIndexer createIndexer(Indexable indexable, Snapshot 
snapshot) {
+            if (INSTANCE == null) {
+                INSTANCE = new TestMethodFinderImpl();
+            }
+            return INSTANCE;
+        }
+
+        @Override
+        public void filesDeleted(Iterable<? extends Indexable> deleted, 
Context context) {
+            File cacheRoot = FileUtil.toFile(context.getIndexFolder());
+            for (Indexable indexable : deleted) {
+                File output = new File(cacheRoot, indexable.getRelativePath() 
+ ".tests"); //NOI18N
+                if (output.exists()) {
+                    output.delete();
+                }
+            }
+        }
+
+        @Override
+        public void filesDirty(Iterable<? extends Indexable> dirty, Context 
context) {
+        }
+
+        @Override
+        public String getIndexerName() {
+            return NAME;
+        }
+
+        @Override
+        public int getIndexVersion() {
+            return VERSION;
+        }
+    }
+}
diff --git 
a/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestMethodFinder.java
 
b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestMethodFinder.java
new file mode 100644
index 0000000..b1dc558
--- /dev/null
+++ 
b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestMethodFinder.java
@@ -0,0 +1,118 @@
+/*
+ * 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.gsf.testrunner.ui.api;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import javax.swing.text.Position;
+import org.netbeans.modules.gsf.testrunner.ui.TestMethodFinderImpl;
+import org.netbeans.modules.parsing.impl.indexing.CacheFolder;
+import org.netbeans.spi.project.SingleMethod;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+
+/**
+ * API to provide a list of {@link TestMethod}s found in sources under 
particular test roots.
+ *
+ * @author Dusan Balek
+ * @since 1.27
+ */
+public final class TestMethodFinder {
+
+    /**
+     * Provides a list of {@link TestMethod}s found in sources under 
particular test roots.
+     *
+     * @param testRoots roots to search test methods for
+     * @param listener a listener to inform about later changes. The listener 
is held weakly.
+     * @return map of test source files to tests methods found
+     * @since 1.27
+     */
+    public static Map<FileObject, Collection<TestMethodController.TestMethod>> 
findTestMethods(Iterable<FileObject> testRoots, BiConsumer<FileObject, 
Collection<TestMethodController.TestMethod>> listener) {
+        if (TestMethodFinderImpl.INSTANCE != null) {
+            TestMethodFinderImpl.INSTANCE.addListener(listener);
+        }
+        Map<FileObject, Collection<TestMethodController.TestMethod>> 
file2TestMethods = new HashMap<>();
+        for (FileObject testRoot : testRoots) {
+            try {
+                FileObject cacheRoot = getCacheRoot(testRoot.toURL());
+                if (cacheRoot != null) {
+                    Enumeration<? extends FileObject> children = 
cacheRoot.getChildren(true);
+                    while (children.hasMoreElements()) {
+                        FileObject child = children.nextElement();
+                        if (child.hasExt("tests")) { //NOI18N
+                            loadTestMethods(child, file2TestMethods);
+                        }
+                    }
+                }
+            } catch (IOException ex) {}
+        }
+        return file2TestMethods;
+    }
+
+    private static void loadTestMethods(FileObject input, Map<FileObject, 
Collection<TestMethodController.TestMethod>> file2TestMethods) {
+        try (BufferedReader br = new BufferedReader(new 
InputStreamReader(input.getInputStream(), "UTF-8"))) {
+            FileObject fo = null;
+            String className = null;
+            Position classPosition = null;
+            Collection<TestMethodController.TestMethod> testMethods = null;
+            String line;
+            while ((line = br.readLine()) != null) {
+                if (line.startsWith("url: ")) { //NOI18N
+                    String url = line.substring(5);
+                    fo = URLMapper.findFileObject(URI.create(url).toURL());
+                    if (fo == null) {
+                        return;
+                    }
+                    testMethods = file2TestMethods.computeIfAbsent(fo, fObj -> 
{
+                        return new ArrayList<>();
+                    });
+                } else if (line.startsWith("class: ")) { //NOI18N
+                    String info = line.substring(7);
+                    int idx = info.lastIndexOf(':');
+                    className = (idx < 0 ? info : info.substring(0, 
idx)).trim();
+                    classPosition = idx < 0 ? null : () -> 
Integer.parseInt(info.substring(idx + 1));
+                } else if (line.startsWith("method: ") && testMethods != null 
&& className != null) { //NOI18N
+                    String info = line.substring(8);
+                    int idx = info.lastIndexOf(':');
+                    String name = (idx < 0 ? info : info.substring(0, 
idx)).trim();
+                    Position methodPosition = idx < 0 ? null : () -> 
Integer.parseInt(info.substring(idx + 1));
+                    testMethods.add(new 
TestMethodController.TestMethod(className, classPosition, new SingleMethod(fo, 
name), methodPosition, null, null));
+                }
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    private static FileObject getCacheRoot(URL root) throws IOException {
+        final FileObject dataFolder = CacheFolder.getDataFolder(root, true);
+        return dataFolder != null ? FileUtil.createFolder(dataFolder, 
TestMethodFinderImpl.NAME + "/" + TestMethodFinderImpl.VERSION) : null; //NOI18N
+    }
+}
diff --git 
a/ide/parsing.indexing/src/org/netbeans/modules/parsing/impl/indexing/RepositoryUpdater.java
 
b/ide/parsing.indexing/src/org/netbeans/modules/parsing/impl/indexing/RepositoryUpdater.java
index a30f189..134442d 100644
--- 
a/ide/parsing.indexing/src/org/netbeans/modules/parsing/impl/indexing/RepositoryUpdater.java
+++ 
b/ide/parsing.indexing/src/org/netbeans/modules/parsing/impl/indexing/RepositoryUpdater.java
@@ -3124,7 +3124,7 @@ public final class RepositoryUpdater implements 
PathRegistryListener, PropertyCh
                     return false;
                 }
 
-                Collection<? extends 
IndexerCache.IndexerInfo<EmbeddingIndexerFactory>> infos = 
eifInfosMap.get(dirty.getMimeType());
+                Collection<? extends 
IndexerCache.IndexerInfo<EmbeddingIndexerFactory>> infos = 
getIndexerInfos(eifInfosMap, dirty.getMimeType());
                 if (infos != null && infos.size() > 0) {
                     final URL url = dirty.getURL();
                     if (url == null) {
@@ -3145,7 +3145,7 @@ public final class RepositoryUpdater implements 
PathRegistryListener, PropertyCh
                             @Override
                             public void run(ResultIterator resultIterator) 
throws Exception {
                                 final String mimeType = 
resultIterator.getSnapshot().getMimeType();
-                                final Collection<? extends 
IndexerCache.IndexerInfo<EmbeddingIndexerFactory>> infos = 
eifInfosMap.get(mimeType);
+                                final Collection<? extends 
IndexerCache.IndexerInfo<EmbeddingIndexerFactory>> infos = 
getIndexerInfos(eifInfosMap, mimeType);
 
                                 if (infos != null && infos.size() > 0) {
                                     boolean finished = false;
@@ -3578,6 +3578,21 @@ public final class RepositoryUpdater implements 
PathRegistryListener, PropertyCh
             return result;
         }
 
+        private static final String ALL_MIME_TYPES = ""; //NOI18N
+
+        private static Collection<? extends 
IndexerCache.IndexerInfo<EmbeddingIndexerFactory>> getIndexerInfos(
+                final Map<String, 
Collection<IndexerCache.IndexerInfo<EmbeddingIndexerFactory>>> eifInfosMap,
+                final String mimeType) {
+            final 
Collection<IndexerCache.IndexerInfo<EmbeddingIndexerFactory>> infos = new 
ArrayList<>();
+            if (eifInfosMap.containsKey(mimeType)) {
+                infos.addAll(eifInfosMap.get(mimeType));
+            }
+            if (eifInfosMap.containsKey(ALL_MIME_TYPES)) {
+                infos.addAll(eifInfosMap.get(ALL_MIME_TYPES));
+            }
+            return infos;
+        }
+
         protected final boolean storeChanges(
                 @NullAllowed final String indexerName,
                 @NonNull final Context ctx,
diff --git a/java/java.lsp.server/nbproject/project.xml 
b/java/java.lsp.server/nbproject/project.xml
index 99f64a7..9e4f7a3 100644
--- a/java/java.lsp.server/nbproject/project.xml
+++ b/java/java.lsp.server/nbproject/project.xml
@@ -214,7 +214,7 @@
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>1.25</specification-version>
+                        <specification-version>1.27</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
index c950560..3e91b48 100644
--- 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
@@ -42,14 +42,12 @@ import java.util.Collections;
 import java.util.EnumMap;
 import java.util.EnumSet;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
@@ -158,8 +156,6 @@ import org.netbeans.api.project.Sources;
 import org.netbeans.lib.editor.util.swing.DocumentUtilities;
 import org.netbeans.modules.editor.java.GoToSupport;
 import org.netbeans.modules.editor.java.GoToSupport.GoToTarget;
-import 
org.netbeans.modules.gsf.testrunner.ui.api.TestMethodController.TestMethod;
-import org.netbeans.modules.gsf.testrunner.ui.spi.ComputeTestMethods;
 import org.netbeans.modules.java.editor.base.fold.JavaElementFoldVisitor;
 import 
org.netbeans.modules.java.editor.base.fold.JavaElementFoldVisitor.FoldCreator;
 import 
org.netbeans.modules.java.editor.base.semantic.MarkOccurrencesHighlighterBase;
@@ -175,7 +171,6 @@ import org.netbeans.modules.java.lsp.server.Utils;
 import org.netbeans.modules.java.lsp.server.debugging.utils.ErrorUtilities;
 import org.netbeans.modules.parsing.api.ParserManager;
 import org.netbeans.modules.parsing.api.ResultIterator;
-import org.netbeans.modules.java.lsp.server.files.OpenedDocuments;
 import org.netbeans.modules.parsing.api.Source;
 import org.netbeans.modules.parsing.api.UserTask;
 import 
org.netbeans.modules.parsing.impl.indexing.implspi.ActiveDocumentProvider.IndexingAware;
@@ -904,8 +899,6 @@ public class TextDocumentServiceImpl implements 
TextDocumentService, LanguageCli
     }
                 
 
-    private ConcurrentHashMap<String, Boolean> upToDateTests = new 
ConcurrentHashMap<>();
-
     @NbBundle.Messages({"# {0} - method name", "LBL_Run=Run {0}",
                         "# {0} - method name", "LBL_Debug=Debug {0}",
                         "# {0} - method name", "# {1} - configuration name", 
"LBL_RunWith=Run {0} with {1}",
@@ -923,38 +916,10 @@ public class TextDocumentServiceImpl implements 
TextDocumentService, LanguageCli
         }
         CompletableFuture<List<? extends CodeLens>> result = new 
CompletableFuture<>();
         try {
-            ParserManager.parse(Collections.singleton(source), new UserTask() {
+            ParserManager.parseWhenScanFinished(Collections.singleton(source), 
new UserTask() {
                 @Override
                 public void run(ResultIterator resultIterator) throws 
Exception {
                     Parser.Result parserResult = 
resultIterator.getParserResult();
-                    //look for test methods:
-                    if (!upToDateTests.getOrDefault(uri, Boolean.FALSE)) {
-                        List<TestMethod> testMethods = new ArrayList<>();
-                        for (ComputeTestMethods ctm : 
MimeLookup.getLookup(parserResult.getSnapshot().getMimePath()).lookupAll(ComputeTestMethods.class))
 {
-                            
testMethods.addAll(ctm.computeTestMethods(parserResult, new AtomicBoolean()));
-                        }
-                        if (!testMethods.isEmpty()) {
-                            String testClassName = null;
-                            Integer testClassLine = null;
-                            List<TestSuiteInfo.TestCaseInfo> tests = new 
ArrayList<>(testMethods.size());
-                            for (TestMethod testMethod : testMethods) {
-                                if (testClassName == null) {
-                                    testClassName = 
testMethod.getTestClassName();
-                                }
-                                if (testClassLine == null) {
-                                    testClassLine = 
testMethod.getTestClassPosition() != null
-                                            ? 
Utils.createPosition(parserResult.getSnapshot().getSource().getFileObject(), 
testMethod.getTestClassPosition().getOffset()).getLine()
-                                            : null;
-                                }
-                                String id = testMethod.getTestClassName() + 
':' + testMethod.method().getMethodName();
-                                String fullName = 
testMethod.getTestClassName() + '.' + testMethod.method().getMethodName();
-                                int testLine = 
Utils.createPosition(parserResult.getSnapshot().getSource().getFileObject(), 
testMethod.start().getOffset()).getLine();
-                                tests.add(new TestSuiteInfo.TestCaseInfo(id, 
testMethod.method().getMethodName(), fullName, uri, testLine, 
TestSuiteInfo.State.Loaded, null));
-                            }
-                            client.notifyTestProgress(new 
TestProgressParams(uri, new TestSuiteInfo(testClassName, uri, testClassLine, 
TestSuiteInfo.State.Loaded, tests)));
-                            upToDateTests.put(uri, Boolean.TRUE);
-                        }
-                    }
                     //look for main methods:
                     List<CodeLens> lens = new ArrayList<>();
                     CompilationController cc = 
CompilationController.get(parserResult);
@@ -1331,7 +1296,6 @@ public class TextDocumentServiceImpl implements 
TextDocumentService, LanguageCli
     @Override
     public void didChange(DidChangeTextDocumentParams params) {
         String uri = params.getTextDocument().getUri();
-        upToDateTests.put(uri, Boolean.FALSE);
         Document doc = server.getOpenedDocuments().getDocument(uri);
         if (doc != null) {
             NbDocument.runAtomic((StyledDocument) doc, () -> {
@@ -1355,7 +1319,6 @@ public class TextDocumentServiceImpl implements 
TextDocumentService, LanguageCli
     public void didClose(DidCloseTextDocumentParams params) {
         try {
             String uri = params.getTextDocument().getUri();
-            upToDateTests.remove(uri);
             // the order here is important ! As the file may cease to exist, 
it's
             // important that the doucment is already gone form the client.
             server.getOpenedDocuments().notifyClosed(uri);
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
index e778c19..c018c71 100644
--- 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
@@ -30,7 +30,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -40,6 +39,9 @@ import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 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.stream.Collectors;
 import javax.lang.model.element.Element;
@@ -61,9 +63,10 @@ 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.editor.mimelookup.MimeLookup;
+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;
@@ -77,7 +80,7 @@ import org.netbeans.api.project.ProjectUtils;
 import org.netbeans.api.project.SourceGroup;
 import org.netbeans.api.project.ui.OpenProjects;
 import org.netbeans.modules.gsf.testrunner.ui.api.TestMethodController;
-import org.netbeans.modules.gsf.testrunner.ui.spi.ComputeTestMethods;
+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;
@@ -85,13 +88,7 @@ import 
org.netbeans.modules.java.lsp.server.debugging.attach.AttachNativeConfigu
 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.api.ParserManager;
-import org.netbeans.modules.parsing.api.ResultIterator;
-import org.netbeans.modules.parsing.api.Source;
-import org.netbeans.modules.parsing.api.UserTask;
 import org.netbeans.modules.parsing.lucene.support.Queries;
-import org.netbeans.modules.parsing.spi.ParseException;
-import org.netbeans.modules.parsing.spi.Parser;
 import org.netbeans.spi.jumpto.type.SearchType;
 import org.netbeans.spi.project.ActionProgress;
 import org.netbeans.spi.project.ActionProvider;
@@ -199,26 +196,50 @@ public final class WorkspaceServiceImpl implements 
WorkspaceService, LanguageCli
                 if (file == null) {
                     return 
CompletableFuture.completedFuture(Collections.emptyList());
                 }
-                return 
server.asyncOpenFileOwner(file).thenCompose(this::getTestRoots).thenApply(testRoots
 -> {
-                    List<TestMethodController.TestMethod> testMethods = new 
ArrayList<>();
-                    findTestMethods(testRoots, testMethods);
-                    if (testMethods.isEmpty()) {
-                        return Collections.emptyList();
-                    }
-                    Map<FileObject, TestSuiteInfo> file2TestSuites = new 
HashMap<>();
-                    for (TestMethodController.TestMethod testMethod : 
testMethods) {
-                        TestSuiteInfo suite = 
file2TestSuites.computeIfAbsent(testMethod.method().getFile(), fo -> {
-                            String foUri = Utils.toUri(fo);
-                            int off = testMethod.getTestClassPosition() != 
null ? testMethod.getTestClassPosition().getOffset() : -1;
-                            Integer line = off < 0 ? null : 
Utils.createPosition(testMethod.method().getFile(), off).getLine();
-                            return new 
TestSuiteInfo(testMethod.getTestClassName(), foUri, line, 
TestSuiteInfo.State.Loaded, new ArrayList<>());
-                        });
-                        String id = testMethod.getTestClassName() + ':' + 
testMethod.method().getMethodName();
-                        String fullName = testMethod.getTestClassName() + '.' 
+ testMethod.method().getMethodName();
-                        int line = 
Utils.createPosition(testMethod.method().getFile(), 
testMethod.start().getOffset()).getLine();
-                        suite.getTests().add(new 
TestSuiteInfo.TestCaseInfo(id, testMethod.method().getMethodName(), fullName, 
suite.getFile(), line, TestSuiteInfo.State.Loaded, null));
+                return 
server.asyncOpenFileOwner(file).thenCompose(this::getTestRoots).thenCompose(testRoots
 -> {
+                    CompletableFuture<Object> future = new 
CompletableFuture<>();
+                    JavaSource js = 
JavaSource.create(ClasspathInfo.create(ClassPath.EMPTY, ClassPath.EMPTY, 
ClassPath.EMPTY));
+                    try {
+                        js.runWhenScanFinished(controller -> {
+                            BiFunction<FileObject, 
Collection<TestMethodController.TestMethod>, TestSuiteInfo> f = (fo, methods) 
-> {
+                                List<TestSuiteInfo.TestCaseInfo> tests = new 
ArrayList<>(methods.size());
+                                String url = Utils.toUri(fo);
+                                String testClassName = null;
+                                Integer testClassLine = null;
+                                for (TestMethodController.TestMethod 
testMethod : methods) {
+                                    if (testClassName == null) {
+                                        testClassName = 
testMethod.getTestClassName();
+                                    }
+                                    if (testClassLine == null) {
+                                        testClassLine = 
testMethod.getTestClassPosition() != null
+                                                ? Utils.createPosition(fo, 
testMethod.getTestClassPosition().getOffset()).getLine()
+                                                : null;
+                                    }
+                                    String id = testMethod.getTestClassName() 
+ ':' + testMethod.method().getMethodName();
+                                    String fullName = 
testMethod.getTestClassName() + '.' + testMethod.method().getMethodName();
+                                    int testLine = Utils.createPosition(fo, 
testMethod.start().getOffset()).getLine();
+                                    tests.add(new 
TestSuiteInfo.TestCaseInfo(id, testMethod.method().getMethodName(), fullName, 
url, testLine, TestSuiteInfo.State.Loaded, null));
+                                }
+                                return new TestSuiteInfo(testClassName, url, 
testClassLine, TestSuiteInfo.State.Loaded, tests);
+                            };
+                            testMethodsListener.compareAndSet(null, (fo, 
methods) -> {
+                                try {
+                                    client.notifyTestProgress(new 
TestProgressParams(Utils.toUri(fo), f.apply(fo, methods)));
+                                } catch (Exception e) {
+                                    testMethodsListener.set(null);
+                                }
+                            });
+                            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.add(f.apply(entry.getKey(), 
entry.getValue()));
+                            }
+                            future.complete(suites);
+                        }, true);
+                    } catch (IOException ex) {
+                        future.completeExceptionally(ex);
                     }
-                    return file2TestSuites.values();
+                    return future;
                 });
             }
             case Server.JAVA_SUPER_IMPLEMENTATION:
@@ -302,7 +323,9 @@ public final class WorkspaceServiceImpl implements 
WorkspaceService, LanguageCli
         }
         throw new UnsupportedOperationException("Command not supported: " + 
params.getCommand());
     }
-    
+
+    private final AtomicReference<BiConsumer<FileObject, 
Collection<TestMethodController.TestMethod>>> testMethodsListener = new 
AtomicReference<>();
+
     private static Map<String, Object> attributesMap(JsonObject json) {
         Map<String, Object> map = new LinkedHashMap<>();
         for (Entry<String, JsonElement> entry : json.entrySet()) {
@@ -382,34 +405,7 @@ public final class WorkspaceServiceImpl implements 
WorkspaceService, LanguageCli
     }
 
     private boolean isTestGroup(SourceGroup sg) {
-        return sg.getName().contains("TestSourceRoot") || 
sg.getName().contains("test.");
-    }
-
-    private void findTestMethods(Set<FileObject> testRoots, 
List<TestMethodController.TestMethod> testMethods) {
-        for (FileObject testRoot : testRoots) {
-            List<Source> sources = new ArrayList<>();
-            Enumeration<? extends FileObject> children = 
testRoot.getChildren(true);
-            while(children.hasMoreElements()) {
-                FileObject fo = children.nextElement();
-                boolean groovy = 
this.client.getNbCodeCapabilities().wantsGroovySupport();
-                if (fo.hasExt("java") || (groovy && fo.hasExt("groovy"))) {
-                    
sources.add(((TextDocumentServiceImpl)server.getTextDocumentService()).getSource(Utils.toUri(fo)));
-                }
-            }
-            if (!sources.isEmpty()) {
-                try {
-                    ParserManager.parseWhenScanFinished(sources, new 
UserTask() {
-                        @Override
-                        public void run(ResultIterator resultIterator) throws 
Exception {
-                            Parser.Result parserResult = 
resultIterator.getParserResult();
-                            for (ComputeTestMethods ctm : 
MimeLookup.getLookup(parserResult.getSnapshot().getMimePath()).lookupAll(ComputeTestMethods.class))
 {
-                                
testMethods.addAll(ctm.computeTestMethods(parserResult, new AtomicBoolean()));
-                            }
-                        }
-                    }).get();
-                } catch (Exception ex) {}
-            }
-        }
+        return UnitTestForSourceQuery.findSources(sg.getRootFolder()).length > 
0;
     }
 
     @Override

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists

Reply via email to