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

jlahoda 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 42caa8adc5 Supporting source file launcher in VS Code extension
     new f06522f08b Merge pull request #6262 from lahodaj/source-launcher-in-lsp
42caa8adc5 is described below

commit 42caa8adc5faf020865c772fa93b2b385804bddf
Author: Jan Lahoda <[email protected]>
AuthorDate: Mon Jul 31 13:07:04 2023 +0200

    Supporting source file launcher in VS Code extension
---
 java/java.api.common/nbproject/project.xml         |   9 +
 .../api/common/singlesourcefile/LaunchProcess.java |  46 ++-
 .../SingleJavaSourceRunActionProvider.java         |   8 +-
 .../api/common/singlesourcefile/JavaFileTest.java  |   3 +-
 .../errors/EnablePreviewSingleSourceFile.java      |   2 +-
 .../hints/infrastructure/JavaErrorProvider.java    |   4 +-
 .../server/protocol/TextDocumentServiceImpl.java   |   2 +-
 .../lsp/server/protocol/WorkspaceServiceImpl.java  |  13 +
 .../singlesourcefile/CompilerOptionsQueryImpl.java | 307 +++++++++++++++++++++
 .../EnablePreviewSingleSourceFile.java             | 125 +++++++++
 .../CompilerOptionsQueryImplTest.java              | 109 ++++++++
 java/java.lsp.server/vscode/src/extension.ts       |   3 +-
 .../java.lsp.server/vscode/src/runConfiguration.ts |   9 +-
 .../modules/java/source/parsing/Hacks.java         |  13 +
 14 files changed, 623 insertions(+), 30 deletions(-)

diff --git a/java/java.api.common/nbproject/project.xml 
b/java/java.api.common/nbproject/project.xml
index 1556766ea3..46ddf51403 100644
--- a/java/java.api.common/nbproject/project.xml
+++ b/java/java.api.common/nbproject/project.xml
@@ -137,6 +137,15 @@
                         <specification-version>1.61</specification-version>
                     </run-dependency>
                 </dependency>
+                <dependency>
+                    
<code-name-base>org.netbeans.modules.extexecution.base</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>2</release-version>
+                        <specification-version>1.27</specification-version>
+                    </run-dependency>
+                </dependency>
                 <dependency>
                     
<code-name-base>org.netbeans.modules.java.platform</code-name-base>
                     <build-prerequisite/>
diff --git 
a/java/java.api.common/src/org/netbeans/modules/java/api/common/singlesourcefile/LaunchProcess.java
 
b/java/java.api.common/src/org/netbeans/modules/java/api/common/singlesourcefile/LaunchProcess.java
index 2d080b64a9..4a574834c7 100644
--- 
a/java/java.api.common/src/org/netbeans/modules/java/api/common/singlesourcefile/LaunchProcess.java
+++ 
b/java/java.api.common/src/org/netbeans/modules/java/api/common/singlesourcefile/LaunchProcess.java
@@ -25,20 +25,22 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.logging.Level;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import org.netbeans.api.extexecution.base.ExplicitProcessParameters;
 import org.netbeans.api.java.platform.JavaPlatformManager;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileUtil;
+import org.openide.util.BaseUtilities;
 
 final class LaunchProcess implements Callable<Process> {
 
     private final FileObject fileObject;
     private final JPDAStart start;
+    private final ExplicitProcessParameters params;
 
-    LaunchProcess(FileObject fileObject, JPDAStart start) {
+    LaunchProcess(FileObject fileObject, JPDAStart start, 
ExplicitProcessParameters params) {
         this.fileObject = fileObject;
         this.start = start;
+        this.params = params;
     }
 
     @Override
@@ -67,19 +69,28 @@ final class LaunchProcess implements Callable<Process> {
             File javaFile = FileUtil.toFile(java);
             String javaPath = javaFile.getAbsolutePath();
 
-            Object argumentsObject = 
fileObject.getAttribute(SingleSourceFileUtil.FILE_ARGUMENTS);
-            String arguments = argumentsObject != null ? ((String) 
argumentsObject).trim() : ""; // NOI18N
-
-            Object vmOptionsObj = 
fileObject.getAttribute(SingleSourceFileUtil.FILE_VM_OPTIONS);
-            String vmOptions = vmOptionsObj != null ? ((String) vmOptionsObj) 
: ""; // NOI18N
+            ExplicitProcessParameters paramsFromAttributes =
+                    ExplicitProcessParameters.builder()
+                                             
.args(readArgumentsFromAttribute(fileObject, 
SingleSourceFileUtil.FILE_ARGUMENTS))
+                                             
.launcherArgs(readArgumentsFromAttribute(fileObject, 
SingleSourceFileUtil.FILE_VM_OPTIONS))
+                                             
.workingDirectory(FileUtil.toFile(fileObject.getParent()))
+                                             .build();
 
+            ExplicitProcessParameters realParameters =
+                    ExplicitProcessParameters.builder()
+                                             .combine(params)
+                                             .combine(paramsFromAttributes)
+                                             .build();
             commandsList.add(javaPath);
-            if (!vmOptions.isEmpty()) {
-                commandsList.addAll(Arrays.asList(vmOptions.split(" ")));  
//NOI18N
+
+            if (realParameters.getLauncherArguments()!= null) {
+                commandsList.addAll(realParameters.getLauncherArguments());
             }
+
             if (port != null) {
                 commandsList.add("-agentlib:jdwp=transport=dt_socket,address=" 
+ port + ",server=n"); //NOI18N
             }
+
             if (compile) {
                 commandsList.add("-cp");
                 
commandsList.add(FileUtil.toFile(fileObject.getParent()).toString());
@@ -88,12 +99,13 @@ final class LaunchProcess implements Callable<Process> {
                 commandsList.add(fileObject.getNameExt());
             }
 
-            if (!arguments.isEmpty()) {
-                commandsList.addAll(Arrays.asList(arguments.split(" ")));  
//NOI18N
+            if (realParameters.getArguments() != null) {
+                commandsList.addAll(realParameters.getArguments());
             }
 
             ProcessBuilder runFileProcessBuilder = new 
ProcessBuilder(commandsList);
-            
runFileProcessBuilder.directory(FileUtil.toFile(fileObject.getParent())); 
//NOI18N
+            
runFileProcessBuilder.environment().putAll(realParameters.getEnvironmentVariables());
+            
runFileProcessBuilder.directory(realParameters.getWorkingDirectory());
             runFileProcessBuilder.redirectErrorStream(true);
             runFileProcessBuilder.redirectOutput();
 
@@ -105,4 +117,12 @@ final class LaunchProcess implements Callable<Process> {
         }
         return null;
     }
+
+    private static List<String> readArgumentsFromAttribute(FileObject 
fileObject, String attributeName) {
+        Object argumentsObject = fileObject.getAttribute(attributeName);
+        if (!(argumentsObject instanceof String)) {
+            return null;
+        }
+        return Arrays.asList(BaseUtilities.parseParameters(((String) 
argumentsObject).trim()));
+    }
 }
diff --git 
a/java/java.api.common/src/org/netbeans/modules/java/api/common/singlesourcefile/SingleJavaSourceRunActionProvider.java
 
b/java/java.api.common/src/org/netbeans/modules/java/api/common/singlesourcefile/SingleJavaSourceRunActionProvider.java
index e245e8e13a..ccbddcecd6 100644
--- 
a/java/java.api.common/src/org/netbeans/modules/java/api/common/singlesourcefile/SingleJavaSourceRunActionProvider.java
+++ 
b/java/java.api.common/src/org/netbeans/modules/java/api/common/singlesourcefile/SingleJavaSourceRunActionProvider.java
@@ -21,6 +21,7 @@ package org.netbeans.modules.java.api.common.singlesourcefile;
 import java.util.concurrent.Future;
 import org.netbeans.api.extexecution.ExecutionDescriptor;
 import org.netbeans.api.extexecution.ExecutionService;
+import org.netbeans.api.extexecution.base.ExplicitProcessParameters;
 import org.netbeans.spi.project.ActionProgress;
 import org.netbeans.spi.project.ActionProvider;
 import org.openide.filesystems.FileObject;
@@ -55,6 +56,7 @@ public final class SingleJavaSourceRunActionProvider 
implements ActionProvider {
         if (fileObject == null) 
             return;
 
+        ExplicitProcessParameters params = 
ExplicitProcessParameters.buildExplicitParameters(context);
         InputOutput io = 
IOProvider.getDefault().getIO(Bundle.CTL_SingleJavaFile(), false);
         ActionProgress progress = ActionProgress.start(context);
         ExecutionDescriptor descriptor = new ExecutionDescriptor().
@@ -65,7 +67,7 @@ public final class SingleJavaSourceRunActionProvider 
implements ActionProvider {
             postExecution((exitCode) -> {
                 progress.finished(exitCode == 0);
             });
-        LaunchProcess process = invokeActionHelper(io, command, fileObject);
+        LaunchProcess process = invokeActionHelper(io, command, fileObject, 
params);
         ExecutionService exeService = ExecutionService.newService(
                     process,
                     descriptor, "Running Single Java File");
@@ -78,10 +80,10 @@ public final class SingleJavaSourceRunActionProvider 
implements ActionProvider {
         return fileObject != null;
     }
     
-    final LaunchProcess invokeActionHelper (InputOutput io, String command, 
FileObject fo) {
+    final LaunchProcess invokeActionHelper (InputOutput io, String command, 
FileObject fo, ExplicitProcessParameters params) {
         JPDAStart start = ActionProvider.COMMAND_DEBUG_SINGLE.equals(command) ?
                 new JPDAStart(io, fo) : null;
-        return new LaunchProcess(fo, start);
+        return new LaunchProcess(fo, start, params);
     }
         
 }
diff --git 
a/java/java.api.common/test/unit/src/org/netbeans/modules/java/api/common/singlesourcefile/JavaFileTest.java
 
b/java/java.api.common/test/unit/src/org/netbeans/modules/java/api/common/singlesourcefile/JavaFileTest.java
index 1c92ace3f2..2c616b2b95 100644
--- 
a/java/java.api.common/test/unit/src/org/netbeans/modules/java/api/common/singlesourcefile/JavaFileTest.java
+++ 
b/java/java.api.common/test/unit/src/org/netbeans/modules/java/api/common/singlesourcefile/JavaFileTest.java
@@ -24,6 +24,7 @@ import java.io.FileWriter;
 import java.io.InputStreamReader;
 import java.util.logging.Logger;
 import static junit.framework.TestCase.assertEquals;
+import org.netbeans.api.extexecution.base.ExplicitProcessParameters;
 import org.netbeans.junit.NbTestCase;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileUtil;
@@ -55,7 +56,7 @@ public class JavaFileTest extends NbTestCase {
         FileObject javaFO = FileUtil.toFileObject(f1);
         assertNotNull("FileObject found: " + f1, javaFO);
         SingleJavaSourceRunActionProvider runActionProvider = new 
SingleJavaSourceRunActionProvider();
-        LaunchProcess process = runActionProvider.invokeActionHelper(null, 
"run.single", javaFO);
+        LaunchProcess process = runActionProvider.invokeActionHelper(null, 
"run.single", javaFO, ExplicitProcessParameters.empty());
         BufferedReader reader
                 = new BufferedReader(new 
InputStreamReader(process.call().getInputStream()));
         StringBuilder builder = new StringBuilder();
diff --git 
a/java/java.hints/src/org/netbeans/modules/java/hints/errors/EnablePreviewSingleSourceFile.java
 
b/java/java.hints/src/org/netbeans/modules/java/hints/errors/EnablePreviewSingleSourceFile.java
index 0ad7fe7c2f..b4f921e9df 100644
--- 
a/java/java.hints/src/org/netbeans/modules/java/hints/errors/EnablePreviewSingleSourceFile.java
+++ 
b/java/java.hints/src/org/netbeans/modules/java/hints/errors/EnablePreviewSingleSourceFile.java
@@ -74,7 +74,7 @@ public class EnablePreviewSingleSourceFile implements 
PreviewEnabler {
         if (compilerArgs.contains(SOURCE_FLAG)) {
             compilerArgs = m.replaceAll("--enable-preview " + SOURCE_FLAG + " 
" + newSourceLevel);
         } else {
-            compilerArgs = (compilerArgs.isEmpty() ? "" : " ") + 
ENABLE_PREVIEW_FLAG + " " + SOURCE_FLAG + " " + newSourceLevel;
+            compilerArgs += (compilerArgs.isEmpty() ? "" : " ") + 
ENABLE_PREVIEW_FLAG + " " + SOURCE_FLAG + " " + newSourceLevel;
         }
         file.setAttribute(FILE_VM_OPTIONS, compilerArgs);
         storeEditableProperties(ep, file);
diff --git 
a/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/JavaErrorProvider.java
 
b/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/JavaErrorProvider.java
index a756a2e9bd..9e5c5e69c4 100644
--- 
a/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/JavaErrorProvider.java
+++ 
b/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/JavaErrorProvider.java
@@ -194,14 +194,14 @@ public class JavaErrorProvider implements ErrorProvider {
         for (Fix f : fixes) {
             if (f instanceof IncompleteClassPath.ResolveFix) {
                 // We know that this is a project problem and that the 
problems reported by ProjectProblemsProvider should be resolved
-                CodeAction action = new CodeAction(f.getText(), new 
Command(f.getText(), "nbls.java.project.resolveProjectProblems"));
+                CodeAction action = new CodeAction(f.getText(), new 
Command(f.getText(), "java.project.resolveProjectProblems"));
                 result.add(action);
             }
             if (f instanceof 
org.netbeans.modules.java.hints.errors.EnablePreview.ResolveFix) {
                 
org.netbeans.modules.java.hints.errors.EnablePreview.ResolveFix rf = 
(org.netbeans.modules.java.hints.errors.EnablePreview.ResolveFix) f;
                 List<Object> params = rf.getNewSourceLevel() != null ? 
Arrays.asList(rf.getNewSourceLevel())
                                                                      : 
Collections.emptyList();
-                CodeAction action = new CodeAction(f.getText(), new 
Command(f.getText(), "nbls.java.project.enable.preview", params));
+                CodeAction action = new CodeAction(f.getText(), new 
Command(f.getText(), "java.project.enable.preview", params));
                 result.add(action);
             }
             if (f instanceof ImportClass.FixImport) {
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 bcdfc93221..dd6d9d723a 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
@@ -289,7 +289,7 @@ public class TextDocumentServiceImpl implements 
TextDocumentService, LanguageCli
         Lookup.getDefault().lookup(RefreshDocument.class).register(this);
     }
 
-    private void reRunDiagnostics() {
+    void reRunDiagnostics() {
         for (String doc : server.getOpenedDocuments().getUris()) {
             runDiagnosticTasks(doc, true);
         }
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 dc9b090e59..628d3d86d2 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
@@ -120,6 +120,7 @@ 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.lsp.server.singlesourcefile.CompilerOptionsQueryImpl;
 import org.netbeans.modules.java.source.ElementHandleAccessor;
 import org.netbeans.modules.java.source.ui.JavaSymbolProvider;
 import org.netbeans.modules.java.source.ui.JavaTypeProvider;
@@ -1216,6 +1217,18 @@ public final class WorkspaceServiceImpl implements 
WorkspaceService, LanguageCli
                 updateJavaImportPreferences(projects[0].getProjectDirectory(), 
((JsonObject) 
params.getSettings()).getAsJsonObject("netbeans").getAsJsonObject("java").getAsJsonObject("imports"));
             }
         });
+        boolean modified = false;
+        String newVMOptions = "";
+        JsonObject javaPlus = ((JsonObject) 
params.getSettings()).getAsJsonObject("java+");
+        if (javaPlus != null) {
+            newVMOptions = 
javaPlus.getAsJsonObject("runConfig").getAsJsonPrimitive("vmOptions").getAsString();
+        }
+        for (CompilerOptionsQueryImpl query : 
Lookup.getDefault().lookupAll(CompilerOptionsQueryImpl.class)) {
+            modified |= query.setConfiguration(client, newVMOptions);
+        }
+        if (modified) {
+            
((TextDocumentServiceImpl)server.getTextDocumentService()).reRunDiagnostics();
+        }
     }
 
     void updateJavaFormatPreferences(FileObject fo, JsonObject configuration) {
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/singlesourcefile/CompilerOptionsQueryImpl.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/singlesourcefile/CompilerOptionsQueryImpl.java
new file mode 100644
index 0000000000..487b8557da
--- /dev/null
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/singlesourcefile/CompilerOptionsQueryImpl.java
@@ -0,0 +1,307 @@
+/*
+ * 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.singlesourcefile;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.stream.Collectors;
+import javax.lang.model.SourceVersion;
+import javax.swing.event.ChangeListener;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.classpath.JavaClassPathConstants;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.ClassPathProvider;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.CompilerOptionsQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.util.ChangeSupport;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.ServiceProvider;
+import org.openide.util.lookup.ServiceProviders;
+
+@ServiceProviders({
+    @ServiceProvider(service=CompilerOptionsQueryImplementation.class, 
position=99),
+    @ServiceProvider(service=ClassPathProvider.class, position=9999), 
//DefaultClassPathProvider has 10000
+    @ServiceProvider(service=CompilerOptionsQueryImpl.class)
+})
+public class CompilerOptionsQueryImpl implements 
CompilerOptionsQueryImplementation, ClassPathProvider, 
SourceLevelQueryImplementation2 {
+
+    private final Map<NbCodeLanguageClient, Configuration> file2Configuration 
= new WeakHashMap<>();
+
+    @Override
+    public CompilerOptionsQueryImplementation.Result getOptions(FileObject 
file) {
+        if (isSingleSourceFile(file)) {
+            NbCodeLanguageClient client = 
Lookup.getDefault().lookup(NbCodeLanguageClient.class);
+            if (client != null) {
+                return getConfiguration(client).compilerOptions;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public ClassPath findClassPath(FileObject file, String type) {
+        if (isSingleSourceFile(file)) {
+            NbCodeLanguageClient client = 
Lookup.getDefault().lookup(NbCodeLanguageClient.class);
+            if (client != null) {
+                switch (type) {
+                    case ClassPath.COMPILE: case 
JavaClassPathConstants.MODULE_CLASS_PATH:
+                        return getConfiguration(client).compileClassPath;
+                    case JavaClassPathConstants.MODULE_COMPILE_PATH:
+                        return getConfiguration(client).moduleCompileClassPath;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceLevelQueryImplementation2.Result getSourceLevel(FileObject 
file) {
+        if (isSingleSourceFile(file)) {
+            NbCodeLanguageClient client = 
Lookup.getDefault().lookup(NbCodeLanguageClient.class);
+            if (client != null) {
+                return getConfiguration(client).sourceLevelResult;
+            }
+        }
+        return null;
+    }
+
+    public boolean setConfiguration(NbCodeLanguageClient client, String 
vmOptions) {
+        return getConfiguration(client).setConfiguration(vmOptions);
+    }
+
+    private synchronized Configuration getConfiguration(NbCodeLanguageClient 
client) {
+        return file2Configuration.computeIfAbsent(client, cl -> {
+            return new Configuration();
+        });
+    }
+
+    //copied from SingleSourceFileUtil:
+    static boolean isSingleSourceFile(FileObject fObj) {
+        Project p = FileOwnerQuery.getOwner(fObj);
+        if (p != null || !fObj.getExt().equalsIgnoreCase("java")) { //NOI18N
+            return false;
+        }
+        return true;
+    }
+
+    private static final class OptionsResultImpl extends 
CompilerOptionsQueryImplementation.Result {
+
+        private final ChangeSupport cs = new ChangeSupport(this);
+        private List<String> args = Collections.emptyList();
+
+        public List<String> doParse(String line) {
+            return parseLine(line);
+        }
+
+        private void setArguments(List<String> newArguments) {
+            synchronized (this) {
+                args = newArguments;
+            }
+            cs.fireChange();
+        }
+
+        @Override
+        public synchronized List<? extends String> getArguments() {
+            return args;
+        }
+
+        @Override
+        public void addChangeListener(ChangeListener listener) {
+            cs.addChangeListener(listener);
+        }
+
+        @Override
+        public void removeChangeListener(ChangeListener listener) {
+            cs.addChangeListener(listener);
+        }
+
+    }
+
+    private static final class SourceLevelResultImpl implements 
SourceLevelQueryImplementation2.Result {
+
+        private static final String DEFAULT_SL = 
String.valueOf(SourceVersion.latest().ordinal() - 
SourceVersion.RELEASE_0.ordinal());
+        private final ChangeSupport cs = new ChangeSupport(this);
+        private String sourceLevel = DEFAULT_SL;
+
+        @Override
+        public synchronized String getSourceLevel() {
+            return sourceLevel;
+        }
+
+        private void setSourceLevel(String sourceLevel) {
+            synchronized (this) {
+                this.sourceLevel = sourceLevel;
+            }
+            cs.fireChange();
+        }
+
+        @Override
+        public void addChangeListener(ChangeListener listener) {
+            cs.addChangeListener(listener);
+        }
+
+        @Override
+        public void removeChangeListener(ChangeListener listener) {
+            cs.addChangeListener(listener);
+        }
+
+    }
+
+    private static final class Configuration {
+        private List<String> currentOptions = Collections.emptyList();
+        public final OptionsResultImpl compilerOptions;
+        public final SourceLevelResultImpl sourceLevelResult;
+        public final ProxyClassPathImplementation 
compileClassPathImplementation;
+        public final ProxyClassPathImplementation 
compileModulePathImplementation;
+        public final ClassPath compileClassPath;
+        public final ClassPath moduleCompileClassPath;
+
+        public Configuration() {
+            compilerOptions = new OptionsResultImpl();
+            sourceLevelResult = new SourceLevelResultImpl();
+            compileClassPathImplementation = new 
ProxyClassPathImplementation();
+            compileModulePathImplementation = new 
ProxyClassPathImplementation();
+            compileClassPath = 
ClassPathFactory.createClassPath(compileClassPathImplementation);
+            moduleCompileClassPath = 
ClassPathFactory.createClassPath(compileModulePathImplementation);
+        }
+
+        private boolean setConfiguration(String vmOptions) {
+            List<String> newOptions = compilerOptions.doParse(vmOptions);
+
+            synchronized (this) {
+                if (currentOptions.equals(newOptions)) {
+                    return false;
+                }
+
+                currentOptions = newOptions;
+            }
+
+            compilerOptions.setArguments(newOptions);
+
+            String classpath = "";
+            String modulepath = "";
+            String sourceLevel = SourceLevelResultImpl.DEFAULT_SL;
+
+            for (int i = 0; i < newOptions.size() - 1; i++) {
+                String parameter = newOptions.get(i + 1);
+
+                switch (newOptions.get(i)) {
+                    case "-classpath": case "-cp": case "--class-path":
+                        classpath = parameter;
+                        break;
+                    case "--module-path": case "-p":
+                        modulepath = parameter;
+                        break;
+                    case "--source":
+                        sourceLevel = parameter;
+                        break;
+                }
+            }
+
+            compileClassPathImplementation.setDelegates(spec2CP(classpath));
+            compileModulePathImplementation.setDelegates(spec2CP(modulepath));
+
+            sourceLevelResult.setSourceLevel(sourceLevel);
+
+            return true;
+        }
+
+        private List<ClassPathImplementation> spec2CP(String spec) {
+            List<PathResourceImplementation> entries;
+
+            if (spec.isEmpty()) {
+                entries = Collections.emptyList();
+            } else {
+                entries = ClassPathSupport.createClassPath(spec)
+                                          .entries()
+                                          .stream()
+                                          .map(e -> e.getURL())
+                                          
.map(ClassPathSupport::createResource)
+                                          .collect(Collectors.toList());
+            }
+            return 
Arrays.asList(ClassPathSupport.createClassPathImplementation(entries));
+        }
+    }
+
+    private static final class ProxyClassPathImplementation implements 
ClassPathImplementation {
+        private final PropertyChangeSupport pcs = new 
PropertyChangeSupport(this);
+        private List<ClassPathImplementation> delegates = 
Collections.emptyList();
+        private List<? extends PathResourceImplementation> cachedResources = 
null;
+
+        public void setDelegates(List<ClassPathImplementation> delegates) {
+            synchronized (delegates) {
+                this.delegates = new ArrayList<>(delegates);
+                this.cachedResources = null;
+            }
+            pcs.firePropertyChange(PROP_RESOURCES, null, null);
+        }
+
+        @Override
+        public List<? extends PathResourceImplementation> getResources() {
+            List<ClassPathImplementation> delegates;
+
+            synchronized (this) {
+                if (cachedResources != null) {
+                    return cachedResources;
+                }
+
+                delegates = this.delegates;
+            }
+
+            List<PathResourceImplementation> allResources = new ArrayList<>();
+
+            delegates.stream().map(d -> 
d.getResources()).forEach(allResources::addAll);
+
+            allResources = Collections.unmodifiableList(allResources);
+
+            synchronized (this) {
+                if (cachedResources == null && this.delegates == delegates) {
+                    cachedResources = allResources;
+                }
+            }
+
+            return allResources;
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener listener) 
{
+            pcs.addPropertyChangeListener(listener);
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener 
listener) {
+            pcs.removePropertyChangeListener(listener);
+        }
+
+    }
+
+}
diff --git 
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/singlesourcefile/EnablePreviewSingleSourceFile.java
 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/singlesourcefile/EnablePreviewSingleSourceFile.java
new file mode 100644
index 0000000000..562f9fc328
--- /dev/null
+++ 
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/singlesourcefile/EnablePreviewSingleSourceFile.java
@@ -0,0 +1,125 @@
+/*
+ * 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.singlesourcefile;
+
+import com.google.gson.JsonPrimitive;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.eclipse.lsp4j.ConfigurationItem;
+import org.eclipse.lsp4j.ConfigurationParams;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.openide.filesystems.FileObject;
+import org.openide.util.Parameters;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.java.hints.spi.preview.PreviewEnabler;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.modules.java.lsp.server.protocol.UpdateConfigParams;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ * Handle error rule "compiler.err.preview.feature.disabled.plural" and provide
+ * the fix for Single Source Java File.
+ *
+ * @author Arunava Sinha
+ */
+public class EnablePreviewSingleSourceFile implements PreviewEnabler {
+
+    private static final String ENABLE_PREVIEW_FLAG = "--enable-preview";   // 
NOI18N
+    private static final String SOURCE_FLAG = "--source";   // NOI18N
+    private static final Pattern SOURCE_FLAG_PATTERN = 
Pattern.compile(SOURCE_FLAG + "[ \t]+[0-9]+");
+
+    private FileObject file;
+
+    private EnablePreviewSingleSourceFile(@NonNull FileObject file) {
+        Parameters.notNull("file", file); //NOI18N
+        this.file = file;
+    }
+
+    @Override
+    public void enablePreview(String newSourceLevel) throws Exception {
+        NbCodeLanguageClient client = 
Lookup.getDefault().lookup(NbCodeLanguageClient.class);
+        if (client == null) {
+            return ;
+        }
+
+        ConfigurationItem conf = new ConfigurationItem();
+        conf.setScopeUri(Utils.toUri(file));
+        conf.setSection("java+.runConfig.vmOptions"); //XXX
+        client.configuration(new 
ConfigurationParams(Collections.singletonList(conf))).thenApply(c -> {
+            String compilerArgs = ((JsonPrimitive) ((List<Object>) 
c).get(0)).getAsString();
+            if (compilerArgs == null) {
+                compilerArgs = "";
+            }
+
+            Matcher m = SOURCE_FLAG_PATTERN.matcher(compilerArgs);
+            String realNewSourceLevel = newSourceLevel;
+
+            if (realNewSourceLevel == null) {
+                realNewSourceLevel = getJdkRunVersion();
+            }
+
+            if (compilerArgs.contains(SOURCE_FLAG)) {
+                compilerArgs = m.replaceAll(ENABLE_PREVIEW_FLAG + " " + 
SOURCE_FLAG + " " + realNewSourceLevel);
+            } else {
+                compilerArgs += (compilerArgs.isEmpty() ? "" : " ") + 
ENABLE_PREVIEW_FLAG + " " + SOURCE_FLAG + " " + realNewSourceLevel;
+            }
+            client.configurationUpdate(new 
UpdateConfigParams("java+.runConfig", "vmOptions", compilerArgs));
+            return null;
+        });
+    }
+
+    private static String getJdkRunVersion() {
+        String javaVersion = System.getProperty("java.specification.version"); 
//NOI18N 
+        if (javaVersion.startsWith("1.")) { //NOI18N
+            javaVersion = javaVersion.substring(2);
+        }
+
+        return javaVersion;
+    }
+
+    @ServiceProvider(service=Factory.class, position=10_000_000)
+    public static final class FactoryImpl implements Factory {
+
+        @Override
+        public PreviewEnabler enablerFor(FileObject file) {
+            if (file != null) {
+                NbCodeLanguageClient client = 
Lookup.getDefault().lookup(NbCodeLanguageClient.class);
+
+                if (client == null) {
+                    return null;
+                }
+
+                Project prj = FileOwnerQuery.getOwner(file);
+
+                if (prj == null) {
+                    return new EnablePreviewSingleSourceFile(file);
+                }
+            }
+
+            return null;
+        }
+
+    }
+
+}
diff --git 
a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/singlesourcefile/CompilerOptionsQueryImplTest.java
 
b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/singlesourcefile/CompilerOptionsQueryImplTest.java
new file mode 100644
index 0000000000..548aac166a
--- /dev/null
+++ 
b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/singlesourcefile/CompilerOptionsQueryImplTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.singlesourcefile;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import static junit.framework.TestCase.assertEquals;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.classpath.JavaClassPathConstants;
+import org.netbeans.junit.NbTestCase;
+import org.netbeans.modules.java.lsp.server.TestCodeLanguageClient;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.spi.java.queries.CompilerOptionsQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.lookup.Lookups;
+
+public class CompilerOptionsQueryImplTest extends NbTestCase {
+
+    public CompilerOptionsQueryImplTest(String name) {
+        super(name);
+    }
+
+    public void testParseCommandLine1() throws Exception {
+        File wd = getWorkDir();
+        FileObject wdFO = FileUtil.toFileObject(wd);
+        FileObject testFO = FileUtil.createData(wdFO, "Test.java");
+        File testJar = new File(wd, "test.jar");
+        CompilerOptionsQueryImpl query = new CompilerOptionsQueryImpl();
+        NbCodeLanguageClient client = new TestCodeLanguageClient() {
+        };
+
+        query.setConfiguration(client, "-classpath " + 
testJar.getAbsolutePath() + " --module-path " + testJar.getAbsolutePath() + " 
--source 21 --enable-preview");
+
+        AtomicReference<CompilerOptionsQueryImplementation.Result> 
optionsResult = new AtomicReference<>();
+        AtomicInteger optionsResultModificationCount = new AtomicInteger();
+        AtomicReference<ClassPath> compileCP = new AtomicReference<>();
+        AtomicInteger compileCPModificationCount = new AtomicInteger();
+        AtomicReference<ClassPath> compileModuleCP = new AtomicReference<>();
+        AtomicInteger compileModuleCPModificationCount = new AtomicInteger();
+        AtomicReference<SourceLevelQueryImplementation2.Result> 
sourceLevelResult = new AtomicReference<>();
+        AtomicInteger sourceLevelResultModificationCount = new AtomicInteger();
+
+        Lookups.executeWith(Lookups.fixed(client), () -> {
+            optionsResult.set(query.getOptions(testFO));
+            compileCP.set(query.findClassPath(testFO, ClassPath.COMPILE));
+            compileModuleCP.set(query.findClassPath(testFO, 
JavaClassPathConstants.MODULE_COMPILE_PATH));
+            sourceLevelResult.set(query.getSourceLevel(testFO));
+        });
+
+        optionsResult.get().addChangeListener(evt -> 
optionsResultModificationCount.incrementAndGet());
+        assertEquals(Arrays.asList("-classpath", testJar.getAbsolutePath(), 
"--module-path", testJar.getAbsolutePath(), "--source", "21", 
"--enable-preview"),
+                     optionsResult.get().getArguments());
+        assertEquals(0, optionsResultModificationCount.get());
+
+        compileCP.get().addPropertyChangeListener(evt -> 
compileCPModificationCount.incrementAndGet());
+        assertEquals(testJar.getAbsolutePath(),
+                     
compileCP.get().toString(ClassPath.PathConversionMode.PRINT));
+        assertEquals(0, compileCPModificationCount.get());
+
+        compileModuleCP.get().addPropertyChangeListener(evt -> 
compileModuleCPModificationCount.incrementAndGet());
+        assertEquals(testJar.getAbsolutePath(),
+                     
compileModuleCP.get().toString(ClassPath.PathConversionMode.PRINT));
+        assertEquals(0, compileModuleCPModificationCount.get());
+
+        sourceLevelResult.get().addChangeListener(evt -> 
sourceLevelResultModificationCount.incrementAndGet());
+        assertEquals("21",
+                     sourceLevelResult.get().getSourceLevel());
+        assertEquals(0, sourceLevelResultModificationCount.get());
+
+        query.setConfiguration(client, "-cp " + testJar.getAbsolutePath() + " 
-p " + testJar.getAbsolutePath() + " --source 17");
+
+        assertEquals(Arrays.asList("-cp", testJar.getAbsolutePath(), "-p", 
testJar.getAbsolutePath(), "--source", "17"),
+                     optionsResult.get().getArguments());
+        assertEquals(1, optionsResultModificationCount.get());
+
+        assertEquals(testJar.getAbsolutePath(),
+                     
compileCP.get().toString(ClassPath.PathConversionMode.PRINT));
+        assertEquals(2, compileCPModificationCount.get());
+
+        assertEquals(testJar.getAbsolutePath(),
+                     
compileModuleCP.get().toString(ClassPath.PathConversionMode.PRINT));
+        assertEquals(2, compileModuleCPModificationCount.get());
+
+        assertEquals("17",
+                     sourceLevelResult.get().getSourceLevel());
+        assertEquals(1, sourceLevelResultModificationCount.get());
+    }
+
+}
diff --git a/java/java.lsp.server/vscode/src/extension.ts 
b/java/java.lsp.server/vscode/src/extension.ts
index df9803109d..8ce5fe55b4 100644
--- a/java/java.lsp.server/vscode/src/extension.ts
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -942,7 +942,8 @@ function doActivateWithJDK(specifiedJDK: string | null, 
context: ExtensionContex
             synchronize: {
                 configurationSection: [
                     'netbeans.format',
-                    'netbeans.java.imports'
+                    'netbeans.java.imports',
+                    'java+.runConfig.vmOptions'
                 ],
                 fileEvents: [
                     workspace.createFileSystemWatcher('**/*.java')
diff --git a/java/java.lsp.server/vscode/src/runConfiguration.ts 
b/java/java.lsp.server/vscode/src/runConfiguration.ts
index e6b148ae93..e8f1e44f37 100644
--- a/java/java.lsp.server/vscode/src/runConfiguration.ts
+++ b/java/java.lsp.server/vscode/src/runConfiguration.ts
@@ -23,14 +23,7 @@ import { homedir } from 'os';
 export async function initializeRunConfiguration(): Promise<boolean> {
        const java = await vscode.workspace.findFiles('**/*.java', 
'**/node_modules/**', 1);
        if (java?.length > 0) {
-               const maven = await vscode.workspace.findFiles('pom.xml', 
'**/node_modules/**', 1);
-               if (maven?.length > 0) {
-                       return true;
-               }
-               const gradle = await vscode.workspace.findFiles('build.gradle', 
'**/node_modules/**', 1);
-               if (gradle?.length > 0) {
-                       return true;
-               }
+            return true;
        }
        return false;
 }
diff --git 
a/java/java.source.base/src/org/netbeans/modules/java/source/parsing/Hacks.java 
b/java/java.source.base/src/org/netbeans/modules/java/source/parsing/Hacks.java
index 0b69a1d310..f187aa339a 100644
--- 
a/java/java.source.base/src/org/netbeans/modules/java/source/parsing/Hacks.java
+++ 
b/java/java.source.base/src/org/netbeans/modules/java/source/parsing/Hacks.java
@@ -20,8 +20,11 @@ package org.netbeans.modules.java.source.parsing;
 
 import com.sun.tools.javac.util.JCDiagnostic;
 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.tools.Diagnostic;
 import 
org.netbeans.modules.java.source.parsing.CompilationInfoImpl.RichDiagnostic;
 
@@ -30,6 +33,8 @@ import 
org.netbeans.modules.java.source.parsing.CompilationInfoImpl.RichDiagnost
  * @author lahvac
  */
 public class Hacks {
+    private static final Logger LOG = Logger.getLogger(Hacks.class.getName());
+
     public static boolean isSyntaxError(Diagnostic<?> d) {
         JCDiagnostic jcd = getJCDiagnostic(d);
         if (jcd == null) {
@@ -70,6 +75,14 @@ public class Hacks {
             return ((JCDiagnostic)d);
         } else if (d instanceof RichDiagnostic && ((RichDiagnostic) 
d).getDelegate() instanceof JCDiagnostic) {
             return (JCDiagnostic)((RichDiagnostic)d).getDelegate();
+        } else if 
("org.netbeans.modules.java.source.parsing.CompilationInfoImpl$DiagnosticListenerImpl$D".equals(d.getClass().getName()))
 {
+            try {
+                Field delegate = d.getClass().getDeclaredField("delegate");
+                delegate.setAccessible(true);
+                return getJCDiagnostic((Diagnostic<?>) delegate.get(d));
+            } catch (Exception ex) {
+                LOG.log(Level.FINE, null, ex);
+            }
         }
         return null;
     }


---------------------------------------------------------------------
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