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 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 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 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) 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 000000000000..548aac166a91 --- /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 optionsResult = new AtomicReference<>(); + AtomicInteger optionsResultModificationCount = new AtomicInteger(); + AtomicReference compileCP = new AtomicReference<>(); + AtomicInteger compileCPModificationCount = new AtomicInteger(); + AtomicReference compileModuleCP = new AtomicReference<>(); + AtomicInteger compileModuleCPModificationCount = new AtomicInteger(); + AtomicReference 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 df9803109d25..8ce5fe55b4da 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 e6b148ae93f4..e8f1e44f3785 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 { 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 0b69a1d31047..f187aa339a7e 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 @@ 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 @@ * @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 @@ private static JCDiagnostic getJCDiagnostic(Diagnostic d) { 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; }