diff --git a/java.api.common/nbproject/project.xml b/java.api.common/nbproject/project.xml --- a/java.api.common/nbproject/project.xml +++ b/java.api.common/nbproject/project.xml @@ -163,7 +163,7 @@ 1 - 1.66 + 1.70 diff --git a/java.api.common/src/org/netbeans/modules/java/api/common/classpath/ClassPathModifier.java b/java.api.common/src/org/netbeans/modules/java/api/common/classpath/ClassPathModifier.java --- a/java.api.common/src/org/netbeans/modules/java/api/common/classpath/ClassPathModifier.java +++ b/java.api.common/src/org/netbeans/modules/java/api/common/classpath/ClassPathModifier.java @@ -117,6 +117,7 @@ ClassPathSupport.ENDORSED, JavaClassPathConstants.PROCESSOR_PATH, JavaClassPathConstants.MODULE_COMPILE_PATH, + JavaClassPathConstants.MODULE_EXECUTE_PATH, }; } diff --git a/java.api.common/src/org/netbeans/modules/java/api/common/classpath/ClassPathProviderImpl.java b/java.api.common/src/org/netbeans/modules/java/api/common/classpath/ClassPathProviderImpl.java --- a/java.api.common/src/org/netbeans/modules/java/api/common/classpath/ClassPathProviderImpl.java +++ b/java.api.common/src/org/netbeans/modules/java/api/common/classpath/ClassPathProviderImpl.java @@ -1324,40 +1324,38 @@ FileObject[] path = getPrimarySrcPath(); for (int i=0; i moduleNames) throws IOException { + assert projectArtifact != null; + assert originalPathType != null; + + Project p = FileOwnerQuery.getOwner(projectArtifact); + if (p == null) { + return false; + } + FileObject modInfo = findModuleInfo(projectArtifact); + if (modInfo == null) { + return false; + } + ProjectModulesModifier delegate = p.getLookup().lookup(ProjectModulesModifier.class); + if (delegate != null) { + return delegate.addRequiredModules(originalPathType, projectArtifact, moduleNames); + } + + switch (originalPathType) { + case ClassPath.COMPILE: + case ClassPath.EXECUTE: + case JavaClassPathConstants.MODULE_COMPILE_PATH: + case JavaClassPathConstants.MODULE_EXECUTE_PATH: + return extendModuleInfo(modInfo, moduleNames); + + default: + return false; + } + } + + @Override + public boolean removeRequiredModules(String originalPathType, FileObject projectArtifact, Collection moduleNames) throws IOException { + assert projectArtifact != null; + assert originalPathType != null; + + Project p = FileOwnerQuery.getOwner(projectArtifact); + if (p == null) { + return false; + } + FileObject modInfo = findModuleInfo(projectArtifact); + if (modInfo == null) { + return false; + } + ProjectModulesModifier delegate = p.getLookup().lookup(ProjectModulesModifier.class); + if (delegate != null) { + return delegate.removeRequiredModules(originalPathType, projectArtifact, moduleNames); + } + switch (originalPathType) { + case ClassPath.COMPILE: + case ClassPath.EXECUTE: + case JavaClassPathConstants.MODULE_COMPILE_PATH: + case JavaClassPathConstants.MODULE_EXECUTE_PATH: + return removeRequiredModuleArtifacts(modInfo, moduleNames); + + default: + return false; + } + } + + private static final SpecificationVersion JDK9_SPEC = new SpecificationVersion("9"); + + @CheckForNull + private static FileObject findModuleInfo(@NonNull final FileObject artifact) { + if (JDK9_SPEC.compareTo(new SpecificationVersion(SourceLevelQuery.getSourceLevel(artifact))) < 0) { + return null; + } + ClassPath cp = ClassPath.getClassPath(artifact, ClassPath.SOURCE); + return cp == null ? null : cp.findResource("module-info.java"); //NOI18N + } + + public static boolean extendModuleInfo( + @NonNull final FileObject info, + @NonNull final Collection modules) throws IOException { + if (info == null || modules.isEmpty()) { + return false; + } + final Collection moduleNames = modules.stream() + .map((url) -> SourceUtils.getModuleName(url, true)) + .filter((name) -> name != null) + .collect(Collectors.toList()); + if (moduleNames.isEmpty()) { + return false; + } + final JavaSource js = JavaSource.forFileObject(info); + if (js == null) { + return false; + } + boolean[] modified = new boolean[1]; + js.runModificationTask((wc) -> { + wc.toPhase(JavaSource.Phase.RESOLVED); + final CompilationUnitTree cu = wc.getCompilationUnit(); + final Set knownModules = new HashSet<>(); + final ModuleTree[] module = new ModuleTree[1]; + final RequiresTree[] lastRequires = new RequiresTree[1]; + cu.accept(new TreeScanner() { + @Override + public Void visitModule(ModuleTree m, Void p) { + module[0] = m; + return super.visitModule(m, p); + } + @Override + public Void visitRequires(RequiresTree r, Void p) { + lastRequires[0] = r; + knownModules.add(r.getModuleName().toString()); + return super.visitRequires(r, p); + } + }, + null); + if (module[0] != null) { + moduleNames.removeAll(knownModules); + modified[0] = !moduleNames.isEmpty(); + final TreeMaker tm = wc.getTreeMaker(); + final List newRequires = moduleNames.stream() + .map((name) -> tm.Requires(false, false, tm.QualIdent(name))) + .collect(Collectors.toList()); + + final List newDirectives = new ArrayList<>( + module[0].getDirectives().size() + newRequires.size()); + if (lastRequires[0] == null) { + newDirectives.addAll(newRequires); + } + for (DirectiveTree dt : module[0].getDirectives()) { + newDirectives.add(dt); + if (dt == lastRequires[0]) { + newDirectives.addAll(newRequires); + } + } + final ModuleTree newModule = tm.Module( + tm.Modifiers(0, module[0].getAnnotations()), + module[0].getModuleType(), + module[0].getName(), + newDirectives); + wc.rewrite(module[0], newModule); + } + }) + .commit(); + save(info); + return modified[0]; + } + + public static boolean removeRequiredModuleArtifacts(FileObject moduleInfo, Collection modules) { + return removeRequiredModules(moduleInfo, + modules.stream() + .map((url) -> SourceUtils.getModuleName(url, true)) + .filter((name) -> name != null) + .collect(Collectors.toSet() + )); + } + + public static boolean removeRequiredModules(FileObject moduleInfo, Collection modules) { + if (moduleInfo == null || modules.isEmpty()) { + return false; + } + final boolean[] modified = new boolean[1]; + try { + final JavaSource js = JavaSource.forFileObject(moduleInfo); + if (js != null) { + js.runModificationTask((wc) -> { + wc.toPhase(JavaSource.Phase.PARSED); + final CompilationUnitTree cu = wc.getCompilationUnit(); + final Set toRemove = new HashSet<>(); + final ModuleTree[] module = new ModuleTree[1]; + cu.accept( + new TreeScanner>() { + @Override + public Void visitModule(final ModuleTree node, Set param) { + module[0] = node; + return super.visitModule(node, param); + } + @Override + public Void visitRequires(final RequiresTree node, final Set param) { + final String fqn = node.getModuleName().toString(); + if (modules.contains(fqn)) { + param.add(node); + } + return super.visitRequires(node, param); + } + }, + toRemove); + if (!toRemove.isEmpty()) { + modified[0] = true; + final List newDirectives = new ArrayList<>(module[0].getDirectives().size()); + for (DirectiveTree dt : module[0].getDirectives()) { + if (!toRemove.contains(dt)) { + newDirectives.add(dt); + } + } + final ModuleTree newModule = wc.getTreeMaker().Module( + wc.getTreeMaker().Modifiers(0, module[0].getAnnotations()), + module[0].getModuleType(), + module[0].getName(), + newDirectives); + wc.rewrite(module[0], newModule); + } + }).commit(); + save(moduleInfo); + } + } catch (IOException ioe) { + Exceptions.printStackTrace(ioe); + } + return modified[0]; + } + + private static void save(@NonNull final FileObject file) throws IOException { + final Savable save = file.getLookup().lookup(Savable.class); + if (save != null) { + save.save(); + } + } +} diff --git a/java.api.common/src/org/netbeans/modules/java/api/common/project/ui/LibrariesNode.java b/java.api.common/src/org/netbeans/modules/java/api/common/project/ui/LibrariesNode.java --- a/java.api.common/src/org/netbeans/modules/java/api/common/project/ui/LibrariesNode.java +++ b/java.api.common/src/org/netbeans/modules/java/api/common/project/ui/LibrariesNode.java @@ -128,6 +128,7 @@ import org.netbeans.modules.java.api.common.classpath.ClassPathModifier; import org.netbeans.modules.java.api.common.classpath.ClassPathSupport; import org.netbeans.modules.java.api.common.SourceRoots; +import org.netbeans.modules.java.api.common.impl.DefaultProjectModulesModifier; import org.netbeans.modules.java.api.common.util.CommonModuleUtils; import org.netbeans.modules.java.api.common.project.ui.customizer.AntArtifactItem; import org.netbeans.modules.java.api.common.project.ui.customizer.EditMediator; @@ -1187,53 +1188,7 @@ .map((url) -> SourceUtils.getModuleName(url, true)) .filter((name) -> name != null) .collect(Collectors.toSet()); - if (!modules.isEmpty()) { - try { - final JavaSource js = JavaSource.forFileObject(moduleInfo); - if (js != null) { - js.runModificationTask((wc) -> { - wc.toPhase(JavaSource.Phase.PARSED); - final CompilationUnitTree cu = wc.getCompilationUnit(); - final Set toRemove = new HashSet<>(); - final ModuleTree[] module = new ModuleTree[1]; - cu.accept( - new TreeScanner>() { - @Override - public Void visitModule(final ModuleTree node, Set param) { - module[0] = node; - return super.visitModule(node, param); - } - @Override - public Void visitRequires(final RequiresTree node, final Set param) { - final String fqn = node.getModuleName().toString(); - if (modules.contains(fqn)) { - param.add(node); - } - return super.visitRequires(node, param); - } - }, - toRemove); - if (!toRemove.isEmpty()) { - final List newDirectives = new ArrayList<>(module[0].getDirectives().size()); - for (DirectiveTree dt : module[0].getDirectives()) { - if (!toRemove.contains(dt)) { - newDirectives.add(dt); - } - } - final ModuleTree newModule = wc.getTreeMaker().Module( - wc.getTreeMaker().Modifiers(0, module[0].getAnnotations()), - module[0].getModuleType(), - module[0].getName(), - newDirectives); - wc.rewrite(module[0], newModule); - } - }).commit(); - save(moduleInfo); - } - } catch (IOException ioe) { - Exceptions.printStackTrace(ioe); - } - } + DefaultProjectModulesModifier.removeRequiredModules(moduleInfo, modules); } } } @@ -1483,7 +1438,7 @@ } } } - + private static class AddProjectAction extends AbstractAction { private final Project project; @@ -1529,9 +1484,7 @@ ClassPath.COMPILE; ProjectClassPathModifier.addAntArtifacts(artifacts, artifactURIs, projectSourcesArtifact, cpType); - if (moduleInfo != null) { - extendModuleInfo(moduleInfo, toURLs(artifactItems)); - } + DefaultProjectModulesModifier.extendModuleInfo(moduleInfo, Arrays.asList(toURLs(artifactItems))); } catch (IOException ioe) { Exceptions.printStackTrace(ioe); } catch (UnsupportedOperationException e) { @@ -1582,9 +1535,7 @@ ClassPath.COMPILE; ProjectClassPathModifier.addLibraries(libraries, projectSourcesArtifact, cpType); - if (moduleInfo != null) { - extendModuleInfo(moduleInfo, toURLs(libraries)); - } + DefaultProjectModulesModifier.extendModuleInfo(moduleInfo, Arrays.asList(toURLs(libraries))); } catch (IOException ioe) { Exceptions.printStackTrace(ioe); } catch (UnsupportedOperationException e) { @@ -1731,9 +1682,7 @@ findSourceGroup(projectSourcesArtifact, modifierImpl), cpType, ClassPathModifier.ADD_NO_HEURISTICS); - if (moduleInfo != null) { - extendModuleInfo(moduleInfo, toAddURLs.toArray(new URL[toAddURLs.size()])); - } + DefaultProjectModulesModifier.extendModuleInfo(moduleInfo, toAddURLs); } } catch (IOException ioe) { Exceptions.printStackTrace(ioe); @@ -1783,76 +1732,6 @@ return res.toArray(new URL[res.size()]); } - private static void extendModuleInfo( - @NonNull final FileObject info, - @NonNull final URL... modules) throws IOException { - final Collection moduleNames = Arrays.stream(modules) - .map((url) -> SourceUtils.getModuleName(url, true)) - .filter((name) -> name != null) - .collect(Collectors.toList()); - if (!moduleNames.isEmpty()) { - final JavaSource js = JavaSource.forFileObject(info); - if (js != null) { - js.runModificationTask((wc) -> { - wc.toPhase(JavaSource.Phase.RESOLVED); - final CompilationUnitTree cu = wc.getCompilationUnit(); - final Set knownModules = new HashSet<>(); - final ModuleTree[] module = new ModuleTree[1]; - final RequiresTree[] lastRequires = new RequiresTree[1]; - cu.accept(new TreeScanner() { - @Override - public Void visitModule(ModuleTree m, Void p) { - module[0] = m; - return super.visitModule(m, p); - } - @Override - public Void visitRequires(RequiresTree r, Void p) { - lastRequires[0] = r; - knownModules.add(r.getModuleName().toString()); - return super.visitRequires(r, p); - } - }, - null); - if (module[0] != null) { - moduleNames.removeAll(knownModules); - final TreeMaker tm = wc.getTreeMaker(); - final List newRequires = moduleNames.stream() - .map((name) -> tm.Requires(false, false, tm.QualIdent(name))) - .collect(Collectors.toList()); - - final List newDirectives = new ArrayList<>( - module[0].getDirectives().size() + newRequires.size()); - if (lastRequires[0] == null) { - newDirectives.addAll(newRequires); - } - for (DirectiveTree dt : module[0].getDirectives()) { - newDirectives.add(dt); - if (dt == lastRequires[0]) { - newDirectives.addAll(newRequires); - } - } - final ModuleTree newModule = tm.Module( - tm.Modifiers(0, module[0].getAnnotations()), - module[0].getModuleType(), - module[0].getName(), - newDirectives); - wc.rewrite(module[0], newModule); - } - }) - .commit(); - save(info); - } - } - } - - private static void save(@NonNull final FileObject file) throws IOException { - final DataObject dobj = DataObject.find(file); - final Savable save = dobj.getLookup().lookup(Savable.class); - if (save != null) { - save.save(); - } - } - private static SourceGroup findSourceGroup(FileObject fo, ClassPathModifier modifierImpl) { SourceGroup[]sgs = modifierImpl.getExtensibleSourceGroups(); for (SourceGroup sg : sgs) { diff --git a/java.project/apichanges.xml b/java.project/apichanges.xml --- a/java.project/apichanges.xml +++ b/java.project/apichanges.xml @@ -109,6 +109,19 @@ + + + Added a SPI to manipulate with project's module-info.java declarations + + + + + + Added a SPI interface, which should be implemented on project to provide backwards compatibility for + ProjectClassPathModifier for modular projects. + + + Added a LookupMerger for CompilerOptionsQueryImplementations diff --git a/java.project/manifest.mf b/java.project/manifest.mf --- a/java.project/manifest.mf +++ b/java.project/manifest.mf @@ -2,6 +2,6 @@ OpenIDE-Module: org.netbeans.modules.java.project/1 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/java/project/Bundle.properties OpenIDE-Module-Needs: javax.script.ScriptEngine.freemarker -OpenIDE-Module-Specification-Version: 1.69 +OpenIDE-Module-Specification-Version: 1.70 AutoUpdate-Show-In-Client: false diff --git a/java.project/src/org/netbeans/api/java/project/classpath/ProjectClassPathModifier.java b/java.project/src/org/netbeans/api/java/project/classpath/ProjectClassPathModifier.java --- a/java.project/src/org/netbeans/api/java/project/classpath/ProjectClassPathModifier.java +++ b/java.project/src/org/netbeans/api/java/project/classpath/ProjectClassPathModifier.java @@ -46,16 +46,26 @@ import java.io.File; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.project.JavaProjectConstants; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.api.project.SourceGroup; import org.netbeans.api.project.ant.AntArtifact; +import org.netbeans.api.project.ant.AntArtifactQuery; import org.netbeans.api.project.libraries.Library; import org.netbeans.modules.java.project.classpath.ProjectClassPathModifierAccessor; import org.netbeans.spi.java.project.classpath.ProjectClassPathModifierImplementation; +import org.netbeans.spi.java.project.classpath.ProjectModulesModifier; import org.netbeans.spi.java.project.support.LookupMergerSupport; import org.netbeans.spi.project.libraries.support.LibrariesSupport; import org.netbeans.spi.project.support.ant.PropertyUtils; @@ -63,6 +73,7 @@ import org.openide.filesystems.FileUtil; import org.openide.filesystems.URLMapper; import org.openide.util.BaseUtilities; +import org.openide.util.Lookup; import org.openide.util.Parameters; /** @@ -73,6 +84,7 @@ * @since org.netbeans.modules.java.project/1 1.10 */ public class ProjectClassPathModifier { + private static final Logger LOG = Logger.getLogger(ProjectClassPathModifier.class.getName()); private ProjectClassPathModifier() {} @@ -94,7 +106,10 @@ if (extensible.pcmi != null) { assert extensible.sg != null; assert extensible.classPathType != null; - return ProjectClassPathModifierAccessor.INSTANCE.addLibraries (libraries, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r1 = ProjectClassPathModifierAccessor.INSTANCE.addLibraries (libraries, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r2 = !extensible.classPathType.equals(classPathType) && + addRootsToModinfo(classPathType, projectArtifact, toURLs(libraries)); + return r1 || r2; } else { boolean result = false; for (Library library : libraries) { @@ -121,7 +136,10 @@ if (extensible.pcmi != null) { assert extensible.sg != null; assert extensible.classPathType != null; - return ProjectClassPathModifierAccessor.INSTANCE.removeLibraries (libraries, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r1 = ProjectClassPathModifierAccessor.INSTANCE.removeLibraries (libraries, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r2 = !extensible.classPathType.equals(classPathType) && + removeRootsFromModinfo(classPathType, projectArtifact, toURLs(libraries)); + return r1 || r2; } else { throw new UnsupportedOperationException("Cannot remove libraries using " + extensible); // NOI18N } @@ -146,7 +164,9 @@ if (extensible.pcmi != null) { assert extensible.sg != null; assert extensible.classPathType != null; - return ProjectClassPathModifierAccessor.INSTANCE.addRoots (classPathRoots, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r1 = ProjectClassPathModifierAccessor.INSTANCE.addRoots (classPathRoots, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r2 = !extensible.classPathType.equals(classPathType) && addRootsToModinfo(classPathType, projectArtifact, Arrays.asList(classPathRoots)); + return r1 || r2; } else { boolean result = false; for (URL urlToAdd : classPathRoots) { @@ -183,7 +203,10 @@ if (extensible.pcmi != null) { assert extensible.sg != null; assert extensible.classPathType != null; - return ProjectClassPathModifierAccessor.INSTANCE.addRoots (classPathRoots, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r1 = ProjectClassPathModifierAccessor.INSTANCE.addRoots (classPathRoots, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r2 = !extensible.classPathType.equals(classPathType) && + addRootsToModinfo(classPathType, projectArtifact, toURLs(Arrays.asList(classPathRoots))); + return r1 || r2; } else { boolean result = false; final Project project = FileOwnerQuery.getOwner(projectArtifact); @@ -226,7 +249,10 @@ if (extensible.pcmi != null) { assert extensible.sg != null; assert extensible.classPathType != null; - return ProjectClassPathModifierAccessor.INSTANCE.removeRoots (classPathRoots, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r1 = ProjectClassPathModifierAccessor.INSTANCE.removeRoots (classPathRoots, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r2 = !extensible.classPathType.equals(classPathType) && + removeRootsFromModinfo(classPathType, projectArtifact, Arrays.asList(classPathRoots)); + return r1 || r2; } else { throw new UnsupportedOperationException("Cannot remove roots from " + extensible); // NOI18N } @@ -279,7 +305,10 @@ if (extensible.pcmi != null) { assert extensible.sg != null; assert extensible.classPathType != null; - return ProjectClassPathModifierAccessor.INSTANCE.addAntArtifacts (artifacts, artifactElements, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r1 = ProjectClassPathModifierAccessor.INSTANCE.addAntArtifacts (artifacts, artifactElements, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r2 = !extensible.classPathType.equals(classPathType) && + addRootsToModinfo(classPathType, projectArtifact, toURLs(Arrays.asList(artifactElements))); + return r1 || r2; } else { boolean result = false; for (int i=0; i< artifacts.length; i++) { @@ -312,7 +341,10 @@ if (extensible.pcmi != null) { assert extensible.sg != null; assert extensible.classPathType != null; - return ProjectClassPathModifierAccessor.INSTANCE.addProjects (projects, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r1 = ProjectClassPathModifierAccessor.INSTANCE.addProjects (projects, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r2 = !extensible.classPathType.equals(classPathType) && + addRootsToModinfo(classPathType, projectArtifact, toURLs(projects)); + return r1 || r2; } else { throw new UnsupportedOperationException("Cannot add project as dependency. Missing ProjectClassPathModifierImplementation service in project type."); } @@ -339,7 +371,10 @@ if (extensible.pcmi != null) { assert extensible.sg != null; assert extensible.classPathType != null; - return ProjectClassPathModifierAccessor.INSTANCE.removeAntArtifacts (artifacts, artifactElements, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r1 = ProjectClassPathModifierAccessor.INSTANCE.removeAntArtifacts (artifacts, artifactElements, extensible.pcmi, extensible.sg, extensible.classPathType); + boolean r2 = !extensible.classPathType.equals(classPathType) && + removeRootsFromModinfo(classPathType, projectArtifact, toURLs(Arrays.asList(artifactElements))); + return r1 || r2; } else { throw new UnsupportedOperationException("Cannot remove artifacts from " + extensible); // NOI18N } @@ -368,6 +403,10 @@ throw new UnsupportedOperationException("No project found to correspond to " + FileUtil.getFileDisplayName(projectArtifact)); // NOI18N } final ProjectClassPathModifierImplementation pm = project.getLookup().lookup(ProjectClassPathModifierImplementation.class); + final ProjectModulesModifier pmm = Lookup.getDefault().lookup(ProjectModulesModifier.class); + + String substModulePath = pmm == null ? null : pmm.provideModularClasspath(projectArtifact, classPathType); + final String _classPathType = substModulePath == null ? classPathType : substModulePath; if (pm != null) { final SourceGroup[] sgs = ProjectClassPathModifierAccessor.INSTANCE.getExtensibleSourceGroups(pm); assert sgs != null : "Class: " + pm.getClass() + " returned null as source groups."; //NOI18N @@ -375,26 +414,33 @@ if ((projectArtifact == sg.getRootFolder() || FileUtil.isParentOf(sg.getRootFolder(),projectArtifact)) && sg.contains(projectArtifact)) { final String[] types = ProjectClassPathModifierAccessor.INSTANCE.getExtensibleClassPathTypes(pm,sg); assert types != null : "Class: " + pm.getClass() + " returned null as classpath types."; //NOI18N + boolean originalFound = false; for (String type : types) { - if (classPathType.equals(type)) { - String label = "ProjectClassPathModifierImplementation for " + classPathType + " on " + FileUtil.getFileDisplayName(sg.getRootFolder()); // NOI18N + if (_classPathType.equals(type)) { + String label = "ProjectClassPathModifierImplementation for " + _classPathType + " on " + FileUtil.getFileDisplayName(sg.getRootFolder()); // NOI18N return new Extensible(pm, sg, type,label); + } else if (classPathType.equals(type)) { + originalFound = true; } } + if (originalFound) { + String label = "ProjectClassPathModifierImplementation for " + classPathType + " on " + FileUtil.getFileDisplayName(sg.getRootFolder()); // NOI18N + return new Extensible(pm, sg, classPathType,label); + } } } throw new UnsupportedOperationException("Project in " + FileUtil.getFileDisplayName(project.getProjectDirectory()) + " of " + project.getClass() + - " has a ProjectClassPathModifierImplementation but it will not handle " + classPathType + " for " + FileUtil.getFileDisplayName(projectArtifact) + + " has a ProjectClassPathModifierImplementation but it will not handle " + _classPathType + " for " + FileUtil.getFileDisplayName(projectArtifact) + " extensible source groups: " + sourceGroupsToString(sgs)); // NOI18N } else { final org.netbeans.spi.java.project.classpath.ProjectClassPathExtender pe = project.getLookup().lookup(org.netbeans.spi.java.project.classpath.ProjectClassPathExtender.class); if (pe != null) { - if (classPathType.equals(ClassPath.COMPILE)) { + if (_classPathType.equals(ClassPath.COMPILE)) { return new Extensible(pe, "ProjectClassPathExtender for " + FileUtil.getFileDisplayName(project.getProjectDirectory())); // NOI18N } else { throw new UnsupportedOperationException("Project in " + FileUtil.getFileDisplayName(project.getProjectDirectory()) + " of " + project.getClass() + - " has a ProjectClassPathExtender in its lookup but no ProjectClassPathModifierImplementation to handle " + classPathType); // NOI18N + " has a ProjectClassPathExtender in its lookup but no ProjectClassPathModifierImplementation to handle " + _classPathType); // NOI18N } } else { throw new UnsupportedOperationException("Project in " + FileUtil.getFileDisplayName(project.getProjectDirectory()) + " of " + project.getClass() + @@ -550,5 +596,62 @@ } }; } - + + private static Collection toURLs(Collection libs) { + URL[] urls = new URL[libs.size()]; + int index = 0; + for (URI u : libs) { + try { + urls[index++] = u.toURL(); + } catch (MalformedURLException ex) { + return Collections.emptyList(); + } + } + return Arrays.asList(urls); + } + + private static Collection toURLs(Project[] prjs) { + return + toURLs( + Arrays.asList(prjs).stream().flatMap( + (prj) -> Arrays.asList( + AntArtifactQuery.findArtifactsByType(prj, JavaProjectConstants.ARTIFACT_TYPE_JAR) + ).stream()). + flatMap((a) -> Arrays.asList(a.getArtifactLocations()).stream()). + collect(Collectors.toList()) + ); + } + + private static Collection toURLs(Library[] libraries) { + return Arrays.stream(libraries).flatMap((l) -> l.getContent("classpath").stream()). + collect(Collectors.toList()); + } + + private static boolean removeRootsFromModinfo(String originalPathType, FileObject artifact, Collection libs) { + ProjectModulesModifier pmm = Lookup.getDefault().lookup(ProjectModulesModifier.class); + if (pmm == null) { + return false; + } else { + try { + return pmm.removeRequiredModules(originalPathType, artifact, libs); + } catch (IOException ex) { + LOG.log(Level.WARNING, "Could not remove module requires", ex); + return false; + } + } + } + + private static boolean addRootsToModinfo(String originalPathType, FileObject artifact, Collection libs) { + ProjectModulesModifier pmm = Lookup.getDefault().lookup(ProjectModulesModifier.class); + if (pmm == null) { + return false; + } else { + try { + return pmm.addRequiredModules(originalPathType, artifact, libs); + } catch (IOException ex) { + LOG.log(Level.WARNING, "Could not declare module requires", ex); + return false; + } + } + } } diff --git a/java.project/src/org/netbeans/modules/java/project/ProjectClassPathProvider.java b/java.project/src/org/netbeans/modules/java/project/ProjectClassPathProvider.java --- a/java.project/src/org/netbeans/modules/java/project/ProjectClassPathProvider.java +++ b/java.project/src/org/netbeans/modules/java/project/ProjectClassPathProvider.java @@ -76,7 +76,7 @@ if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "findClassPath({0}, {1}) -> {2} from {3}", new Object[] {file, type, result, cpp}); } - return result; + return result != null ? result : ClassPath.EMPTY; } else { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "cpp.findClassPath({0}, {1}) -> null", new Object[] {file, type}); diff --git a/java.project/src/org/netbeans/spi/java/project/classpath/ProjectModulesModifier.java b/java.project/src/org/netbeans/spi/java/project/classpath/ProjectModulesModifier.java new file mode 100644 --- /dev/null +++ b/java.project/src/org/netbeans/spi/java/project/classpath/ProjectModulesModifier.java @@ -0,0 +1,109 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + */ +package org.netbeans.spi.java.project.classpath; + +import java.io.IOException; +import java.net.URL; +import java.util.Collection; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.classpath.JavaClassPathConstants; +import org.openide.filesystems.FileObject; + +/** + * Allows to update the module declaration in projects which support JDK9. For modular projects, + * it is not sufficient just to add artifacts on classpath - modular projects cannot access unnamed module, + * so the project would not see such classes/resources during compilation. Even if added to the module path, + * a directive needs to be added so that compiler and runtime honour the dependency. + *

+ * This SPI allows the project implementation for modular project to diverge additions to a more suitable classpath, + * and to update the module information. + *

+ * Project-specific implementation should be placed in project lookup. If not present, default implementation + * will handle the requests, so that artifacts, libraries and projects will land on {@link JavaClassPathConstants#MODULE_COMPILE_PATH} + * or {@link JavaClassPathConstants#MODULE_EXECUTE_PATH} respectively. The default implementation does nothing for projects (sources), which do + * not use {@code module-info} declaration, although they target JDK 9. + * + * @author sdedic + * @since 1.70 + */ +public interface ProjectModulesModifier { + /** + * Translates the classpath type into a proper modular one, if necessary. If the project + * is modular, artifacts added to {@link ClassPath#COMPILE} does not make sense, since it + * is inaccessible (without specific startup parameters), so modular project may need to + * diverge the request to some other classpath type. + * If the project is not modular, or modular project can use artifacts from the original classpath + * type, the value of {@code null} should be returned. If {@code null} is returned, + * the {@link #addRequiredModules} or {@link #removeRequiredModuels} should not be called. + * + * @param classPathType original classpath type + * @param projectArtifact source, directory which will determine the proper {@code module-info.java} + * @return modular replacement for the {@code classPathType}. + */ + public String provideModularClasspath(FileObject projectArtifact, String classPathType); + + /** + * Adds "requires" clauses to module-info. "requires" directive will be created + * for each of the module names in module-info which applies to the "source" file. + * The implementation may ignore requests for classpath types, which do not interact with the + * module system. + * + * @param projectArtifact source, directory which will determine the proper {@code module-info.java} + * @param locations module names to add required directives for + * @param pathType the original path type + * @return true, if some modification has been made + * @throws IOException on error + */ + public boolean addRequiredModules(String pathType, FileObject projectArtifact, Collection locations) throws IOException; + + /** + * Removes "requires" clauses from module-info. Requires directives will be removed for all + * modules in the "moduleNames". + * The implementation may ignore requests for classpath types, which do not interact with the + * module system. + * + * @param projectArtifact source, directory which will determine the proper {@code module-info.java} + * @param locations module names whose required directives should be removed + * @param pathType the original path type + * @return true, if some modification has been made + * @throws IOException on error + */ + public boolean removeRequiredModules(String pathType, FileObject projectArtifact, Collection locations) throws IOException; +} diff --git a/jshell.support/src/org/netbeans/modules/jshell/project/REPLAction2.java b/jshell.support/src/org/netbeans/modules/jshell/project/REPLAction2.java --- a/jshell.support/src/org/netbeans/modules/jshell/project/REPLAction2.java +++ b/jshell.support/src/org/netbeans/modules/jshell/project/REPLAction2.java @@ -46,6 +46,7 @@ import org.netbeans.api.project.Project; import org.netbeans.modules.jshell.env.JShellEnvironment; import org.netbeans.modules.jshell.env.ShellRegistry; +import org.netbeans.modules.projectapi.ProjectFileBuiltQuery; import org.netbeans.spi.project.ui.support.ProjectActionPerformer; import org.netbeans.spi.project.ui.support.ProjectSensitiveActions; import org.openide.awt.ActionID; @@ -81,6 +82,7 @@ public void perform(Project project) { JShellEnvironment env; try { + env = ShellRegistry.get().openProjectSession(project); env.open(); } catch (IOException ex) { diff --git a/maven/src/org/netbeans/modules/maven/classpath/CPExtender.java b/maven/src/org/netbeans/modules/maven/classpath/CPExtender.java --- a/maven/src/org/netbeans/modules/maven/classpath/CPExtender.java +++ b/maven/src/org/netbeans/modules/maven/classpath/CPExtender.java @@ -254,6 +254,8 @@ ClassPath.COMPILE, ClassPath.EXECUTE, JavaClassPathConstants.COMPILE_ONLY, + JavaClassPathConstants.MODULE_COMPILE_PATH, + JavaClassPathConstants.MODULE_EXECUTE_PATH, JavaClassPathConstants.PROCESSOR_PATH }; } @@ -379,7 +381,7 @@ } private static String findScope(SourceGroup grp, String type) { - String scope = ClassPath.EXECUTE.equals(type) ? Artifact.SCOPE_RUNTIME : null; //NOI18N + String scope = ClassPath.EXECUTE.equals(type) || JavaClassPathConstants.MODULE_EXECUTE_PATH.equals(type)? Artifact.SCOPE_RUNTIME : null; //NOI18N //figure if we deal with test or regular sources. String name = grp.getName(); if (MavenSourcesImpl.NAME_TESTSOURCE.equals(name)) {