diff --git a/java/java.file.launcher/nbproject/project.properties b/java/java.file.launcher/nbproject/project.properties index 2699b073bc7c..5738f2b3880a 100644 --- a/java/java.file.launcher/nbproject/project.properties +++ b/java/java.file.launcher/nbproject/project.properties @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -javac.source=1.8 +javac.release=17 cp.extra=${tools.jar} requires.nb.javac=true javac.compilerargs=-Xlint -Xlint:-serial diff --git a/java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/SingleSourceFileUtil.java b/java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/SingleSourceFileUtil.java index 8ca36068a060..0152f195164d 100644 --- a/java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/SingleSourceFileUtil.java +++ b/java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/SingleSourceFileUtil.java @@ -30,7 +30,7 @@ import java.util.logging.Logger; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.api.java.platform.JavaPlatform; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.modules.java.file.launcher.queries.MultiSourceRootProvider; import org.netbeans.modules.java.file.launcher.spi.SingleFileOptionsQueryImplementation; @@ -49,6 +49,7 @@ public final class SingleSourceFileUtil { public static final Logger LOG = Logger.getLogger(SingleSourceFileUtil.class.getPackage().getName()); + // TODO this checks the runtime JDK of NB! public static int findJavaVersion() throws NumberFormatException { // JEP-330 is supported only on JDK-11 and above. String javaVersion = System.getProperty("java.specification.version"); //NOI18N @@ -59,7 +60,9 @@ public static int findJavaVersion() throws NumberFormatException { return version; } + // synced with JavaNode public static final String FILE_ARGUMENTS = "single_file_run_arguments"; //NOI18N + public static final String FILE_JDK = "single_file_run_jdk"; //NOI18N public static final String FILE_VM_OPTIONS = "single_file_vm_options"; //NOI18N public static FileObject getJavaFileWithoutProjectFromLookup(Lookup lookup) { @@ -101,8 +104,8 @@ public static boolean isSupportedFile(FileObject file) { return false; } } - public static Process compileJavaSource(FileObject fileObject) { - FileObject javac = JavaPlatformManager.getDefault().getDefaultPlatform().findTool("javac"); //NOI18N + public static Process compileJavaSource(FileObject fileObject, JavaPlatform jdk) { + FileObject javac = jdk.findTool("javac"); //NOI18N File javacFile = FileUtil.toFile(javac); String javacPath = javacFile.getAbsolutePath(); List compileCommandList = new ArrayList<>(); diff --git a/java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/actions/LaunchProcess.java b/java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/actions/LaunchProcess.java index 51e6b9182f5a..befa4cf8c89f 100644 --- a/java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/actions/LaunchProcess.java +++ b/java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/actions/LaunchProcess.java @@ -26,7 +26,9 @@ import java.util.List; import java.util.concurrent.Callable; import java.util.logging.Level; +import java.util.logging.Logger; import org.netbeans.api.extexecution.base.ExplicitProcessParameters; +import org.netbeans.api.java.platform.JavaPlatform; import org.netbeans.api.java.platform.JavaPlatformManager; import org.netbeans.modules.java.file.launcher.SingleSourceFileUtil; import org.openide.filesystems.FileObject; @@ -59,8 +61,10 @@ private Process setupProcess(String port) throws InterruptedException { try { boolean compile = SingleSourceFileUtil.findJavaVersion() < 11 || SingleSourceFileUtil.hasClassSibling(fileObject); + JavaPlatform jdk = readRunJdkFromAttribute(fileObject); + if (compile) { - Process p = SingleSourceFileUtil.compileJavaSource(fileObject); + Process p = SingleSourceFileUtil.compileJavaSource(fileObject, jdk); if (p.waitFor() != 0) { return p; } @@ -68,7 +72,7 @@ private Process setupProcess(String port) throws InterruptedException { List commandsList = new ArrayList<>(); - FileObject java = JavaPlatformManager.getDefault().getDefaultPlatform().findTool("java"); //NOI18N + FileObject java = jdk.findTool("java"); //NOI18N File javaFile = FileUtil.toFile(java); String javaPath = javaFile.getAbsolutePath(); URI cwd = SingleSourceFileUtil.getOptionsFor(fileObject).getWorkDirectory(); @@ -123,6 +127,19 @@ private Process setupProcess(String port) throws InterruptedException { return null; } + private static JavaPlatform readRunJdkFromAttribute(FileObject fo) { + String runJDKAttribute = fo.getAttribute(SingleSourceFileUtil.FILE_JDK) instanceof String str ? str : null; + if (runJDKAttribute != null && !runJDKAttribute.isBlank()) { + for (JavaPlatform jdk : JavaPlatformManager.getDefault().getInstalledPlatforms()) { + if (runJDKAttribute.equals(jdk.getDisplayName())) { + return jdk; + } + } + Logger.getLogger(LaunchProcess.class.getName()).log(Level.WARNING, "Unknown JDK: [{0}]", runJDKAttribute); + } + return JavaPlatformManager.getDefault().getDefaultPlatform(); + } + private static List readArgumentsFromAttribute(FileObject fileObject, String attributeName) { Object argumentsObject = fileObject.getAttribute(attributeName); if (!(argumentsObject instanceof String)) { diff --git a/java/java.source/src/org/netbeans/modules/java/Bundle.properties b/java/java.source/src/org/netbeans/modules/java/Bundle.properties index ab7092de7c0a..74949b6a2eb2 100644 --- a/java/java.source/src/org/netbeans/modules/java/Bundle.properties +++ b/java/java.source/src/org/netbeans/modules/java/Bundle.properties @@ -31,6 +31,12 @@ LBL_JavaNode_sheet_classpaths=Classpaths HINT_JavaNode_sheet_classpaths=Effective classpaths used by the IDE for this source file, for example as reported by a project. Sources displayed where applicable. LBL_JavaNode_sheet_classfile=Class File HINT_JavaNode_sheet_classfile=Java Class File Attributes +PROP_JavaNode_singlefile_jdk=JDK +HINT_JavaNode_singlefile_jdk=JDK used to run the file. +PROP_JavaNode_singlefile_arguments=Program Arguments +HINT_JavaNode_singlefile_arguments=Arguments passed to the main method while running the file. +PROP_JavaNode_singlefile_options=VM Options +HINT_JavaNode_singlefile_options=VM Options to be considered while running the file. PROP_JavaNode_classfile_version=Classfile Version HINT_JavaNode_classfile_version=The Java API and Language Level of this class file PROP_JavaNode_compile_classpath=Compile Classpath diff --git a/java/java.source/src/org/netbeans/modules/java/JavaNode.java b/java/java.source/src/org/netbeans/modules/java/JavaNode.java index 470a80a2119f..6dec93234e29 100644 --- a/java/java.source/src/org/netbeans/modules/java/JavaNode.java +++ b/java/java.source/src/org/netbeans/modules/java/JavaNode.java @@ -21,8 +21,12 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; +import java.awt.Component; import org.netbeans.api.java.source.support.ErrorAwareTreeScanner; import java.awt.Image; +import java.beans.PropertyChangeListener; +import java.beans.PropertyEditor; +import java.beans.PropertyEditorSupport; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -37,24 +41,27 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.HexFormat; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Stream; import javax.lang.model.element.Modifier; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.annotations.common.NullAllowed; import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.platform.JavaPlatformManager; import org.netbeans.api.java.source.CompilationController; import org.netbeans.api.java.source.JavaSource; -import org.netbeans.api.java.source.ModificationResult; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.api.queries.FileBuiltQuery; @@ -63,14 +70,11 @@ import org.netbeans.modules.classfile.ClassFile; import org.netbeans.modules.classfile.InvalidClassFormatException; import org.netbeans.modules.java.source.usages.ExecutableFilesIndex; -import org.netbeans.modules.parsing.api.ResultIterator; -import org.netbeans.modules.parsing.api.Source; -import org.netbeans.modules.parsing.api.UserTask; -import org.netbeans.modules.parsing.spi.ParseException; import org.netbeans.spi.java.loaders.RenameHandler; import org.openide.filesystems.*; import org.openide.loaders.DataNode; import org.openide.loaders.DataObject; +import org.openide.modules.SpecificationVersion; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.PropertySupport; @@ -103,7 +107,9 @@ public final class JavaNode extends DataNode implements ChangeListener { private static final String ANNOTATION_ICON_BASE = "org/netbeans/modules/java/resources/annotation_file.png"; //NOI18N private static final String EXECUTABLE_BADGE_URL = "org/netbeans/modules/java/resources/executable-badge.png"; //NOI18N private static final String NEEDS_COMPILE_BADGE_URL = "org/netbeans/modules/java/resources/needs-compile.png"; //NOI18N + // synced with org.netbeans.modules.java.file.launcher.SingleSourceFileUtil private static final String FILE_ARGUMENTS = "single_file_run_arguments"; //NOI18N + private static final String FILE_JDK = "single_file_run_jdk"; //NOI18N private static final String FILE_VM_OPTIONS = "single_file_vm_options"; //NOI18N private static final Map IMAGE_CACHE = new ConcurrentHashMap<>(); @@ -232,72 +238,16 @@ protected final Sheet createSheet () { DataObject dObj = super.getDataObject(); // If any of the parent folders is a project, user won't have the option to specify these attributes to the java files. if (parentProject == null) { - Node.Property arguments = new PropertySupport.ReadWrite ( - "runFileArguments", // NOI18N - String.class, - "Arguments", - "Arguments passed to the main method while running the file." - ) { - @Override - public String getValue () { - Object arguments = dObj.getPrimaryFile().getAttribute(FILE_ARGUMENTS); - return arguments != null ? (String) arguments : ""; - } - - @Override - public void setValue (String o) { - try { - dObj.getPrimaryFile().setAttribute(FILE_ARGUMENTS, o); - } catch (IOException ex) { - LOG.log( - Level.WARNING, - "Java File does not exist : {0}", //NOI18N - dObj.getPrimaryFile().getName()); - } - } - }; - Node.Property vmOptions = new PropertySupport.ReadWrite ( - "runFileVMOptions", // NOI18N - String.class, - "VM Options", - "VM Options to be considered while running the file." - ) { - @Override - public String getValue () { - Object vmOptions = dObj.getPrimaryFile().getAttribute(FILE_VM_OPTIONS); - return vmOptions != null ? (String) vmOptions : ""; - } - - @Override - public void setValue(String o) { - try { - dObj.getPrimaryFile().setAttribute(FILE_VM_OPTIONS, o); - Source s = Source.create(dObj.getPrimaryFile()); - ModificationResult result = ModificationResult.runModificationTask(List.of(s), new UserTask() { - - @Override - public void run(ResultIterator resultIterator) { - } - }); - result.commit(); - } catch (IOException | ParseException ex) { - LOG.log( - Level.WARNING, - "Java File does not exist : {0}", //NOI18N - dObj.getPrimaryFile().getName()); - } - } - }; Sheet.Set ss = new Sheet.Set(); ss.setName("runFileArguments"); // NOI18N - ss.setDisplayName(getMessage(JavaNode.class, "LBL_JavaNode_without_project_run")); + ss.setDisplayName(getMessage(JavaNode.class, "LBL_JavaNode_without_project_run")); // NOI18N ss.setShortDescription("Run the file's source code."); - ss.put (arguments); - ss.put (vmOptions); + ss.put(new RunFileJDKProperty(dObj)); + ss.put(new JavaFileAttributeProperty(dObj, FILE_ARGUMENTS, "runFileArguments", "singlefile_arguments")); // NOI18N + ss.put(new JavaFileAttributeProperty(dObj, FILE_VM_OPTIONS, "runFileVMOptions", "singlefile_options")); // NOI18N sheet.put(ss); } - - + @SuppressWarnings("LocalVariableHidesMemberVariable") PropertySet[] propertySets = sheet.toArray(); @@ -429,6 +379,120 @@ public String getValue() { } } + private static final class RunFileJDKProperty extends PropertySupport.ReadWrite { + + private final PropertyEditorSupport jdkPicker = new JDKPicker(); + private final DataObject dObj; + + public RunFileJDKProperty(DataObject dObj) { + super("runFileJDK", String.class, // NOI18N + getMessage(JavaNode.class, "PROP_JavaNode_singlefile_jdk"), // NOI18N + getMessage(JavaNode.class, "HINT_JavaNode_singlefile_jdk")); // NOI18N + this.dObj = dObj; + } + + @Override + public String getValue() { + return dObj.getPrimaryFile().getAttribute(FILE_JDK) instanceof String jdk ? jdk : ""; + } + + @Override + public void setValue(String text) { + try { + dObj.getPrimaryFile().setAttribute(FILE_JDK, text); + } catch (IOException ex) { + LOG.log(Level.WARNING, "Java File does not exist : {0}", dObj.getPrimaryFile().getName()); //NOI18N + } + } + + @Override + public PropertyEditor getPropertyEditor() { + return jdkPicker; + } + + // inline combobox for jdk property + private static final class JDKPicker extends PropertyEditorSupport { + + private final JComboBox jdkCombo = new JComboBox(); + private final PropertyChangeListener modelUpdater = e -> initModel(); + private String[] jdks; + + public JDKPicker() { + initModel(); + jdkCombo.addActionListener(e -> super.setValue(jdkCombo.getSelectedItem())); + JavaPlatformManager jdkman = JavaPlatformManager.getDefault(); + jdkman.addPropertyChangeListener(WeakListeners.propertyChange(modelUpdater, jdkman)); + } + + private void initModel() { + jdks = Stream.of(JavaPlatformManager.getDefault().getInstalledPlatforms()) + .filter(jdk -> isCompatible(jdk.getSpecification().getVersion())) + .map(jdk -> jdk.getDisplayName()) + .sorted(Comparator.reverseOrder()) + .toArray(String[]::new); + jdkCombo.setModel(new DefaultComboBoxModel(jdks)); + } + + private static boolean isCompatible(SpecificationVersion jdkVerison) { + try { + return Integer.parseInt(jdkVerison.toString()) >= 11; + } catch (NumberFormatException ignore) {} + return false; + } + + @Override + public void setAsText(String text) throws IllegalArgumentException { + jdkCombo.setSelectedItem(text); + } + + @Override + public void setValue(Object value) { + jdkCombo.setSelectedItem(value); + super.setValue(value); + } + + @Override + @SuppressWarnings("ReturnOfCollectionOrArrayField") + public String[] getTags() { + return jdks; + } + + @Override + public Component getCustomEditor() { + return jdkCombo; + } + + } + + } + + // editable file attribute + private static final class JavaFileAttributeProperty extends PropertySupport.ReadWrite { + + private final String attribute; + private final DataObject dObj; + + public JavaFileAttributeProperty(DataObject dObj, String attribute, String name, String msgKeyPart) { + super(name, String.class, getMessage(JavaNode.class, "PROP_JavaNode_" + msgKeyPart), getMessage(JavaNode.class, "HINT_JavaNode_" + msgKeyPart)); // NOI18N + this.dObj = dObj; + this.attribute = attribute; + } + + @Override + public String getValue() { + return dObj.getPrimaryFile().getAttribute(attribute) instanceof String val ? val : ""; + } + + @Override + public void setValue(String o) { + try { + dObj.getPrimaryFile().setAttribute(attribute, o); + } catch (IOException ex) { + LOG.log(Level.WARNING, "Java File does not exist : {0}", dObj.getPrimaryFile().getName()); //NOI18N + } + } + } + @Override public void stateChanged(ChangeEvent e) { WORKER.post(new BuildStatusTask(this));