Skip to content

Commit

Permalink
Adding user-level option to enable multi-source launcher in the NetBe…
Browse files Browse the repository at this point in the history
…ans IDE
  • Loading branch information
lahodaj committed Sep 10, 2024
1 parent ed150ca commit ef4a2e5
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@
import org.openide.filesystems.FileUtil;
import org.openide.util.ChangeSupport;
import org.openide.util.Lookup;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service=SingleFileOptionsQueryImplementation.class)
public class AttributeBasedSingleFileOptions implements SingleFileOptionsQueryImplementation {

private static final RequestProcessor WORKER = new RequestProcessor(AttributeBasedSingleFileOptions.class.getName(), 1, false, false);

@Override
public Result optionsFor(FileObject file) {
if (!SingleSourceFileUtil.isSupportedFile(file)) {
Expand Down Expand Up @@ -66,6 +69,15 @@ private static final class ResultImpl implements Result {
private final FileChangeListener attributeChanges = new FileChangeAdapter() {
@Override
public void fileAttributeChanged(FileAttributeEvent fe) {
if (root != null && registerRoot()) {
//propagation of flags from files to the root is usually only
//started when the root is indexed. And when the registerRoot
//flag is flipped to true on a file in a non-indexed root,
//there's no other mechanism to propagate the flag to the root.
//So, when the flag is set to true on a file, force the propagation
//of the flags for the given root:
WORKER.post(() -> SharedRootData.ensureRootRegistered(root));
}
cs.fireChange();
}
};
Expand Down Expand Up @@ -100,6 +112,14 @@ public URI getWorkDirectory() {
return root != null ? root.toURI() : source.getParent().toURI();
}

@Override
public boolean registerRoot() {
Object value = source != null ? source.getAttribute(SingleSourceFileUtil.FILE_REGISTER_ROOT)
: root != null ? root.getAttribute(SingleSourceFileUtil.FILE_REGISTER_ROOT)
: null;
return SingleSourceFileUtil.isTrue(value);
}

@Override
public void addChangeListener(ChangeListener listener) {
cs.addChangeListener(listener);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.modules.java.file.launcher.api.SourceLauncher;
import org.openide.filesystems.FileAttributeEvent;
Expand All @@ -45,26 +47,32 @@ public class SharedRootData {
private static final Map<FileObject, SharedRootData> root2Data = new HashMap<>();

public static synchronized void ensureRootRegistered(FileObject root) {
root2Data.computeIfAbsent(root, r -> new SharedRootData(r));
if (root2Data.get(root) != null) {
return ;
}

SharedRootData data = root2Data.computeIfAbsent(root, r -> new SharedRootData(r));

data.init();
}

public static synchronized @CheckForNull SharedRootData getDataForRoot(FileObject root) {
return root2Data.get(root);
}

private final FileObject root;
private final Map<String, String> options = new TreeMap<>();
private final Map<String, FileProperties> properties = new TreeMap<>();
private final FileChangeListener listener = new FileChangeAdapter() {
@Override
public void fileAttributeChanged(FileAttributeEvent fe) {
Map<String, String> newProperties = new HashMap<>();
Map<String, FileProperties> newProperties = new HashMap<>();

addPropertiesFor(fe.getFile(), newProperties);
setNewProperties(newProperties);
}
@Override
public void fileDeleted(FileEvent fe) {
Map<String, String> newProperties = new HashMap<>();
Map<String, FileProperties> newProperties = new HashMap<>();

newProperties.put(FileUtil.getRelativePath(root, fe.getFile()), null);
setNewProperties(newProperties);
Expand All @@ -73,42 +81,67 @@ public void fileDeleted(FileEvent fe) {

private SharedRootData(FileObject root) {
this.root = root;
}

private void init() {
root.addRecursiveListener(listener);
Enumeration<? extends FileObject> todo = root.getChildren(true);
Map<String, String> newProperties = new HashMap<>();
Map<String, FileProperties> newProperties = new HashMap<>();
while (todo.hasMoreElements()) {
FileObject current = todo.nextElement();
addPropertiesFor(current, newProperties);
}
setNewProperties(newProperties);
}

private void addPropertiesFor(FileObject file, Map<String, String> newProperties) {
private void addPropertiesFor(FileObject file, Map<String, FileProperties> newProperties) {
if (file.isData() && "text/x-java".equals(file.getMIMEType())) {
newProperties.put(FileUtil.getRelativePath(root, file), (String) file.getAttribute(SingleSourceFileUtil.FILE_VM_OPTIONS));
newProperties.put(FileUtil.getRelativePath(root, file), new FileProperties((String) file.getAttribute(SingleSourceFileUtil.FILE_VM_OPTIONS),
SingleSourceFileUtil.isTrue(file.getAttribute(SingleSourceFileUtil.FILE_REGISTER_ROOT))));
}
}

private synchronized void setNewProperties(Map<String, String> newProperties) {
private synchronized void setNewProperties(Map<String, FileProperties> newProperties) {
if (newProperties.isEmpty()) {
return ;
}
for (String key : newProperties.keySet()) {
String value = newProperties.get(key);
if (value == null) {
options.remove(key);
FileProperties fileProperties = newProperties.get(key);
if (fileProperties == null) {
properties.remove(key);
} else {
options.put(key, value);
properties.put(key, fileProperties);
}
}
String joinedCommandLine = SourceLauncher.joinCommandLines(options.values());

List<String> vmOptions = properties.values()
.stream()
.map(p -> p.vmOptions)
.filter(p -> p != null)
.collect(Collectors.toList());
String joinedCommandLine = SourceLauncher.joinCommandLines(vmOptions);
try {
if (!joinedCommandLine.equals(root.getAttribute(SingleSourceFileUtil.FILE_VM_OPTIONS))) {
root.setAttribute(SingleSourceFileUtil.FILE_VM_OPTIONS, joinedCommandLine);
}
} catch (IOException ex) {
LOG.log(Level.INFO, "Failed to set " + SingleSourceFileUtil.FILE_VM_OPTIONS + " for " + root.getPath(), ex);
}
Boolean registerRoot = properties.values()
.stream()
.map(p -> p.registerRoot)
.filter(r -> r)
.findAny()
.isPresent();
try {
if (!registerRoot.equals(root.getAttribute(SingleSourceFileUtil.FILE_REGISTER_ROOT))) {
root.setAttribute(SingleSourceFileUtil.FILE_REGISTER_ROOT, registerRoot);
}
} catch (IOException ex) {
LOG.log(Level.INFO, "Failed to set " + SingleSourceFileUtil.FILE_REGISTER_ROOT + " for " + root.getPath(), ex);
}
}

record FileProperties(String vmOptions, boolean registerRoot) {}

}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public static int findJavaVersion() throws NumberFormatException {
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 final String FILE_REGISTER_ROOT = "register_root"; //NOI18N

public static FileObject getJavaFileWithoutProjectFromLookup(Lookup lookup) {
for (DataObject dObj : lookup.lookupAll(DataObject.class)) {
Expand Down Expand Up @@ -153,6 +154,10 @@ public static List<String> parseLine(String line, URI workingDirectory) {
return PARSER.doParse(line, workingDirectory);
}

public static boolean isTrue(Object value) {
return value instanceof Boolean b && b;
}

private static final LineParser PARSER = new LineParser();

private static class LineParser extends CompilerOptionsQueryImplementation.Result {
Expand Down Expand Up @@ -217,6 +222,10 @@ public URI getWorkDirectory() {
return delegate.getWorkDirectory();
}

public boolean registerRoot() {
return delegate.registerRoot();
}

@Override
public void addChangeListener(ChangeListener listener) {
cs.addChangeListener(listener);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -90,6 +91,8 @@ public class MultiSourceRootProvider implements ClassPathProvider {
//TODO: the cache will probably be never cleared, as the ClassPath/value refers to the key(?)
private Map<FileObject, ClassPath> file2SourceCP = new WeakHashMap<>();
private Map<FileObject, ClassPath> root2SourceCP = new WeakHashMap<>();
private Map<FileObject, Runnable> root2RegistrationRefresh = new WeakHashMap<>();
private final Set<FileObject> registeredRoots = Collections.newSetFromMap(new WeakHashMap<>());
private Map<FileObject, ClassPath> file2AllPath = new WeakHashMap<>();
private Map<FileObject, ClassPath> file2ClassPath = new WeakHashMap<>();
private Map<FileObject, ClassPath> file2ModulePath = new WeakHashMap<>();
Expand All @@ -105,11 +108,13 @@ boolean isSupportedFile(FileObject file) {
return true;
}

if (!registerRoots()) {
return false;
Set<FileObject> registeredRootsCopy;

synchronized (registeredRoots) {
registeredRootsCopy = registeredRoots;
}

for (FileObject existingRoot : root2SourceCP.keySet()) {
for (FileObject existingRoot : registeredRootsCopy) {
if (file.equals(existingRoot) || FileUtil.isParentOf(existingRoot, file)) {
return true;
}
Expand Down Expand Up @@ -165,13 +170,17 @@ private ClassPath getSourcePath(FileObject file) {
}
}

return root2SourceCP.computeIfAbsent(root, r -> {
ClassPath srcCP = ClassPathSupport.createClassPath(Arrays.asList(new RootPathResourceImplementation(r)));
if (registerRoots()) {
GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {srcCP});
}
return srcCP;
ClassPath srcCP = root2SourceCP.computeIfAbsent(root, r -> {
return ClassPathSupport.createClassPath(Arrays.asList(new RootPathResourceImplementation(r)));
});

ParsedFileOptions options = SingleSourceFileUtil.getOptionsFor(root);

if (options != null) {
WORKER.post(root2RegistrationRefresh.computeIfAbsent(root, r -> new RegistrationRefresh(srcCP, options, r)));
}

return srcCP;
} catch (IOException ex) {
LOG.log(Level.FINE, "Failed to read sourcefile " + file, ex);
}
Expand Down Expand Up @@ -286,13 +295,6 @@ private ClassPath attributeBasedPath(FileObject file, Map<FileObject, ClassPath>
}
}

@Messages({
"SETTING_AutoRegisterAsRoot=false"
})
private static boolean registerRoots() {
return "true".equals(Bundle.SETTING_AutoRegisterAsRoot());
}

private static final class AttributeBasedClassPathImplementation extends FileChangeAdapter implements ChangeListener, ClassPathImplementation {
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private final Task updateDelegatesTask = WORKER.create(this::doUpdateDelegates);
Expand Down Expand Up @@ -488,4 +490,43 @@ public void removePropertyChangeListener(PropertyChangeListener listener) {
}

}

private class RegistrationRefresh implements ChangeListener, Runnable {
private final ClassPath srcCP;
private final ParsedFileOptions options;
private final FileObject root;

public RegistrationRefresh(ClassPath srcCP,
ParsedFileOptions options,
FileObject root) {
this.srcCP = srcCP;
this.options = options;
this.root = root;
options.addChangeListener(this);
}

@Override
public void run() {
GlobalPathRegistry registry = GlobalPathRegistry.getDefault();
if (options.registerRoot()) {
synchronized (registeredRoots) {
registeredRoots.add(root);
}
registry.register(ClassPath.SOURCE, new ClassPath[] {srcCP});
} else {
synchronized (registeredRoots) {
registeredRoots.remove(root);
}
if (registry.getPaths(ClassPath.SOURCE).contains(srcCP)) {
registry.unregister(ClassPath.SOURCE, new ClassPath[] {srcCP});
}
}
}

@Override
public void stateChanged(ChangeEvent e) {
WORKER.post(this);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ public interface SingleFileOptionsQueryImplementation {
public Result optionsFor(FileObject file);

public interface Result {
public String getOptions();
public @NonNull String getOptions();
public default @NonNull URI getWorkDirectory() {
throw new UnsupportedOperationException();
}
public default boolean registerRoot() {
return false;
}
public void addChangeListener(ChangeListener l);
public void removeChangeListener(ChangeListener l);
}
Expand Down
Loading

0 comments on commit ef4a2e5

Please sign in to comment.