sdedic commented on code in PR #6329: URL: https://github.com/apache/netbeans/pull/6329#discussion_r1298270797
########## java/java.api.common/src/org/netbeans/modules/java/api/common/queries/GenericModuleInfoAccessibilityQuery.java: ########## @@ -0,0 +1,234 @@ +/* + * 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.api.common.queries; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.DirectiveTree; +import com.sun.source.tree.ExportsTree; +import com.sun.source.tree.ModuleTree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.util.JavacTask; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.net.URI; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.queries.AccessibilityQuery; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation2; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileObject; +import org.openide.util.ChangeSupport; +import org.openide.util.RequestProcessor; +import org.openide.util.RequestProcessor.Task; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service=AccessibilityQueryImplementation2.class) +public class GenericModuleInfoAccessibilityQuery implements AccessibilityQueryImplementation2 { + + private final Map<ClassPath, ClassPathListener> sourcePath2Listener = new WeakHashMap<>(); + private final Map<FileObject, Result> path2Result = new WeakHashMap<>(); + + @Override + public Result isPubliclyAccessible(FileObject folder) { + return path2Result.computeIfAbsent(folder, f -> { Review Comment: is it OK, if more CPListeners is created for a single file (the coIA is not synchronized) ? ########## java/java.api.common/src/org/netbeans/modules/java/api/common/queries/GenericModuleInfoAccessibilityQuery.java: ########## @@ -0,0 +1,234 @@ +/* + * 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.api.common.queries; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.DirectiveTree; +import com.sun.source.tree.ExportsTree; +import com.sun.source.tree.ModuleTree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.util.JavacTask; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.net.URI; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.queries.AccessibilityQuery; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation2; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileObject; +import org.openide.util.ChangeSupport; +import org.openide.util.RequestProcessor; +import org.openide.util.RequestProcessor.Task; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service=AccessibilityQueryImplementation2.class) +public class GenericModuleInfoAccessibilityQuery implements AccessibilityQueryImplementation2 { + + private final Map<ClassPath, ClassPathListener> sourcePath2Listener = new WeakHashMap<>(); + private final Map<FileObject, Result> path2Result = new WeakHashMap<>(); + + @Override + public Result isPubliclyAccessible(FileObject folder) { + return path2Result.computeIfAbsent(folder, f -> { + Project p = FileOwnerQuery.getOwner(f); + if (p != null && (p.getLookup().lookup(AccessibilityQueryImplementation2.class) != null || + p.getLookup().lookup(AccessibilityQueryImplementation.class) != null)) { + //if there's a project-based AccessibilityQuery for this file, don't provide the generic results + return null; + } + ClassPath sourcePath = ClassPath.getClassPath(folder, ClassPath.SOURCE); + if (sourcePath == null) { + return null; + } + ClassPathListener cpl = sourcePath2Listener.computeIfAbsent(sourcePath, sp -> new ClassPathListener(sourcePath)); + return new ResultImpl(cpl, sourcePath, folder); + }); + } + + private static final class ResultImpl implements Result, ChangeListener { + + private final ChangeSupport cs = new ChangeSupport(this); + private final ClassPathListener listener; + private final Reference<ClassPath> sourcePath; + private final Reference<FileObject> folder; + + public ResultImpl(ClassPathListener listener, ClassPath sourcePath, FileObject folder) { + this.listener = listener; + this.sourcePath = new WeakReference<>(sourcePath); + this.folder = new WeakReference<>(folder); + listener.addChangeListener(this); + } + + @Override + public AccessibilityQuery.Accessibility getAccessibility() { + ClassPath sourcePath = this.sourcePath.get(); + FileObject folder = this.folder.get(); + Set<String> exported = listener.getExportedPackages(); + + if (sourcePath == null || folder == null || exported == null) { + return AccessibilityQuery.Accessibility.UNKNOWN; + } + String packageName = sourcePath.getResourceName(folder).replace('/', '.'); + return exported.contains(packageName) ? AccessibilityQuery.Accessibility.EXPORTED + : AccessibilityQuery.Accessibility.PRIVATE; + } + + @Override + public void addChangeListener(ChangeListener listener) { + cs.addChangeListener(listener); + } + + @Override + public void removeChangeListener(ChangeListener listener) { + cs.removeChangeListener(listener); + } + + @Override + public void stateChanged(ChangeEvent e) { + cs.fireChange(); + } + + } + + private static final class ClassPathListener implements PropertyChangeListener { + + private static final RequestProcessor WORKER = new RequestProcessor(ClassPathListener.class.getName(), 1, false, false); + private static final int DELAY = 100; + private final ChangeSupport cs = new ChangeSupport(this); + private final AtomicReference<Set<String>> exportedPackages = new AtomicReference<>(null); + private final Reference<ClassPath> sourcePath; + private final Task parseTask; + private final Task rootsTask; + private final FileChangeAdapter folderListener = new FileChangeAdapter() { + @Override + public void fileDataCreated(FileEvent fe) { + if (fe.getFile().getNameExt().equalsIgnoreCase("module-info.java")) { + rootsTask.schedule(DELAY); + } + } + }; + private final FileChangeAdapter moduleInfoListener = new FileChangeAdapter() { + @Override + public void fileChanged(FileEvent fe) { + parseTask.schedule(DELAY); + } + }; + + public ClassPathListener(ClassPath sourcePath) { + this.sourcePath = new WeakReference<>(sourcePath); + this.parseTask = WORKER.create(() -> { + FileObject moduleInfo = sourcePath.findResource("module-info.java"); + Set<String> exported; + + if (moduleInfo != null) { + exported = new HashSet<>(); + + try { + String code = moduleInfo.asText(); + JavacTask compilerTask = (JavacTask) ToolProvider.getSystemJavaCompiler().getTask(null, null, null, null, null, Collections.singleton(new TextJFO(code, moduleInfo.toURI()))); + CompilationUnitTree cut = compilerTask.parse().iterator().next(); + ModuleTree mt = cut.getModule(); + if (mt != null) { + for (DirectiveTree dt : mt.getDirectives()) { + if (dt.getKind() == Kind.EXPORTS) { + ExportsTree et = (ExportsTree) dt; + if (et.getModuleNames() == null || et.getModuleNames().isEmpty()) { + exported.add(et.getPackageName().toString()); + } + } + } + } + } catch (IOException ex) { + //TODO: log + ex.printStackTrace(); + } + } else { + exported = null; + } + + exportedPackages.set(exported); + cs.fireChange(); + }); + sourcePath.addPropertyChangeListener(this); + rootsTask = WORKER.create(() -> { + ClassPath cp = ClassPathListener.this.sourcePath.get(); + for (FileObject root : cp.getRoots()) { + root.removeFileChangeListener(folderListener); Review Comment: I don't understand this -- it does not remove listeneres from obsoleted CP roots (= not present now) IMHO. ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/SharedRootData.java: ########## @@ -0,0 +1,121 @@ +/* + * 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.file.launcher; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.modules.java.file.launcher.SingleSourceFileUtil; +import org.openide.filesystems.FileAttributeEvent; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileChangeListener; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; + +/** + * + * @author lahvac + */ +public class SharedRootData { + + private static final Pattern ENABLE_PREVIEW_PATTERN = Pattern.compile("(^|[ \t])--enable-preview($|[ \t])"); + private static final Pattern SOURCE_PATTERN = Pattern.compile("(^|[ \t])--source[ \t]+([0-9]+(\\.[0-9]+)*)($|[ \t])"); + private static Map<FileObject, SharedRootData> root2Data = new HashMap<>(); + + public static synchronized void ensureRootRegistered(FileObject root) { + root2Data.computeIfAbsent(root, r -> new SharedRootData(r)); + } + + public static synchronized @CheckForNull SharedRootData getDataForRoot(FileObject root) { + return root2Data.get(root); + } + + private final FileObject root; + private final Properties options = new Properties(); + private final FileChangeListener listener = new FileChangeAdapter() { + @Override + public void fileAttributeChanged(FileAttributeEvent fe) { + Map<String, String> newProperties = new HashMap<>(); + + addPropertiesFor(fe.getFile(), newProperties); + setNewProperties(newProperties); + } + }; + + private SharedRootData(FileObject root) { + this.root = root; + root.addRecursiveListener(listener); + Enumeration<? extends FileObject> todo = root.getChildren(true); + Map<String, String> newProperties = new HashMap<>(); + while (todo.hasMoreElements()) { + FileObject current = todo.nextElement(); + addPropertiesFor(current, newProperties); + } + setNewProperties(newProperties); + } + + private void addPropertiesFor(FileObject file, Map<String, String> newProperties) { + if (file.isData() && "text/x-java".equals(file.getMIMEType())) { + newProperties.put(FileUtil.getRelativePath(root, file), (String) file.getAttribute(SingleSourceFileUtil.FILE_VM_OPTIONS)); + } + } + + private synchronized void setNewProperties(Map<String, String> newProperties) { + if (newProperties.isEmpty()) { + return ; + } + for (String key : newProperties.keySet()) { + String value = newProperties.get(key); + if (value == null) { + options.remove(key); + } else { + options.setProperty(key, value); + } + } + Map<String, String> joinedOptions = new HashMap<>(); + for (String key : options.stringPropertyNames()) { + String value = options.getProperty(key); + if (ENABLE_PREVIEW_PATTERN.matcher(value).find()) { + joinedOptions.put("--enable-preview", null); + } + Matcher sourceMatcher = SOURCE_PATTERN.matcher(value); + if (sourceMatcher.find()) { + //TODO: merging sources! + joinedOptions.put("--source", sourceMatcher.group(2)); Review Comment: :warning: This is an important TODO ! ########## java/java.api.common/src/org/netbeans/modules/java/api/common/queries/GenericModuleInfoAccessibilityQuery.java: ########## @@ -0,0 +1,234 @@ +/* + * 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.api.common.queries; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.DirectiveTree; +import com.sun.source.tree.ExportsTree; +import com.sun.source.tree.ModuleTree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.util.JavacTask; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.net.URI; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.queries.AccessibilityQuery; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation2; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileObject; +import org.openide.util.ChangeSupport; +import org.openide.util.RequestProcessor; +import org.openide.util.RequestProcessor.Task; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service=AccessibilityQueryImplementation2.class) +public class GenericModuleInfoAccessibilityQuery implements AccessibilityQueryImplementation2 { + + private final Map<ClassPath, ClassPathListener> sourcePath2Listener = new WeakHashMap<>(); + private final Map<FileObject, Result> path2Result = new WeakHashMap<>(); + + @Override + public Result isPubliclyAccessible(FileObject folder) { + return path2Result.computeIfAbsent(folder, f -> { + Project p = FileOwnerQuery.getOwner(f); + if (p != null && (p.getLookup().lookup(AccessibilityQueryImplementation2.class) != null || + p.getLookup().lookup(AccessibilityQueryImplementation.class) != null)) { + //if there's a project-based AccessibilityQuery for this file, don't provide the generic results + return null; + } + ClassPath sourcePath = ClassPath.getClassPath(folder, ClassPath.SOURCE); + if (sourcePath == null) { + return null; + } + ClassPathListener cpl = sourcePath2Listener.computeIfAbsent(sourcePath, sp -> new ClassPathListener(sourcePath)); + return new ResultImpl(cpl, sourcePath, folder); + }); + } + + private static final class ResultImpl implements Result, ChangeListener { + + private final ChangeSupport cs = new ChangeSupport(this); + private final ClassPathListener listener; + private final Reference<ClassPath> sourcePath; + private final Reference<FileObject> folder; + + public ResultImpl(ClassPathListener listener, ClassPath sourcePath, FileObject folder) { + this.listener = listener; + this.sourcePath = new WeakReference<>(sourcePath); + this.folder = new WeakReference<>(folder); + listener.addChangeListener(this); + } + + @Override + public AccessibilityQuery.Accessibility getAccessibility() { + ClassPath sourcePath = this.sourcePath.get(); + FileObject folder = this.folder.get(); + Set<String> exported = listener.getExportedPackages(); + + if (sourcePath == null || folder == null || exported == null) { + return AccessibilityQuery.Accessibility.UNKNOWN; + } + String packageName = sourcePath.getResourceName(folder).replace('/', '.'); + return exported.contains(packageName) ? AccessibilityQuery.Accessibility.EXPORTED + : AccessibilityQuery.Accessibility.PRIVATE; + } + + @Override + public void addChangeListener(ChangeListener listener) { + cs.addChangeListener(listener); + } + + @Override + public void removeChangeListener(ChangeListener listener) { + cs.removeChangeListener(listener); + } + + @Override + public void stateChanged(ChangeEvent e) { + cs.fireChange(); + } + + } + + private static final class ClassPathListener implements PropertyChangeListener { + + private static final RequestProcessor WORKER = new RequestProcessor(ClassPathListener.class.getName(), 1, false, false); + private static final int DELAY = 100; + private final ChangeSupport cs = new ChangeSupport(this); + private final AtomicReference<Set<String>> exportedPackages = new AtomicReference<>(null); + private final Reference<ClassPath> sourcePath; + private final Task parseTask; + private final Task rootsTask; + private final FileChangeAdapter folderListener = new FileChangeAdapter() { + @Override + public void fileDataCreated(FileEvent fe) { + if (fe.getFile().getNameExt().equalsIgnoreCase("module-info.java")) { + rootsTask.schedule(DELAY); + } + } + }; + private final FileChangeAdapter moduleInfoListener = new FileChangeAdapter() { + @Override + public void fileChanged(FileEvent fe) { + parseTask.schedule(DELAY); + } + }; + + public ClassPathListener(ClassPath sourcePath) { + this.sourcePath = new WeakReference<>(sourcePath); + this.parseTask = WORKER.create(() -> { + FileObject moduleInfo = sourcePath.findResource("module-info.java"); + Set<String> exported; + + if (moduleInfo != null) { + exported = new HashSet<>(); + + try { + String code = moduleInfo.asText(); + JavacTask compilerTask = (JavacTask) ToolProvider.getSystemJavaCompiler().getTask(null, null, null, null, null, Collections.singleton(new TextJFO(code, moduleInfo.toURI()))); + CompilationUnitTree cut = compilerTask.parse().iterator().next(); + ModuleTree mt = cut.getModule(); + if (mt != null) { + for (DirectiveTree dt : mt.getDirectives()) { + if (dt.getKind() == Kind.EXPORTS) { + ExportsTree et = (ExportsTree) dt; + if (et.getModuleNames() == null || et.getModuleNames().isEmpty()) { + exported.add(et.getPackageName().toString()); + } + } + } + } + } catch (IOException ex) { + //TODO: log + ex.printStackTrace(); + } + } else { + exported = null; + } + + exportedPackages.set(exported); + cs.fireChange(); + }); + sourcePath.addPropertyChangeListener(this); + rootsTask = WORKER.create(() -> { + ClassPath cp = ClassPathListener.this.sourcePath.get(); + for (FileObject root : cp.getRoots()) { Review Comment: Null check `cp`. ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/queries/LauncherSourceLevelQueryImpl.java: ########## @@ -0,0 +1,73 @@ +/* + * 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.file.launcher.queries; + +import javax.swing.event.ChangeListener; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.modules.java.file.launcher.SharedRootData; +import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2; +import org.openide.*; +import org.openide.filesystems.FileObject; +import org.openide.util.ChangeSupport; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author lahvac + */ +@ServiceProvider(service=SourceLevelQueryImplementation2.class, position=9_999) +public class LauncherSourceLevelQueryImpl implements SourceLevelQueryImplementation2 { + + @Override + public Result getSourceLevel(FileObject javaFile) { + //for files, DefaultSourceLevelQueryImpl will respond: + if (javaFile.isFolder()) { Review Comment: Shouldn't it bail out if `FileOwnerQuery.getOwner(javaFile) != null` ? ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/queries/MultiSourceRootProvider.java: ########## @@ -0,0 +1,169 @@ +/* + * 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.file.launcher.queries; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.classpath.GlobalPathRegistry; +import org.netbeans.api.java.classpath.JavaClassPathConstants; +import org.netbeans.api.java.lexer.JavaTokenId; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.queries.FileEncodingQuery; +import org.netbeans.spi.java.classpath.ClassPathProvider; +import org.netbeans.spi.java.classpath.support.ClassPathSupport; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; + +@ServiceProviders({ + @ServiceProvider(service=ClassPathProvider.class, position=100_000), + @ServiceProvider(service=MultiSourceRootProvider.class) +}) +public class MultiSourceRootProvider implements ClassPathProvider { + + public static boolean DISABLE_MULTI_SOURCE_ROOT = false; Review Comment: Get the value from a system property ... or is it just for local troubleshooting ? ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/queries/MultiSourceRootProvider.java: ########## @@ -0,0 +1,169 @@ +/* + * 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.file.launcher.queries; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.classpath.GlobalPathRegistry; +import org.netbeans.api.java.classpath.JavaClassPathConstants; +import org.netbeans.api.java.lexer.JavaTokenId; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.queries.FileEncodingQuery; +import org.netbeans.spi.java.classpath.ClassPathProvider; +import org.netbeans.spi.java.classpath.support.ClassPathSupport; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; + +@ServiceProviders({ + @ServiceProvider(service=ClassPathProvider.class, position=100_000), + @ServiceProvider(service=MultiSourceRootProvider.class) +}) +public class MultiSourceRootProvider implements ClassPathProvider { + + public static boolean DISABLE_MULTI_SOURCE_ROOT = false; + + //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<>(); + + @Override + public ClassPath findClassPath(FileObject file, String type) { + if (DISABLE_MULTI_SOURCE_ROOT) return null; + switch (type) { + case ClassPath.SOURCE: return getSourcePath(file); + case ClassPath.BOOT: + case JavaClassPathConstants.MODULE_BOOT_PATH: + return getBootPath(file); + } + return null; + } + + private ClassPath getSourcePath(FileObject file) { + synchronized (this) { + //XXX: what happens if there's a Java file in user's home??? + if (file.isData() && "text/x-java".equals(file.getMIMEType())) { + return file2SourceCP.computeIfAbsent(file, f -> { + try { + String content = new String(file.asBytes(), FileEncodingQuery.getEncoding(file)); + String packName = findPackage(content); + FileObject root = file.getParent(); + + if (packName != null) { + List<String> packageParts = Arrays.asList(packName.split("\\.")); + + Collections.reverse(packageParts); + + for (String packagePart : packageParts) { + if (!root.getNameExt().equalsIgnoreCase(packagePart)) { + //ignore files outside of proper package structure, + //those may too easily lead to using a too general + //directory as a root, leading to too much indexing: + return null; + } + root = root.getParent(); + } + } + + return root2SourceCP.computeIfAbsent(root, r -> { //XXX: weak.... + ClassPath srcCP = ClassPathSupport.createClassPath(r); + GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {srcCP}); + return srcCP; + }); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return null; + }); + } else { + FileObject folder = file; + + while (!folder.isRoot()) { + ClassPath cp = root2SourceCP.get(folder); + + if (cp != null) { + return cp; + } + + folder = folder.getParent(); + } + + return null; + } + } + } + + public synchronized boolean isSourceLauncher(FileObject file) { + for (FileObject root : root2SourceCP.keySet()) { + if (FileUtil.isParentOf(root, file) || root.equals(file)) { + return true; + } + } + + return false; + } + + private ClassPath getBootPath(FileObject file) { + if (isSourceLauncher(file)) { + return JavaPlatformManager.getDefault() + .getDefaultPlatform() + .getBootstrapLibraries(); + } + + return null; + } + + private static final Set<JavaTokenId> IGNORED_TOKENS = EnumSet.of( + JavaTokenId.BLOCK_COMMENT, + JavaTokenId.JAVADOC_COMMENT, + JavaTokenId.LINE_COMMENT, + JavaTokenId.WHITESPACE + ); + + static String findPackage(String fileContext) { Review Comment: Question: given that `package` statement has to be a first non-comment, non-whitespace content in the file, more lightweight search can be done (just skipping whitespaces and comments). Not sure if the performance would increase enough to leverage the impl pain. ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/queries/SingleSourceCompilerOptQueryImpl.java: ########## @@ -37,10 +44,15 @@ public class SingleSourceCompilerOptQueryImpl implements CompilerOptionsQueryImp @Override public CompilerOptionsQueryImplementation.Result getOptions(FileObject file) { - String fileName = file.getName(); Result res = null; if (SingleSourceFileUtil.isSingleSourceFile(file)) { - res = new ResultImpl(file); + return new ResultImpl(file); + } + if (file.isFolder()) { + SharedRootData data = SharedRootData.getDataForRoot(file); Review Comment: Results depends on whether `getSourcePath()` was called for this or sibling file. ########## java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/singlesourcefile/CompilerOptionsQueryImpl.java: ########## @@ -107,10 +108,13 @@ private synchronized Configuration getConfiguration(NbCodeLanguageClient client) //copied from SingleSourceFileUtil: static boolean isSingleSourceFile(FileObject fObj) { Project p = FileOwnerQuery.getOwner(fObj); - if (p != null || !fObj.getExt().equalsIgnoreCase("java")) { //NOI18N + if (p != null) { + return false; + } + if (!fObj.isFolder() && !fObj.getExt().equalsIgnoreCase("java")) { //NOI18N Review Comment: Possibly inverted condition for isFolder() ? ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/SharedRootData.java: ########## @@ -0,0 +1,121 @@ +/* + * 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.file.launcher; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.modules.java.file.launcher.SingleSourceFileUtil; +import org.openide.filesystems.FileAttributeEvent; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileChangeListener; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; + +/** + * + * @author lahvac + */ +public class SharedRootData { + + private static final Pattern ENABLE_PREVIEW_PATTERN = Pattern.compile("(^|[ \t])--enable-preview($|[ \t])"); + private static final Pattern SOURCE_PATTERN = Pattern.compile("(^|[ \t])--source[ \t]+([0-9]+(\\.[0-9]+)*)($|[ \t])"); Review Comment: Would be great, if it supported Javac 8 syntax (1.8, 1.7) too. ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/api/SourceLauncher.java: ########## @@ -0,0 +1,31 @@ +/* + * 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.file.launcher.api; + +import org.netbeans.modules.java.file.launcher.queries.MultiSourceRootProvider; +import org.openide.filesystems.FileObject; +import org.openide.util.Lookup; + +public class SourceLauncher { Review Comment: `final` + javadoc ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/SharedRootData.java: ########## @@ -0,0 +1,121 @@ +/* + * 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.file.launcher; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.modules.java.file.launcher.SingleSourceFileUtil; +import org.openide.filesystems.FileAttributeEvent; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileChangeListener; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; + +/** + * + * @author lahvac + */ +public class SharedRootData { + + private static final Pattern ENABLE_PREVIEW_PATTERN = Pattern.compile("(^|[ \t])--enable-preview($|[ \t])"); + private static final Pattern SOURCE_PATTERN = Pattern.compile("(^|[ \t])--source[ \t]+([0-9]+(\\.[0-9]+)*)($|[ \t])"); + private static Map<FileObject, SharedRootData> root2Data = new HashMap<>(); + + public static synchronized void ensureRootRegistered(FileObject root) { + root2Data.computeIfAbsent(root, r -> new SharedRootData(r)); + } + + public static synchronized @CheckForNull SharedRootData getDataForRoot(FileObject root) { + return root2Data.get(root); + } + + private final FileObject root; + private final Properties options = new Properties(); + private final FileChangeListener listener = new FileChangeAdapter() { + @Override + public void fileAttributeChanged(FileAttributeEvent fe) { + Map<String, String> newProperties = new HashMap<>(); + + addPropertiesFor(fe.getFile(), newProperties); + setNewProperties(newProperties); Review Comment: :warning: Question: the Listener is attached recursively; `addPropertiesFor` will create a single key from the file's relative path. Then `setNewProperties` will remove all other file's entries ? ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/SharedRootData.java: ########## @@ -0,0 +1,121 @@ +/* + * 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.file.launcher; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.modules.java.file.launcher.SingleSourceFileUtil; +import org.openide.filesystems.FileAttributeEvent; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileChangeListener; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; + +/** + * + * @author lahvac + */ +public class SharedRootData { + + private static final Pattern ENABLE_PREVIEW_PATTERN = Pattern.compile("(^|[ \t])--enable-preview($|[ \t])"); + private static final Pattern SOURCE_PATTERN = Pattern.compile("(^|[ \t])--source[ \t]+([0-9]+(\\.[0-9]+)*)($|[ \t])"); + private static Map<FileObject, SharedRootData> root2Data = new HashMap<>(); Review Comment: Can be final ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/queries/SingleSourceCompilerOptQueryImpl.java: ########## @@ -16,16 +16,23 @@ * specific language governing permissions and limitations * under the License. */ -package org.netbeans.modules.java.api.common.singlesourcefile; +package org.netbeans.modules.java.file.launcher.queries; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; import javax.swing.event.ChangeListener; +import org.netbeans.api.project.FileOwnerQuery; Review Comment: Unused ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/indexer/CompilerOptionsIndexer.java: ########## @@ -0,0 +1,79 @@ +/* + * 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.file.launcher.indexer; + +import org.netbeans.modules.java.file.launcher.SharedRootData; +import org.netbeans.api.editor.mimelookup.MimeRegistration; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.modules.parsing.spi.indexing.Context; +import org.netbeans.modules.parsing.spi.indexing.CustomIndexer; +import org.netbeans.modules.parsing.spi.indexing.CustomIndexerFactory; +import org.netbeans.modules.parsing.spi.indexing.Indexable; + +/** + * + * @author lahvac + */ +public class CompilerOptionsIndexer extends CustomIndexer { + + + @Override + protected void index(Iterable<? extends Indexable> files, Context context) { + if (FileOwnerQuery.getOwner(context.getRoot()) != null) { + return ; //ignore roots under projects + } + SharedRootData.ensureRootRegistered(context.getRoot()); + } + + + @MimeRegistration(mimeType="text/x-java", service=CustomIndexerFactory.class, position=1_000) + public static final class FactoryImpl extends CustomIndexerFactory { + + @Override + public CustomIndexer createIndexer() { + return new CompilerOptionsIndexer(); + } + + @Override + public boolean supportsEmbeddedIndexers() { + return true; + } + + @Override + public void filesDeleted(Iterable<? extends Indexable> deleted, Context context) { + //TODO: Review Comment: what's here to do ? ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/queries/LauncherSourceLevelQueryImpl.java: ########## @@ -0,0 +1,73 @@ +/* + * 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.file.launcher.queries; + +import javax.swing.event.ChangeListener; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.modules.java.file.launcher.SharedRootData; +import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2; +import org.openide.*; +import org.openide.filesystems.FileObject; +import org.openide.util.ChangeSupport; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author lahvac + */ +@ServiceProvider(service=SourceLevelQueryImplementation2.class, position=9_999) +public class LauncherSourceLevelQueryImpl implements SourceLevelQueryImplementation2 { + + @Override + public Result getSourceLevel(FileObject javaFile) { + //for files, DefaultSourceLevelQueryImpl will respond: + if (javaFile.isFolder()) { + while (javaFile != null) { + SharedRootData d = SharedRootData.getDataForRoot(javaFile); + if (d != null) { + return new ResultImpl(); + } + javaFile = javaFile.getParent(); + } + } + + return null; + } + + private static final class ResultImpl implements Result { + + private final ChangeSupport cs = new ChangeSupport(this); Review Comment: :warning: `cs` is never called to fire changes ########## java/java.api.common/src/org/netbeans/modules/java/api/common/queries/GenericModuleInfoAccessibilityQuery.java: ########## @@ -0,0 +1,234 @@ +/* + * 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.api.common.queries; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.DirectiveTree; +import com.sun.source.tree.ExportsTree; +import com.sun.source.tree.ModuleTree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.util.JavacTask; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.net.URI; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.queries.AccessibilityQuery; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation2; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileObject; +import org.openide.util.ChangeSupport; +import org.openide.util.RequestProcessor; +import org.openide.util.RequestProcessor.Task; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service=AccessibilityQueryImplementation2.class) +public class GenericModuleInfoAccessibilityQuery implements AccessibilityQueryImplementation2 { + + private final Map<ClassPath, ClassPathListener> sourcePath2Listener = new WeakHashMap<>(); + private final Map<FileObject, Result> path2Result = new WeakHashMap<>(); + + @Override + public Result isPubliclyAccessible(FileObject folder) { + return path2Result.computeIfAbsent(folder, f -> { + Project p = FileOwnerQuery.getOwner(f); + if (p != null && (p.getLookup().lookup(AccessibilityQueryImplementation2.class) != null || + p.getLookup().lookup(AccessibilityQueryImplementation.class) != null)) { + //if there's a project-based AccessibilityQuery for this file, don't provide the generic results + return null; + } + ClassPath sourcePath = ClassPath.getClassPath(folder, ClassPath.SOURCE); + if (sourcePath == null) { + return null; + } + ClassPathListener cpl = sourcePath2Listener.computeIfAbsent(sourcePath, sp -> new ClassPathListener(sourcePath)); + return new ResultImpl(cpl, sourcePath, folder); + }); + } + + private static final class ResultImpl implements Result, ChangeListener { + + private final ChangeSupport cs = new ChangeSupport(this); + private final ClassPathListener listener; + private final Reference<ClassPath> sourcePath; + private final Reference<FileObject> folder; + + public ResultImpl(ClassPathListener listener, ClassPath sourcePath, FileObject folder) { + this.listener = listener; + this.sourcePath = new WeakReference<>(sourcePath); + this.folder = new WeakReference<>(folder); + listener.addChangeListener(this); + } + + @Override + public AccessibilityQuery.Accessibility getAccessibility() { + ClassPath sourcePath = this.sourcePath.get(); + FileObject folder = this.folder.get(); + Set<String> exported = listener.getExportedPackages(); + + if (sourcePath == null || folder == null || exported == null) { + return AccessibilityQuery.Accessibility.UNKNOWN; + } + String packageName = sourcePath.getResourceName(folder).replace('/', '.'); + return exported.contains(packageName) ? AccessibilityQuery.Accessibility.EXPORTED + : AccessibilityQuery.Accessibility.PRIVATE; + } + + @Override + public void addChangeListener(ChangeListener listener) { + cs.addChangeListener(listener); + } + + @Override + public void removeChangeListener(ChangeListener listener) { + cs.removeChangeListener(listener); + } + + @Override + public void stateChanged(ChangeEvent e) { + cs.fireChange(); + } + + } + + private static final class ClassPathListener implements PropertyChangeListener { + + private static final RequestProcessor WORKER = new RequestProcessor(ClassPathListener.class.getName(), 1, false, false); + private static final int DELAY = 100; + private final ChangeSupport cs = new ChangeSupport(this); + private final AtomicReference<Set<String>> exportedPackages = new AtomicReference<>(null); + private final Reference<ClassPath> sourcePath; + private final Task parseTask; + private final Task rootsTask; + private final FileChangeAdapter folderListener = new FileChangeAdapter() { + @Override + public void fileDataCreated(FileEvent fe) { + if (fe.getFile().getNameExt().equalsIgnoreCase("module-info.java")) { + rootsTask.schedule(DELAY); + } + } + }; + private final FileChangeAdapter moduleInfoListener = new FileChangeAdapter() { + @Override + public void fileChanged(FileEvent fe) { + parseTask.schedule(DELAY); + } + }; + + public ClassPathListener(ClassPath sourcePath) { + this.sourcePath = new WeakReference<>(sourcePath); + this.parseTask = WORKER.create(() -> { + FileObject moduleInfo = sourcePath.findResource("module-info.java"); + Set<String> exported; + + if (moduleInfo != null) { + exported = new HashSet<>(); + + try { + String code = moduleInfo.asText(); + JavacTask compilerTask = (JavacTask) ToolProvider.getSystemJavaCompiler().getTask(null, null, null, null, null, Collections.singleton(new TextJFO(code, moduleInfo.toURI()))); + CompilationUnitTree cut = compilerTask.parse().iterator().next(); + ModuleTree mt = cut.getModule(); + if (mt != null) { + for (DirectiveTree dt : mt.getDirectives()) { + if (dt.getKind() == Kind.EXPORTS) { + ExportsTree et = (ExportsTree) dt; + if (et.getModuleNames() == null || et.getModuleNames().isEmpty()) { + exported.add(et.getPackageName().toString()); + } + } + } + } + } catch (IOException ex) { + //TODO: log + ex.printStackTrace(); + } + } else { + exported = null; + } + + exportedPackages.set(exported); + cs.fireChange(); + }); + sourcePath.addPropertyChangeListener(this); Review Comment: Nitpick: attach listener only after rootsTask is initialized (race) ########## java/java.api.common/src/org/netbeans/modules/java/api/common/queries/GenericModuleInfoAccessibilityQuery.java: ########## @@ -0,0 +1,234 @@ +/* + * 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.api.common.queries; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.DirectiveTree; +import com.sun.source.tree.ExportsTree; +import com.sun.source.tree.ModuleTree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.util.JavacTask; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.net.URI; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.queries.AccessibilityQuery; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation2; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileObject; +import org.openide.util.ChangeSupport; +import org.openide.util.RequestProcessor; +import org.openide.util.RequestProcessor.Task; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service=AccessibilityQueryImplementation2.class) +public class GenericModuleInfoAccessibilityQuery implements AccessibilityQueryImplementation2 { + + private final Map<ClassPath, ClassPathListener> sourcePath2Listener = new WeakHashMap<>(); + private final Map<FileObject, Result> path2Result = new WeakHashMap<>(); + + @Override + public Result isPubliclyAccessible(FileObject folder) { + return path2Result.computeIfAbsent(folder, f -> { + Project p = FileOwnerQuery.getOwner(f); + if (p != null && (p.getLookup().lookup(AccessibilityQueryImplementation2.class) != null || + p.getLookup().lookup(AccessibilityQueryImplementation.class) != null)) { + //if there's a project-based AccessibilityQuery for this file, don't provide the generic results + return null; + } + ClassPath sourcePath = ClassPath.getClassPath(folder, ClassPath.SOURCE); + if (sourcePath == null) { + return null; + } + ClassPathListener cpl = sourcePath2Listener.computeIfAbsent(sourcePath, sp -> new ClassPathListener(sourcePath)); + return new ResultImpl(cpl, sourcePath, folder); + }); + } + + private static final class ResultImpl implements Result, ChangeListener { + + private final ChangeSupport cs = new ChangeSupport(this); + private final ClassPathListener listener; + private final Reference<ClassPath> sourcePath; + private final Reference<FileObject> folder; + + public ResultImpl(ClassPathListener listener, ClassPath sourcePath, FileObject folder) { + this.listener = listener; + this.sourcePath = new WeakReference<>(sourcePath); + this.folder = new WeakReference<>(folder); + listener.addChangeListener(this); + } + + @Override + public AccessibilityQuery.Accessibility getAccessibility() { + ClassPath sourcePath = this.sourcePath.get(); + FileObject folder = this.folder.get(); + Set<String> exported = listener.getExportedPackages(); + + if (sourcePath == null || folder == null || exported == null) { Review Comment: Consider a situation: A client gets AccessibilityQuery.Result and forgots about the FileObject. Could the FileObject expire and make the Result to start returning UNKNOWN ? ########## java/java.api.common/src/org/netbeans/modules/java/api/common/queries/GenericModuleInfoAccessibilityQuery.java: ########## @@ -0,0 +1,234 @@ +/* + * 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.api.common.queries; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.DirectiveTree; +import com.sun.source.tree.ExportsTree; +import com.sun.source.tree.ModuleTree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.util.JavacTask; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.net.URI; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.queries.AccessibilityQuery; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation2; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileObject; +import org.openide.util.ChangeSupport; +import org.openide.util.RequestProcessor; +import org.openide.util.RequestProcessor.Task; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service=AccessibilityQueryImplementation2.class) +public class GenericModuleInfoAccessibilityQuery implements AccessibilityQueryImplementation2 { + + private final Map<ClassPath, ClassPathListener> sourcePath2Listener = new WeakHashMap<>(); + private final Map<FileObject, Result> path2Result = new WeakHashMap<>(); + + @Override + public Result isPubliclyAccessible(FileObject folder) { + return path2Result.computeIfAbsent(folder, f -> { + Project p = FileOwnerQuery.getOwner(f); + if (p != null && (p.getLookup().lookup(AccessibilityQueryImplementation2.class) != null || + p.getLookup().lookup(AccessibilityQueryImplementation.class) != null)) { + //if there's a project-based AccessibilityQuery for this file, don't provide the generic results + return null; + } + ClassPath sourcePath = ClassPath.getClassPath(folder, ClassPath.SOURCE); + if (sourcePath == null) { + return null; + } + ClassPathListener cpl = sourcePath2Listener.computeIfAbsent(sourcePath, sp -> new ClassPathListener(sourcePath)); Review Comment: I thought that just ResultImpl hard-references CPL, so that forgetting results will release CPLs ... or is it intentional to keep CPLs as a form of cache ? ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/queries/LauncherSourceLevelQueryImpl.java: ########## @@ -0,0 +1,73 @@ +/* + * 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.file.launcher.queries; + +import javax.swing.event.ChangeListener; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.modules.java.file.launcher.SharedRootData; +import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2; +import org.openide.*; Review Comment: Unused ? ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/queries/MultiSourceRootProvider.java: ########## @@ -0,0 +1,169 @@ +/* + * 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.file.launcher.queries; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.classpath.GlobalPathRegistry; +import org.netbeans.api.java.classpath.JavaClassPathConstants; +import org.netbeans.api.java.lexer.JavaTokenId; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.queries.FileEncodingQuery; +import org.netbeans.spi.java.classpath.ClassPathProvider; +import org.netbeans.spi.java.classpath.support.ClassPathSupport; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; + +@ServiceProviders({ + @ServiceProvider(service=ClassPathProvider.class, position=100_000), + @ServiceProvider(service=MultiSourceRootProvider.class) +}) +public class MultiSourceRootProvider implements ClassPathProvider { + + public static boolean DISABLE_MULTI_SOURCE_ROOT = false; + + //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<>(); + + @Override + public ClassPath findClassPath(FileObject file, String type) { + if (DISABLE_MULTI_SOURCE_ROOT) return null; + switch (type) { + case ClassPath.SOURCE: return getSourcePath(file); + case ClassPath.BOOT: + case JavaClassPathConstants.MODULE_BOOT_PATH: + return getBootPath(file); + } + return null; + } + + private ClassPath getSourcePath(FileObject file) { + synchronized (this) { + //XXX: what happens if there's a Java file in user's home??? + if (file.isData() && "text/x-java".equals(file.getMIMEType())) { + return file2SourceCP.computeIfAbsent(file, f -> { + try { + String content = new String(file.asBytes(), FileEncodingQuery.getEncoding(file)); + String packName = findPackage(content); + FileObject root = file.getParent(); + + if (packName != null) { + List<String> packageParts = Arrays.asList(packName.split("\\.")); + + Collections.reverse(packageParts); + + for (String packagePart : packageParts) { + if (!root.getNameExt().equalsIgnoreCase(packagePart)) { + //ignore files outside of proper package structure, + //those may too easily lead to using a too general + //directory as a root, leading to too much indexing: + return null; + } + root = root.getParent(); + } + } + + return root2SourceCP.computeIfAbsent(root, r -> { //XXX: weak.... + ClassPath srcCP = ClassPathSupport.createClassPath(r); + GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {srcCP}); + return srcCP; + }); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return null; + }); + } else { + FileObject folder = file; + + while (!folder.isRoot()) { + ClassPath cp = root2SourceCP.get(folder); + + if (cp != null) { + return cp; + } + + folder = folder.getParent(); + } + + return null; + } + } + } + + public synchronized boolean isSourceLauncher(FileObject file) { + for (FileObject root : root2SourceCP.keySet()) { + if (FileUtil.isParentOf(root, file) || root.equals(file)) { + return true; + } + } + + return false; + } + + private ClassPath getBootPath(FileObject file) { + if (isSourceLauncher(file)) { + return JavaPlatformManager.getDefault() + .getDefaultPlatform() + .getBootstrapLibraries(); + } + + return null; + } + + private static final Set<JavaTokenId> IGNORED_TOKENS = EnumSet.of( + JavaTokenId.BLOCK_COMMENT, + JavaTokenId.JAVADOC_COMMENT, + JavaTokenId.LINE_COMMENT, + JavaTokenId.WHITESPACE + ); + + static String findPackage(String fileContext) { + TokenHierarchy<String> th = TokenHierarchy.create(fileContext, true, JavaTokenId.language(), IGNORED_TOKENS, null); + TokenSequence<JavaTokenId> ts = th.tokenSequence(JavaTokenId.language()); + + ts.moveStart(); + + while (ts.moveNext()) { + if (ts.token().id() == JavaTokenId.PACKAGE) { Review Comment: Should the cycle really (possibly) read the whole file, especially for default-package sources ? ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/queries/LauncherSourceLevelQueryImpl.java: ########## @@ -0,0 +1,73 @@ +/* + * 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.file.launcher.queries; + +import javax.swing.event.ChangeListener; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.modules.java.file.launcher.SharedRootData; +import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2; +import org.openide.*; +import org.openide.filesystems.FileObject; +import org.openide.util.ChangeSupport; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author lahvac + */ +@ServiceProvider(service=SourceLevelQueryImplementation2.class, position=9_999) +public class LauncherSourceLevelQueryImpl implements SourceLevelQueryImplementation2 { + + @Override + public Result getSourceLevel(FileObject javaFile) { + //for files, DefaultSourceLevelQueryImpl will respond: + if (javaFile.isFolder()) { + while (javaFile != null) { + SharedRootData d = SharedRootData.getDataForRoot(javaFile); + if (d != null) { + return new ResultImpl(); + } + javaFile = javaFile.getParent(); Review Comment: Hm, wouldn't it help to locate a potential 'owning' shared root faster, if the root Map would be a TreeMap sorted by path ? Just an idea. I sort of don't like the traversal up to the root :) but the same is done by FileOwnerQuery, I admit. ########## java/java.api.common/src/org/netbeans/modules/java/api/common/queries/GenericModuleInfoAccessibilityQuery.java: ########## @@ -0,0 +1,234 @@ +/* + * 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.api.common.queries; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.DirectiveTree; +import com.sun.source.tree.ExportsTree; +import com.sun.source.tree.ModuleTree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.util.JavacTask; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.net.URI; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.queries.AccessibilityQuery; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation; +import org.netbeans.spi.java.queries.AccessibilityQueryImplementation2; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileObject; +import org.openide.util.ChangeSupport; +import org.openide.util.RequestProcessor; +import org.openide.util.RequestProcessor.Task; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service=AccessibilityQueryImplementation2.class) +public class GenericModuleInfoAccessibilityQuery implements AccessibilityQueryImplementation2 { + + private final Map<ClassPath, ClassPathListener> sourcePath2Listener = new WeakHashMap<>(); + private final Map<FileObject, Result> path2Result = new WeakHashMap<>(); + + @Override + public Result isPubliclyAccessible(FileObject folder) { + return path2Result.computeIfAbsent(folder, f -> { + Project p = FileOwnerQuery.getOwner(f); + if (p != null && (p.getLookup().lookup(AccessibilityQueryImplementation2.class) != null || + p.getLookup().lookup(AccessibilityQueryImplementation.class) != null)) { + //if there's a project-based AccessibilityQuery for this file, don't provide the generic results + return null; + } + ClassPath sourcePath = ClassPath.getClassPath(folder, ClassPath.SOURCE); + if (sourcePath == null) { + return null; + } + ClassPathListener cpl = sourcePath2Listener.computeIfAbsent(sourcePath, sp -> new ClassPathListener(sourcePath)); + return new ResultImpl(cpl, sourcePath, folder); + }); + } + + private static final class ResultImpl implements Result, ChangeListener { + + private final ChangeSupport cs = new ChangeSupport(this); + private final ClassPathListener listener; + private final Reference<ClassPath> sourcePath; + private final Reference<FileObject> folder; + + public ResultImpl(ClassPathListener listener, ClassPath sourcePath, FileObject folder) { + this.listener = listener; + this.sourcePath = new WeakReference<>(sourcePath); + this.folder = new WeakReference<>(folder); + listener.addChangeListener(this); + } + + @Override + public AccessibilityQuery.Accessibility getAccessibility() { + ClassPath sourcePath = this.sourcePath.get(); + FileObject folder = this.folder.get(); + Set<String> exported = listener.getExportedPackages(); + + if (sourcePath == null || folder == null || exported == null) { + return AccessibilityQuery.Accessibility.UNKNOWN; + } + String packageName = sourcePath.getResourceName(folder).replace('/', '.'); + return exported.contains(packageName) ? AccessibilityQuery.Accessibility.EXPORTED + : AccessibilityQuery.Accessibility.PRIVATE; + } + + @Override + public void addChangeListener(ChangeListener listener) { + cs.addChangeListener(listener); + } + + @Override + public void removeChangeListener(ChangeListener listener) { + cs.removeChangeListener(listener); + } + + @Override + public void stateChanged(ChangeEvent e) { + cs.fireChange(); + } + + } + + private static final class ClassPathListener implements PropertyChangeListener { + + private static final RequestProcessor WORKER = new RequestProcessor(ClassPathListener.class.getName(), 1, false, false); + private static final int DELAY = 100; + private final ChangeSupport cs = new ChangeSupport(this); + private final AtomicReference<Set<String>> exportedPackages = new AtomicReference<>(null); + private final Reference<ClassPath> sourcePath; + private final Task parseTask; + private final Task rootsTask; + private final FileChangeAdapter folderListener = new FileChangeAdapter() { + @Override + public void fileDataCreated(FileEvent fe) { + if (fe.getFile().getNameExt().equalsIgnoreCase("module-info.java")) { + rootsTask.schedule(DELAY); + } + } + }; + private final FileChangeAdapter moduleInfoListener = new FileChangeAdapter() { + @Override + public void fileChanged(FileEvent fe) { + parseTask.schedule(DELAY); + } + }; + + public ClassPathListener(ClassPath sourcePath) { + this.sourcePath = new WeakReference<>(sourcePath); + this.parseTask = WORKER.create(() -> { + FileObject moduleInfo = sourcePath.findResource("module-info.java"); + Set<String> exported; + + if (moduleInfo != null) { + exported = new HashSet<>(); + + try { + String code = moduleInfo.asText(); + JavacTask compilerTask = (JavacTask) ToolProvider.getSystemJavaCompiler().getTask(null, null, null, null, null, Collections.singleton(new TextJFO(code, moduleInfo.toURI()))); + CompilationUnitTree cut = compilerTask.parse().iterator().next(); + ModuleTree mt = cut.getModule(); + if (mt != null) { + for (DirectiveTree dt : mt.getDirectives()) { + if (dt.getKind() == Kind.EXPORTS) { + ExportsTree et = (ExportsTree) dt; + if (et.getModuleNames() == null || et.getModuleNames().isEmpty()) { + exported.add(et.getPackageName().toString()); + } + } + } + } + } catch (IOException ex) { + //TODO: log + ex.printStackTrace(); Review Comment: a failed `module-info` parse results in empty set of exported packages (not null). OK ? ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/queries/MultiSourceRootProvider.java: ########## @@ -0,0 +1,169 @@ +/* + * 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.file.launcher.queries; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.classpath.GlobalPathRegistry; +import org.netbeans.api.java.classpath.JavaClassPathConstants; +import org.netbeans.api.java.lexer.JavaTokenId; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.queries.FileEncodingQuery; +import org.netbeans.spi.java.classpath.ClassPathProvider; +import org.netbeans.spi.java.classpath.support.ClassPathSupport; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; + +@ServiceProviders({ + @ServiceProvider(service=ClassPathProvider.class, position=100_000), + @ServiceProvider(service=MultiSourceRootProvider.class) +}) +public class MultiSourceRootProvider implements ClassPathProvider { + + public static boolean DISABLE_MULTI_SOURCE_ROOT = false; + + //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<>(); + + @Override + public ClassPath findClassPath(FileObject file, String type) { + if (DISABLE_MULTI_SOURCE_ROOT) return null; + switch (type) { + case ClassPath.SOURCE: return getSourcePath(file); Review Comment: `MODULE_BOOT_PATH` handled, but not `MODULE_CLASS_PATH` ? ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/SharedRootData.java: ########## @@ -0,0 +1,121 @@ +/* + * 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.file.launcher; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.modules.java.file.launcher.SingleSourceFileUtil; Review Comment: Unused ? ########## java/java.file.launcher/nbproject/project.xml: ########## @@ -0,0 +1,268 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<project xmlns="http://www.netbeans.org/ns/project/1"> + <type>org.netbeans.modules.apisupport.project</type> + <configuration> + <data xmlns="http://www.netbeans.org/ns/nb-module-project/3"> + <code-name-base>org.netbeans.modules.java.file.launcher</code-name-base> + <module-dependencies> + <dependency> + <code-name-base>org.netbeans.api.annotations.common</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>1</release-version> + <specification-version>1.48</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.api.debugger.jpda</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>2</release-version> + <specification-version>3.30</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.api.java</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>1</release-version> + <specification-version>1.86</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.api.java.classpath</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>1</release-version> + <specification-version>1.74</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.api.templates</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <specification-version>1.28</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.libs.javacapi</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <specification-version>8.44</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.modules.editor.mimelookup</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>1</release-version> + <specification-version>1.60</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.modules.extexecution</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>2</release-version> + <specification-version>1.69</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.modules.extexecution.base</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>2</release-version> + <specification-version>1.28</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.modules.java.lexer</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>1</release-version> + <specification-version>1.56</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.modules.java.platform</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>1</release-version> + <specification-version>1.62</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.modules.java.project.ui</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>1</release-version> + <specification-version>1.95</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.modules.lexer</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>2</release-version> + <specification-version>1.84</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.modules.parsing.indexing</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <specification-version>9.29</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.modules.projectapi</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>1</release-version> + <specification-version>1.91</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.netbeans.modules.queries</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <release-version>1</release-version> + <specification-version>1.63</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.openide.filesystems</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <specification-version>9.33</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.openide.io</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <specification-version>1.69</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.openide.loaders</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <specification-version>7.90</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.openide.modules</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <specification-version>7.68</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.openide.nodes</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <specification-version>7.65</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.openide.util</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <specification-version>9.28</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.openide.util.lookup</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <specification-version>8.54</specification-version> + </run-dependency> + </dependency> + <dependency> + <code-name-base>org.openide.util.ui</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <specification-version>9.29</specification-version> + </run-dependency> + </dependency> + </module-dependencies> + <test-dependencies> + <test-type> + <name>unit</name> + <test-dependency> + <code-name-base>org.netbeans.libs.junit4</code-name-base> + <compile-dependency/> + </test-dependency> + <test-dependency> + <code-name-base>org.netbeans.modules.java.j2seplatform</code-name-base> + <compile-dependency/> + </test-dependency> + <test-dependency> + <code-name-base>org.netbeans.modules.java.source.base</code-name-base> + <compile-dependency/> + <test/> + </test-dependency> + <test-dependency> + <code-name-base>org.netbeans.modules.nbjunit</code-name-base> + <recursive/> + <compile-dependency/> + </test-dependency> + <test-dependency> + <code-name-base>org.netbeans.modules.projectapi.nb</code-name-base> + <compile-dependency/> + </test-dependency> + </test-type> + </test-dependencies> + <friend-packages> Review Comment: Hm, we should e.g. come up with a plan to publish the API, so it won't stay just friend forever as many others ... we have too many friends already. ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/queries/MultiSourceRootProvider.java: ########## @@ -0,0 +1,169 @@ +/* + * 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.file.launcher.queries; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.classpath.GlobalPathRegistry; +import org.netbeans.api.java.classpath.JavaClassPathConstants; +import org.netbeans.api.java.lexer.JavaTokenId; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.queries.FileEncodingQuery; +import org.netbeans.spi.java.classpath.ClassPathProvider; +import org.netbeans.spi.java.classpath.support.ClassPathSupport; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; + +@ServiceProviders({ + @ServiceProvider(service=ClassPathProvider.class, position=100_000), + @ServiceProvider(service=MultiSourceRootProvider.class) +}) +public class MultiSourceRootProvider implements ClassPathProvider { + + public static boolean DISABLE_MULTI_SOURCE_ROOT = false; + + //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<>(); + + @Override + public ClassPath findClassPath(FileObject file, String type) { + if (DISABLE_MULTI_SOURCE_ROOT) return null; + switch (type) { + case ClassPath.SOURCE: return getSourcePath(file); + case ClassPath.BOOT: + case JavaClassPathConstants.MODULE_BOOT_PATH: + return getBootPath(file); + } + return null; + } + + private ClassPath getSourcePath(FileObject file) { + synchronized (this) { + //XXX: what happens if there's a Java file in user's home??? + if (file.isData() && "text/x-java".equals(file.getMIMEType())) { + return file2SourceCP.computeIfAbsent(file, f -> { + try { + String content = new String(file.asBytes(), FileEncodingQuery.getEncoding(file)); + String packName = findPackage(content); + FileObject root = file.getParent(); + + if (packName != null) { + List<String> packageParts = Arrays.asList(packName.split("\\.")); + + Collections.reverse(packageParts); + + for (String packagePart : packageParts) { + if (!root.getNameExt().equalsIgnoreCase(packagePart)) { Review Comment: equalsIgnoreCase -- maybe just on case-insensitive OSes (Win) ? ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/queries/MultiSourceRootProvider.java: ########## @@ -0,0 +1,169 @@ +/* + * 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.file.launcher.queries; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.classpath.GlobalPathRegistry; +import org.netbeans.api.java.classpath.JavaClassPathConstants; +import org.netbeans.api.java.lexer.JavaTokenId; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.queries.FileEncodingQuery; +import org.netbeans.spi.java.classpath.ClassPathProvider; +import org.netbeans.spi.java.classpath.support.ClassPathSupport; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; + +@ServiceProviders({ + @ServiceProvider(service=ClassPathProvider.class, position=100_000), + @ServiceProvider(service=MultiSourceRootProvider.class) +}) +public class MultiSourceRootProvider implements ClassPathProvider { + + public static boolean DISABLE_MULTI_SOURCE_ROOT = false; + + //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<>(); + + @Override + public ClassPath findClassPath(FileObject file, String type) { + if (DISABLE_MULTI_SOURCE_ROOT) return null; + switch (type) { + case ClassPath.SOURCE: return getSourcePath(file); + case ClassPath.BOOT: + case JavaClassPathConstants.MODULE_BOOT_PATH: + return getBootPath(file); + } + return null; + } + + private ClassPath getSourcePath(FileObject file) { + synchronized (this) { + //XXX: what happens if there's a Java file in user's home??? + if (file.isData() && "text/x-java".equals(file.getMIMEType())) { + return file2SourceCP.computeIfAbsent(file, f -> { + try { + String content = new String(file.asBytes(), FileEncodingQuery.getEncoding(file)); + String packName = findPackage(content); + FileObject root = file.getParent(); + + if (packName != null) { + List<String> packageParts = Arrays.asList(packName.split("\\.")); + + Collections.reverse(packageParts); + + for (String packagePart : packageParts) { + if (!root.getNameExt().equalsIgnoreCase(packagePart)) { + //ignore files outside of proper package structure, + //those may too easily lead to using a too general + //directory as a root, leading to too much indexing: + return null; + } + root = root.getParent(); + } + } + + return root2SourceCP.computeIfAbsent(root, r -> { //XXX: weak.... + ClassPath srcCP = ClassPathSupport.createClassPath(r); + GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {srcCP}); + return srcCP; + }); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return null; + }); + } else { + FileObject folder = file; + + while (!folder.isRoot()) { + ClassPath cp = root2SourceCP.get(folder); + + if (cp != null) { + return cp; + } + + folder = folder.getParent(); + } + + return null; + } + } + } + + public synchronized boolean isSourceLauncher(FileObject file) { + for (FileObject root : root2SourceCP.keySet()) { + if (FileUtil.isParentOf(root, file) || root.equals(file)) { + return true; + } + } + + return false; Review Comment: if no root is found and the file is Java, a lookup can be done to trigger the logic in `getSourcePath` eventually defining a root. May return bad results, if called before `getSourcePath` for java files in the subtree structure. ########## java/java.file.launcher/src/org/netbeans/modules/java/file/launcher/queries/MultiSourceRootProvider.java: ########## @@ -0,0 +1,169 @@ +/* + * 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.file.launcher.queries; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.classpath.GlobalPathRegistry; +import org.netbeans.api.java.classpath.JavaClassPathConstants; +import org.netbeans.api.java.lexer.JavaTokenId; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.queries.FileEncodingQuery; +import org.netbeans.spi.java.classpath.ClassPathProvider; +import org.netbeans.spi.java.classpath.support.ClassPathSupport; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; + +@ServiceProviders({ + @ServiceProvider(service=ClassPathProvider.class, position=100_000), + @ServiceProvider(service=MultiSourceRootProvider.class) +}) +public class MultiSourceRootProvider implements ClassPathProvider { + + public static boolean DISABLE_MULTI_SOURCE_ROOT = false; + + //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<>(); + + @Override + public ClassPath findClassPath(FileObject file, String type) { + if (DISABLE_MULTI_SOURCE_ROOT) return null; + switch (type) { + case ClassPath.SOURCE: return getSourcePath(file); + case ClassPath.BOOT: + case JavaClassPathConstants.MODULE_BOOT_PATH: + return getBootPath(file); + } + return null; + } + + private ClassPath getSourcePath(FileObject file) { + synchronized (this) { + //XXX: what happens if there's a Java file in user's home??? + if (file.isData() && "text/x-java".equals(file.getMIMEType())) { + return file2SourceCP.computeIfAbsent(file, f -> { + try { + String content = new String(file.asBytes(), FileEncodingQuery.getEncoding(file)); + String packName = findPackage(content); + FileObject root = file.getParent(); + + if (packName != null) { + List<String> packageParts = Arrays.asList(packName.split("\\.")); + + Collections.reverse(packageParts); + + for (String packagePart : packageParts) { + if (!root.getNameExt().equalsIgnoreCase(packagePart)) { + //ignore files outside of proper package structure, + //those may too easily lead to using a too general + //directory as a root, leading to too much indexing: + return null; + } + root = root.getParent(); + } + } + + return root2SourceCP.computeIfAbsent(root, r -> { //XXX: weak.... + ClassPath srcCP = ClassPathSupport.createClassPath(r); + GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {srcCP}); + return srcCP; + }); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return null; + }); + } else { + FileObject folder = file; + + while (!folder.isRoot()) { + ClassPath cp = root2SourceCP.get(folder); + + if (cp != null) { Review Comment: This can lead to an inconsistent answer if no .java file is queried before: the result could be `null`, but after a .java is queried in the proper subtree, subsequent query on a non-java file starts to return non-`null`. I'm not sure if this is fixable. ########## java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java: ########## @@ -311,7 +312,10 @@ public void indexingComplete(Set<URL> indexedRoots) { delegates = this.delegates.toArray(new TextDocumentServiceImpl[this.delegates.size()]); } for (TextDocumentServiceImpl delegate : delegates) { - delegate.reRunDiagnostics(); + ProxyLookup augmentedLookup = new ProxyLookup(Lookup.getDefault(), Lookups.fixed(delegate.client)); Review Comment: I'd suggest to place `delegate.client` before defautl Lookup. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected] For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists
