diff --git a/java/java.api.common/nbproject/project.xml b/java/java.api.common/nbproject/project.xml
index 1556766ea37a..46ddf51403fe 100644
--- a/java/java.api.common/nbproject/project.xml
+++ b/java/java.api.common/nbproject/project.xml
@@ -137,6 +137,15 @@
1.61
+
+ org.netbeans.modules.extexecution.base
+
+
+
+ 2
+ 1.27
+
+
org.netbeans.modules.java.platform
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 2d080b64a9f2..4a574834c794 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.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 {
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 @@ private Process setupProcess(String port) throws InterruptedException {
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 @@ private Process setupProcess(String port) throws InterruptedException {
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 @@ private Process setupProcess(String port) throws InterruptedException {
}
return null;
}
+
+ private static List 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 e245e8e13ae6..ccbddcecd642 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 @@
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 void invokeAction(String command, Lookup context) throws IllegalArgumentE
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 void invokeAction(String command, Lookup context) throws IllegalArgumentE
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 boolean isActionEnabled(String command, Lookup context) throws IllegalArg
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 1c92ace3f22f..2c616b2b9504 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.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 void testSingleJavaSourceRun() throws Exception {
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 0ad7fe7c2f48..b4f921e9dfb9 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 void enablePreview(String newSourceLevel) throws Exception {
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 a756a2e9bd72..9e5c5e69c40c 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 @@ private static List convertFixes(ErrorDescription err, Consumer 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 bcdfc9322145..dd6d9d723afd 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 dc9b090e59f4..628d3d86d2dc 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.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 void didChangeConfiguration(DidChangeConfigurationParams params) {
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 000000000000..487b8557dad1
--- /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 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 args = Collections.emptyList();
+
+ public List doParse(String line) {
+ return parseLine(line);
+ }
+
+ private void setArguments(List 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 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 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 spec2CP(String spec) {
+ List 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 delegates = Collections.emptyList();
+ private List extends PathResourceImplementation> cachedResources = null;
+
+ public void setDelegates(List delegates) {
+ synchronized (delegates) {
+ this.delegates = new ArrayList<>(delegates);
+ this.cachedResources = null;
+ }
+ pcs.firePropertyChange(PROP_RESOURCES, null, null);
+ }
+
+ @Override
+ public List extends PathResourceImplementation> getResources() {
+ List delegates;
+
+ synchronized (this) {
+ if (cachedResources != null) {
+ return cachedResources;
+ }
+
+ delegates = this.delegates;
+ }
+
+ List 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 000000000000..562f9fc32824
--- /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