Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Roots (and directories) containing multi-file launcher sources should respond to ClassPath queries. #7733

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,20 +91,41 @@ 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<>();

static boolean isSupportedFile(FileObject file) {
return SingleSourceFileUtil.isSingleSourceFile(file)
// MultiSourceRootProvider assumes it can convert FileObject to
// java.io.File, so filter here
&& Objects.equals("file", file.toURI().getScheme());
boolean isSupportedFile(FileObject file) {
// MultiSourceRootProvider assumes it can convert FileObject to
// java.io.File, so filter here
if (!Objects.equals("file", file.toURI().getScheme())) {
return false;
}

if (SingleSourceFileUtil.isSingleSourceFile(file)) {
return true;
}

Set<FileObject> registeredRootsCopy;

synchronized (registeredRoots) {
registeredRootsCopy = registeredRoots;
}

for (FileObject existingRoot : registeredRootsCopy) {
if (file.equals(existingRoot) || FileUtil.isParentOf(existingRoot, file)) {
return true;
}
}

return false;
}

@Override
public ClassPath findClassPath(FileObject file, String type) {
if (! isSupportedFile(file)) {
if (!isSupportedFile(file)) {
return null;
}
switch (type) {
Expand Down Expand Up @@ -148,13 +170,17 @@ private ClassPath getSourcePath(FileObject file) {
}
}

return root2SourceCP.computeIfAbsent(root, r -> {
ClassPath srcCP = ClassPathSupport.createClassPath(Arrays.asList(new RootPathResourceImplementation(r)));
if (registerRoot(r)) {
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 @@ -269,13 +295,6 @@ private ClassPath attributeBasedPath(FileObject file, Map<FileObject, ClassPath>
}
}

@Messages({
"SETTING_AutoRegisterAsRoot=false"
})
private static boolean registerRoot(FileObject root) {
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 @@ -355,7 +374,10 @@ private void doUpdateDelegates() {
for (File expanded : expandedPaths) {
URL u = FileUtil.urlForArchiveOrDir(expanded);
if (u == null) {
throw new IllegalArgumentException("Path entry looks to be invalid: " + piece); // NOI18N
LOG.log(Level.INFO,
"While parsing command line option '{0}' with parameter '{1}', path entry looks to be invalid: '{2}'",
new Object[] {currentOption, parsed.get(i + 1), piece});
continue;
}
newURLs.add(u);
newDelegates.add(ClassPathSupport.createResource(u));
Expand Down Expand Up @@ -468,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
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,4 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

SETTING_AutoRegisterAsRoot=true
Loading
Loading