You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@netbeans.apache.org by GitBox <gi...@apache.org> on 2020/12/03 08:42:07 UTC

[GitHub] [netbeans] JaroslavTulach opened a new pull request #2575: Open GraalVM sources out of the box

JaroslavTulach opened a new pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575


   The [GraalVM](http://graalvm.org) sources use a fine tuned build system called [mx](http://github.com/graalvm/mx) (shorthand of [MaXine](https://en.wikipedia.org/wiki/Maxine_Virtual_Machine)). The `mx` command seems to work fine for the special purposes of building own enhanced JVM and it looks like it is going to stay. The goal of this PR is to establish a solid infrastructure to make sure the _Apache NetBeans IDE_ as well as products based on top of it (let's name [IGV](https://www.graalvm.org/tools/igv/) and [VSNetBeans](https://cwiki.apache.org/confluence/display/NETBEANS/Apache+NetBeans+Extension+for+Visual+Studio+Code)) understand the `suite.py` metadata files and can use the `mx` command to build and run/debug unit tests.
   
   The `mx` build system integrates with various IDEs via `mx ideinit` command. That command generates essential metadata for Eclipse, IntelliJ as well as NetBeans. However the NetBeans solution is based on *Ant* scripts. It has some limitations and moreover the Apache NetBeans project decided to focus on Maven (and Gradle) rather than promoting the old fashioned Ant based projects. As such, I'd like to deprecate the `mx netbeansinit` command in `mx` and rather propose all NetBeans users to switch to native *mx projects* support proposed by this PR.
   
   The `mx` build system isn't in wide spread use (however it *is* used by independent groups like at the [Postdam university](https://github.com/hpi-swa/trufflesqueak) for its unique features allowing to easily extend the JVM). Providing support for such exotic build system may seem unnecessary, but there are reasons to do it. We already have support for _OpenJDK projects_, which is also kind of special as well. As far as I know there were no problems with it. It fully builds on the modular architecture of NetBeans and comes as a single module. Moreover it gives NetBeans a fantatic story for OpenJDK developers. I'd like to repeat the same with the *mx projects* support - a single, isolated module causing no harm, handy (out of the box) for those who use `mx` as their build system. Acceptable?
   
   The PR isn't ready to be merged right now, I still need to find out what to do with the tests. Still, I wanted to bring this idea to your attention. If we agree this code is _"integratable"_ I'll polish the code and tests to comply with the Apache requirements.
   
   PS: My hope is that the approach of this PR is found acceptable for Apache NetBeans project. If that happens the code of this module is going to be more easily approachable to other people building NetBeans and together we can improve experience of NetBeans users even when working with other projects (I dream about improved _compile on save capability_, better API for _external execution_ & co.).


----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] gilles-duboscq commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
gilles-duboscq commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544107561



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {

Review comment:
       There is indeed a 2-level limit in mx: starting from the directory in which the various repositories are checked out, the "importer" specifies if the imported suite is found at depth 1 or 2, no other depth can be specified (default is depth 1 and a `"subdir" : True` attribute switches the depth to 2).




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544076389



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteClassPathProvider.java
##########
@@ -0,0 +1,100 @@
+/*
+ * 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.mx.project;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+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.platform.JavaPlatformManager;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery.Result;
+import org.netbeans.spi.java.classpath.ClassPathProvider;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.AnnotationProcessingQueryImplementation;
+import org.netbeans.spi.project.ui.ProjectOpenedHook;
+import org.openide.filesystems.FileObject;
+
+final class SuiteClassPathProvider extends ProjectOpenedHook implements ClassPathProvider, AnnotationProcessingQueryImplementation {
+    private final SuiteProject project;
+    private final ClassPath bootCP;
+
+    public SuiteClassPathProvider(SuiteProject project) {
+        this.project = project;
+        List<ClassPath.Entry> entries = JavaPlatformManager.getDefault().getDefaultPlatform().getBootstrapLibraries().entries();
+        List<URL> roots = new ArrayList<>();
+        for (ClassPath.Entry entry : entries) {
+            URL root = entry.getURL();
+            if (root.getPath().contains("/graal-sdk.jar")) {

Review comment:
       Unless I am mistaken this is because GraalVM (based on JDK8) contains these JARs on boot class path and we want to avoid them and rather use JARs built from the project sources.




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544176060



##########
File path: java/java.mx.project/test/unit/src/org/netbeans/modules/java/mx/project/SuiteCheck.java
##########
@@ -0,0 +1,257 @@
+/*
+ * 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.mx.project;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+import javax.tools.Diagnostic;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertSame;
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.junit.Assume;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.classpath.JavaClassPathConstants;
+import org.netbeans.api.java.queries.BinaryForSourceQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.java.source.CompilationController;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.ProjectUtils;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.api.project.ui.OpenProjects;
+import org.netbeans.junit.NbTestCase;
+import org.netbeans.modules.parsing.api.indexing.IndexingManager;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.Utilities;
+
+abstract class SuiteCheck extends NbTestCase {
+    SuiteCheck(String name) {
+        super(name);
+        log(Level.INFO, "Test created by %s classloader", getClass().getClassLoader());
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        log(Level.INFO, "setUp - init");
+        super.setUp();
+        final Logger tooVerboseLogger = Logger.getLogger("org.netbeans.core.startup.InstalledFileLocatorImpl");
+        tooVerboseLogger.setUseParentHandlers(false);
+        try {
+            MxSuite.parse(null);
+        } catch (LinkageError err) {
+            Assume.assumeNoException("Cannot initialize Polyglot API, are you using GraalVM?", err);
+        }
+        log(Level.INFO, "setUp - exit");
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        Enumeration<String> en = LogManager.getLogManager().getLoggerNames();
+        while (en.hasMoreElements()) {
+            String n = en.nextElement();
+            Logger l = LogManager.getLogManager().getLogger(n);
+            boolean first = true;
+            if (l == null || l.getHandlers() == null) {
+                continue;
+            }
+            for (Handler h : l.getHandlers()) {
+                if (first) {
+                    System.err.println("cleaning logger '" + n + "'");
+                    first = false;
+                }
+                System.err.println("  removing handler: " + h);
+                l.removeHandler(h);
+            }
+        }
+    }
+
+    @Override
+    protected int timeOut() {
+        return 1_200_000;
+    }
+
+    protected final void verifyNoErrorsInSuite(final String suiteName, String... onlySourceGroups) throws IllegalArgumentException, IOException, URISyntaxException {
+        long begin = System.currentTimeMillis();
+        File sibling = findSuite(suiteName);
+
+        FileObject fo = FileUtil.toFileObject(sibling);
+        assertNotNull("project directory found", fo);
+
+        log(Level.INFO, "Recognizing project %s", fo);
+        long now = System.currentTimeMillis();
+        Project p = ProjectManager.getDefault().findProject(fo);
+        long took = System.currentTimeMillis() - now;
+        assertNotNull("project found", p);
+        log(Level.INFO, "Project found %s in %d ms", p, took);
+        assertEquals("It is suite project: " + p, "SuiteProject", p.getClass().getSimpleName());
+        OpenProjects.getDefault().open(new Project[]{p}, false);
+
+        StringBuilder errors = new StringBuilder();
+        FileObject[] errornous = { null };
+        Sources src = ProjectUtils.getSources(p);
+        int cnt = 0;
+        for (SourceGroup sourceGroup : src.getSourceGroups("java")) {
+            if (sourceGroup instanceof Compliance.Provider) {
+                Compliance c = ((Compliance.Provider) sourceGroup).getCompliance();
+                if (!c.includes(8)) {
+                    log(Level.INFO, "Skipping check of %s with compliance %s", sourceGroup, c);
+                    continue;
+                }
+            }
+            FOUND: if (onlySourceGroups.length > 0) {
+                for (String gName : onlySourceGroups) {
+                    if (sourceGroup.getDisplayName().equals(gName)) {
+                        cnt++;
+                        break FOUND;
+                    }
+                }
+                // not found
+                continue;
+            }
+            assertSourcesNoError(p, errornous, errors, sourceGroup.getRootFolder(), begin);
+        }
+        assertCompilationErrors(errors, errornous);
+
+        assertEquals("Exactly as many source groups tested as requested", onlySourceGroups.length, cnt);
+    }
+
+    protected final File findSuite(String suite) throws URISyntaxException {
+        File location = getDataDir();
+        while (location != null) {
+            File graal = new File(location, "graal");
+            File suiteDir = new File(graal, suite);
+            if (suiteDir.isDirectory()) {
+                return suiteDir;
+            }
+            location = location.getParentFile();
+        }
+        fail("Cannot find truffle next to " + getDataDir());
+        return null;
+    }
+
+    private void assertSourcesNoError(Project project, FileObject[] errornous, StringBuilder errors, FileObject dir, long begin) throws IOException {
+        long now = System.currentTimeMillis();
+        log(Level.INFO, "assertSourcesNoError for %s", dir);
+        IndexingManager.getDefault().refreshIndexAndWait(dir.toURL(), null, false);
+        log(Level.INFO, "      refresh done       %s", dir);
+        Enumeration<? extends FileObject> en = dir.getChildren(true);
+        int nonJavaCount = 0;
+        int javaCount = 0;
+        while (en.hasMoreElements()) {
+            FileObject fo = en.nextElement();
+            if (fo.isFolder()) {
+                continue;
+            }
+            Project prj = FileOwnerQuery.getOwner(fo);
+            assertSame("FileOwnerQuery returns the right project", project, prj);
+            if (!fo.hasExt("java")) {
+                nonJavaCount++;
+                continue;
+            }
+            JavaSource source = JavaSource.forFileObject(fo);
+            if (source == null) {
+                fail("No source for " + fo);
+            }
+            javaCount++;
+            BinaryForSourceQuery.Result res = BinaryForSourceQuery.findBinaryRoots(dir.toURL());
+            assertEquals("There is one binary root: " + Arrays.toString(res.getRoots()), 1, res.getRoots().length);
+            OK: for (URL root : res.getRoots()) {
+                SourceForBinaryQuery.Result2 res2 = SourceForBinaryQuery.findSourceRoots2(root);
+                assertTrue("Has to prefer sources", res2.preferSources());
+                for (FileObject src : res2.getRoots()) {
+                    if (src.equals(dir)) {
+                        break OK;
+                    }
+                }
+                fail("Expecting to find " + dir + " among:\n" + Arrays.toString(res2.getRoots()));
+            }
+            source.runUserActionTask((CompilationController p) -> {

Review comment:
       Is there a better way to verify project sources are fine from a NetBeans Java Source support perspective?




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] jlahoda commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
jlahoda commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r537034948



##########
File path: java/java.mx.project/release/org.eclipse.jdt.core.prefs
##########
@@ -0,0 +1,442 @@
+# Licensed to the Apache Software Foundation (ASF) under one

Review comment:
       What is the specific use of this file? Is it better to have it hardcoded, or read the version from mx?

##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/CoreSuite.java
##########
@@ -0,0 +1,732 @@
+/*
+ * 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.mx.project;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary.Arch;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+
+final class CoreSuite {
+    private static final class MapOf<K,V> {
+        private final Map<K,V> map = new HashMap<>();
+
+        MapOf<K, V> of(K k, V v) {
+            map.put(k, v);
+            return this;
+        }
+
+
+        Map<K,V> build() {
+            return map;
+        }
+    }
+
+    private static <K,V> MapOf<K,V> mapOf(Class<K> keyClass, Class<V> valueClass) {
+        return new MapOf<>();
+    }
+
+    private static MxSuite createMxSuite(
+        String defaultLicense,
+        Map<String, MxDistribution> distributions,
+        MxImports imports,
+        Map<String, MxLibrary> jdkLibraries,
+        Map<String, MxLibrary> libraries,
+        String mxversion,
+        String name,
+        Map<String, MxProject> projects
+    ) {
+        return new MxSuite() {
+            @Override
+            public String mxversion() {
+                return mxversion;
+            }
+
+            @Override
+            public String name() {
+                return name;
+            }
+
+            @Override
+            public String defaultLicense() {
+                return defaultLicense;
+            }
+
+            @Override
+            public Map<String, MxLibrary> libraries() {
+                return libraries;
+            }
+
+            @Override
+            public Map<String, MxLibrary> jdklibraries() {
+                return jdkLibraries;
+            }
+
+            @Override
+            public MxImports imports() {
+                return imports;
+            }
+
+            @Override
+            public Map<String, MxProject> projects() {
+                return projects;
+            }
+
+            @Override
+            public Map<String, MxDistribution> distributions() {
+                return distributions;
+            }
+        };
+    }
+
+    private static MxDistribution createMxDistribution(
+        List<String> dependencies,
+        List<String> distDependencies,
+        List<String> exclude,
+        List<String> strip
+    ) {
+        return new MxDistribution() {
+            @Override
+            public List<String> dependencies() {
+                return dependencies;
+            }
+
+            @Override
+            public List<String> distDependencies() {
+                return distDependencies;
+            }
+
+            @Override
+            public List<String> exclude() {
+                return exclude;
+            }
+
+            @Override
+            public List<String> strip() {
+                return strip;
+            }
+        };
+    }
+
+    private static MxProject createMxProject(
+        List<String> annotationProcessors,
+        List<String> dependencies,
+        String dir,
+        String javaCompliance,
+        List<String> sourceDirs,
+        String subDir
+    ) {
+        return new MxProject() {
+            @Override
+            public String dir() {
+                return dir;
+            }
+
+            @Override
+            public String subDir() {
+                return subDir;
+            }
+
+            @Override
+            public List<String> sourceDirs() {
+                return sourceDirs;
+            }
+
+            @Override
+            public List<String> dependencies() {
+                return dependencies;
+            }
+
+            @Override
+            public List<String> annotationProcessors() {
+                return annotationProcessors;
+            }
+
+            @Override
+            public String javaCompliance() {
+                return javaCompliance;
+            }
+        };
+    }
+
+    private static MxLibrary createMxLibrary(
+        List<String> dependencies,
+        Map<String, MxLibrary.Arch> osArch,
+        String path,
+        String sha1,
+        List<String> urls
+    ) {
+        return new MxLibrary() {
+            @Override
+            public String sha1() {
+                return sha1;
+            }
+
+            @Override
+            public List<String> urls() {
+                return urls;
+            }
+
+            @Override
+            public Map<String, MxLibrary.Arch> os_arch() {
+                return osArch;
+            }
+
+            @Override
+            public List<String> dependencies() {
+                return dependencies;
+            }
+
+            @Override
+            public String path() {
+                return path;
+            }
+
+        };
+    }
+
+    private static MxLibrary.Arch createArch(MxLibrary amd64) {
+        return new MxLibrary.Arch() {
+            @Override
+            public MxLibrary amd64() {
+                return amd64;
+            }
+        };
+    }
+
+    static final MxSuite CORE_5_279_0;
+    static {

Review comment:
       I wonder - can we parse this at runtime from something? Like, maybe, mx/suite.py? It feels weird to have this information hardcoded.




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r538077621



##########
File path: java/java.mx.project/release/org.eclipse.jdt.core.prefs
##########
@@ -0,0 +1,442 @@
+# Licensed to the Apache Software Foundation (ASF) under one

Review comment:
       This file was added by @dbalek to support [Eclipse Formatter by Benno](http://plugins.netbeans.org/plugin/50877/eclipse-code-formatter-for-java-eclipse-mars-4-5).




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544102847



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static SuiteSources findSuiteSources(FileObject root, String name) throws IllegalArgumentException {
+        FileObject impDir = root.getFileObject(name);
+        if (impDir != null) {
+            try {
+                Project impPrj = ProjectManager.getDefault().findProject(impDir);
+                return impPrj == null ? null : impPrj.getLookup().lookup(SuiteSources.class);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceGroup[] getSourceGroups(String string) {
+        return groups();
+    }
+
+    Group[] groups() {
+        return groups.toArray(new Group[0]);
+    }
+
+    Group findGroup(FileObject fo) {
+        for (Group g : groups) {
+            if (g.contains(fo)) {
+                return g;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void addChangeListener(ChangeListener cl) {
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener cl) {
+    }
+
+    private static FileObject findPrjDir(FileObject dir, String prjName, MxProject prj) {
+        if (dir == null) {
+            return null;
+        }
+        if (prj.dir() != null) {
+            return dir.getFileObject(prj.dir());
+        }
+        if (prj.subDir() != null) {
+            dir = dir.getFileObject(prj.subDir());
+            if (dir == null) {
+                return null;
+            }
+        }
+        return dir.getFileObject(prjName);
+    }
+
+    private Collection<Dep> transitiveDeps(Dep current, Map<String, Dep> fill) {
+        current.owner().computeTransitiveDeps();
+        final Collection<Dep> currentAllDeps = current.allDeps();
+        if (currentAllDeps == Collections.<Dep>emptySet()) {
+            throw new IllegalStateException("Cyclic dep on " + current.getName());
+        } else if (currentAllDeps != null) {
+            return currentAllDeps;
+        }
+        current.setAllDeps(Collections.emptySet());
+        TreeSet<Dep> computing = new TreeSet<>();
+        computing.add(current);
+        for (String depName : current.depNames()) {
+            Dep dep = fill.get(depName);
+            if (dep == null) {
+                int colon = depName.lastIndexOf(':');
+                dep = fill.get(depName.substring(colon + 1));
+                if (dep == null) {
+                    LOG.log(Level.INFO, "dep not found: {0}", depName);
+                    continue;
+                }
+            }
+            Collection<Dep> allDeps = transitiveDeps(dep, fill);
+            computing.addAll(allDeps);
+        }
+        current.setAllDeps(computing);
+        return computing;
+    }
+
+    private static void registerLibs(Map<String, MxLibrary> collect, String prefix, Map<String, MxLibrary> libraries) {
+        for (Map.Entry<String, MxLibrary> entry : libraries.entrySet()) {
+            String key = entry.getKey();
+            MxLibrary lib = entry.getValue();
+            if (prefix == null) {
+                collect.put(key, lib);
+            } else {
+                collect.put(prefix + ":" + key, lib);
+            }
+        }
+    }
+
+    private void registerDeps(String prefix, Map<String, Dep> fillDeps) {
+        for (Library library : libraries) {
+            fillDeps.put(prefix + ":" + library.getName(), library);
+        }
+        for (Dist d : distributions) {
+            fillDeps.put(prefix + ":" + d.getName(), d);
+        }
+        for (Map.Entry<String, SuiteSources> s : imported.entrySet()) {
+            s.getValue().registerDeps(s.getKey(), fillDeps);
+        }
+    }
+
+    @Override
+    public Group findBinaryRoots2(URL url) {
+        final FileObject srcFo = URLMapper.findFileObject(url);
+        for (Group group : this.groups) {
+            if (group.contains(srcFo)) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public URL[] computeRoots(Group group) {
+        if (group.binDir != null) {
+            return new URL[] { group.binDir.toURL() };
+        } else {
+            return new URL[0];
+        }
+    }
+
+    @Override
+    public boolean computePreferBinaries(Group result) {
+        return true;
+    }
+
+    @Override
+    public void computeChangeListener(Group result, boolean bln, ChangeListener cl) {
+    }
+
+    @Override
+    public SourceForBinaryQueryImplementation2.Result findSourceRoots2(URL url) {
+        this.computeTransitiveDeps();
+        for (Dist dist : this.distributions) {
+            URL jar;
+            try {
+                jar = dist.getJarRoot();
+                if (jar == null) {
+                    continue;
+                }
+            } catch (MalformedURLException ok) {
+                continue;
+            }
+            if (jar.equals(url)) {
+                List<FileObject> roots = new ArrayList<>();
+                for (Group d : dist.getContributingGroups()) {
+                    roots.add(d.srcDir);
+                    roots.add(d.srcGenDir);
+                }
+                return new ImmutableResult(roots.toArray(new FileObject[roots.size()]));
+            }
+        }
+        for (Group group : this.groups) {
+            if (group.binDir != null && group.binDir.toURL().equals(url)) {
+                return new ImmutableResult(group.srcDir, group.srcGenDir);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceForBinaryQuery.Result findSourceRoots(URL url) {
+        return findSourceRoots2(url);
+    }
+
+    final Iterable<File> jdks() {
+        Set<File> jdks = new LinkedHashSet<>();
+        String home = System.getProperty("user.home");
+        if (home != null) {
+            File userEnv = new File(new File(new File(home), ".mx"), "env");
+            findJdksInEnv(jdks, userEnv);
+        }
+        FileObject suiteEnv = dir.getFileObject("mx." + dir.getNameExt() + "/env");
+        if (suiteEnv != null) {
+            findJdksInEnv(jdks, FileUtil.toFile(suiteEnv));
+        }
+
+        String javaHomeEnv = System.getenv("JAVA_HOME");
+        if (javaHomeEnv != null) {
+            jdks.add(new File(javaHomeEnv));
+        }
+        String javaHomeProp = System.getProperty("java.home");
+        if (javaHomeProp != null) {
+            jdks.add(new File(javaHomeProp));
+        }
+        return jdks;
+    }
+
+    private void findJdksInEnv(Set<File> jdks, File env) {
+        if (env == null || !env.isFile()) {
+            return;
+        }
+        try (final FileInputStream is = new FileInputStream(env)) {
+            Properties p = new Properties();
+            p.load(is);
+
+            String javaHome = p.getProperty("JAVA_HOME");
+            if (javaHome != null) {
+                jdks.add(new File(javaHome));
+            }
+
+            String extraJavaHomes = p.getProperty("EXTRA_JAVA_HOMES");
+            if (extraJavaHomes != null) {
+                for (String extraHome : extraJavaHomes.split(File.pathSeparator)) {
+                    jdks.add(new File(extraHome));
+                }
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public SourceLevelQueryImplementation2.Result getSourceLevel(FileObject fo) {
+        Group g = findGroup(fo);
+        if (g == null) {
+            return null;
+        }
+        return new SourceLevelQueryImplementation2.Result2() {
+            @Override
+            public SourceLevelQuery.Profile getProfile() {
+                return SourceLevelQuery.Profile.DEFAULT;
+            }
+
+            @Override
+            public String getSourceLevel() {
+                return g.getCompliance().getSourceLevel();
+            }
+
+            @Override
+            public void addChangeListener(ChangeListener cl) {
+            }
+
+            @Override
+            public void removeChangeListener(ChangeListener cl) {
+            }
+        };
+    }
+
+    @Override
+    public Set<? extends Project> getSubprojects() {
+        Set<Project> prjs = new HashSet<>();
+        for (SuiteSources imp : imported.values()) {
+            prjs.add(imp.prj);
+        }
+        return prjs;
+    }
+
+    @Override
+    public URL[] findUnitTests(FileObject fo) {
+        return new URL[0];
+    }
+
+    @Override
+    public URL[] findSources(FileObject fo) {
+        Group g = findGroup(fo);
+        return g == null ? new URL[0] : new URL[] { g.getRootFolder().toURL() };
+    }
+
+    static interface Dep extends Comparable<Dep> {
+        String getName();
+
+        Collection<String> depNames();
+
+        Collection<Dep> allDeps();
+
+        void setAllDeps(Collection<Dep> set);
+
+        @Override
+        public default int compareTo(Dep o) {
+            return getName().compareTo(o.getName());
+        }
+
+        SuiteSources owner();
+    }
+
+    final class Dist implements Dep, FlaggedClassPathImplementation {
+        final String name;
+        final MxDistribution dist;
+        Collection<Dep> allDeps;
+        private final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        private Boolean exists;
+        private Collection<Group> groups;
+
+        public Dist(String name, MxDistribution dist) {
+            this.name = name;
+            this.dist = dist;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            Set<String> deps = new TreeSet<>();
+            deps.addAll(dist.distDependencies());
+            deps.addAll(dist.exclude());
+            return deps;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return this.allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public String getName() {
+            return this.name;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        private FileObject getJar(boolean ignore) {
+            if (SuiteSources.this.dir == null) {
+                return null;
+            }
+            FileObject dists = SuiteSources.this.dir.getFileObject("mxbuild/dists");
+            if (dists == null) {
+                return null;
+            }
+            List<FileObject> dist = Arrays.stream(dists.getChildren()).filter((fo) -> fo.isFolder() && fo.getName().startsWith("jdk")).collect(Collectors.toList());
+            dist.sort((fo1, fo2) -> fo2.getName().compareTo(fo1.getName()));
+            for (FileObject jdkDir : dist) {
+                FileObject jar = jdkDir.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+                if (jar != null) {
+                    return jar;
+                }
+            }
+            FileObject jar = dists.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+            if (jar != null) {
+                return jar;
+            }
+            return null;
+        }
+
+        @Override
+        public List<? extends PathResourceImplementation> getResources() {
+            computeTransitiveDeps();
+            FileObject jar = getJar(exists == null);
+            final boolean existsNow = jar != null && jar.isData();
+            if (exists == null) {
+                exists = existsNow;
+            } else {
+                if (exists != existsNow) {
+                    exists = existsNow;
+                    support.firePropertyChange(PROP_FLAGS, !exists, (boolean) exists);
+                }
+            }
+            if (jar != null) {
+                PathResourceImplementation res;
+                try {
+                    res = ClassPathSupport.createResource(getJarRoot());
+                    return Collections.singletonList(res);
+                } catch (MalformedURLException ex) {
+                    // OK
+                }
+            }
+            return Collections.emptyList();
+        }
+
+        private URL getJarRoot() throws MalformedURLException {
+            FileObject jar = getJar(true);
+            if (jar != null) {
+                return new URL("jar:" + jar.toURL() + "!/");
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener pl) {
+            support.addPropertyChangeListener(pl);
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener pl) {
+            support.removePropertyChangeListener(pl);
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+
+        private void computeSourceRoots(Map<String, Dep> collectedDeps) {
+            if (groups != null) {
+                return;
+            }
+            Set<Group> contributingGroups = new LinkedHashSet<>();
+            for (String d : this.dist.dependencies()) {
+                Dep dep = collectedDeps.get(d);
+                if (dep == null || dep.allDeps() == null) {
+                    continue;
+                }
+                for (Dep d2 : dep.allDeps()) {
+                    if (d2 instanceof Group) {
+                        contributingGroups.add((Group) d2);
+                    }
+                }
+            }
+            for (String d : this.dist.distDependencies()) {
+                final Dep anyDep = collectedDeps.get(d);
+                if (anyDep instanceof Dist) {
+                    Dist dep = (Dist) anyDep;
+                    dep.computeSourceRoots(collectedDeps);
+                    contributingGroups.removeAll(dep.getContributingGroups());
+                }
+            }
+            groups = contributingGroups;
+        }
+
+        public Collection<Group> getContributingGroups() {
+            return groups;
+        }
+
+        @Override
+        public String toString() {
+            return "Dist[name=" + name + "]";
+        }
+    }
+
+    final class Group implements SourceGroup, Dep, AnnotationProcessingQuery.Result,
+            Compliance.Provider {
+        private final String mxName;
+        private final MxProject mxPrj;
+        private final FileObject srcDir;
+        private final FileObject srcGenDir;
+        private final FileObject binDir;
+        private final String name;
+        private final String displayName;
+        private final Compliance compliance;
+        private ClassPath sourceCP;
+        private ClassPath cp;
+        private ClassPath processorPath;
+        private Collection<Dep> allDeps;
+
+        Group(String mxName, MxProject mxPrj, FileObject srcDir, FileObject srcGenDir, FileObject binDir, String name, String displayName) {
+            this.mxName = mxName;
+            this.mxPrj = mxPrj;
+            this.srcDir = srcDir;
+            this.srcGenDir = srcGenDir;
+            this.binDir = binDir;
+            this.name = name;
+            this.displayName = displayName;
+            this.compliance = Compliance.parse(mxPrj.javaCompliance());
+        }
+
+        @Override
+        public FileObject getRootFolder() {
+            return srcDir;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        @Override
+        public Icon getIcon(boolean opened) {
+            return null;
+        }
+
+        @Override
+        public Compliance getCompliance() {
+            return compliance;
+        }
+
+        @Override
+        public boolean contains(FileObject file) {
+            if (file == srcDir || file == srcGenDir || FileUtil.isParentOf(srcDir, file) || (srcGenDir != null && FileUtil.isParentOf(srcGenDir, file))) {
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public String toString() {
+            return "SuiteSources.Group[name=" + name + ",rootFolder=" + srcDir + "]"; // NOI18N
+        }
+
+        ClassPath getSourceCP() {
+            computeTransitiveDeps();
+            return sourceCP;
+        }
+
+        ClassPath getCP() {
+            computeTransitiveDeps();
+            return cp;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return mxPrj.dependencies();
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            allDeps = set;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        private void computeClassPath(Map<String, Dep> transDeps) {
+            for (Dep d : transDeps.values()) {
+                d.owner().computeTransitiveDeps();
+            }
+
+            List<Group> arr = new ArrayList<>();
+            List<ClassPathImplementation> libs = new ArrayList<>();
+            processTransDep(transDeps.get(mxName), arr, libs);
+            cp = composeClassPath(arr, libs);
+            List<FileObject> roots = new ArrayList<>();
+            if (srcDir != null) {
+                roots.add(srcDir);
+            }
+            if (srcGenDir != null) {
+                roots.add(srcGenDir);
+            }
+            sourceCP = ClassPathSupport.createClassPath(roots.toArray(new FileObject[roots.size()]));
+
+            if (mxPrj.annotationProcessors().isEmpty()) {
+                processorPath = null;
+            } else {
+                List<Group> groups = new ArrayList<>();
+                List<ClassPathImplementation> jars = new ArrayList<>();
+                for (String dep : mxPrj.annotationProcessors()) {
+                    processTransDep(transDeps.get(dep), groups, jars);
+                }
+                processorPath = composeClassPath(groups, jars);
+            }
+        }
+
+        private void processTransDep(Dep dep, List<Group> addGroups, List<ClassPathImplementation> addJars) {
+            if (dep != null) {
+                dep.owner().computeTransitiveDeps();
+                for (Dep d : dep.allDeps()) {
+                    if (d == this) {
+                        continue;
+                    }
+                    d.owner().computeTransitiveDeps();
+                    if (d instanceof Group) {
+                        addGroups.add((Group) d);
+                    } else if (d instanceof ClassPathImplementation) {
+                        addJars.add((ClassPathImplementation) d);
+                    }
+                }
+            }
+        }
+
+        private ClassPath composeClassPath(List<Group> arr, List<ClassPathImplementation> libs) {
+            Set<FileObject> roots = new LinkedHashSet<>();
+            final int depsCount = arr.size();
+            for (int i = 0; i < depsCount; i++) {
+                final Group g = arr.get(i);
+                if (g.binDir != null) {
+                    roots.add(g.binDir);
+                }
+            }
+            ClassPath prjCp = ClassPathSupport.createClassPath(roots.toArray(new FileObject[0]));
+            if (!libs.isEmpty()) {
+                if (libs.size() == 1) {
+                    prjCp = ClassPathSupport.createProxyClassPath(prjCp,
+                                                                  ClassPathFactory.createClassPath(libs.get(0))
+                    );
+                } else {
+                    prjCp = ClassPathSupport.createProxyClassPath(prjCp,
+                                                                  ClassPathFactory.createClassPath(
+                                                                                  ClassPathSupport.createProxyClassPathImplementation(
+                                                                                                  libs.toArray(new ClassPathImplementation[0])
+                                                                                  )
+                                                                  )
+                    );
+                }
+            }
+            return prjCp;
+        }
+
+        ClassPath getProcessorCP() {
+            computeTransitiveDeps();
+            return processorPath;
+        }
+
+        @Override
+        public Set<? extends AnnotationProcessingQuery.Trigger> annotationProcessingEnabled() {
+            return EnumSet.of(AnnotationProcessingQuery.Trigger.ON_SCAN, AnnotationProcessingQuery.Trigger.IN_EDITOR);
+        }
+
+        @Override
+        public Iterable<? extends String> annotationProcessorsToRun() {
+            return null;
+        }
+
+        @Override
+        public URL sourceOutputDirectory() {
+            return srcGenDir == null ? null : srcGenDir.toURL();
+        }
+
+        @Override
+        public Map<? extends String, ? extends String> processorOptions() {
+            return Collections.emptyMap();
+        }
+
+        @Override
+        public void addChangeListener(ChangeListener l) {
+        }
+
+        @Override
+        public void removeChangeListener(ChangeListener l) {
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+    }
+
+    private class Library implements FlaggedClassPathImplementation, Dep {
+        final MxLibrary lib;
+        final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        final String libName;
+        Collection<Dep> allDeps;
+        Boolean exists;
+
+        Library(String libName, MxLibrary lib) {
+            this.libName = libName;
+            this.lib = getOSSLibrary(lib);
+        }
+
+        final MxLibrary getOSSLibrary(MxLibrary lib) {
+            if (lib.sha1() == null && !lib.os_arch().isEmpty()) {
+                Map<String, MxLibrary.Arch> os_dep_libs = lib.os_arch();
+                String os = System.getProperty("os.name").toLowerCase();
+                for (Map.Entry<String, MxLibrary.Arch> entry : os_dep_libs.entrySet()) {
+                    if (os.contains(entry.getKey())) {
+                        return entry.getValue().amd64();
+                    }
+                }
+            }
+            return lib;
+        }
+
+        File getJar(boolean dumpIfMissing) {
+            File mxCache;
+            String cache = System.getenv("MX_CACHE_DIR");
+            if (cache != null) {
+                mxCache = new File(cache);
+            } else {
+                mxCache = new File(new File(new File(System.getProperty("user.home")), ".mx"), "cache");
+            }
+            int prefix = libName.indexOf(':');
+            final String simpleName = libName.substring(prefix + 1);
+
+            File simpleJar = new File(mxCache, simpleName + "_" + lib.sha1() + ".jar");
+            if (simpleJar.exists()) {
+                return simpleJar;
+            }
+            File dir = new File(mxCache, simpleName + "_" + lib.sha1());
+            File jar = new File(dir, simpleName.replace('_', '-').toLowerCase(Locale.ENGLISH) + ".jar");
+
+            if (dumpIfMissing && !jar.exists()) {
+                for (File f = jar;; f = f.getParentFile()) {
+                    if (!f.exists()) {
+                        LOG.log(Level.WARNING, "{0} does not exist", f);
+                    } else {
+                        StringBuilder sb = new StringBuilder();
+                        sb.append(f).append(" exists:\n");
+                        String[] kids = f.list();
+                        if (kids != null) {
+                            for (String n : kids) {
+                                sb.append("  ").append(n).append("\n");
+                            }
+                        }
+                        LOG.log(Level.INFO, sb.toString());
+                        break;
+                    }
+                }
+            }
+            return jar;
+        }
+
+        @Override
+        public String getName() {
+            return libName;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return lib.dependencies();
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);

Review comment:
       Good catch: 9e9567e4729




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544101313



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);

Review comment:
       Running the tests I haven't seen a case where there was more groups than `firstGroup`. But I don't want to replace it with `assert` now.




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] neilcsmith-net commented on pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
neilcsmith-net commented on pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#issuecomment-737788086


   Thanks for the request to look at this.  I was thinking about alternative build systems and NetBeans earlier this year but not had time to look further (after [Bach](https://github.com/sormuras/bach) talk at our room at FOSDEM).  I do wonder whether trying to add internal support for other specific build systems is the right approach.  Maybe a standard declarative project form (with optional JS, JShell, etc.) scripting and/or support for [Build Server Protocol](https://build-server-protocol.github.io/) might be a better way forward?


----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#issuecomment-739464295


   > Unapproved license in 4 file(s) 
   > java/mx/project/Bundle.properties
   > release/org.eclipse.jdt.core.prefs
   
   Both files fixed by adding the license header.
   
   > modules/java/mx/project/suitepy/compiler-suite.py
   
   Only a test class, but removed, the parsing is now tested as part of the 977d1eb integration tests
   
   > mx-suite.py
   
   Removed in f1fadbb 


----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544098761



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static SuiteSources findSuiteSources(FileObject root, String name) throws IllegalArgumentException {
+        FileObject impDir = root.getFileObject(name);
+        if (impDir != null) {
+            try {
+                Project impPrj = ProjectManager.getDefault().findProject(impDir);
+                return impPrj == null ? null : impPrj.getLookup().lookup(SuiteSources.class);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceGroup[] getSourceGroups(String string) {
+        return groups();
+    }
+
+    Group[] groups() {
+        return groups.toArray(new Group[0]);
+    }
+
+    Group findGroup(FileObject fo) {
+        for (Group g : groups) {
+            if (g.contains(fo)) {
+                return g;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void addChangeListener(ChangeListener cl) {
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener cl) {
+    }
+
+    private static FileObject findPrjDir(FileObject dir, String prjName, MxProject prj) {
+        if (dir == null) {
+            return null;
+        }
+        if (prj.dir() != null) {
+            return dir.getFileObject(prj.dir());
+        }
+        if (prj.subDir() != null) {
+            dir = dir.getFileObject(prj.subDir());
+            if (dir == null) {
+                return null;
+            }
+        }
+        return dir.getFileObject(prjName);
+    }
+
+    private Collection<Dep> transitiveDeps(Dep current, Map<String, Dep> fill) {
+        current.owner().computeTransitiveDeps();
+        final Collection<Dep> currentAllDeps = current.allDeps();
+        if (currentAllDeps == Collections.<Dep>emptySet()) {
+            throw new IllegalStateException("Cyclic dep on " + current.getName());
+        } else if (currentAllDeps != null) {
+            return currentAllDeps;
+        }
+        current.setAllDeps(Collections.emptySet());
+        TreeSet<Dep> computing = new TreeSet<>();
+        computing.add(current);
+        for (String depName : current.depNames()) {
+            Dep dep = fill.get(depName);
+            if (dep == null) {
+                int colon = depName.lastIndexOf(':');
+                dep = fill.get(depName.substring(colon + 1));
+                if (dep == null) {
+                    LOG.log(Level.INFO, "dep not found: {0}", depName);
+                    continue;
+                }
+            }
+            Collection<Dep> allDeps = transitiveDeps(dep, fill);
+            computing.addAll(allDeps);
+        }
+        current.setAllDeps(computing);
+        return computing;
+    }
+
+    private static void registerLibs(Map<String, MxLibrary> collect, String prefix, Map<String, MxLibrary> libraries) {
+        for (Map.Entry<String, MxLibrary> entry : libraries.entrySet()) {
+            String key = entry.getKey();
+            MxLibrary lib = entry.getValue();
+            if (prefix == null) {
+                collect.put(key, lib);
+            } else {
+                collect.put(prefix + ":" + key, lib);
+            }
+        }
+    }
+
+    private void registerDeps(String prefix, Map<String, Dep> fillDeps) {
+        for (Library library : libraries) {
+            fillDeps.put(prefix + ":" + library.getName(), library);
+        }
+        for (Dist d : distributions) {
+            fillDeps.put(prefix + ":" + d.getName(), d);
+        }
+        for (Map.Entry<String, SuiteSources> s : imported.entrySet()) {
+            s.getValue().registerDeps(s.getKey(), fillDeps);
+        }
+    }
+
+    @Override
+    public Group findBinaryRoots2(URL url) {
+        final FileObject srcFo = URLMapper.findFileObject(url);
+        for (Group group : this.groups) {
+            if (group.contains(srcFo)) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public URL[] computeRoots(Group group) {
+        if (group.binDir != null) {
+            return new URL[] { group.binDir.toURL() };
+        } else {
+            return new URL[0];
+        }
+    }
+
+    @Override
+    public boolean computePreferBinaries(Group result) {
+        return true;
+    }
+
+    @Override
+    public void computeChangeListener(Group result, boolean bln, ChangeListener cl) {
+    }
+
+    @Override
+    public SourceForBinaryQueryImplementation2.Result findSourceRoots2(URL url) {
+        this.computeTransitiveDeps();
+        for (Dist dist : this.distributions) {
+            URL jar;
+            try {
+                jar = dist.getJarRoot();
+                if (jar == null) {
+                    continue;
+                }
+            } catch (MalformedURLException ok) {
+                continue;
+            }
+            if (jar.equals(url)) {
+                List<FileObject> roots = new ArrayList<>();
+                for (Group d : dist.getContributingGroups()) {
+                    roots.add(d.srcDir);
+                    roots.add(d.srcGenDir);
+                }
+                return new ImmutableResult(roots.toArray(new FileObject[roots.size()]));
+            }
+        }
+        for (Group group : this.groups) {
+            if (group.binDir != null && group.binDir.toURL().equals(url)) {
+                return new ImmutableResult(group.srcDir, group.srcGenDir);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceForBinaryQuery.Result findSourceRoots(URL url) {
+        return findSourceRoots2(url);
+    }
+
+    final Iterable<File> jdks() {
+        Set<File> jdks = new LinkedHashSet<>();
+        String home = System.getProperty("user.home");
+        if (home != null) {
+            File userEnv = new File(new File(new File(home), ".mx"), "env");
+            findJdksInEnv(jdks, userEnv);
+        }
+        FileObject suiteEnv = dir.getFileObject("mx." + dir.getNameExt() + "/env");
+        if (suiteEnv != null) {
+            findJdksInEnv(jdks, FileUtil.toFile(suiteEnv));
+        }
+
+        String javaHomeEnv = System.getenv("JAVA_HOME");
+        if (javaHomeEnv != null) {
+            jdks.add(new File(javaHomeEnv));
+        }
+        String javaHomeProp = System.getProperty("java.home");
+        if (javaHomeProp != null) {
+            jdks.add(new File(javaHomeProp));
+        }
+        return jdks;
+    }
+
+    private void findJdksInEnv(Set<File> jdks, File env) {
+        if (env == null || !env.isFile()) {
+            return;
+        }
+        try (final FileInputStream is = new FileInputStream(env)) {
+            Properties p = new Properties();
+            p.load(is);
+
+            String javaHome = p.getProperty("JAVA_HOME");
+            if (javaHome != null) {
+                jdks.add(new File(javaHome));
+            }
+
+            String extraJavaHomes = p.getProperty("EXTRA_JAVA_HOMES");
+            if (extraJavaHomes != null) {
+                for (String extraHome : extraJavaHomes.split(File.pathSeparator)) {
+                    jdks.add(new File(extraHome));
+                }
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public SourceLevelQueryImplementation2.Result getSourceLevel(FileObject fo) {
+        Group g = findGroup(fo);
+        if (g == null) {
+            return null;
+        }
+        return new SourceLevelQueryImplementation2.Result2() {
+            @Override
+            public SourceLevelQuery.Profile getProfile() {
+                return SourceLevelQuery.Profile.DEFAULT;
+            }
+
+            @Override
+            public String getSourceLevel() {
+                return g.getCompliance().getSourceLevel();
+            }
+
+            @Override
+            public void addChangeListener(ChangeListener cl) {
+            }
+
+            @Override
+            public void removeChangeListener(ChangeListener cl) {
+            }
+        };
+    }
+
+    @Override
+    public Set<? extends Project> getSubprojects() {
+        Set<Project> prjs = new HashSet<>();
+        for (SuiteSources imp : imported.values()) {
+            prjs.add(imp.prj);
+        }
+        return prjs;
+    }
+
+    @Override
+    public URL[] findUnitTests(FileObject fo) {
+        return new URL[0];
+    }
+
+    @Override
+    public URL[] findSources(FileObject fo) {
+        Group g = findGroup(fo);
+        return g == null ? new URL[0] : new URL[] { g.getRootFolder().toURL() };
+    }
+
+    static interface Dep extends Comparable<Dep> {
+        String getName();
+
+        Collection<String> depNames();
+
+        Collection<Dep> allDeps();
+
+        void setAllDeps(Collection<Dep> set);
+
+        @Override
+        public default int compareTo(Dep o) {
+            return getName().compareTo(o.getName());
+        }
+
+        SuiteSources owner();
+    }
+
+    final class Dist implements Dep, FlaggedClassPathImplementation {
+        final String name;
+        final MxDistribution dist;
+        Collection<Dep> allDeps;
+        private final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        private Boolean exists;
+        private Collection<Group> groups;
+
+        public Dist(String name, MxDistribution dist) {
+            this.name = name;
+            this.dist = dist;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            Set<String> deps = new TreeSet<>();
+            deps.addAll(dist.distDependencies());
+            deps.addAll(dist.exclude());
+            return deps;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return this.allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public String getName() {
+            return this.name;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        private FileObject getJar(boolean ignore) {
+            if (SuiteSources.this.dir == null) {
+                return null;
+            }
+            FileObject dists = SuiteSources.this.dir.getFileObject("mxbuild/dists");
+            if (dists == null) {
+                return null;
+            }
+            List<FileObject> dist = Arrays.stream(dists.getChildren()).filter((fo) -> fo.isFolder() && fo.getName().startsWith("jdk")).collect(Collectors.toList());
+            dist.sort((fo1, fo2) -> fo2.getName().compareTo(fo1.getName()));
+            for (FileObject jdkDir : dist) {
+                FileObject jar = jdkDir.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+                if (jar != null) {
+                    return jar;
+                }
+            }
+            FileObject jar = dists.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+            if (jar != null) {
+                return jar;
+            }
+            return null;
+        }
+
+        @Override
+        public List<? extends PathResourceImplementation> getResources() {
+            computeTransitiveDeps();
+            FileObject jar = getJar(exists == null);
+            final boolean existsNow = jar != null && jar.isData();
+            if (exists == null) {
+                exists = existsNow;
+            } else {
+                if (exists != existsNow) {
+                    exists = existsNow;
+                    support.firePropertyChange(PROP_FLAGS, !exists, (boolean) exists);
+                }
+            }
+            if (jar != null) {
+                PathResourceImplementation res;
+                try {
+                    res = ClassPathSupport.createResource(getJarRoot());
+                    return Collections.singletonList(res);
+                } catch (MalformedURLException ex) {
+                    // OK
+                }
+            }
+            return Collections.emptyList();
+        }
+
+        private URL getJarRoot() throws MalformedURLException {
+            FileObject jar = getJar(true);
+            if (jar != null) {
+                return new URL("jar:" + jar.toURL() + "!/");
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener pl) {
+            support.addPropertyChangeListener(pl);
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener pl) {
+            support.removePropertyChangeListener(pl);
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+
+        private void computeSourceRoots(Map<String, Dep> collectedDeps) {
+            if (groups != null) {
+                return;
+            }
+            Set<Group> contributingGroups = new LinkedHashSet<>();
+            for (String d : this.dist.dependencies()) {
+                Dep dep = collectedDeps.get(d);
+                if (dep == null || dep.allDeps() == null) {
+                    continue;
+                }
+                for (Dep d2 : dep.allDeps()) {
+                    if (d2 instanceof Group) {
+                        contributingGroups.add((Group) d2);
+                    }
+                }
+            }
+            for (String d : this.dist.distDependencies()) {
+                final Dep anyDep = collectedDeps.get(d);
+                if (anyDep instanceof Dist) {
+                    Dist dep = (Dist) anyDep;
+                    dep.computeSourceRoots(collectedDeps);
+                    contributingGroups.removeAll(dep.getContributingGroups());
+                }
+            }
+            groups = contributingGroups;
+        }
+
+        public Collection<Group> getContributingGroups() {
+            return groups;
+        }
+
+        @Override
+        public String toString() {
+            return "Dist[name=" + name + "]";
+        }
+    }
+
+    final class Group implements SourceGroup, Dep, AnnotationProcessingQuery.Result,
+            Compliance.Provider {
+        private final String mxName;
+        private final MxProject mxPrj;
+        private final FileObject srcDir;
+        private final FileObject srcGenDir;
+        private final FileObject binDir;
+        private final String name;
+        private final String displayName;
+        private final Compliance compliance;
+        private ClassPath sourceCP;
+        private ClassPath cp;
+        private ClassPath processorPath;
+        private Collection<Dep> allDeps;
+
+        Group(String mxName, MxProject mxPrj, FileObject srcDir, FileObject srcGenDir, FileObject binDir, String name, String displayName) {
+            this.mxName = mxName;
+            this.mxPrj = mxPrj;
+            this.srcDir = srcDir;
+            this.srcGenDir = srcGenDir;
+            this.binDir = binDir;
+            this.name = name;
+            this.displayName = displayName;
+            this.compliance = Compliance.parse(mxPrj.javaCompliance());
+        }
+
+        @Override
+        public FileObject getRootFolder() {
+            return srcDir;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        @Override
+        public Icon getIcon(boolean opened) {
+            return null;
+        }
+
+        @Override
+        public Compliance getCompliance() {
+            return compliance;
+        }
+
+        @Override
+        public boolean contains(FileObject file) {
+            if (file == srcDir || file == srcGenDir || FileUtil.isParentOf(srcDir, file) || (srcGenDir != null && FileUtil.isParentOf(srcGenDir, file))) {
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public String toString() {
+            return "SuiteSources.Group[name=" + name + ",rootFolder=" + srcDir + "]"; // NOI18N
+        }
+
+        ClassPath getSourceCP() {
+            computeTransitiveDeps();
+            return sourceCP;
+        }
+
+        ClassPath getCP() {
+            computeTransitiveDeps();
+            return cp;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return mxPrj.dependencies();
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            allDeps = set;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        private void computeClassPath(Map<String, Dep> transDeps) {
+            for (Dep d : transDeps.values()) {
+                d.owner().computeTransitiveDeps();
+            }
+
+            List<Group> arr = new ArrayList<>();
+            List<ClassPathImplementation> libs = new ArrayList<>();
+            processTransDep(transDeps.get(mxName), arr, libs);
+            cp = composeClassPath(arr, libs);
+            List<FileObject> roots = new ArrayList<>();
+            if (srcDir != null) {
+                roots.add(srcDir);
+            }
+            if (srcGenDir != null) {
+                roots.add(srcGenDir);
+            }
+            sourceCP = ClassPathSupport.createClassPath(roots.toArray(new FileObject[roots.size()]));
+
+            if (mxPrj.annotationProcessors().isEmpty()) {
+                processorPath = null;
+            } else {
+                List<Group> groups = new ArrayList<>();
+                List<ClassPathImplementation> jars = new ArrayList<>();
+                for (String dep : mxPrj.annotationProcessors()) {
+                    processTransDep(transDeps.get(dep), groups, jars);
+                }
+                processorPath = composeClassPath(groups, jars);
+            }
+        }
+
+        private void processTransDep(Dep dep, List<Group> addGroups, List<ClassPathImplementation> addJars) {
+            if (dep != null) {
+                dep.owner().computeTransitiveDeps();
+                for (Dep d : dep.allDeps()) {
+                    if (d == this) {
+                        continue;
+                    }
+                    d.owner().computeTransitiveDeps();
+                    if (d instanceof Group) {
+                        addGroups.add((Group) d);
+                    } else if (d instanceof ClassPathImplementation) {
+                        addJars.add((ClassPathImplementation) d);
+                    }
+                }
+            }
+        }
+
+        private ClassPath composeClassPath(List<Group> arr, List<ClassPathImplementation> libs) {
+            Set<FileObject> roots = new LinkedHashSet<>();
+            final int depsCount = arr.size();
+            for (int i = 0; i < depsCount; i++) {
+                final Group g = arr.get(i);
+                if (g.binDir != null) {
+                    roots.add(g.binDir);
+                }
+            }
+            ClassPath prjCp = ClassPathSupport.createClassPath(roots.toArray(new FileObject[0]));
+            if (!libs.isEmpty()) {
+                if (libs.size() == 1) {
+                    prjCp = ClassPathSupport.createProxyClassPath(prjCp,
+                                                                  ClassPathFactory.createClassPath(libs.get(0))
+                    );
+                } else {
+                    prjCp = ClassPathSupport.createProxyClassPath(prjCp,
+                                                                  ClassPathFactory.createClassPath(
+                                                                                  ClassPathSupport.createProxyClassPathImplementation(
+                                                                                                  libs.toArray(new ClassPathImplementation[0])
+                                                                                  )
+                                                                  )
+                    );
+                }
+            }
+            return prjCp;
+        }
+
+        ClassPath getProcessorCP() {
+            computeTransitiveDeps();
+            return processorPath;
+        }
+
+        @Override
+        public Set<? extends AnnotationProcessingQuery.Trigger> annotationProcessingEnabled() {
+            return EnumSet.of(AnnotationProcessingQuery.Trigger.ON_SCAN, AnnotationProcessingQuery.Trigger.IN_EDITOR);
+        }
+
+        @Override
+        public Iterable<? extends String> annotationProcessorsToRun() {
+            return null;
+        }
+
+        @Override
+        public URL sourceOutputDirectory() {
+            return srcGenDir == null ? null : srcGenDir.toURL();
+        }
+
+        @Override
+        public Map<? extends String, ? extends String> processorOptions() {
+            return Collections.emptyMap();
+        }
+
+        @Override
+        public void addChangeListener(ChangeListener l) {
+        }
+
+        @Override
+        public void removeChangeListener(ChangeListener l) {
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+    }
+
+    private class Library implements FlaggedClassPathImplementation, Dep {
+        final MxLibrary lib;
+        final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        final String libName;
+        Collection<Dep> allDeps;
+        Boolean exists;
+
+        Library(String libName, MxLibrary lib) {
+            this.libName = libName;
+            this.lib = getOSSLibrary(lib);
+        }
+
+        final MxLibrary getOSSLibrary(MxLibrary lib) {
+            if (lib.sha1() == null && !lib.os_arch().isEmpty()) {
+                Map<String, MxLibrary.Arch> os_dep_libs = lib.os_arch();
+                String os = System.getProperty("os.name").toLowerCase();
+                for (Map.Entry<String, MxLibrary.Arch> entry : os_dep_libs.entrySet()) {
+                    if (os.contains(entry.getKey())) {
+                        return entry.getValue().amd64();

Review comment:
       This silently assumes all developer laptops and workstations are AMD64. True for now, until Apple own processor becomes popular among devs. I guess it is going to take a bit of time and it will need deeper fixes in `mx` itself then.




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544107601



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static SuiteSources findSuiteSources(FileObject root, String name) throws IllegalArgumentException {
+        FileObject impDir = root.getFileObject(name);
+        if (impDir != null) {
+            try {
+                Project impPrj = ProjectManager.getDefault().findProject(impDir);
+                return impPrj == null ? null : impPrj.getLookup().lookup(SuiteSources.class);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceGroup[] getSourceGroups(String string) {
+        return groups();
+    }
+
+    Group[] groups() {
+        return groups.toArray(new Group[0]);
+    }
+
+    Group findGroup(FileObject fo) {
+        for (Group g : groups) {
+            if (g.contains(fo)) {
+                return g;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void addChangeListener(ChangeListener cl) {
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener cl) {
+    }
+
+    private static FileObject findPrjDir(FileObject dir, String prjName, MxProject prj) {
+        if (dir == null) {
+            return null;
+        }
+        if (prj.dir() != null) {
+            return dir.getFileObject(prj.dir());
+        }
+        if (prj.subDir() != null) {
+            dir = dir.getFileObject(prj.subDir());
+            if (dir == null) {
+                return null;
+            }
+        }
+        return dir.getFileObject(prjName);
+    }
+
+    private Collection<Dep> transitiveDeps(Dep current, Map<String, Dep> fill) {
+        current.owner().computeTransitiveDeps();
+        final Collection<Dep> currentAllDeps = current.allDeps();
+        if (currentAllDeps == Collections.<Dep>emptySet()) {
+            throw new IllegalStateException("Cyclic dep on " + current.getName());
+        } else if (currentAllDeps != null) {
+            return currentAllDeps;
+        }
+        current.setAllDeps(Collections.emptySet());
+        TreeSet<Dep> computing = new TreeSet<>();
+        computing.add(current);
+        for (String depName : current.depNames()) {
+            Dep dep = fill.get(depName);
+            if (dep == null) {
+                int colon = depName.lastIndexOf(':');
+                dep = fill.get(depName.substring(colon + 1));
+                if (dep == null) {
+                    LOG.log(Level.INFO, "dep not found: {0}", depName);
+                    continue;
+                }
+            }
+            Collection<Dep> allDeps = transitiveDeps(dep, fill);
+            computing.addAll(allDeps);
+        }
+        current.setAllDeps(computing);
+        return computing;
+    }
+
+    private static void registerLibs(Map<String, MxLibrary> collect, String prefix, Map<String, MxLibrary> libraries) {
+        for (Map.Entry<String, MxLibrary> entry : libraries.entrySet()) {
+            String key = entry.getKey();
+            MxLibrary lib = entry.getValue();
+            if (prefix == null) {
+                collect.put(key, lib);
+            } else {
+                collect.put(prefix + ":" + key, lib);
+            }
+        }
+    }
+
+    private void registerDeps(String prefix, Map<String, Dep> fillDeps) {
+        for (Library library : libraries) {
+            fillDeps.put(prefix + ":" + library.getName(), library);
+        }
+        for (Dist d : distributions) {
+            fillDeps.put(prefix + ":" + d.getName(), d);
+        }
+        for (Map.Entry<String, SuiteSources> s : imported.entrySet()) {
+            s.getValue().registerDeps(s.getKey(), fillDeps);
+        }
+    }
+
+    @Override
+    public Group findBinaryRoots2(URL url) {
+        final FileObject srcFo = URLMapper.findFileObject(url);
+        for (Group group : this.groups) {
+            if (group.contains(srcFo)) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public URL[] computeRoots(Group group) {
+        if (group.binDir != null) {
+            return new URL[] { group.binDir.toURL() };
+        } else {
+            return new URL[0];
+        }
+    }
+
+    @Override
+    public boolean computePreferBinaries(Group result) {
+        return true;
+    }
+
+    @Override
+    public void computeChangeListener(Group result, boolean bln, ChangeListener cl) {
+    }
+
+    @Override
+    public SourceForBinaryQueryImplementation2.Result findSourceRoots2(URL url) {
+        this.computeTransitiveDeps();
+        for (Dist dist : this.distributions) {
+            URL jar;
+            try {
+                jar = dist.getJarRoot();
+                if (jar == null) {
+                    continue;
+                }
+            } catch (MalformedURLException ok) {
+                continue;
+            }
+            if (jar.equals(url)) {
+                List<FileObject> roots = new ArrayList<>();
+                for (Group d : dist.getContributingGroups()) {
+                    roots.add(d.srcDir);
+                    roots.add(d.srcGenDir);
+                }
+                return new ImmutableResult(roots.toArray(new FileObject[roots.size()]));
+            }
+        }
+        for (Group group : this.groups) {
+            if (group.binDir != null && group.binDir.toURL().equals(url)) {
+                return new ImmutableResult(group.srcDir, group.srcGenDir);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceForBinaryQuery.Result findSourceRoots(URL url) {
+        return findSourceRoots2(url);
+    }
+
+    final Iterable<File> jdks() {
+        Set<File> jdks = new LinkedHashSet<>();
+        String home = System.getProperty("user.home");
+        if (home != null) {
+            File userEnv = new File(new File(new File(home), ".mx"), "env");
+            findJdksInEnv(jdks, userEnv);
+        }
+        FileObject suiteEnv = dir.getFileObject("mx." + dir.getNameExt() + "/env");
+        if (suiteEnv != null) {
+            findJdksInEnv(jdks, FileUtil.toFile(suiteEnv));
+        }
+
+        String javaHomeEnv = System.getenv("JAVA_HOME");
+        if (javaHomeEnv != null) {
+            jdks.add(new File(javaHomeEnv));
+        }
+        String javaHomeProp = System.getProperty("java.home");
+        if (javaHomeProp != null) {
+            jdks.add(new File(javaHomeProp));
+        }
+        return jdks;
+    }
+
+    private void findJdksInEnv(Set<File> jdks, File env) {
+        if (env == null || !env.isFile()) {
+            return;
+        }
+        try (final FileInputStream is = new FileInputStream(env)) {
+            Properties p = new Properties();
+            p.load(is);
+
+            String javaHome = p.getProperty("JAVA_HOME");
+            if (javaHome != null) {
+                jdks.add(new File(javaHome));
+            }
+
+            String extraJavaHomes = p.getProperty("EXTRA_JAVA_HOMES");
+            if (extraJavaHomes != null) {
+                for (String extraHome : extraJavaHomes.split(File.pathSeparator)) {
+                    jdks.add(new File(extraHome));
+                }
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public SourceLevelQueryImplementation2.Result getSourceLevel(FileObject fo) {
+        Group g = findGroup(fo);
+        if (g == null) {
+            return null;
+        }
+        return new SourceLevelQueryImplementation2.Result2() {
+            @Override
+            public SourceLevelQuery.Profile getProfile() {
+                return SourceLevelQuery.Profile.DEFAULT;
+            }
+
+            @Override
+            public String getSourceLevel() {
+                return g.getCompliance().getSourceLevel();
+            }
+
+            @Override
+            public void addChangeListener(ChangeListener cl) {
+            }
+
+            @Override
+            public void removeChangeListener(ChangeListener cl) {
+            }
+        };
+    }
+
+    @Override
+    public Set<? extends Project> getSubprojects() {
+        Set<Project> prjs = new HashSet<>();
+        for (SuiteSources imp : imported.values()) {
+            prjs.add(imp.prj);
+        }
+        return prjs;
+    }
+
+    @Override
+    public URL[] findUnitTests(FileObject fo) {
+        return new URL[0];
+    }
+
+    @Override
+    public URL[] findSources(FileObject fo) {
+        Group g = findGroup(fo);
+        return g == null ? new URL[0] : new URL[] { g.getRootFolder().toURL() };
+    }
+
+    static interface Dep extends Comparable<Dep> {
+        String getName();
+
+        Collection<String> depNames();
+
+        Collection<Dep> allDeps();
+
+        void setAllDeps(Collection<Dep> set);
+
+        @Override
+        public default int compareTo(Dep o) {
+            return getName().compareTo(o.getName());
+        }
+
+        SuiteSources owner();
+    }
+
+    final class Dist implements Dep, FlaggedClassPathImplementation {
+        final String name;
+        final MxDistribution dist;
+        Collection<Dep> allDeps;
+        private final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        private Boolean exists;
+        private Collection<Group> groups;
+
+        public Dist(String name, MxDistribution dist) {
+            this.name = name;
+            this.dist = dist;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            Set<String> deps = new TreeSet<>();
+            deps.addAll(dist.distDependencies());
+            deps.addAll(dist.exclude());
+            return deps;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return this.allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public String getName() {
+            return this.name;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        private FileObject getJar(boolean ignore) {
+            if (SuiteSources.this.dir == null) {
+                return null;
+            }
+            FileObject dists = SuiteSources.this.dir.getFileObject("mxbuild/dists");
+            if (dists == null) {
+                return null;
+            }
+            List<FileObject> dist = Arrays.stream(dists.getChildren()).filter((fo) -> fo.isFolder() && fo.getName().startsWith("jdk")).collect(Collectors.toList());
+            dist.sort((fo1, fo2) -> fo2.getName().compareTo(fo1.getName()));
+            for (FileObject jdkDir : dist) {
+                FileObject jar = jdkDir.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+                if (jar != null) {
+                    return jar;
+                }
+            }
+            FileObject jar = dists.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+            if (jar != null) {
+                return jar;
+            }
+            return null;
+        }
+
+        @Override
+        public List<? extends PathResourceImplementation> getResources() {
+            computeTransitiveDeps();
+            FileObject jar = getJar(exists == null);
+            final boolean existsNow = jar != null && jar.isData();
+            if (exists == null) {
+                exists = existsNow;
+            } else {
+                if (exists != existsNow) {
+                    exists = existsNow;
+                    support.firePropertyChange(PROP_FLAGS, !exists, (boolean) exists);
+                }
+            }
+            if (jar != null) {
+                PathResourceImplementation res;
+                try {
+                    res = ClassPathSupport.createResource(getJarRoot());
+                    return Collections.singletonList(res);
+                } catch (MalformedURLException ex) {
+                    // OK
+                }
+            }
+            return Collections.emptyList();
+        }
+
+        private URL getJarRoot() throws MalformedURLException {
+            FileObject jar = getJar(true);
+            if (jar != null) {
+                return new URL("jar:" + jar.toURL() + "!/");
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener pl) {
+            support.addPropertyChangeListener(pl);
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener pl) {
+            support.removePropertyChangeListener(pl);
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+
+        private void computeSourceRoots(Map<String, Dep> collectedDeps) {
+            if (groups != null) {
+                return;
+            }
+            Set<Group> contributingGroups = new LinkedHashSet<>();
+            for (String d : this.dist.dependencies()) {
+                Dep dep = collectedDeps.get(d);
+                if (dep == null || dep.allDeps() == null) {
+                    continue;
+                }
+                for (Dep d2 : dep.allDeps()) {
+                    if (d2 instanceof Group) {
+                        contributingGroups.add((Group) d2);
+                    }
+                }
+            }
+            for (String d : this.dist.distDependencies()) {
+                final Dep anyDep = collectedDeps.get(d);
+                if (anyDep instanceof Dist) {
+                    Dist dep = (Dist) anyDep;
+                    dep.computeSourceRoots(collectedDeps);
+                    contributingGroups.removeAll(dep.getContributingGroups());
+                }
+            }
+            groups = contributingGroups;
+        }
+
+        public Collection<Group> getContributingGroups() {
+            return groups;
+        }
+
+        @Override
+        public String toString() {
+            return "Dist[name=" + name + "]";
+        }
+    }
+
+    final class Group implements SourceGroup, Dep, AnnotationProcessingQuery.Result,
+            Compliance.Provider {
+        private final String mxName;
+        private final MxProject mxPrj;
+        private final FileObject srcDir;
+        private final FileObject srcGenDir;
+        private final FileObject binDir;
+        private final String name;
+        private final String displayName;
+        private final Compliance compliance;
+        private ClassPath sourceCP;
+        private ClassPath cp;
+        private ClassPath processorPath;
+        private Collection<Dep> allDeps;
+
+        Group(String mxName, MxProject mxPrj, FileObject srcDir, FileObject srcGenDir, FileObject binDir, String name, String displayName) {
+            this.mxName = mxName;
+            this.mxPrj = mxPrj;
+            this.srcDir = srcDir;
+            this.srcGenDir = srcGenDir;
+            this.binDir = binDir;
+            this.name = name;
+            this.displayName = displayName;
+            this.compliance = Compliance.parse(mxPrj.javaCompliance());
+        }
+
+        @Override
+        public FileObject getRootFolder() {
+            return srcDir;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        @Override
+        public Icon getIcon(boolean opened) {
+            return null;
+        }
+
+        @Override
+        public Compliance getCompliance() {
+            return compliance;
+        }
+
+        @Override
+        public boolean contains(FileObject file) {
+            if (file == srcDir || file == srcGenDir || FileUtil.isParentOf(srcDir, file) || (srcGenDir != null && FileUtil.isParentOf(srcGenDir, file))) {
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public String toString() {
+            return "SuiteSources.Group[name=" + name + ",rootFolder=" + srcDir + "]"; // NOI18N
+        }
+
+        ClassPath getSourceCP() {
+            computeTransitiveDeps();
+            return sourceCP;
+        }
+
+        ClassPath getCP() {
+            computeTransitiveDeps();
+            return cp;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return mxPrj.dependencies();
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            allDeps = set;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        private void computeClassPath(Map<String, Dep> transDeps) {
+            for (Dep d : transDeps.values()) {
+                d.owner().computeTransitiveDeps();

Review comment:
       The transitive deps are processed just once. Let's make it clearer by better naming 5b589ee5930f




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544175393



##########
File path: java/java.mx.project/test/unit/src/org/netbeans/modules/java/mx/project/SuiteCheck.java
##########
@@ -0,0 +1,257 @@
+/*
+ * 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.mx.project;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+import javax.tools.Diagnostic;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertSame;
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.junit.Assume;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.classpath.JavaClassPathConstants;
+import org.netbeans.api.java.queries.BinaryForSourceQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.java.source.CompilationController;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.ProjectUtils;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.api.project.ui.OpenProjects;
+import org.netbeans.junit.NbTestCase;
+import org.netbeans.modules.parsing.api.indexing.IndexingManager;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.Utilities;
+
+abstract class SuiteCheck extends NbTestCase {
+    SuiteCheck(String name) {
+        super(name);
+        log(Level.INFO, "Test created by %s classloader", getClass().getClassLoader());
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        log(Level.INFO, "setUp - init");
+        super.setUp();
+        final Logger tooVerboseLogger = Logger.getLogger("org.netbeans.core.startup.InstalledFileLocatorImpl");
+        tooVerboseLogger.setUseParentHandlers(false);
+        try {
+            MxSuite.parse(null);
+        } catch (LinkageError err) {
+            Assume.assumeNoException("Cannot initialize Polyglot API, are you using GraalVM?", err);
+        }
+        log(Level.INFO, "setUp - exit");
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        Enumeration<String> en = LogManager.getLogManager().getLoggerNames();
+        while (en.hasMoreElements()) {
+            String n = en.nextElement();
+            Logger l = LogManager.getLogManager().getLogger(n);
+            boolean first = true;
+            if (l == null || l.getHandlers() == null) {
+                continue;
+            }
+            for (Handler h : l.getHandlers()) {
+                if (first) {
+                    System.err.println("cleaning logger '" + n + "'");
+                    first = false;
+                }
+                System.err.println("  removing handler: " + h);
+                l.removeHandler(h);
+            }
+        }
+    }
+
+    @Override
+    protected int timeOut() {
+        return 1_200_000;
+    }
+
+    protected final void verifyNoErrorsInSuite(final String suiteName, String... onlySourceGroups) throws IllegalArgumentException, IOException, URISyntaxException {
+        long begin = System.currentTimeMillis();
+        File sibling = findSuite(suiteName);
+
+        FileObject fo = FileUtil.toFileObject(sibling);
+        assertNotNull("project directory found", fo);
+
+        log(Level.INFO, "Recognizing project %s", fo);
+        long now = System.currentTimeMillis();
+        Project p = ProjectManager.getDefault().findProject(fo);
+        long took = System.currentTimeMillis() - now;
+        assertNotNull("project found", p);
+        log(Level.INFO, "Project found %s in %d ms", p, took);
+        assertEquals("It is suite project: " + p, "SuiteProject", p.getClass().getSimpleName());
+        OpenProjects.getDefault().open(new Project[]{p}, false);
+
+        StringBuilder errors = new StringBuilder();
+        FileObject[] errornous = { null };
+        Sources src = ProjectUtils.getSources(p);
+        int cnt = 0;
+        for (SourceGroup sourceGroup : src.getSourceGroups("java")) {
+            if (sourceGroup instanceof Compliance.Provider) {
+                Compliance c = ((Compliance.Provider) sourceGroup).getCompliance();
+                if (!c.includes(8)) {
+                    log(Level.INFO, "Skipping check of %s with compliance %s", sourceGroup, c);
+                    continue;
+                }
+            }
+            FOUND: if (onlySourceGroups.length > 0) {
+                for (String gName : onlySourceGroups) {
+                    if (sourceGroup.getDisplayName().equals(gName)) {
+                        cnt++;
+                        break FOUND;
+                    }
+                }
+                // not found
+                continue;
+            }
+            assertSourcesNoError(p, errornous, errors, sourceGroup.getRootFolder(), begin);
+        }
+        assertCompilationErrors(errors, errornous);
+
+        assertEquals("Exactly as many source groups tested as requested", onlySourceGroups.length, cnt);
+    }
+
+    protected final File findSuite(String suite) throws URISyntaxException {
+        File location = getDataDir();
+        while (location != null) {
+            File graal = new File(location, "graal");
+            File suiteDir = new File(graal, suite);
+            if (suiteDir.isDirectory()) {
+                return suiteDir;
+            }
+            location = location.getParentFile();
+        }
+        fail("Cannot find truffle next to " + getDataDir());
+        return null;
+    }
+
+    private void assertSourcesNoError(Project project, FileObject[] errornous, StringBuilder errors, FileObject dir, long begin) throws IOException {
+        long now = System.currentTimeMillis();
+        log(Level.INFO, "assertSourcesNoError for %s", dir);
+        IndexingManager.getDefault().refreshIndexAndWait(dir.toURL(), null, false);
+        log(Level.INFO, "      refresh done       %s", dir);
+        Enumeration<? extends FileObject> en = dir.getChildren(true);
+        int nonJavaCount = 0;
+        int javaCount = 0;
+        while (en.hasMoreElements()) {
+            FileObject fo = en.nextElement();
+            if (fo.isFolder()) {
+                continue;
+            }
+            Project prj = FileOwnerQuery.getOwner(fo);
+            assertSame("FileOwnerQuery returns the right project", project, prj);
+            if (!fo.hasExt("java")) {
+                nonJavaCount++;
+                continue;
+            }
+            JavaSource source = JavaSource.forFileObject(fo);

Review comment:
       Alas, this test seems to [randomly fail](https://travis-ci.org/github/apache/netbeans/jobs/749982297).




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544076389



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteClassPathProvider.java
##########
@@ -0,0 +1,100 @@
+/*
+ * 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.mx.project;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+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.platform.JavaPlatformManager;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery.Result;
+import org.netbeans.spi.java.classpath.ClassPathProvider;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.AnnotationProcessingQueryImplementation;
+import org.netbeans.spi.project.ui.ProjectOpenedHook;
+import org.openide.filesystems.FileObject;
+
+final class SuiteClassPathProvider extends ProjectOpenedHook implements ClassPathProvider, AnnotationProcessingQueryImplementation {
+    private final SuiteProject project;
+    private final ClassPath bootCP;
+
+    public SuiteClassPathProvider(SuiteProject project) {
+        this.project = project;
+        List<ClassPath.Entry> entries = JavaPlatformManager.getDefault().getDefaultPlatform().getBootstrapLibraries().entries();
+        List<URL> roots = new ArrayList<>();
+        for (ClassPath.Entry entry : entries) {
+            URL root = entry.getURL();
+            if (root.getPath().contains("/graal-sdk.jar")) {

Review comment:
       Unless I am mistaken this is because GraalVM (based on JDK8) contains these JARs on boot class path and we want to avoid them.




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] lkishalmi commented on pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
lkishalmi commented on pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#issuecomment-738138822


   ```
   Unapproved license in 4 file(s) 
   https://github.com/apache/netbeans/tree/master/java/java.mx.project/src/org/netbeans/modules/java/mx/project/mx-suite.py
   https://github.com/apache/netbeans/tree/master/java/java.mx.project/src/org/netbeans/modules/java/mx/project/Bundle.properties
   https://github.com/apache/netbeans/tree/master/java/java.mx.project/release/org.eclipse.jdt.core.prefs
   https://github.com/apache/netbeans/tree/master/java/java.mx.project/test/unit/src/org/netbeans/modules/java/mx/project/suitepy/compiler-suite.py
   ```


----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#issuecomment-745945049


   Thanks a lot for your reviews. As soon as the gates seem fine, I merge.


----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] lkishalmi commented on pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
lkishalmi commented on pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#issuecomment-745073241


   It seems Travis is complaining on some jaxb stuff for Java 8 tests, I guess that's unrelated with this PR. @JaroslavTulach please feel free to Squash and merge this one.


----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] matthiasblaesing commented on pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
matthiasblaesing commented on pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#issuecomment-739487771


   I only skimmed the code. I still have a WTF feeling when I consider, that once again a build system was invented.
   
   If I'm not mistaken, it has the same problems, that gradle exhibits, i.e. the configuration format is an executable file (at least I understand, that the suite.py is the configuration and the file ending indicates full blown python file). This raises for me the question: Will the approach to statically parse that file scale to the future?
   
   In `CoreSuite.java` a big set of libraries seems to be generated. Are these dependencies set in stone? It looks like a strange approach to dependency management.
   
   The one question in my mind: I read mx as a special system invented in the context of graal, does it pull enough weight to be implemented in the core of netbeans or could it live a s a plugin in the plugin portal?


----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544081844



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {

Review comment:
       The existing sources that I am aware of are find with 2-level limit. I am not sure if the 2-level parent limit is in the {{mx}} itself or not. Scanning more deeply might have negative effect on performance. Let's leave it as it is unless 
   @gilles-duboscq wants to comment.




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#issuecomment-739689602


   Thanks for the review.
   
   > I only skimmed the code. I still have a WTF feeling when I consider, that once again a build system was invented.
   
   I share your feelings. Looks to me every bigger project ends up inventing own build system (NetBeans included). I spent my first year in OracleLabs trying to remove `mx` (at least it now integrates with Maven central), but when it got to _either me or mx_ decision point I gave up and let `mx` be. Having `mx` creates some weird situations (like the need for this PR), but overall one can get used to it.
   
   > If I'm not mistaken, it has the same problems, that gradle exhibits, i.e. the configuration format is an executable file (at least I understand, that the suite.py is the configuration and the file ending indicates full blown python file). 
   
   Right the `suite.py` configuration is written in Python syntax. However there is 1:1 mapping to JSON and that's what this module is using to parse the configuration files in a declarative way. E.g. we can avoid the Gradle situation. 
   
   > This raises for me the question: Will the approach to statically parse that file scale to the future?
   
   * There are other systems in OracleLabs infrastructure that use the conversion to JSON and subsequent parsing. 
   * There seems to be an overall consensus that this is desirable. 
   * There are _gate checks_ that run the code used in this PR - should that ever get broken integration into any Graal repository gets rejected
   * Once this PR is accepted into NetBeans I modify the gates to use the NetBeans to do the actual parsing/verification - btw. it would be great if there was `--verifyprojectconsistency dir` option to run NetBeans in headless mode and verify project structure is still without any errors from a NetBeans IDE point of view, CCing @jlahoda! Recently also [requested on a mailing list](https://lists.apache.org/thread.html/r799decd155c9197bc2dbe6fc1185e090ce1dc7fafcdb91e55fc0b0e1%40%3Cdev.netbeans.apache.org%3E).
   
   Overall the conversion to JSON and parsing approach shall be safe forever.
   
   > In `CoreSuite.java` a big set of libraries seems to be generated. Are these dependencies set in stone? It looks like a strange approach to dependency management.
   
   These libraries are mostly infrastructure JARs used by the actual `mx` Python code to perform unit testing, benchmarking, signature testing, code coverage tasks. These libraries may grow over time (just [recently a newer version of sigtest](https://github.com/graalvm/mx/commit/68fd66578b5b13f79fbf72a2c1e96164f5f43dc7) was added). But overall they are kept stable and immutable.
   
   > The one question in my mind: I read mx as a special system invented in the context of graal, does it pull enough weight to be implemented in the core of netbeans or could it live a s a plugin in the plugin portal?
   
   I'd like to get this PR in for 12.3 in its simple form, but long term I have to agree - it would be better to provide this module as a standard Apache NetBeans extension on plugin portal. We need:
   * build system changes to create a cluster(?) that is not part of final ZIP, but its NBMs are part of the release
   * make sure ergonomics module scans these NBMs for various registrations
   * polish the UX of downloading NBMs via ergonomics - for example when `mx.*/suite.py` folder is found
   * ultimately we can modify installer to install just `platform`, `ide`, `ergonomics` clusters by default and let the rest be downloaded on a fly!
   
   I always wanted [ergonomics to allow downloads](http://wiki.netbeans.org/FitnessForever), decrease the download size, promote features to install and I am thrilled to see this being considered again. However, assuming this PR doesn't negatively affects other modules when people don't use `mx`, I'd prefer to integrate it for 12.3 in its simple form and let the great visions materialize later. By integrating this PR as is, the module is immediately available in VSNetBeans - it would be great if this support could get into VSNetBeans as soon as possible.


----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r537058419



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/CoreSuite.java
##########
@@ -0,0 +1,732 @@
+/*
+ * 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.mx.project;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary.Arch;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+
+final class CoreSuite {
+    private static final class MapOf<K,V> {
+        private final Map<K,V> map = new HashMap<>();
+
+        MapOf<K, V> of(K k, V v) {
+            map.put(k, v);
+            return this;
+        }
+
+
+        Map<K,V> build() {
+            return map;
+        }
+    }
+
+    private static <K,V> MapOf<K,V> mapOf(Class<K> keyClass, Class<V> valueClass) {
+        return new MapOf<>();
+    }
+
+    private static MxSuite createMxSuite(
+        String defaultLicense,
+        Map<String, MxDistribution> distributions,
+        MxImports imports,
+        Map<String, MxLibrary> jdkLibraries,
+        Map<String, MxLibrary> libraries,
+        String mxversion,
+        String name,
+        Map<String, MxProject> projects
+    ) {
+        return new MxSuite() {
+            @Override
+            public String mxversion() {
+                return mxversion;
+            }
+
+            @Override
+            public String name() {
+                return name;
+            }
+
+            @Override
+            public String defaultLicense() {
+                return defaultLicense;
+            }
+
+            @Override
+            public Map<String, MxLibrary> libraries() {
+                return libraries;
+            }
+
+            @Override
+            public Map<String, MxLibrary> jdklibraries() {
+                return jdkLibraries;
+            }
+
+            @Override
+            public MxImports imports() {
+                return imports;
+            }
+
+            @Override
+            public Map<String, MxProject> projects() {
+                return projects;
+            }
+
+            @Override
+            public Map<String, MxDistribution> distributions() {
+                return distributions;
+            }
+        };
+    }
+
+    private static MxDistribution createMxDistribution(
+        List<String> dependencies,
+        List<String> distDependencies,
+        List<String> exclude,
+        List<String> strip
+    ) {
+        return new MxDistribution() {
+            @Override
+            public List<String> dependencies() {
+                return dependencies;
+            }
+
+            @Override
+            public List<String> distDependencies() {
+                return distDependencies;
+            }
+
+            @Override
+            public List<String> exclude() {
+                return exclude;
+            }
+
+            @Override
+            public List<String> strip() {
+                return strip;
+            }
+        };
+    }
+
+    private static MxProject createMxProject(
+        List<String> annotationProcessors,
+        List<String> dependencies,
+        String dir,
+        String javaCompliance,
+        List<String> sourceDirs,
+        String subDir
+    ) {
+        return new MxProject() {
+            @Override
+            public String dir() {
+                return dir;
+            }
+
+            @Override
+            public String subDir() {
+                return subDir;
+            }
+
+            @Override
+            public List<String> sourceDirs() {
+                return sourceDirs;
+            }
+
+            @Override
+            public List<String> dependencies() {
+                return dependencies;
+            }
+
+            @Override
+            public List<String> annotationProcessors() {
+                return annotationProcessors;
+            }
+
+            @Override
+            public String javaCompliance() {
+                return javaCompliance;
+            }
+        };
+    }
+
+    private static MxLibrary createMxLibrary(
+        List<String> dependencies,
+        Map<String, MxLibrary.Arch> osArch,
+        String path,
+        String sha1,
+        List<String> urls
+    ) {
+        return new MxLibrary() {
+            @Override
+            public String sha1() {
+                return sha1;
+            }
+
+            @Override
+            public List<String> urls() {
+                return urls;
+            }
+
+            @Override
+            public Map<String, MxLibrary.Arch> os_arch() {
+                return osArch;
+            }
+
+            @Override
+            public List<String> dependencies() {
+                return dependencies;
+            }
+
+            @Override
+            public String path() {
+                return path;
+            }
+
+        };
+    }
+
+    private static MxLibrary.Arch createArch(MxLibrary amd64) {
+        return new MxLibrary.Arch() {
+            @Override
+            public MxLibrary amd64() {
+                return amd64;
+            }
+        };
+    }
+
+    static final MxSuite CORE_5_279_0;
+    static {

Review comment:
       That would require `mx.mx/suite.py` being around. While `mx` is needed for execution/debugging of tests, it is not needed for opening the projects and navigating the sources right now. However, to properly resolve "standard mx libraries" we need to have information about them. That is what this file provides.
   
   Having the information here improves out of box experience. One can `git clone https://github.com/oracle/graal` and immediately open and inspect the sources.
   
   Of course, when new version of `mx` introduces new "standard libraries", we might need to release an update to this `CoreSuite` data. Possibly we could use this as default/fallback. Once `mx` is found/specified we could switch to its `mx.mx/suite.py` definitions.




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544078828



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteClassPathProvider.java
##########
@@ -0,0 +1,100 @@
+/*
+ * 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.mx.project;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+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.platform.JavaPlatformManager;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery.Result;
+import org.netbeans.spi.java.classpath.ClassPathProvider;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.AnnotationProcessingQueryImplementation;
+import org.netbeans.spi.project.ui.ProjectOpenedHook;
+import org.openide.filesystems.FileObject;
+
+final class SuiteClassPathProvider extends ProjectOpenedHook implements ClassPathProvider, AnnotationProcessingQueryImplementation {
+    private final SuiteProject project;
+    private final ClassPath bootCP;
+
+    public SuiteClassPathProvider(SuiteProject project) {
+        this.project = project;
+        List<ClassPath.Entry> entries = JavaPlatformManager.getDefault().getDefaultPlatform().getBootstrapLibraries().entries();
+        List<URL> roots = new ArrayList<>();
+        for (ClassPath.Entry entry : entries) {
+            URL root = entry.getURL();
+            if (root.getPath().contains("/graal-sdk.jar")) {
+                continue;
+            }
+            if (root.getPath().contains("/graaljs-scriptengine.jar")) {
+                continue;
+            }
+            if (root.getPath().contains("/graal-sdk.src.zip")) {
+                continue;
+            }
+            roots.add(entry.getURL());
+        }
+        this.bootCP = ClassPathSupport.createClassPath(roots.toArray(new URL[0]));
+    }
+
+    @Override
+    public ClassPath findClassPath(FileObject file, String type) {
+        SuiteSources.Group g = project.getSources().findGroup(file);
+        if (g == null) {
+            return null;
+        }
+        if (ClassPath.BOOT.equals(type)) {
+            return bootCP;

Review comment:
       Let's put this on TODO list: [NETBEANS-5159](https://issues.apache.org/jira/browse/NETBEANS-5159). If you face a buggy behavior, please describe reproducer there and turn that issue into bug.




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544121867



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static SuiteSources findSuiteSources(FileObject root, String name) throws IllegalArgumentException {
+        FileObject impDir = root.getFileObject(name);
+        if (impDir != null) {
+            try {
+                Project impPrj = ProjectManager.getDefault().findProject(impDir);
+                return impPrj == null ? null : impPrj.getLookup().lookup(SuiteSources.class);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceGroup[] getSourceGroups(String string) {
+        return groups();
+    }
+
+    Group[] groups() {
+        return groups.toArray(new Group[0]);
+    }
+
+    Group findGroup(FileObject fo) {
+        for (Group g : groups) {
+            if (g.contains(fo)) {
+                return g;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void addChangeListener(ChangeListener cl) {
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener cl) {
+    }
+
+    private static FileObject findPrjDir(FileObject dir, String prjName, MxProject prj) {
+        if (dir == null) {
+            return null;
+        }
+        if (prj.dir() != null) {
+            return dir.getFileObject(prj.dir());
+        }
+        if (prj.subDir() != null) {
+            dir = dir.getFileObject(prj.subDir());
+            if (dir == null) {
+                return null;
+            }
+        }
+        return dir.getFileObject(prjName);
+    }
+
+    private Collection<Dep> transitiveDeps(Dep current, Map<String, Dep> fill) {
+        current.owner().computeTransitiveDeps();
+        final Collection<Dep> currentAllDeps = current.allDeps();
+        if (currentAllDeps == Collections.<Dep>emptySet()) {
+            throw new IllegalStateException("Cyclic dep on " + current.getName());
+        } else if (currentAllDeps != null) {
+            return currentAllDeps;
+        }
+        current.setAllDeps(Collections.emptySet());
+        TreeSet<Dep> computing = new TreeSet<>();
+        computing.add(current);
+        for (String depName : current.depNames()) {
+            Dep dep = fill.get(depName);
+            if (dep == null) {
+                int colon = depName.lastIndexOf(':');
+                dep = fill.get(depName.substring(colon + 1));
+                if (dep == null) {
+                    LOG.log(Level.INFO, "dep not found: {0}", depName);
+                    continue;
+                }
+            }
+            Collection<Dep> allDeps = transitiveDeps(dep, fill);
+            computing.addAll(allDeps);
+        }
+        current.setAllDeps(computing);
+        return computing;
+    }
+
+    private static void registerLibs(Map<String, MxLibrary> collect, String prefix, Map<String, MxLibrary> libraries) {
+        for (Map.Entry<String, MxLibrary> entry : libraries.entrySet()) {
+            String key = entry.getKey();
+            MxLibrary lib = entry.getValue();
+            if (prefix == null) {
+                collect.put(key, lib);
+            } else {
+                collect.put(prefix + ":" + key, lib);
+            }
+        }
+    }
+
+    private void registerDeps(String prefix, Map<String, Dep> fillDeps) {
+        for (Library library : libraries) {
+            fillDeps.put(prefix + ":" + library.getName(), library);
+        }
+        for (Dist d : distributions) {
+            fillDeps.put(prefix + ":" + d.getName(), d);
+        }
+        for (Map.Entry<String, SuiteSources> s : imported.entrySet()) {
+            s.getValue().registerDeps(s.getKey(), fillDeps);
+        }
+    }
+
+    @Override
+    public Group findBinaryRoots2(URL url) {
+        final FileObject srcFo = URLMapper.findFileObject(url);
+        for (Group group : this.groups) {
+            if (group.contains(srcFo)) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public URL[] computeRoots(Group group) {
+        if (group.binDir != null) {
+            return new URL[] { group.binDir.toURL() };
+        } else {
+            return new URL[0];
+        }
+    }
+
+    @Override
+    public boolean computePreferBinaries(Group result) {
+        return true;
+    }
+
+    @Override
+    public void computeChangeListener(Group result, boolean bln, ChangeListener cl) {
+    }
+
+    @Override
+    public SourceForBinaryQueryImplementation2.Result findSourceRoots2(URL url) {
+        this.computeTransitiveDeps();
+        for (Dist dist : this.distributions) {
+            URL jar;
+            try {
+                jar = dist.getJarRoot();
+                if (jar == null) {
+                    continue;
+                }
+            } catch (MalformedURLException ok) {
+                continue;
+            }
+            if (jar.equals(url)) {
+                List<FileObject> roots = new ArrayList<>();
+                for (Group d : dist.getContributingGroups()) {
+                    roots.add(d.srcDir);
+                    roots.add(d.srcGenDir);
+                }
+                return new ImmutableResult(roots.toArray(new FileObject[roots.size()]));
+            }
+        }
+        for (Group group : this.groups) {
+            if (group.binDir != null && group.binDir.toURL().equals(url)) {
+                return new ImmutableResult(group.srcDir, group.srcGenDir);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceForBinaryQuery.Result findSourceRoots(URL url) {
+        return findSourceRoots2(url);
+    }
+
+    final Iterable<File> jdks() {
+        Set<File> jdks = new LinkedHashSet<>();
+        String home = System.getProperty("user.home");
+        if (home != null) {
+            File userEnv = new File(new File(new File(home), ".mx"), "env");
+            findJdksInEnv(jdks, userEnv);
+        }
+        FileObject suiteEnv = dir.getFileObject("mx." + dir.getNameExt() + "/env");
+        if (suiteEnv != null) {
+            findJdksInEnv(jdks, FileUtil.toFile(suiteEnv));
+        }
+
+        String javaHomeEnv = System.getenv("JAVA_HOME");
+        if (javaHomeEnv != null) {
+            jdks.add(new File(javaHomeEnv));
+        }
+        String javaHomeProp = System.getProperty("java.home");
+        if (javaHomeProp != null) {
+            jdks.add(new File(javaHomeProp));
+        }
+        return jdks;
+    }
+
+    private void findJdksInEnv(Set<File> jdks, File env) {
+        if (env == null || !env.isFile()) {
+            return;
+        }
+        try (final FileInputStream is = new FileInputStream(env)) {
+            Properties p = new Properties();
+            p.load(is);
+
+            String javaHome = p.getProperty("JAVA_HOME");
+            if (javaHome != null) {
+                jdks.add(new File(javaHome));
+            }
+
+            String extraJavaHomes = p.getProperty("EXTRA_JAVA_HOMES");
+            if (extraJavaHomes != null) {
+                for (String extraHome : extraJavaHomes.split(File.pathSeparator)) {
+                    jdks.add(new File(extraHome));
+                }
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public SourceLevelQueryImplementation2.Result getSourceLevel(FileObject fo) {
+        Group g = findGroup(fo);
+        if (g == null) {
+            return null;
+        }
+        return new SourceLevelQueryImplementation2.Result2() {
+            @Override
+            public SourceLevelQuery.Profile getProfile() {
+                return SourceLevelQuery.Profile.DEFAULT;
+            }
+
+            @Override
+            public String getSourceLevel() {
+                return g.getCompliance().getSourceLevel();
+            }
+
+            @Override
+            public void addChangeListener(ChangeListener cl) {
+            }
+
+            @Override
+            public void removeChangeListener(ChangeListener cl) {
+            }
+        };
+    }
+
+    @Override
+    public Set<? extends Project> getSubprojects() {
+        Set<Project> prjs = new HashSet<>();
+        for (SuiteSources imp : imported.values()) {
+            prjs.add(imp.prj);
+        }
+        return prjs;
+    }
+
+    @Override
+    public URL[] findUnitTests(FileObject fo) {
+        return new URL[0];
+    }
+
+    @Override
+    public URL[] findSources(FileObject fo) {
+        Group g = findGroup(fo);
+        return g == null ? new URL[0] : new URL[] { g.getRootFolder().toURL() };
+    }
+
+    static interface Dep extends Comparable<Dep> {
+        String getName();
+
+        Collection<String> depNames();
+
+        Collection<Dep> allDeps();
+
+        void setAllDeps(Collection<Dep> set);
+
+        @Override
+        public default int compareTo(Dep o) {
+            return getName().compareTo(o.getName());
+        }
+
+        SuiteSources owner();
+    }
+
+    final class Dist implements Dep, FlaggedClassPathImplementation {
+        final String name;
+        final MxDistribution dist;
+        Collection<Dep> allDeps;
+        private final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        private Boolean exists;
+        private Collection<Group> groups;
+
+        public Dist(String name, MxDistribution dist) {
+            this.name = name;
+            this.dist = dist;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            Set<String> deps = new TreeSet<>();
+            deps.addAll(dist.distDependencies());
+            deps.addAll(dist.exclude());
+            return deps;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return this.allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public String getName() {
+            return this.name;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        private FileObject getJar(boolean ignore) {
+            if (SuiteSources.this.dir == null) {
+                return null;
+            }
+            FileObject dists = SuiteSources.this.dir.getFileObject("mxbuild/dists");
+            if (dists == null) {
+                return null;
+            }
+            List<FileObject> dist = Arrays.stream(dists.getChildren()).filter((fo) -> fo.isFolder() && fo.getName().startsWith("jdk")).collect(Collectors.toList());
+            dist.sort((fo1, fo2) -> fo2.getName().compareTo(fo1.getName()));
+            for (FileObject jdkDir : dist) {
+                FileObject jar = jdkDir.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+                if (jar != null) {
+                    return jar;
+                }
+            }
+            FileObject jar = dists.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+            if (jar != null) {
+                return jar;
+            }
+            return null;
+        }
+
+        @Override
+        public List<? extends PathResourceImplementation> getResources() {
+            computeTransitiveDeps();
+            FileObject jar = getJar(exists == null);
+            final boolean existsNow = jar != null && jar.isData();
+            if (exists == null) {
+                exists = existsNow;
+            } else {
+                if (exists != existsNow) {
+                    exists = existsNow;
+                    support.firePropertyChange(PROP_FLAGS, !exists, (boolean) exists);
+                }
+            }
+            if (jar != null) {
+                PathResourceImplementation res;
+                try {
+                    res = ClassPathSupport.createResource(getJarRoot());
+                    return Collections.singletonList(res);
+                } catch (MalformedURLException ex) {
+                    // OK
+                }
+            }
+            return Collections.emptyList();
+        }
+
+        private URL getJarRoot() throws MalformedURLException {
+            FileObject jar = getJar(true);
+            if (jar != null) {
+                return new URL("jar:" + jar.toURL() + "!/");
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener pl) {
+            support.addPropertyChangeListener(pl);
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener pl) {
+            support.removePropertyChangeListener(pl);
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+
+        private void computeSourceRoots(Map<String, Dep> collectedDeps) {
+            if (groups != null) {
+                return;
+            }
+            Set<Group> contributingGroups = new LinkedHashSet<>();
+            for (String d : this.dist.dependencies()) {
+                Dep dep = collectedDeps.get(d);
+                if (dep == null || dep.allDeps() == null) {
+                    continue;
+                }
+                for (Dep d2 : dep.allDeps()) {
+                    if (d2 instanceof Group) {
+                        contributingGroups.add((Group) d2);
+                    }
+                }
+            }
+            for (String d : this.dist.distDependencies()) {
+                final Dep anyDep = collectedDeps.get(d);
+                if (anyDep instanceof Dist) {
+                    Dist dep = (Dist) anyDep;
+                    dep.computeSourceRoots(collectedDeps);
+                    contributingGroups.removeAll(dep.getContributingGroups());
+                }
+            }
+            groups = contributingGroups;
+        }
+
+        public Collection<Group> getContributingGroups() {
+            return groups;
+        }
+
+        @Override
+        public String toString() {
+            return "Dist[name=" + name + "]";
+        }
+    }
+
+    final class Group implements SourceGroup, Dep, AnnotationProcessingQuery.Result,
+            Compliance.Provider {
+        private final String mxName;
+        private final MxProject mxPrj;
+        private final FileObject srcDir;
+        private final FileObject srcGenDir;
+        private final FileObject binDir;
+        private final String name;
+        private final String displayName;
+        private final Compliance compliance;
+        private ClassPath sourceCP;
+        private ClassPath cp;
+        private ClassPath processorPath;
+        private Collection<Dep> allDeps;
+
+        Group(String mxName, MxProject mxPrj, FileObject srcDir, FileObject srcGenDir, FileObject binDir, String name, String displayName) {
+            this.mxName = mxName;
+            this.mxPrj = mxPrj;
+            this.srcDir = srcDir;
+            this.srcGenDir = srcGenDir;
+            this.binDir = binDir;
+            this.name = name;
+            this.displayName = displayName;
+            this.compliance = Compliance.parse(mxPrj.javaCompliance());
+        }
+
+        @Override
+        public FileObject getRootFolder() {
+            return srcDir;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        @Override
+        public Icon getIcon(boolean opened) {
+            return null;
+        }
+
+        @Override
+        public Compliance getCompliance() {
+            return compliance;
+        }
+
+        @Override
+        public boolean contains(FileObject file) {
+            if (file == srcDir || file == srcGenDir || FileUtil.isParentOf(srcDir, file) || (srcGenDir != null && FileUtil.isParentOf(srcGenDir, file))) {
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public String toString() {
+            return "SuiteSources.Group[name=" + name + ",rootFolder=" + srcDir + "]"; // NOI18N
+        }
+
+        ClassPath getSourceCP() {
+            computeTransitiveDeps();
+            return sourceCP;
+        }
+
+        ClassPath getCP() {
+            computeTransitiveDeps();
+            return cp;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return mxPrj.dependencies();
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            allDeps = set;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        private void computeClassPath(Map<String, Dep> transDeps) {
+            for (Dep d : transDeps.values()) {
+                d.owner().computeTransitiveDeps();
+            }
+
+            List<Group> arr = new ArrayList<>();
+            List<ClassPathImplementation> libs = new ArrayList<>();
+            processTransDep(transDeps.get(mxName), arr, libs);
+            cp = composeClassPath(arr, libs);
+            List<FileObject> roots = new ArrayList<>();
+            if (srcDir != null) {
+                roots.add(srcDir);
+            }
+            if (srcGenDir != null) {
+                roots.add(srcGenDir);
+            }
+            sourceCP = ClassPathSupport.createClassPath(roots.toArray(new FileObject[roots.size()]));
+
+            if (mxPrj.annotationProcessors().isEmpty()) {
+                processorPath = null;
+            } else {
+                List<Group> groups = new ArrayList<>();
+                List<ClassPathImplementation> jars = new ArrayList<>();
+                for (String dep : mxPrj.annotationProcessors()) {
+                    processTransDep(transDeps.get(dep), groups, jars);
+                }
+                processorPath = composeClassPath(groups, jars);
+            }
+        }
+
+        private void processTransDep(Dep dep, List<Group> addGroups, List<ClassPathImplementation> addJars) {
+            if (dep != null) {
+                dep.owner().computeTransitiveDeps();
+                for (Dep d : dep.allDeps()) {
+                    if (d == this) {
+                        continue;
+                    }
+                    d.owner().computeTransitiveDeps();
+                    if (d instanceof Group) {
+                        addGroups.add((Group) d);
+                    } else if (d instanceof ClassPathImplementation) {
+                        addJars.add((ClassPathImplementation) d);
+                    }
+                }
+            }
+        }
+
+        private ClassPath composeClassPath(List<Group> arr, List<ClassPathImplementation> libs) {
+            Set<FileObject> roots = new LinkedHashSet<>();
+            final int depsCount = arr.size();
+            for (int i = 0; i < depsCount; i++) {
+                final Group g = arr.get(i);
+                if (g.binDir != null) {
+                    roots.add(g.binDir);
+                }
+            }
+            ClassPath prjCp = ClassPathSupport.createClassPath(roots.toArray(new FileObject[0]));
+            if (!libs.isEmpty()) {
+                if (libs.size() == 1) {
+                    prjCp = ClassPathSupport.createProxyClassPath(prjCp,
+                                                                  ClassPathFactory.createClassPath(libs.get(0))
+                    );
+                } else {
+                    prjCp = ClassPathSupport.createProxyClassPath(prjCp,
+                                                                  ClassPathFactory.createClassPath(
+                                                                                  ClassPathSupport.createProxyClassPathImplementation(
+                                                                                                  libs.toArray(new ClassPathImplementation[0])
+                                                                                  )
+                                                                  )
+                    );
+                }
+            }
+            return prjCp;
+        }
+
+        ClassPath getProcessorCP() {
+            computeTransitiveDeps();
+            return processorPath;
+        }
+
+        @Override
+        public Set<? extends AnnotationProcessingQuery.Trigger> annotationProcessingEnabled() {
+            return EnumSet.of(AnnotationProcessingQuery.Trigger.ON_SCAN, AnnotationProcessingQuery.Trigger.IN_EDITOR);
+        }
+
+        @Override
+        public Iterable<? extends String> annotationProcessorsToRun() {
+            return null;
+        }
+
+        @Override
+        public URL sourceOutputDirectory() {
+            return srcGenDir == null ? null : srcGenDir.toURL();
+        }
+
+        @Override
+        public Map<? extends String, ? extends String> processorOptions() {
+            return Collections.emptyMap();
+        }
+
+        @Override
+        public void addChangeListener(ChangeListener l) {
+        }
+
+        @Override
+        public void removeChangeListener(ChangeListener l) {
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+    }
+
+    private class Library implements FlaggedClassPathImplementation, Dep {
+        final MxLibrary lib;
+        final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        final String libName;
+        Collection<Dep> allDeps;
+        Boolean exists;
+
+        Library(String libName, MxLibrary lib) {
+            this.libName = libName;
+            this.lib = getOSSLibrary(lib);
+        }
+
+        final MxLibrary getOSSLibrary(MxLibrary lib) {
+            if (lib.sha1() == null && !lib.os_arch().isEmpty()) {
+                Map<String, MxLibrary.Arch> os_dep_libs = lib.os_arch();
+                String os = System.getProperty("os.name").toLowerCase();
+                for (Map.Entry<String, MxLibrary.Arch> entry : os_dep_libs.entrySet()) {
+                    if (os.contains(entry.getKey())) {
+                        return entry.getValue().amd64();
+                    }
+                }
+            }
+            return lib;
+        }
+
+        File getJar(boolean dumpIfMissing) {
+            File mxCache;
+            String cache = System.getenv("MX_CACHE_DIR");
+            if (cache != null) {
+                mxCache = new File(cache);
+            } else {
+                mxCache = new File(new File(new File(System.getProperty("user.home")), ".mx"), "cache");
+            }
+            int prefix = libName.indexOf(':');
+            final String simpleName = libName.substring(prefix + 1);
+
+            File simpleJar = new File(mxCache, simpleName + "_" + lib.sha1() + ".jar");
+            if (simpleJar.exists()) {
+                return simpleJar;
+            }
+            File dir = new File(mxCache, simpleName + "_" + lib.sha1());
+            File jar = new File(dir, simpleName.replace('_', '-').toLowerCase(Locale.ENGLISH) + ".jar");
+
+            if (dumpIfMissing && !jar.exists()) {
+                for (File f = jar;; f = f.getParentFile()) {
+                    if (!f.exists()) {
+                        LOG.log(Level.WARNING, "{0} does not exist", f);
+                    } else {
+                        StringBuilder sb = new StringBuilder();
+                        sb.append(f).append(" exists:\n");
+                        String[] kids = f.list();
+                        if (kids != null) {
+                            for (String n : kids) {
+                                sb.append("  ").append(n).append("\n");
+                            }
+                        }
+                        LOG.log(Level.INFO, sb.toString());
+                        break;
+                    }
+                }
+            }
+            return jar;
+        }
+
+        @Override
+        public String getName() {
+            return libName;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return lib.dependencies();
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        @Override
+        public List<? extends PathResourceImplementation> getResources() {
+            File jar = getJar(exists == null);
+            if (exists == null) {
+                exists = jar.exists();
+            } else {
+                if (exists != jar.exists()) {
+                    exists = jar.exists();
+                    support.firePropertyChange(PROP_FLAGS, !exists, (boolean) exists);
+                }
+            }
+            PathResourceImplementation res;
+            try {
+                res = ClassPathSupport.createResource(new URL("jar:" + Utilities.toURI(jar).toURL() + "!/"));
+                return Collections.singletonList(res);
+            } catch (MalformedURLException ex) {
+                return Collections.emptyList();
+            }
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener pl) {
+            support.addPropertyChangeListener(pl);
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener pl) {
+            support.removePropertyChangeListener(pl);
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+    }
+
+    private class JdkLibrary extends Library {
+        JdkLibrary(String libName, MxLibrary lib) {
+            super(libName, lib);
+        }
+
+        @Override
+        File getJar(boolean dumpIfMissing) {
+            File first = null;
+            for (File jdk : jdks()) {
+                File jre = new File(jdk, "jre");
+                File jrePath = new File(jre, lib.path().replace('/', File.separatorChar));
+                if (jrePath.exists()) {
+                    return jrePath;
+                }
+
+                if (first == null) {
+                    first = jrePath;
+                }
+
+                File jdkPath = new File(jdk, lib.path().replace('/', File.separatorChar));
+
+                if (jdkPath.exists()) {
+                    return jdkPath;
+                }
+
+            }
+
+            if (dumpIfMissing) {
+                for (File jdk : jdks()) {
+                    File libPath = new File(jdk, lib.path().replace('/', File.separatorChar));
+                    if (!libPath.exists()) {
+                        LOG.log(Level.WARNING, "{0} does not exist", libPath);
+                    } else {
+                        StringBuilder sb = new StringBuilder();
+                        sb.append(libPath).append(" exists:\n");
+                        String[] kids = libPath.list();
+                        if (kids != null) {
+                            for (String n : kids) {
+                                sb.append("  ").append(n).append("\n");
+                            }
+                        }
+                        LOG.log(Level.INFO, sb.toString());
+                        break;
+                    }
+                }
+            }
+            return first;
+        }
+
+        @Override
+        public String getName() {
+            return libName;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return lib.dependencies();
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        @Override
+        public List<? extends PathResourceImplementation> getResources() {

Review comment:
       Excellent idea: af12108




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544091339



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static SuiteSources findSuiteSources(FileObject root, String name) throws IllegalArgumentException {
+        FileObject impDir = root.getFileObject(name);
+        if (impDir != null) {
+            try {
+                Project impPrj = ProjectManager.getDefault().findProject(impDir);
+                return impPrj == null ? null : impPrj.getLookup().lookup(SuiteSources.class);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceGroup[] getSourceGroups(String string) {
+        return groups();
+    }
+
+    Group[] groups() {
+        return groups.toArray(new Group[0]);
+    }
+
+    Group findGroup(FileObject fo) {
+        for (Group g : groups) {
+            if (g.contains(fo)) {
+                return g;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void addChangeListener(ChangeListener cl) {
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener cl) {
+    }
+
+    private static FileObject findPrjDir(FileObject dir, String prjName, MxProject prj) {
+        if (dir == null) {
+            return null;
+        }
+        if (prj.dir() != null) {
+            return dir.getFileObject(prj.dir());
+        }
+        if (prj.subDir() != null) {
+            dir = dir.getFileObject(prj.subDir());
+            if (dir == null) {
+                return null;
+            }
+        }
+        return dir.getFileObject(prjName);
+    }
+
+    private Collection<Dep> transitiveDeps(Dep current, Map<String, Dep> fill) {
+        current.owner().computeTransitiveDeps();
+        final Collection<Dep> currentAllDeps = current.allDeps();
+        if (currentAllDeps == Collections.<Dep>emptySet()) {
+            throw new IllegalStateException("Cyclic dep on " + current.getName());
+        } else if (currentAllDeps != null) {
+            return currentAllDeps;
+        }
+        current.setAllDeps(Collections.emptySet());
+        TreeSet<Dep> computing = new TreeSet<>();
+        computing.add(current);
+        for (String depName : current.depNames()) {
+            Dep dep = fill.get(depName);
+            if (dep == null) {
+                int colon = depName.lastIndexOf(':');
+                dep = fill.get(depName.substring(colon + 1));
+                if (dep == null) {
+                    LOG.log(Level.INFO, "dep not found: {0}", depName);
+                    continue;
+                }
+            }
+            Collection<Dep> allDeps = transitiveDeps(dep, fill);
+            computing.addAll(allDeps);
+        }
+        current.setAllDeps(computing);
+        return computing;
+    }
+
+    private static void registerLibs(Map<String, MxLibrary> collect, String prefix, Map<String, MxLibrary> libraries) {
+        for (Map.Entry<String, MxLibrary> entry : libraries.entrySet()) {
+            String key = entry.getKey();
+            MxLibrary lib = entry.getValue();
+            if (prefix == null) {
+                collect.put(key, lib);
+            } else {
+                collect.put(prefix + ":" + key, lib);
+            }
+        }
+    }
+
+    private void registerDeps(String prefix, Map<String, Dep> fillDeps) {
+        for (Library library : libraries) {
+            fillDeps.put(prefix + ":" + library.getName(), library);
+        }
+        for (Dist d : distributions) {
+            fillDeps.put(prefix + ":" + d.getName(), d);
+        }
+        for (Map.Entry<String, SuiteSources> s : imported.entrySet()) {
+            s.getValue().registerDeps(s.getKey(), fillDeps);
+        }
+    }
+
+    @Override
+    public Group findBinaryRoots2(URL url) {
+        final FileObject srcFo = URLMapper.findFileObject(url);
+        for (Group group : this.groups) {
+            if (group.contains(srcFo)) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public URL[] computeRoots(Group group) {
+        if (group.binDir != null) {
+            return new URL[] { group.binDir.toURL() };
+        } else {
+            return new URL[0];
+        }
+    }
+
+    @Override
+    public boolean computePreferBinaries(Group result) {
+        return true;
+    }
+
+    @Override
+    public void computeChangeListener(Group result, boolean bln, ChangeListener cl) {
+    }
+
+    @Override
+    public SourceForBinaryQueryImplementation2.Result findSourceRoots2(URL url) {
+        this.computeTransitiveDeps();
+        for (Dist dist : this.distributions) {
+            URL jar;
+            try {
+                jar = dist.getJarRoot();
+                if (jar == null) {
+                    continue;
+                }
+            } catch (MalformedURLException ok) {
+                continue;
+            }
+            if (jar.equals(url)) {
+                List<FileObject> roots = new ArrayList<>();
+                for (Group d : dist.getContributingGroups()) {
+                    roots.add(d.srcDir);
+                    roots.add(d.srcGenDir);
+                }
+                return new ImmutableResult(roots.toArray(new FileObject[roots.size()]));
+            }
+        }
+        for (Group group : this.groups) {
+            if (group.binDir != null && group.binDir.toURL().equals(url)) {
+                return new ImmutableResult(group.srcDir, group.srcGenDir);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceForBinaryQuery.Result findSourceRoots(URL url) {
+        return findSourceRoots2(url);
+    }
+
+    final Iterable<File> jdks() {
+        Set<File> jdks = new LinkedHashSet<>();
+        String home = System.getProperty("user.home");
+        if (home != null) {
+            File userEnv = new File(new File(new File(home), ".mx"), "env");
+            findJdksInEnv(jdks, userEnv);
+        }
+        FileObject suiteEnv = dir.getFileObject("mx." + dir.getNameExt() + "/env");
+        if (suiteEnv != null) {
+            findJdksInEnv(jdks, FileUtil.toFile(suiteEnv));
+        }
+
+        String javaHomeEnv = System.getenv("JAVA_HOME");
+        if (javaHomeEnv != null) {
+            jdks.add(new File(javaHomeEnv));
+        }
+        String javaHomeProp = System.getProperty("java.home");
+        if (javaHomeProp != null) {
+            jdks.add(new File(javaHomeProp));
+        }
+        return jdks;
+    }
+
+    private void findJdksInEnv(Set<File> jdks, File env) {
+        if (env == null || !env.isFile()) {
+            return;
+        }
+        try (final FileInputStream is = new FileInputStream(env)) {
+            Properties p = new Properties();
+            p.load(is);
+
+            String javaHome = p.getProperty("JAVA_HOME");
+            if (javaHome != null) {
+                jdks.add(new File(javaHome));
+            }
+
+            String extraJavaHomes = p.getProperty("EXTRA_JAVA_HOMES");
+            if (extraJavaHomes != null) {
+                for (String extraHome : extraJavaHomes.split(File.pathSeparator)) {
+                    jdks.add(new File(extraHome));
+                }
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public SourceLevelQueryImplementation2.Result getSourceLevel(FileObject fo) {
+        Group g = findGroup(fo);
+        if (g == null) {
+            return null;
+        }
+        return new SourceLevelQueryImplementation2.Result2() {
+            @Override
+            public SourceLevelQuery.Profile getProfile() {
+                return SourceLevelQuery.Profile.DEFAULT;
+            }
+
+            @Override
+            public String getSourceLevel() {
+                return g.getCompliance().getSourceLevel();
+            }
+
+            @Override
+            public void addChangeListener(ChangeListener cl) {
+            }
+
+            @Override
+            public void removeChangeListener(ChangeListener cl) {
+            }
+        };
+    }
+
+    @Override
+    public Set<? extends Project> getSubprojects() {
+        Set<Project> prjs = new HashSet<>();
+        for (SuiteSources imp : imported.values()) {
+            prjs.add(imp.prj);
+        }
+        return prjs;
+    }
+
+    @Override
+    public URL[] findUnitTests(FileObject fo) {
+        return new URL[0];
+    }
+
+    @Override
+    public URL[] findSources(FileObject fo) {
+        Group g = findGroup(fo);
+        return g == null ? new URL[0] : new URL[] { g.getRootFolder().toURL() };
+    }
+
+    static interface Dep extends Comparable<Dep> {
+        String getName();
+
+        Collection<String> depNames();
+
+        Collection<Dep> allDeps();
+
+        void setAllDeps(Collection<Dep> set);
+
+        @Override
+        public default int compareTo(Dep o) {
+            return getName().compareTo(o.getName());
+        }
+
+        SuiteSources owner();
+    }
+
+    final class Dist implements Dep, FlaggedClassPathImplementation {
+        final String name;
+        final MxDistribution dist;
+        Collection<Dep> allDeps;
+        private final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        private Boolean exists;
+        private Collection<Group> groups;
+
+        public Dist(String name, MxDistribution dist) {
+            this.name = name;
+            this.dist = dist;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            Set<String> deps = new TreeSet<>();
+            deps.addAll(dist.distDependencies());
+            deps.addAll(dist.exclude());
+            return deps;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return this.allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public String getName() {
+            return this.name;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        private FileObject getJar(boolean ignore) {

Review comment:
       Done in 3933d300360




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544094438



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static SuiteSources findSuiteSources(FileObject root, String name) throws IllegalArgumentException {
+        FileObject impDir = root.getFileObject(name);
+        if (impDir != null) {
+            try {
+                Project impPrj = ProjectManager.getDefault().findProject(impDir);
+                return impPrj == null ? null : impPrj.getLookup().lookup(SuiteSources.class);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceGroup[] getSourceGroups(String string) {
+        return groups();
+    }
+
+    Group[] groups() {
+        return groups.toArray(new Group[0]);
+    }
+
+    Group findGroup(FileObject fo) {
+        for (Group g : groups) {
+            if (g.contains(fo)) {
+                return g;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void addChangeListener(ChangeListener cl) {
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener cl) {
+    }
+
+    private static FileObject findPrjDir(FileObject dir, String prjName, MxProject prj) {
+        if (dir == null) {
+            return null;
+        }
+        if (prj.dir() != null) {
+            return dir.getFileObject(prj.dir());
+        }
+        if (prj.subDir() != null) {
+            dir = dir.getFileObject(prj.subDir());
+            if (dir == null) {
+                return null;
+            }
+        }
+        return dir.getFileObject(prjName);
+    }
+
+    private Collection<Dep> transitiveDeps(Dep current, Map<String, Dep> fill) {
+        current.owner().computeTransitiveDeps();
+        final Collection<Dep> currentAllDeps = current.allDeps();
+        if (currentAllDeps == Collections.<Dep>emptySet()) {
+            throw new IllegalStateException("Cyclic dep on " + current.getName());
+        } else if (currentAllDeps != null) {
+            return currentAllDeps;
+        }
+        current.setAllDeps(Collections.emptySet());
+        TreeSet<Dep> computing = new TreeSet<>();
+        computing.add(current);
+        for (String depName : current.depNames()) {
+            Dep dep = fill.get(depName);
+            if (dep == null) {
+                int colon = depName.lastIndexOf(':');
+                dep = fill.get(depName.substring(colon + 1));
+                if (dep == null) {
+                    LOG.log(Level.INFO, "dep not found: {0}", depName);
+                    continue;
+                }
+            }
+            Collection<Dep> allDeps = transitiveDeps(dep, fill);
+            computing.addAll(allDeps);
+        }
+        current.setAllDeps(computing);
+        return computing;
+    }
+
+    private static void registerLibs(Map<String, MxLibrary> collect, String prefix, Map<String, MxLibrary> libraries) {
+        for (Map.Entry<String, MxLibrary> entry : libraries.entrySet()) {
+            String key = entry.getKey();
+            MxLibrary lib = entry.getValue();
+            if (prefix == null) {
+                collect.put(key, lib);
+            } else {
+                collect.put(prefix + ":" + key, lib);
+            }
+        }
+    }
+
+    private void registerDeps(String prefix, Map<String, Dep> fillDeps) {
+        for (Library library : libraries) {
+            fillDeps.put(prefix + ":" + library.getName(), library);
+        }
+        for (Dist d : distributions) {
+            fillDeps.put(prefix + ":" + d.getName(), d);
+        }
+        for (Map.Entry<String, SuiteSources> s : imported.entrySet()) {
+            s.getValue().registerDeps(s.getKey(), fillDeps);
+        }
+    }
+
+    @Override
+    public Group findBinaryRoots2(URL url) {
+        final FileObject srcFo = URLMapper.findFileObject(url);
+        for (Group group : this.groups) {
+            if (group.contains(srcFo)) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public URL[] computeRoots(Group group) {
+        if (group.binDir != null) {
+            return new URL[] { group.binDir.toURL() };
+        } else {
+            return new URL[0];
+        }
+    }
+
+    @Override
+    public boolean computePreferBinaries(Group result) {
+        return true;
+    }
+
+    @Override
+    public void computeChangeListener(Group result, boolean bln, ChangeListener cl) {
+    }
+
+    @Override
+    public SourceForBinaryQueryImplementation2.Result findSourceRoots2(URL url) {
+        this.computeTransitiveDeps();
+        for (Dist dist : this.distributions) {
+            URL jar;
+            try {
+                jar = dist.getJarRoot();
+                if (jar == null) {
+                    continue;
+                }
+            } catch (MalformedURLException ok) {
+                continue;
+            }
+            if (jar.equals(url)) {
+                List<FileObject> roots = new ArrayList<>();
+                for (Group d : dist.getContributingGroups()) {
+                    roots.add(d.srcDir);
+                    roots.add(d.srcGenDir);
+                }
+                return new ImmutableResult(roots.toArray(new FileObject[roots.size()]));
+            }
+        }
+        for (Group group : this.groups) {
+            if (group.binDir != null && group.binDir.toURL().equals(url)) {
+                return new ImmutableResult(group.srcDir, group.srcGenDir);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceForBinaryQuery.Result findSourceRoots(URL url) {
+        return findSourceRoots2(url);
+    }
+
+    final Iterable<File> jdks() {
+        Set<File> jdks = new LinkedHashSet<>();
+        String home = System.getProperty("user.home");
+        if (home != null) {
+            File userEnv = new File(new File(new File(home), ".mx"), "env");
+            findJdksInEnv(jdks, userEnv);
+        }
+        FileObject suiteEnv = dir.getFileObject("mx." + dir.getNameExt() + "/env");
+        if (suiteEnv != null) {
+            findJdksInEnv(jdks, FileUtil.toFile(suiteEnv));
+        }
+
+        String javaHomeEnv = System.getenv("JAVA_HOME");
+        if (javaHomeEnv != null) {
+            jdks.add(new File(javaHomeEnv));
+        }
+        String javaHomeProp = System.getProperty("java.home");
+        if (javaHomeProp != null) {
+            jdks.add(new File(javaHomeProp));
+        }
+        return jdks;
+    }
+
+    private void findJdksInEnv(Set<File> jdks, File env) {
+        if (env == null || !env.isFile()) {
+            return;
+        }
+        try (final FileInputStream is = new FileInputStream(env)) {
+            Properties p = new Properties();
+            p.load(is);
+
+            String javaHome = p.getProperty("JAVA_HOME");
+            if (javaHome != null) {
+                jdks.add(new File(javaHome));
+            }
+
+            String extraJavaHomes = p.getProperty("EXTRA_JAVA_HOMES");
+            if (extraJavaHomes != null) {
+                for (String extraHome : extraJavaHomes.split(File.pathSeparator)) {
+                    jdks.add(new File(extraHome));
+                }
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public SourceLevelQueryImplementation2.Result getSourceLevel(FileObject fo) {
+        Group g = findGroup(fo);
+        if (g == null) {
+            return null;
+        }
+        return new SourceLevelQueryImplementation2.Result2() {
+            @Override
+            public SourceLevelQuery.Profile getProfile() {
+                return SourceLevelQuery.Profile.DEFAULT;
+            }
+
+            @Override
+            public String getSourceLevel() {
+                return g.getCompliance().getSourceLevel();
+            }
+
+            @Override
+            public void addChangeListener(ChangeListener cl) {
+            }
+
+            @Override
+            public void removeChangeListener(ChangeListener cl) {
+            }
+        };
+    }
+
+    @Override
+    public Set<? extends Project> getSubprojects() {
+        Set<Project> prjs = new HashSet<>();
+        for (SuiteSources imp : imported.values()) {
+            prjs.add(imp.prj);
+        }
+        return prjs;
+    }
+
+    @Override
+    public URL[] findUnitTests(FileObject fo) {
+        return new URL[0];

Review comment:
       This is one of the reasons why special `mx` support is desirable. Typical NetBeans projects assume two kinds of directories - main and test. This is not true for `mx` there is no association of main code and test code. Tests are regular source roots with classes annotated by `@Test`. As such this code returns `new URL[0]`.
   
   Feel free to improve it, if you know how.




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#issuecomment-737834954


   >  [Build Server Protocol](https://build-server-protocol.github.io/) might be a better way forward?
   
   Interesting approach. Thanks for sharing the link. After skimming through the docs three questions puzzle in my mind:
   
   1. While promised in the teaser pages, there is no specification for **debugging** - that's tough one to abstract
   2. I was hoping to have the *mx projects* support in 12.3 - yet the BSP looks very **experimental** (like the missing debugging support)
   3. Should NetBeans acts as a **server or client**? The NetBeans infrastructure is actually ideal for abstracting over various build systems and responding to the BSP queries!
   
   Ideally we get this PR in, someone sits and implements BSP server based on NetBeans project infrastructure and then we'll have BSP for `mx`! Did I turned it all upside down?


----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] sdedic commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
sdedic commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r538171530



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteClassPathProvider.java
##########
@@ -0,0 +1,100 @@
+/*
+ * 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.mx.project;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+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.platform.JavaPlatformManager;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery.Result;
+import org.netbeans.spi.java.classpath.ClassPathProvider;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.AnnotationProcessingQueryImplementation;
+import org.netbeans.spi.project.ui.ProjectOpenedHook;
+import org.openide.filesystems.FileObject;
+
+final class SuiteClassPathProvider extends ProjectOpenedHook implements ClassPathProvider, AnnotationProcessingQueryImplementation {
+    private final SuiteProject project;
+    private final ClassPath bootCP;
+
+    public SuiteClassPathProvider(SuiteProject project) {
+        this.project = project;
+        List<ClassPath.Entry> entries = JavaPlatformManager.getDefault().getDefaultPlatform().getBootstrapLibraries().entries();
+        List<URL> roots = new ArrayList<>();
+        for (ClassPath.Entry entry : entries) {
+            URL root = entry.getURL();
+            if (root.getPath().contains("/graal-sdk.jar")) {

Review comment:
       Maybe this exclusion is worth a comment in source; why the exclusion is needed.

##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteActionProvider.java
##########
@@ -0,0 +1,245 @@
+/*
+ * 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.mx.project;
+
+import java.awt.Toolkit;
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.Future;
+import org.netbeans.api.actions.Openable;
+import org.netbeans.api.debugger.*;
+import org.netbeans.api.debugger.jpda.ListeningDICookie;
+import org.netbeans.api.extexecution.ExecutionDescriptor;
+import org.netbeans.api.extexecution.ExecutionService;
+import org.netbeans.api.extexecution.base.ProcessBuilder;
+import org.netbeans.api.extexecution.print.ConvertedLine;
+import org.netbeans.spi.project.ActionProvider;
+import org.netbeans.spi.project.SingleMethod;
+import org.openide.LifecycleManager;
+import org.openide.cookies.LineCookie;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.text.Line;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.util.RequestProcessor;
+import org.openide.windows.OutputEvent;
+import org.openide.windows.OutputListener;
+
+final class SuiteActionProvider implements ActionProvider {
+    private final SuiteProject prj;
+
+    SuiteActionProvider(SuiteProject prj) {
+        this.prj = prj;
+    }
+
+    @Override
+    public String[] getSupportedActions() {
+        return new String[] {
+            ActionProvider.COMMAND_CLEAN,
+            ActionProvider.COMMAND_BUILD,
+            ActionProvider.COMMAND_COMPILE_SINGLE,
+            ActionProvider.COMMAND_REBUILD,
+            ActionProvider.COMMAND_TEST_SINGLE,
+            ActionProvider.COMMAND_RUN_SINGLE,
+            ActionProvider.COMMAND_DEBUG_TEST_SINGLE,
+            ActionProvider.COMMAND_DEBUG_SINGLE,
+            SingleMethod.COMMAND_DEBUG_SINGLE_METHOD,
+            SingleMethod.COMMAND_RUN_SINGLE_METHOD,
+        };
+    }
+
+    @NbBundle.Messages({
+        "MSG_Clean=mx clean {0}",
+        "MSG_Build=mx build {0}",
+        "MSG_BuildOnly=mx build {0} --only {1}",
+        "MSG_Rebuild=mx rebuild {0}",
+        "MSG_Unittest=mx unittest {0}",
+    })
+    @Override
+    public void invokeAction(String action, Lookup context) throws IllegalArgumentException {
+        FileObject fo = context.lookup(FileObject.class);
+        String testSuffix = "";
+        switch (action) {
+            case ActionProvider.COMMAND_CLEAN:
+                runMx(Bundle.MSG_Clean(prj.getName()), "clean"); // NOI18N
+                break;
+            case ActionProvider.COMMAND_BUILD:
+                runMx(Bundle.MSG_Build(prj.getName()), "build"); // NOI18N
+                break;
+            case ActionProvider.COMMAND_REBUILD:
+                runMx(Bundle.MSG_Rebuild(prj.getName()), "build"); // NOI18N
+                break;
+            case ActionProvider.COMMAND_COMPILE_SINGLE: {
+                SuiteSources.Group grp = prj.getSources().findGroup(fo);
+                if (grp == null) {
+                    Toolkit.getDefaultToolkit().beep();
+                    return;
+                }
+                final String name = grp.getDisplayName();
+                runMx(Bundle.MSG_BuildOnly(prj.getName(), name), "build", "--only", name); // NOI18N
+                break;
+            }
+            case SingleMethod.COMMAND_RUN_SINGLE_METHOD: {
+                SingleMethod m = context.lookup(SingleMethod.class);
+                if (m != null && fo == null) {
+                    fo = m.getFile();
+                    testSuffix = "#" + m.getMethodName();
+                }
+                // fallthrough
+            }
+            case ActionProvider.COMMAND_TEST_SINGLE:
+            case ActionProvider.COMMAND_RUN_SINGLE:
+                if (fo == null) {
+                    Toolkit.getDefaultToolkit().beep();
+                    return;
+                }
+                runMx(Bundle.MSG_Unittest(fo.getName()), "unittest", fo.getName() + testSuffix); // NOI18N
+                break;
+            case SingleMethod.COMMAND_DEBUG_SINGLE_METHOD: {
+                SingleMethod m = context.lookup(SingleMethod.class);
+                if (m != null && fo == null) {
+                    fo = m.getFile();
+                    testSuffix = "#" + m.getMethodName();
+                }
+                // fallthrough
+            }
+            case ActionProvider.COMMAND_DEBUG_TEST_SINGLE:
+            case ActionProvider.COMMAND_DEBUG_SINGLE:
+                if (fo == null) {
+                    Toolkit.getDefaultToolkit().beep();
+                    return;
+                }
+                ListeningDICookie ldic = ListeningDICookie.create(-1);
+                Object obj = ldic.getArgs().get("port"); // NOI18N
+                DebuggerInfo di = DebuggerInfo.create(ListeningDICookie.ID, ldic);
+                DebuggerEngine[] engines = { null };
+                RequestProcessor.getDefault().post(() -> {
+                    DebuggerEngine[] engs = DebuggerManager.getDebuggerManager().startDebugging(di);
+                    engines[0] = engs[0];
+                });
+                int port = ldic.getPortNumber();
+                runMx(Bundle.MSG_Unittest(fo.getName()), "--attach", "" + port, "unittest", fo.getName() + testSuffix); // NOI18N
+                break;
+            default:
+                throw new UnsupportedOperationException(action);
+        }
+    }
+
+    private boolean runMx(String taskName, String... args) {
+        final File suiteDir = FileUtil.toFile(prj.getProjectDirectory());
+        if (!suiteDir.isDirectory()) {
+            Toolkit.getDefaultToolkit().beep();
+            return true;
+        }
+        LifecycleManager.getDefault().saveAll();
+        ExecutionDescriptor descriptor = new ExecutionDescriptor()
+                .frontWindow(true).controllable(true)
+                .errConvertorFactory(() -> {
+                    return (String line) -> {
+                        String[] segments = line.split(":");
+                        if (segments.length > 2) {
+                            File src = new File(segments[0]);
+                            if (src.exists()) {
+                                int lineNumber = parseLineNumber(segments) - 1;
+                                return Collections.singletonList(ConvertedLine.forText(line, new OutputListener() {
+                                    @Override
+                                    public void outputLineSelected(OutputEvent ev) {
+                                        openLine(Line.ShowOpenType.NONE, Line.ShowVisibilityType.FRONT);
+                                    }
+
+                                    @Override
+                                    public void outputLineAction(OutputEvent ev) {
+                                        openLine(Line.ShowOpenType.OPEN, Line.ShowVisibilityType.FOCUS);
+                                    }
+
+                                    private boolean openLine(final Line.ShowOpenType openType, final Line.ShowVisibilityType visibilityType) throws IndexOutOfBoundsException {
+                                        FileObject fo = FileUtil.toFileObject(src);
+                                        if (fo != null) {
+                                            Lookup lkp = fo.getLookup();
+                                            final LineCookie lines = lkp.lookup(LineCookie.class);
+                                            if (lines != null) {
+                                                Line open = lines.getLineSet().getOriginal(lineNumber);
+                                                if (open != null) {
+                                                    open.show(openType, visibilityType);
+                                                    return true;
+                                                }
+                                            }
+                                            Openable open = lkp.lookup(Openable.class);
+                                            if (open != null) {
+                                                open.open();
+                                            } else {
+                                                Toolkit.getDefaultToolkit().beep();
+                                            }
+                                        }
+                                        return false;
+                                    }
+
+                                    @Override
+                                    public void outputLineCleared(OutputEvent ev) {
+                                    }
+                                }));
+                            }
+                        }
+                        return null;
+                    };
+                });
+        ProcessBuilder processBuilder = ProcessBuilder.getLocal();
+        processBuilder.setWorkingDirectory(suiteDir.getPath());
+        processBuilder.setExecutable("mx"); // NOI18N

Review comment:
       This seems to assume `mx` is on the IDE process'  `PATH`. Shouldn't it be configurable in Tools | Options, similar to maven/ant, especially when NB does not distribute `mx` ? If reasonable, I recommend to track it as JIRA enhancement.

##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteClassPathProvider.java
##########
@@ -0,0 +1,100 @@
+/*
+ * 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.mx.project;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+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.platform.JavaPlatformManager;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery.Result;
+import org.netbeans.spi.java.classpath.ClassPathProvider;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.AnnotationProcessingQueryImplementation;
+import org.netbeans.spi.project.ui.ProjectOpenedHook;
+import org.openide.filesystems.FileObject;
+
+final class SuiteClassPathProvider extends ProjectOpenedHook implements ClassPathProvider, AnnotationProcessingQueryImplementation {
+    private final SuiteProject project;
+    private final ClassPath bootCP;
+
+    public SuiteClassPathProvider(SuiteProject project) {
+        this.project = project;
+        List<ClassPath.Entry> entries = JavaPlatformManager.getDefault().getDefaultPlatform().getBootstrapLibraries().entries();
+        List<URL> roots = new ArrayList<>();
+        for (ClassPath.Entry entry : entries) {
+            URL root = entry.getURL();
+            if (root.getPath().contains("/graal-sdk.jar")) {
+                continue;
+            }
+            if (root.getPath().contains("/graaljs-scriptengine.jar")) {
+                continue;
+            }
+            if (root.getPath().contains("/graal-sdk.src.zip")) {
+                continue;
+            }
+            roots.add(entry.getURL());
+        }
+        this.bootCP = ClassPathSupport.createClassPath(roots.toArray(new URL[0]));
+    }
+
+    @Override
+    public ClassPath findClassPath(FileObject file, String type) {
+        SuiteSources.Group g = project.getSources().findGroup(file);
+        if (g == null) {
+            return null;
+        }
+        if (ClassPath.BOOT.equals(type)) {
+            return bootCP;

Review comment:
       I am not sure about this fixed bootclasspath, as there may be different `JAVA_HOME` defined in `~/.mx/env` or `mx.{projectname}/env` 

##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static SuiteSources findSuiteSources(FileObject root, String name) throws IllegalArgumentException {
+        FileObject impDir = root.getFileObject(name);
+        if (impDir != null) {
+            try {
+                Project impPrj = ProjectManager.getDefault().findProject(impDir);
+                return impPrj == null ? null : impPrj.getLookup().lookup(SuiteSources.class);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceGroup[] getSourceGroups(String string) {
+        return groups();
+    }
+
+    Group[] groups() {
+        return groups.toArray(new Group[0]);
+    }
+
+    Group findGroup(FileObject fo) {
+        for (Group g : groups) {
+            if (g.contains(fo)) {
+                return g;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void addChangeListener(ChangeListener cl) {
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener cl) {
+    }
+
+    private static FileObject findPrjDir(FileObject dir, String prjName, MxProject prj) {
+        if (dir == null) {
+            return null;
+        }
+        if (prj.dir() != null) {
+            return dir.getFileObject(prj.dir());
+        }
+        if (prj.subDir() != null) {
+            dir = dir.getFileObject(prj.subDir());
+            if (dir == null) {
+                return null;
+            }
+        }
+        return dir.getFileObject(prjName);
+    }
+
+    private Collection<Dep> transitiveDeps(Dep current, Map<String, Dep> fill) {
+        current.owner().computeTransitiveDeps();
+        final Collection<Dep> currentAllDeps = current.allDeps();
+        if (currentAllDeps == Collections.<Dep>emptySet()) {
+            throw new IllegalStateException("Cyclic dep on " + current.getName());
+        } else if (currentAllDeps != null) {
+            return currentAllDeps;
+        }
+        current.setAllDeps(Collections.emptySet());
+        TreeSet<Dep> computing = new TreeSet<>();
+        computing.add(current);
+        for (String depName : current.depNames()) {
+            Dep dep = fill.get(depName);
+            if (dep == null) {
+                int colon = depName.lastIndexOf(':');
+                dep = fill.get(depName.substring(colon + 1));
+                if (dep == null) {
+                    LOG.log(Level.INFO, "dep not found: {0}", depName);
+                    continue;
+                }
+            }
+            Collection<Dep> allDeps = transitiveDeps(dep, fill);
+            computing.addAll(allDeps);
+        }
+        current.setAllDeps(computing);
+        return computing;
+    }
+
+    private static void registerLibs(Map<String, MxLibrary> collect, String prefix, Map<String, MxLibrary> libraries) {
+        for (Map.Entry<String, MxLibrary> entry : libraries.entrySet()) {
+            String key = entry.getKey();
+            MxLibrary lib = entry.getValue();
+            if (prefix == null) {
+                collect.put(key, lib);
+            } else {
+                collect.put(prefix + ":" + key, lib);
+            }
+        }
+    }
+
+    private void registerDeps(String prefix, Map<String, Dep> fillDeps) {
+        for (Library library : libraries) {
+            fillDeps.put(prefix + ":" + library.getName(), library);
+        }
+        for (Dist d : distributions) {
+            fillDeps.put(prefix + ":" + d.getName(), d);
+        }
+        for (Map.Entry<String, SuiteSources> s : imported.entrySet()) {
+            s.getValue().registerDeps(s.getKey(), fillDeps);
+        }
+    }
+
+    @Override
+    public Group findBinaryRoots2(URL url) {
+        final FileObject srcFo = URLMapper.findFileObject(url);
+        for (Group group : this.groups) {
+            if (group.contains(srcFo)) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public URL[] computeRoots(Group group) {
+        if (group.binDir != null) {
+            return new URL[] { group.binDir.toURL() };
+        } else {
+            return new URL[0];
+        }
+    }
+
+    @Override
+    public boolean computePreferBinaries(Group result) {
+        return true;
+    }
+
+    @Override
+    public void computeChangeListener(Group result, boolean bln, ChangeListener cl) {
+    }
+
+    @Override
+    public SourceForBinaryQueryImplementation2.Result findSourceRoots2(URL url) {
+        this.computeTransitiveDeps();
+        for (Dist dist : this.distributions) {
+            URL jar;
+            try {
+                jar = dist.getJarRoot();
+                if (jar == null) {
+                    continue;
+                }
+            } catch (MalformedURLException ok) {
+                continue;
+            }
+            if (jar.equals(url)) {
+                List<FileObject> roots = new ArrayList<>();
+                for (Group d : dist.getContributingGroups()) {
+                    roots.add(d.srcDir);
+                    roots.add(d.srcGenDir);
+                }
+                return new ImmutableResult(roots.toArray(new FileObject[roots.size()]));
+            }
+        }
+        for (Group group : this.groups) {
+            if (group.binDir != null && group.binDir.toURL().equals(url)) {
+                return new ImmutableResult(group.srcDir, group.srcGenDir);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceForBinaryQuery.Result findSourceRoots(URL url) {
+        return findSourceRoots2(url);
+    }
+
+    final Iterable<File> jdks() {
+        Set<File> jdks = new LinkedHashSet<>();
+        String home = System.getProperty("user.home");
+        if (home != null) {
+            File userEnv = new File(new File(new File(home), ".mx"), "env");
+            findJdksInEnv(jdks, userEnv);
+        }
+        FileObject suiteEnv = dir.getFileObject("mx." + dir.getNameExt() + "/env");
+        if (suiteEnv != null) {
+            findJdksInEnv(jdks, FileUtil.toFile(suiteEnv));
+        }
+
+        String javaHomeEnv = System.getenv("JAVA_HOME");
+        if (javaHomeEnv != null) {
+            jdks.add(new File(javaHomeEnv));
+        }
+        String javaHomeProp = System.getProperty("java.home");
+        if (javaHomeProp != null) {
+            jdks.add(new File(javaHomeProp));
+        }
+        return jdks;
+    }
+
+    private void findJdksInEnv(Set<File> jdks, File env) {
+        if (env == null || !env.isFile()) {
+            return;
+        }
+        try (final FileInputStream is = new FileInputStream(env)) {
+            Properties p = new Properties();
+            p.load(is);
+
+            String javaHome = p.getProperty("JAVA_HOME");
+            if (javaHome != null) {
+                jdks.add(new File(javaHome));
+            }
+
+            String extraJavaHomes = p.getProperty("EXTRA_JAVA_HOMES");
+            if (extraJavaHomes != null) {
+                for (String extraHome : extraJavaHomes.split(File.pathSeparator)) {
+                    jdks.add(new File(extraHome));
+                }
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public SourceLevelQueryImplementation2.Result getSourceLevel(FileObject fo) {
+        Group g = findGroup(fo);
+        if (g == null) {
+            return null;
+        }
+        return new SourceLevelQueryImplementation2.Result2() {
+            @Override
+            public SourceLevelQuery.Profile getProfile() {
+                return SourceLevelQuery.Profile.DEFAULT;
+            }
+
+            @Override
+            public String getSourceLevel() {
+                return g.getCompliance().getSourceLevel();
+            }
+
+            @Override
+            public void addChangeListener(ChangeListener cl) {
+            }
+
+            @Override
+            public void removeChangeListener(ChangeListener cl) {
+            }
+        };
+    }
+
+    @Override
+    public Set<? extends Project> getSubprojects() {
+        Set<Project> prjs = new HashSet<>();
+        for (SuiteSources imp : imported.values()) {
+            prjs.add(imp.prj);
+        }
+        return prjs;
+    }
+
+    @Override
+    public URL[] findUnitTests(FileObject fo) {
+        return new URL[0];

Review comment:
       no unit tests reported ?

##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {

Review comment:
       Is the 2-level parent limit hardocded in `mx` as well ?

##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static SuiteSources findSuiteSources(FileObject root, String name) throws IllegalArgumentException {
+        FileObject impDir = root.getFileObject(name);
+        if (impDir != null) {
+            try {
+                Project impPrj = ProjectManager.getDefault().findProject(impDir);
+                return impPrj == null ? null : impPrj.getLookup().lookup(SuiteSources.class);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceGroup[] getSourceGroups(String string) {
+        return groups();
+    }
+
+    Group[] groups() {
+        return groups.toArray(new Group[0]);
+    }
+
+    Group findGroup(FileObject fo) {
+        for (Group g : groups) {
+            if (g.contains(fo)) {
+                return g;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void addChangeListener(ChangeListener cl) {
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener cl) {
+    }
+
+    private static FileObject findPrjDir(FileObject dir, String prjName, MxProject prj) {
+        if (dir == null) {
+            return null;
+        }
+        if (prj.dir() != null) {
+            return dir.getFileObject(prj.dir());
+        }
+        if (prj.subDir() != null) {
+            dir = dir.getFileObject(prj.subDir());
+            if (dir == null) {
+                return null;
+            }
+        }
+        return dir.getFileObject(prjName);
+    }
+
+    private Collection<Dep> transitiveDeps(Dep current, Map<String, Dep> fill) {
+        current.owner().computeTransitiveDeps();
+        final Collection<Dep> currentAllDeps = current.allDeps();
+        if (currentAllDeps == Collections.<Dep>emptySet()) {
+            throw new IllegalStateException("Cyclic dep on " + current.getName());
+        } else if (currentAllDeps != null) {
+            return currentAllDeps;
+        }
+        current.setAllDeps(Collections.emptySet());
+        TreeSet<Dep> computing = new TreeSet<>();
+        computing.add(current);
+        for (String depName : current.depNames()) {
+            Dep dep = fill.get(depName);
+            if (dep == null) {
+                int colon = depName.lastIndexOf(':');
+                dep = fill.get(depName.substring(colon + 1));
+                if (dep == null) {
+                    LOG.log(Level.INFO, "dep not found: {0}", depName);
+                    continue;
+                }
+            }
+            Collection<Dep> allDeps = transitiveDeps(dep, fill);
+            computing.addAll(allDeps);
+        }
+        current.setAllDeps(computing);
+        return computing;
+    }
+
+    private static void registerLibs(Map<String, MxLibrary> collect, String prefix, Map<String, MxLibrary> libraries) {
+        for (Map.Entry<String, MxLibrary> entry : libraries.entrySet()) {
+            String key = entry.getKey();
+            MxLibrary lib = entry.getValue();
+            if (prefix == null) {
+                collect.put(key, lib);
+            } else {
+                collect.put(prefix + ":" + key, lib);
+            }
+        }
+    }
+
+    private void registerDeps(String prefix, Map<String, Dep> fillDeps) {
+        for (Library library : libraries) {
+            fillDeps.put(prefix + ":" + library.getName(), library);
+        }
+        for (Dist d : distributions) {
+            fillDeps.put(prefix + ":" + d.getName(), d);
+        }
+        for (Map.Entry<String, SuiteSources> s : imported.entrySet()) {
+            s.getValue().registerDeps(s.getKey(), fillDeps);
+        }
+    }
+
+    @Override
+    public Group findBinaryRoots2(URL url) {
+        final FileObject srcFo = URLMapper.findFileObject(url);
+        for (Group group : this.groups) {
+            if (group.contains(srcFo)) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public URL[] computeRoots(Group group) {
+        if (group.binDir != null) {
+            return new URL[] { group.binDir.toURL() };
+        } else {
+            return new URL[0];
+        }
+    }
+
+    @Override
+    public boolean computePreferBinaries(Group result) {
+        return true;
+    }
+
+    @Override
+    public void computeChangeListener(Group result, boolean bln, ChangeListener cl) {
+    }
+
+    @Override
+    public SourceForBinaryQueryImplementation2.Result findSourceRoots2(URL url) {
+        this.computeTransitiveDeps();
+        for (Dist dist : this.distributions) {
+            URL jar;
+            try {
+                jar = dist.getJarRoot();
+                if (jar == null) {
+                    continue;
+                }
+            } catch (MalformedURLException ok) {
+                continue;
+            }
+            if (jar.equals(url)) {
+                List<FileObject> roots = new ArrayList<>();
+                for (Group d : dist.getContributingGroups()) {
+                    roots.add(d.srcDir);
+                    roots.add(d.srcGenDir);
+                }
+                return new ImmutableResult(roots.toArray(new FileObject[roots.size()]));
+            }
+        }
+        for (Group group : this.groups) {
+            if (group.binDir != null && group.binDir.toURL().equals(url)) {
+                return new ImmutableResult(group.srcDir, group.srcGenDir);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceForBinaryQuery.Result findSourceRoots(URL url) {
+        return findSourceRoots2(url);
+    }
+
+    final Iterable<File> jdks() {
+        Set<File> jdks = new LinkedHashSet<>();
+        String home = System.getProperty("user.home");
+        if (home != null) {
+            File userEnv = new File(new File(new File(home), ".mx"), "env");
+            findJdksInEnv(jdks, userEnv);
+        }
+        FileObject suiteEnv = dir.getFileObject("mx." + dir.getNameExt() + "/env");
+        if (suiteEnv != null) {
+            findJdksInEnv(jdks, FileUtil.toFile(suiteEnv));
+        }
+
+        String javaHomeEnv = System.getenv("JAVA_HOME");
+        if (javaHomeEnv != null) {
+            jdks.add(new File(javaHomeEnv));
+        }
+        String javaHomeProp = System.getProperty("java.home");
+        if (javaHomeProp != null) {
+            jdks.add(new File(javaHomeProp));
+        }
+        return jdks;
+    }
+
+    private void findJdksInEnv(Set<File> jdks, File env) {
+        if (env == null || !env.isFile()) {
+            return;
+        }
+        try (final FileInputStream is = new FileInputStream(env)) {
+            Properties p = new Properties();
+            p.load(is);
+
+            String javaHome = p.getProperty("JAVA_HOME");
+            if (javaHome != null) {
+                jdks.add(new File(javaHome));
+            }
+
+            String extraJavaHomes = p.getProperty("EXTRA_JAVA_HOMES");
+            if (extraJavaHomes != null) {
+                for (String extraHome : extraJavaHomes.split(File.pathSeparator)) {
+                    jdks.add(new File(extraHome));
+                }
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public SourceLevelQueryImplementation2.Result getSourceLevel(FileObject fo) {
+        Group g = findGroup(fo);
+        if (g == null) {
+            return null;
+        }
+        return new SourceLevelQueryImplementation2.Result2() {
+            @Override
+            public SourceLevelQuery.Profile getProfile() {
+                return SourceLevelQuery.Profile.DEFAULT;
+            }
+
+            @Override
+            public String getSourceLevel() {
+                return g.getCompliance().getSourceLevel();
+            }
+
+            @Override
+            public void addChangeListener(ChangeListener cl) {
+            }
+
+            @Override
+            public void removeChangeListener(ChangeListener cl) {
+            }
+        };
+    }
+
+    @Override
+    public Set<? extends Project> getSubprojects() {
+        Set<Project> prjs = new HashSet<>();
+        for (SuiteSources imp : imported.values()) {
+            prjs.add(imp.prj);
+        }
+        return prjs;
+    }
+
+    @Override
+    public URL[] findUnitTests(FileObject fo) {
+        return new URL[0];
+    }
+
+    @Override
+    public URL[] findSources(FileObject fo) {
+        Group g = findGroup(fo);
+        return g == null ? new URL[0] : new URL[] { g.getRootFolder().toURL() };
+    }
+
+    static interface Dep extends Comparable<Dep> {
+        String getName();
+
+        Collection<String> depNames();
+
+        Collection<Dep> allDeps();
+
+        void setAllDeps(Collection<Dep> set);
+
+        @Override
+        public default int compareTo(Dep o) {
+            return getName().compareTo(o.getName());
+        }
+
+        SuiteSources owner();
+    }
+
+    final class Dist implements Dep, FlaggedClassPathImplementation {
+        final String name;
+        final MxDistribution dist;
+        Collection<Dep> allDeps;
+        private final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        private Boolean exists;
+        private Collection<Group> groups;
+
+        public Dist(String name, MxDistribution dist) {
+            this.name = name;
+            this.dist = dist;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            Set<String> deps = new TreeSet<>();
+            deps.addAll(dist.distDependencies());
+            deps.addAll(dist.exclude());
+            return deps;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return this.allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public String getName() {
+            return this.name;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        private FileObject getJar(boolean ignore) {
+            if (SuiteSources.this.dir == null) {
+                return null;
+            }
+            FileObject dists = SuiteSources.this.dir.getFileObject("mxbuild/dists");
+            if (dists == null) {
+                return null;
+            }
+            List<FileObject> dist = Arrays.stream(dists.getChildren()).filter((fo) -> fo.isFolder() && fo.getName().startsWith("jdk")).collect(Collectors.toList());
+            dist.sort((fo1, fo2) -> fo2.getName().compareTo(fo1.getName()));
+            for (FileObject jdkDir : dist) {
+                FileObject jar = jdkDir.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+                if (jar != null) {
+                    return jar;
+                }
+            }
+            FileObject jar = dists.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+            if (jar != null) {
+                return jar;
+            }
+            return null;
+        }
+
+        @Override
+        public List<? extends PathResourceImplementation> getResources() {
+            computeTransitiveDeps();
+            FileObject jar = getJar(exists == null);
+            final boolean existsNow = jar != null && jar.isData();
+            if (exists == null) {
+                exists = existsNow;
+            } else {
+                if (exists != existsNow) {
+                    exists = existsNow;
+                    support.firePropertyChange(PROP_FLAGS, !exists, (boolean) exists);
+                }
+            }
+            if (jar != null) {
+                PathResourceImplementation res;
+                try {
+                    res = ClassPathSupport.createResource(getJarRoot());
+                    return Collections.singletonList(res);
+                } catch (MalformedURLException ex) {
+                    // OK
+                }
+            }
+            return Collections.emptyList();
+        }
+
+        private URL getJarRoot() throws MalformedURLException {
+            FileObject jar = getJar(true);
+            if (jar != null) {
+                return new URL("jar:" + jar.toURL() + "!/");
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener pl) {
+            support.addPropertyChangeListener(pl);
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener pl) {
+            support.removePropertyChangeListener(pl);
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+
+        private void computeSourceRoots(Map<String, Dep> collectedDeps) {
+            if (groups != null) {
+                return;
+            }
+            Set<Group> contributingGroups = new LinkedHashSet<>();
+            for (String d : this.dist.dependencies()) {
+                Dep dep = collectedDeps.get(d);
+                if (dep == null || dep.allDeps() == null) {
+                    continue;
+                }
+                for (Dep d2 : dep.allDeps()) {
+                    if (d2 instanceof Group) {
+                        contributingGroups.add((Group) d2);
+                    }
+                }
+            }
+            for (String d : this.dist.distDependencies()) {
+                final Dep anyDep = collectedDeps.get(d);
+                if (anyDep instanceof Dist) {
+                    Dist dep = (Dist) anyDep;
+                    dep.computeSourceRoots(collectedDeps);
+                    contributingGroups.removeAll(dep.getContributingGroups());
+                }
+            }
+            groups = contributingGroups;
+        }
+
+        public Collection<Group> getContributingGroups() {
+            return groups;
+        }
+
+        @Override
+        public String toString() {
+            return "Dist[name=" + name + "]";
+        }
+    }
+
+    final class Group implements SourceGroup, Dep, AnnotationProcessingQuery.Result,
+            Compliance.Provider {
+        private final String mxName;
+        private final MxProject mxPrj;
+        private final FileObject srcDir;
+        private final FileObject srcGenDir;
+        private final FileObject binDir;
+        private final String name;
+        private final String displayName;
+        private final Compliance compliance;
+        private ClassPath sourceCP;
+        private ClassPath cp;
+        private ClassPath processorPath;
+        private Collection<Dep> allDeps;
+
+        Group(String mxName, MxProject mxPrj, FileObject srcDir, FileObject srcGenDir, FileObject binDir, String name, String displayName) {
+            this.mxName = mxName;
+            this.mxPrj = mxPrj;
+            this.srcDir = srcDir;
+            this.srcGenDir = srcGenDir;
+            this.binDir = binDir;
+            this.name = name;
+            this.displayName = displayName;
+            this.compliance = Compliance.parse(mxPrj.javaCompliance());
+        }
+
+        @Override
+        public FileObject getRootFolder() {
+            return srcDir;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        @Override
+        public Icon getIcon(boolean opened) {
+            return null;
+        }
+
+        @Override
+        public Compliance getCompliance() {
+            return compliance;
+        }
+
+        @Override
+        public boolean contains(FileObject file) {
+            if (file == srcDir || file == srcGenDir || FileUtil.isParentOf(srcDir, file) || (srcGenDir != null && FileUtil.isParentOf(srcGenDir, file))) {
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public String toString() {
+            return "SuiteSources.Group[name=" + name + ",rootFolder=" + srcDir + "]"; // NOI18N
+        }
+
+        ClassPath getSourceCP() {
+            computeTransitiveDeps();
+            return sourceCP;
+        }
+
+        ClassPath getCP() {
+            computeTransitiveDeps();
+            return cp;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return mxPrj.dependencies();
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            allDeps = set;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        private void computeClassPath(Map<String, Dep> transDeps) {
+            for (Dep d : transDeps.values()) {
+                d.owner().computeTransitiveDeps();
+            }
+
+            List<Group> arr = new ArrayList<>();
+            List<ClassPathImplementation> libs = new ArrayList<>();
+            processTransDep(transDeps.get(mxName), arr, libs);
+            cp = composeClassPath(arr, libs);
+            List<FileObject> roots = new ArrayList<>();
+            if (srcDir != null) {
+                roots.add(srcDir);
+            }
+            if (srcGenDir != null) {
+                roots.add(srcGenDir);
+            }
+            sourceCP = ClassPathSupport.createClassPath(roots.toArray(new FileObject[roots.size()]));
+
+            if (mxPrj.annotationProcessors().isEmpty()) {
+                processorPath = null;
+            } else {
+                List<Group> groups = new ArrayList<>();
+                List<ClassPathImplementation> jars = new ArrayList<>();
+                for (String dep : mxPrj.annotationProcessors()) {
+                    processTransDep(transDeps.get(dep), groups, jars);
+                }
+                processorPath = composeClassPath(groups, jars);
+            }
+        }
+
+        private void processTransDep(Dep dep, List<Group> addGroups, List<ClassPathImplementation> addJars) {
+            if (dep != null) {
+                dep.owner().computeTransitiveDeps();
+                for (Dep d : dep.allDeps()) {
+                    if (d == this) {
+                        continue;
+                    }
+                    d.owner().computeTransitiveDeps();
+                    if (d instanceof Group) {
+                        addGroups.add((Group) d);
+                    } else if (d instanceof ClassPathImplementation) {
+                        addJars.add((ClassPathImplementation) d);
+                    }
+                }
+            }
+        }
+
+        private ClassPath composeClassPath(List<Group> arr, List<ClassPathImplementation> libs) {
+            Set<FileObject> roots = new LinkedHashSet<>();
+            final int depsCount = arr.size();
+            for (int i = 0; i < depsCount; i++) {
+                final Group g = arr.get(i);
+                if (g.binDir != null) {
+                    roots.add(g.binDir);
+                }
+            }
+            ClassPath prjCp = ClassPathSupport.createClassPath(roots.toArray(new FileObject[0]));
+            if (!libs.isEmpty()) {
+                if (libs.size() == 1) {
+                    prjCp = ClassPathSupport.createProxyClassPath(prjCp,
+                                                                  ClassPathFactory.createClassPath(libs.get(0))
+                    );
+                } else {
+                    prjCp = ClassPathSupport.createProxyClassPath(prjCp,
+                                                                  ClassPathFactory.createClassPath(
+                                                                                  ClassPathSupport.createProxyClassPathImplementation(
+                                                                                                  libs.toArray(new ClassPathImplementation[0])
+                                                                                  )
+                                                                  )
+                    );
+                }
+            }
+            return prjCp;
+        }
+
+        ClassPath getProcessorCP() {
+            computeTransitiveDeps();
+            return processorPath;
+        }
+
+        @Override
+        public Set<? extends AnnotationProcessingQuery.Trigger> annotationProcessingEnabled() {
+            return EnumSet.of(AnnotationProcessingQuery.Trigger.ON_SCAN, AnnotationProcessingQuery.Trigger.IN_EDITOR);
+        }
+
+        @Override
+        public Iterable<? extends String> annotationProcessorsToRun() {
+            return null;
+        }
+
+        @Override
+        public URL sourceOutputDirectory() {
+            return srcGenDir == null ? null : srcGenDir.toURL();
+        }
+
+        @Override
+        public Map<? extends String, ? extends String> processorOptions() {
+            return Collections.emptyMap();
+        }
+
+        @Override
+        public void addChangeListener(ChangeListener l) {
+        }
+
+        @Override
+        public void removeChangeListener(ChangeListener l) {
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+    }
+
+    private class Library implements FlaggedClassPathImplementation, Dep {
+        final MxLibrary lib;
+        final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        final String libName;
+        Collection<Dep> allDeps;
+        Boolean exists;
+
+        Library(String libName, MxLibrary lib) {
+            this.libName = libName;
+            this.lib = getOSSLibrary(lib);
+        }
+
+        final MxLibrary getOSSLibrary(MxLibrary lib) {
+            if (lib.sha1() == null && !lib.os_arch().isEmpty()) {
+                Map<String, MxLibrary.Arch> os_dep_libs = lib.os_arch();
+                String os = System.getProperty("os.name").toLowerCase();
+                for (Map.Entry<String, MxLibrary.Arch> entry : os_dep_libs.entrySet()) {
+                    if (os.contains(entry.getKey())) {
+                        return entry.getValue().amd64();
+                    }
+                }
+            }
+            return lib;
+        }
+
+        File getJar(boolean dumpIfMissing) {
+            File mxCache;
+            String cache = System.getenv("MX_CACHE_DIR");
+            if (cache != null) {
+                mxCache = new File(cache);
+            } else {
+                mxCache = new File(new File(new File(System.getProperty("user.home")), ".mx"), "cache");
+            }
+            int prefix = libName.indexOf(':');
+            final String simpleName = libName.substring(prefix + 1);
+
+            File simpleJar = new File(mxCache, simpleName + "_" + lib.sha1() + ".jar");
+            if (simpleJar.exists()) {
+                return simpleJar;
+            }
+            File dir = new File(mxCache, simpleName + "_" + lib.sha1());
+            File jar = new File(dir, simpleName.replace('_', '-').toLowerCase(Locale.ENGLISH) + ".jar");
+
+            if (dumpIfMissing && !jar.exists()) {
+                for (File f = jar;; f = f.getParentFile()) {
+                    if (!f.exists()) {
+                        LOG.log(Level.WARNING, "{0} does not exist", f);
+                    } else {
+                        StringBuilder sb = new StringBuilder();
+                        sb.append(f).append(" exists:\n");
+                        String[] kids = f.list();
+                        if (kids != null) {
+                            for (String n : kids) {
+                                sb.append("  ").append(n).append("\n");
+                            }
+                        }
+                        LOG.log(Level.INFO, sb.toString());
+                        break;
+                    }
+                }
+            }
+            return jar;
+        }
+
+        @Override
+        public String getName() {
+            return libName;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return lib.dependencies();
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);

Review comment:
       better check exists != null as well

##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);

Review comment:
       Silly question, but why just the 1st group ?

##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static SuiteSources findSuiteSources(FileObject root, String name) throws IllegalArgumentException {
+        FileObject impDir = root.getFileObject(name);
+        if (impDir != null) {
+            try {
+                Project impPrj = ProjectManager.getDefault().findProject(impDir);
+                return impPrj == null ? null : impPrj.getLookup().lookup(SuiteSources.class);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceGroup[] getSourceGroups(String string) {
+        return groups();
+    }
+
+    Group[] groups() {
+        return groups.toArray(new Group[0]);
+    }
+
+    Group findGroup(FileObject fo) {
+        for (Group g : groups) {
+            if (g.contains(fo)) {
+                return g;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void addChangeListener(ChangeListener cl) {
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener cl) {
+    }
+
+    private static FileObject findPrjDir(FileObject dir, String prjName, MxProject prj) {
+        if (dir == null) {
+            return null;
+        }
+        if (prj.dir() != null) {
+            return dir.getFileObject(prj.dir());
+        }
+        if (prj.subDir() != null) {
+            dir = dir.getFileObject(prj.subDir());
+            if (dir == null) {
+                return null;
+            }
+        }
+        return dir.getFileObject(prjName);
+    }
+
+    private Collection<Dep> transitiveDeps(Dep current, Map<String, Dep> fill) {
+        current.owner().computeTransitiveDeps();
+        final Collection<Dep> currentAllDeps = current.allDeps();
+        if (currentAllDeps == Collections.<Dep>emptySet()) {
+            throw new IllegalStateException("Cyclic dep on " + current.getName());
+        } else if (currentAllDeps != null) {
+            return currentAllDeps;
+        }
+        current.setAllDeps(Collections.emptySet());
+        TreeSet<Dep> computing = new TreeSet<>();
+        computing.add(current);
+        for (String depName : current.depNames()) {
+            Dep dep = fill.get(depName);
+            if (dep == null) {
+                int colon = depName.lastIndexOf(':');
+                dep = fill.get(depName.substring(colon + 1));
+                if (dep == null) {
+                    LOG.log(Level.INFO, "dep not found: {0}", depName);
+                    continue;
+                }
+            }
+            Collection<Dep> allDeps = transitiveDeps(dep, fill);
+            computing.addAll(allDeps);
+        }
+        current.setAllDeps(computing);
+        return computing;
+    }
+
+    private static void registerLibs(Map<String, MxLibrary> collect, String prefix, Map<String, MxLibrary> libraries) {
+        for (Map.Entry<String, MxLibrary> entry : libraries.entrySet()) {
+            String key = entry.getKey();
+            MxLibrary lib = entry.getValue();
+            if (prefix == null) {
+                collect.put(key, lib);
+            } else {
+                collect.put(prefix + ":" + key, lib);
+            }
+        }
+    }
+
+    private void registerDeps(String prefix, Map<String, Dep> fillDeps) {
+        for (Library library : libraries) {
+            fillDeps.put(prefix + ":" + library.getName(), library);
+        }
+        for (Dist d : distributions) {
+            fillDeps.put(prefix + ":" + d.getName(), d);
+        }
+        for (Map.Entry<String, SuiteSources> s : imported.entrySet()) {
+            s.getValue().registerDeps(s.getKey(), fillDeps);
+        }
+    }
+
+    @Override
+    public Group findBinaryRoots2(URL url) {
+        final FileObject srcFo = URLMapper.findFileObject(url);
+        for (Group group : this.groups) {
+            if (group.contains(srcFo)) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public URL[] computeRoots(Group group) {
+        if (group.binDir != null) {
+            return new URL[] { group.binDir.toURL() };
+        } else {
+            return new URL[0];
+        }
+    }
+
+    @Override
+    public boolean computePreferBinaries(Group result) {
+        return true;
+    }
+
+    @Override
+    public void computeChangeListener(Group result, boolean bln, ChangeListener cl) {
+    }
+
+    @Override
+    public SourceForBinaryQueryImplementation2.Result findSourceRoots2(URL url) {
+        this.computeTransitiveDeps();
+        for (Dist dist : this.distributions) {
+            URL jar;
+            try {
+                jar = dist.getJarRoot();
+                if (jar == null) {
+                    continue;
+                }
+            } catch (MalformedURLException ok) {
+                continue;
+            }
+            if (jar.equals(url)) {
+                List<FileObject> roots = new ArrayList<>();
+                for (Group d : dist.getContributingGroups()) {
+                    roots.add(d.srcDir);
+                    roots.add(d.srcGenDir);
+                }
+                return new ImmutableResult(roots.toArray(new FileObject[roots.size()]));
+            }
+        }
+        for (Group group : this.groups) {
+            if (group.binDir != null && group.binDir.toURL().equals(url)) {
+                return new ImmutableResult(group.srcDir, group.srcGenDir);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceForBinaryQuery.Result findSourceRoots(URL url) {
+        return findSourceRoots2(url);
+    }
+
+    final Iterable<File> jdks() {
+        Set<File> jdks = new LinkedHashSet<>();
+        String home = System.getProperty("user.home");
+        if (home != null) {
+            File userEnv = new File(new File(new File(home), ".mx"), "env");
+            findJdksInEnv(jdks, userEnv);
+        }
+        FileObject suiteEnv = dir.getFileObject("mx." + dir.getNameExt() + "/env");
+        if (suiteEnv != null) {
+            findJdksInEnv(jdks, FileUtil.toFile(suiteEnv));
+        }
+
+        String javaHomeEnv = System.getenv("JAVA_HOME");
+        if (javaHomeEnv != null) {
+            jdks.add(new File(javaHomeEnv));
+        }
+        String javaHomeProp = System.getProperty("java.home");
+        if (javaHomeProp != null) {
+            jdks.add(new File(javaHomeProp));
+        }
+        return jdks;
+    }
+
+    private void findJdksInEnv(Set<File> jdks, File env) {
+        if (env == null || !env.isFile()) {
+            return;
+        }
+        try (final FileInputStream is = new FileInputStream(env)) {
+            Properties p = new Properties();
+            p.load(is);
+
+            String javaHome = p.getProperty("JAVA_HOME");
+            if (javaHome != null) {
+                jdks.add(new File(javaHome));
+            }
+
+            String extraJavaHomes = p.getProperty("EXTRA_JAVA_HOMES");
+            if (extraJavaHomes != null) {
+                for (String extraHome : extraJavaHomes.split(File.pathSeparator)) {
+                    jdks.add(new File(extraHome));
+                }
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public SourceLevelQueryImplementation2.Result getSourceLevel(FileObject fo) {
+        Group g = findGroup(fo);
+        if (g == null) {
+            return null;
+        }
+        return new SourceLevelQueryImplementation2.Result2() {
+            @Override
+            public SourceLevelQuery.Profile getProfile() {
+                return SourceLevelQuery.Profile.DEFAULT;
+            }
+
+            @Override
+            public String getSourceLevel() {
+                return g.getCompliance().getSourceLevel();
+            }
+
+            @Override
+            public void addChangeListener(ChangeListener cl) {
+            }
+
+            @Override
+            public void removeChangeListener(ChangeListener cl) {
+            }
+        };
+    }
+
+    @Override
+    public Set<? extends Project> getSubprojects() {
+        Set<Project> prjs = new HashSet<>();
+        for (SuiteSources imp : imported.values()) {
+            prjs.add(imp.prj);
+        }
+        return prjs;
+    }
+
+    @Override
+    public URL[] findUnitTests(FileObject fo) {
+        return new URL[0];
+    }
+
+    @Override
+    public URL[] findSources(FileObject fo) {
+        Group g = findGroup(fo);
+        return g == null ? new URL[0] : new URL[] { g.getRootFolder().toURL() };
+    }
+
+    static interface Dep extends Comparable<Dep> {
+        String getName();
+
+        Collection<String> depNames();
+
+        Collection<Dep> allDeps();
+
+        void setAllDeps(Collection<Dep> set);
+
+        @Override
+        public default int compareTo(Dep o) {
+            return getName().compareTo(o.getName());
+        }
+
+        SuiteSources owner();
+    }
+
+    final class Dist implements Dep, FlaggedClassPathImplementation {
+        final String name;
+        final MxDistribution dist;
+        Collection<Dep> allDeps;
+        private final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        private Boolean exists;
+        private Collection<Group> groups;
+
+        public Dist(String name, MxDistribution dist) {
+            this.name = name;
+            this.dist = dist;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            Set<String> deps = new TreeSet<>();
+            deps.addAll(dist.distDependencies());
+            deps.addAll(dist.exclude());
+            return deps;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return this.allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public String getName() {
+            return this.name;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        private FileObject getJar(boolean ignore) {
+            if (SuiteSources.this.dir == null) {
+                return null;
+            }
+            FileObject dists = SuiteSources.this.dir.getFileObject("mxbuild/dists");
+            if (dists == null) {
+                return null;
+            }
+            List<FileObject> dist = Arrays.stream(dists.getChildren()).filter((fo) -> fo.isFolder() && fo.getName().startsWith("jdk")).collect(Collectors.toList());
+            dist.sort((fo1, fo2) -> fo2.getName().compareTo(fo1.getName()));
+            for (FileObject jdkDir : dist) {
+                FileObject jar = jdkDir.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+                if (jar != null) {
+                    return jar;
+                }
+            }
+            FileObject jar = dists.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+            if (jar != null) {
+                return jar;
+            }
+            return null;
+        }
+
+        @Override
+        public List<? extends PathResourceImplementation> getResources() {
+            computeTransitiveDeps();
+            FileObject jar = getJar(exists == null);
+            final boolean existsNow = jar != null && jar.isData();
+            if (exists == null) {
+                exists = existsNow;
+            } else {
+                if (exists != existsNow) {
+                    exists = existsNow;
+                    support.firePropertyChange(PROP_FLAGS, !exists, (boolean) exists);
+                }
+            }
+            if (jar != null) {
+                PathResourceImplementation res;
+                try {
+                    res = ClassPathSupport.createResource(getJarRoot());
+                    return Collections.singletonList(res);
+                } catch (MalformedURLException ex) {
+                    // OK
+                }
+            }
+            return Collections.emptyList();
+        }
+
+        private URL getJarRoot() throws MalformedURLException {
+            FileObject jar = getJar(true);
+            if (jar != null) {
+                return new URL("jar:" + jar.toURL() + "!/");
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener pl) {
+            support.addPropertyChangeListener(pl);
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener pl) {
+            support.removePropertyChangeListener(pl);
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+
+        private void computeSourceRoots(Map<String, Dep> collectedDeps) {
+            if (groups != null) {
+                return;
+            }
+            Set<Group> contributingGroups = new LinkedHashSet<>();
+            for (String d : this.dist.dependencies()) {
+                Dep dep = collectedDeps.get(d);
+                if (dep == null || dep.allDeps() == null) {
+                    continue;
+                }
+                for (Dep d2 : dep.allDeps()) {
+                    if (d2 instanceof Group) {
+                        contributingGroups.add((Group) d2);
+                    }
+                }
+            }
+            for (String d : this.dist.distDependencies()) {
+                final Dep anyDep = collectedDeps.get(d);
+                if (anyDep instanceof Dist) {
+                    Dist dep = (Dist) anyDep;
+                    dep.computeSourceRoots(collectedDeps);
+                    contributingGroups.removeAll(dep.getContributingGroups());
+                }
+            }
+            groups = contributingGroups;
+        }
+
+        public Collection<Group> getContributingGroups() {
+            return groups;
+        }
+
+        @Override
+        public String toString() {
+            return "Dist[name=" + name + "]";
+        }
+    }
+
+    final class Group implements SourceGroup, Dep, AnnotationProcessingQuery.Result,
+            Compliance.Provider {
+        private final String mxName;
+        private final MxProject mxPrj;
+        private final FileObject srcDir;
+        private final FileObject srcGenDir;
+        private final FileObject binDir;
+        private final String name;
+        private final String displayName;
+        private final Compliance compliance;
+        private ClassPath sourceCP;
+        private ClassPath cp;
+        private ClassPath processorPath;
+        private Collection<Dep> allDeps;
+
+        Group(String mxName, MxProject mxPrj, FileObject srcDir, FileObject srcGenDir, FileObject binDir, String name, String displayName) {
+            this.mxName = mxName;
+            this.mxPrj = mxPrj;
+            this.srcDir = srcDir;
+            this.srcGenDir = srcGenDir;
+            this.binDir = binDir;
+            this.name = name;
+            this.displayName = displayName;
+            this.compliance = Compliance.parse(mxPrj.javaCompliance());
+        }
+
+        @Override
+        public FileObject getRootFolder() {
+            return srcDir;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        @Override
+        public Icon getIcon(boolean opened) {
+            return null;
+        }
+
+        @Override
+        public Compliance getCompliance() {
+            return compliance;
+        }
+
+        @Override
+        public boolean contains(FileObject file) {
+            if (file == srcDir || file == srcGenDir || FileUtil.isParentOf(srcDir, file) || (srcGenDir != null && FileUtil.isParentOf(srcGenDir, file))) {
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public String toString() {
+            return "SuiteSources.Group[name=" + name + ",rootFolder=" + srcDir + "]"; // NOI18N
+        }
+
+        ClassPath getSourceCP() {
+            computeTransitiveDeps();
+            return sourceCP;
+        }
+
+        ClassPath getCP() {
+            computeTransitiveDeps();
+            return cp;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return mxPrj.dependencies();
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            allDeps = set;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        private void computeClassPath(Map<String, Dep> transDeps) {
+            for (Dep d : transDeps.values()) {
+                d.owner().computeTransitiveDeps();

Review comment:
       `processTransDeps` computes transitive deps on `d.owner()`, isn't it computed 2nd time here for all deps not just those used in classpath construction ?

##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static SuiteSources findSuiteSources(FileObject root, String name) throws IllegalArgumentException {
+        FileObject impDir = root.getFileObject(name);
+        if (impDir != null) {
+            try {
+                Project impPrj = ProjectManager.getDefault().findProject(impDir);
+                return impPrj == null ? null : impPrj.getLookup().lookup(SuiteSources.class);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceGroup[] getSourceGroups(String string) {
+        return groups();
+    }
+
+    Group[] groups() {
+        return groups.toArray(new Group[0]);
+    }
+
+    Group findGroup(FileObject fo) {
+        for (Group g : groups) {
+            if (g.contains(fo)) {
+                return g;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void addChangeListener(ChangeListener cl) {
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener cl) {
+    }
+
+    private static FileObject findPrjDir(FileObject dir, String prjName, MxProject prj) {
+        if (dir == null) {
+            return null;
+        }
+        if (prj.dir() != null) {
+            return dir.getFileObject(prj.dir());
+        }
+        if (prj.subDir() != null) {
+            dir = dir.getFileObject(prj.subDir());
+            if (dir == null) {
+                return null;
+            }
+        }
+        return dir.getFileObject(prjName);
+    }
+
+    private Collection<Dep> transitiveDeps(Dep current, Map<String, Dep> fill) {
+        current.owner().computeTransitiveDeps();
+        final Collection<Dep> currentAllDeps = current.allDeps();
+        if (currentAllDeps == Collections.<Dep>emptySet()) {
+            throw new IllegalStateException("Cyclic dep on " + current.getName());
+        } else if (currentAllDeps != null) {
+            return currentAllDeps;
+        }
+        current.setAllDeps(Collections.emptySet());
+        TreeSet<Dep> computing = new TreeSet<>();
+        computing.add(current);
+        for (String depName : current.depNames()) {
+            Dep dep = fill.get(depName);
+            if (dep == null) {
+                int colon = depName.lastIndexOf(':');
+                dep = fill.get(depName.substring(colon + 1));
+                if (dep == null) {
+                    LOG.log(Level.INFO, "dep not found: {0}", depName);
+                    continue;
+                }
+            }
+            Collection<Dep> allDeps = transitiveDeps(dep, fill);
+            computing.addAll(allDeps);
+        }
+        current.setAllDeps(computing);
+        return computing;
+    }
+
+    private static void registerLibs(Map<String, MxLibrary> collect, String prefix, Map<String, MxLibrary> libraries) {
+        for (Map.Entry<String, MxLibrary> entry : libraries.entrySet()) {
+            String key = entry.getKey();
+            MxLibrary lib = entry.getValue();
+            if (prefix == null) {
+                collect.put(key, lib);
+            } else {
+                collect.put(prefix + ":" + key, lib);
+            }
+        }
+    }
+
+    private void registerDeps(String prefix, Map<String, Dep> fillDeps) {
+        for (Library library : libraries) {
+            fillDeps.put(prefix + ":" + library.getName(), library);
+        }
+        for (Dist d : distributions) {
+            fillDeps.put(prefix + ":" + d.getName(), d);
+        }
+        for (Map.Entry<String, SuiteSources> s : imported.entrySet()) {
+            s.getValue().registerDeps(s.getKey(), fillDeps);
+        }
+    }
+
+    @Override
+    public Group findBinaryRoots2(URL url) {
+        final FileObject srcFo = URLMapper.findFileObject(url);
+        for (Group group : this.groups) {
+            if (group.contains(srcFo)) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public URL[] computeRoots(Group group) {
+        if (group.binDir != null) {
+            return new URL[] { group.binDir.toURL() };
+        } else {
+            return new URL[0];
+        }
+    }
+
+    @Override
+    public boolean computePreferBinaries(Group result) {
+        return true;
+    }
+
+    @Override
+    public void computeChangeListener(Group result, boolean bln, ChangeListener cl) {
+    }
+
+    @Override
+    public SourceForBinaryQueryImplementation2.Result findSourceRoots2(URL url) {
+        this.computeTransitiveDeps();
+        for (Dist dist : this.distributions) {
+            URL jar;
+            try {
+                jar = dist.getJarRoot();
+                if (jar == null) {
+                    continue;
+                }
+            } catch (MalformedURLException ok) {
+                continue;
+            }
+            if (jar.equals(url)) {
+                List<FileObject> roots = new ArrayList<>();
+                for (Group d : dist.getContributingGroups()) {
+                    roots.add(d.srcDir);
+                    roots.add(d.srcGenDir);
+                }
+                return new ImmutableResult(roots.toArray(new FileObject[roots.size()]));
+            }
+        }
+        for (Group group : this.groups) {
+            if (group.binDir != null && group.binDir.toURL().equals(url)) {
+                return new ImmutableResult(group.srcDir, group.srcGenDir);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceForBinaryQuery.Result findSourceRoots(URL url) {
+        return findSourceRoots2(url);
+    }
+
+    final Iterable<File> jdks() {
+        Set<File> jdks = new LinkedHashSet<>();
+        String home = System.getProperty("user.home");
+        if (home != null) {
+            File userEnv = new File(new File(new File(home), ".mx"), "env");
+            findJdksInEnv(jdks, userEnv);
+        }
+        FileObject suiteEnv = dir.getFileObject("mx." + dir.getNameExt() + "/env");
+        if (suiteEnv != null) {
+            findJdksInEnv(jdks, FileUtil.toFile(suiteEnv));
+        }
+
+        String javaHomeEnv = System.getenv("JAVA_HOME");
+        if (javaHomeEnv != null) {
+            jdks.add(new File(javaHomeEnv));
+        }
+        String javaHomeProp = System.getProperty("java.home");
+        if (javaHomeProp != null) {
+            jdks.add(new File(javaHomeProp));
+        }
+        return jdks;
+    }
+
+    private void findJdksInEnv(Set<File> jdks, File env) {
+        if (env == null || !env.isFile()) {
+            return;
+        }
+        try (final FileInputStream is = new FileInputStream(env)) {
+            Properties p = new Properties();
+            p.load(is);
+
+            String javaHome = p.getProperty("JAVA_HOME");
+            if (javaHome != null) {
+                jdks.add(new File(javaHome));
+            }
+
+            String extraJavaHomes = p.getProperty("EXTRA_JAVA_HOMES");
+            if (extraJavaHomes != null) {
+                for (String extraHome : extraJavaHomes.split(File.pathSeparator)) {
+                    jdks.add(new File(extraHome));
+                }
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public SourceLevelQueryImplementation2.Result getSourceLevel(FileObject fo) {
+        Group g = findGroup(fo);
+        if (g == null) {
+            return null;
+        }
+        return new SourceLevelQueryImplementation2.Result2() {
+            @Override
+            public SourceLevelQuery.Profile getProfile() {
+                return SourceLevelQuery.Profile.DEFAULT;
+            }
+
+            @Override
+            public String getSourceLevel() {
+                return g.getCompliance().getSourceLevel();
+            }
+
+            @Override
+            public void addChangeListener(ChangeListener cl) {
+            }
+
+            @Override
+            public void removeChangeListener(ChangeListener cl) {
+            }
+        };
+    }
+
+    @Override
+    public Set<? extends Project> getSubprojects() {
+        Set<Project> prjs = new HashSet<>();
+        for (SuiteSources imp : imported.values()) {
+            prjs.add(imp.prj);
+        }
+        return prjs;
+    }
+
+    @Override
+    public URL[] findUnitTests(FileObject fo) {
+        return new URL[0];
+    }
+
+    @Override
+    public URL[] findSources(FileObject fo) {
+        Group g = findGroup(fo);
+        return g == null ? new URL[0] : new URL[] { g.getRootFolder().toURL() };
+    }
+
+    static interface Dep extends Comparable<Dep> {
+        String getName();
+
+        Collection<String> depNames();
+
+        Collection<Dep> allDeps();
+
+        void setAllDeps(Collection<Dep> set);
+
+        @Override
+        public default int compareTo(Dep o) {
+            return getName().compareTo(o.getName());
+        }
+
+        SuiteSources owner();
+    }
+
+    final class Dist implements Dep, FlaggedClassPathImplementation {
+        final String name;
+        final MxDistribution dist;
+        Collection<Dep> allDeps;
+        private final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        private Boolean exists;
+        private Collection<Group> groups;
+
+        public Dist(String name, MxDistribution dist) {
+            this.name = name;
+            this.dist = dist;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            Set<String> deps = new TreeSet<>();
+            deps.addAll(dist.distDependencies());
+            deps.addAll(dist.exclude());
+            return deps;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return this.allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public String getName() {
+            return this.name;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        private FileObject getJar(boolean ignore) {

Review comment:
       Maybe an useless parameter

##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static SuiteSources findSuiteSources(FileObject root, String name) throws IllegalArgumentException {
+        FileObject impDir = root.getFileObject(name);
+        if (impDir != null) {
+            try {
+                Project impPrj = ProjectManager.getDefault().findProject(impDir);
+                return impPrj == null ? null : impPrj.getLookup().lookup(SuiteSources.class);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceGroup[] getSourceGroups(String string) {
+        return groups();
+    }
+
+    Group[] groups() {
+        return groups.toArray(new Group[0]);
+    }
+
+    Group findGroup(FileObject fo) {
+        for (Group g : groups) {
+            if (g.contains(fo)) {
+                return g;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void addChangeListener(ChangeListener cl) {
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener cl) {
+    }
+
+    private static FileObject findPrjDir(FileObject dir, String prjName, MxProject prj) {
+        if (dir == null) {
+            return null;
+        }
+        if (prj.dir() != null) {
+            return dir.getFileObject(prj.dir());
+        }
+        if (prj.subDir() != null) {
+            dir = dir.getFileObject(prj.subDir());
+            if (dir == null) {
+                return null;
+            }
+        }
+        return dir.getFileObject(prjName);
+    }
+
+    private Collection<Dep> transitiveDeps(Dep current, Map<String, Dep> fill) {
+        current.owner().computeTransitiveDeps();
+        final Collection<Dep> currentAllDeps = current.allDeps();
+        if (currentAllDeps == Collections.<Dep>emptySet()) {
+            throw new IllegalStateException("Cyclic dep on " + current.getName());
+        } else if (currentAllDeps != null) {
+            return currentAllDeps;
+        }
+        current.setAllDeps(Collections.emptySet());
+        TreeSet<Dep> computing = new TreeSet<>();
+        computing.add(current);
+        for (String depName : current.depNames()) {
+            Dep dep = fill.get(depName);
+            if (dep == null) {
+                int colon = depName.lastIndexOf(':');
+                dep = fill.get(depName.substring(colon + 1));
+                if (dep == null) {
+                    LOG.log(Level.INFO, "dep not found: {0}", depName);
+                    continue;
+                }
+            }
+            Collection<Dep> allDeps = transitiveDeps(dep, fill);
+            computing.addAll(allDeps);
+        }
+        current.setAllDeps(computing);
+        return computing;
+    }
+
+    private static void registerLibs(Map<String, MxLibrary> collect, String prefix, Map<String, MxLibrary> libraries) {
+        for (Map.Entry<String, MxLibrary> entry : libraries.entrySet()) {
+            String key = entry.getKey();
+            MxLibrary lib = entry.getValue();
+            if (prefix == null) {
+                collect.put(key, lib);
+            } else {
+                collect.put(prefix + ":" + key, lib);
+            }
+        }
+    }
+
+    private void registerDeps(String prefix, Map<String, Dep> fillDeps) {
+        for (Library library : libraries) {
+            fillDeps.put(prefix + ":" + library.getName(), library);
+        }
+        for (Dist d : distributions) {
+            fillDeps.put(prefix + ":" + d.getName(), d);
+        }
+        for (Map.Entry<String, SuiteSources> s : imported.entrySet()) {
+            s.getValue().registerDeps(s.getKey(), fillDeps);
+        }
+    }
+
+    @Override
+    public Group findBinaryRoots2(URL url) {
+        final FileObject srcFo = URLMapper.findFileObject(url);
+        for (Group group : this.groups) {
+            if (group.contains(srcFo)) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public URL[] computeRoots(Group group) {
+        if (group.binDir != null) {
+            return new URL[] { group.binDir.toURL() };
+        } else {
+            return new URL[0];
+        }
+    }
+
+    @Override
+    public boolean computePreferBinaries(Group result) {
+        return true;
+    }
+
+    @Override
+    public void computeChangeListener(Group result, boolean bln, ChangeListener cl) {
+    }
+
+    @Override
+    public SourceForBinaryQueryImplementation2.Result findSourceRoots2(URL url) {
+        this.computeTransitiveDeps();
+        for (Dist dist : this.distributions) {
+            URL jar;
+            try {
+                jar = dist.getJarRoot();
+                if (jar == null) {
+                    continue;
+                }
+            } catch (MalformedURLException ok) {
+                continue;
+            }
+            if (jar.equals(url)) {
+                List<FileObject> roots = new ArrayList<>();
+                for (Group d : dist.getContributingGroups()) {
+                    roots.add(d.srcDir);
+                    roots.add(d.srcGenDir);
+                }
+                return new ImmutableResult(roots.toArray(new FileObject[roots.size()]));
+            }
+        }
+        for (Group group : this.groups) {
+            if (group.binDir != null && group.binDir.toURL().equals(url)) {
+                return new ImmutableResult(group.srcDir, group.srcGenDir);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceForBinaryQuery.Result findSourceRoots(URL url) {
+        return findSourceRoots2(url);
+    }
+
+    final Iterable<File> jdks() {
+        Set<File> jdks = new LinkedHashSet<>();
+        String home = System.getProperty("user.home");
+        if (home != null) {
+            File userEnv = new File(new File(new File(home), ".mx"), "env");
+            findJdksInEnv(jdks, userEnv);
+        }
+        FileObject suiteEnv = dir.getFileObject("mx." + dir.getNameExt() + "/env");
+        if (suiteEnv != null) {
+            findJdksInEnv(jdks, FileUtil.toFile(suiteEnv));
+        }
+
+        String javaHomeEnv = System.getenv("JAVA_HOME");
+        if (javaHomeEnv != null) {
+            jdks.add(new File(javaHomeEnv));
+        }
+        String javaHomeProp = System.getProperty("java.home");
+        if (javaHomeProp != null) {
+            jdks.add(new File(javaHomeProp));
+        }
+        return jdks;
+    }
+
+    private void findJdksInEnv(Set<File> jdks, File env) {
+        if (env == null || !env.isFile()) {
+            return;
+        }
+        try (final FileInputStream is = new FileInputStream(env)) {
+            Properties p = new Properties();
+            p.load(is);
+
+            String javaHome = p.getProperty("JAVA_HOME");
+            if (javaHome != null) {
+                jdks.add(new File(javaHome));
+            }
+
+            String extraJavaHomes = p.getProperty("EXTRA_JAVA_HOMES");
+            if (extraJavaHomes != null) {
+                for (String extraHome : extraJavaHomes.split(File.pathSeparator)) {
+                    jdks.add(new File(extraHome));
+                }
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public SourceLevelQueryImplementation2.Result getSourceLevel(FileObject fo) {
+        Group g = findGroup(fo);
+        if (g == null) {
+            return null;
+        }
+        return new SourceLevelQueryImplementation2.Result2() {
+            @Override
+            public SourceLevelQuery.Profile getProfile() {
+                return SourceLevelQuery.Profile.DEFAULT;
+            }
+
+            @Override
+            public String getSourceLevel() {
+                return g.getCompliance().getSourceLevel();
+            }
+
+            @Override
+            public void addChangeListener(ChangeListener cl) {
+            }
+
+            @Override
+            public void removeChangeListener(ChangeListener cl) {
+            }
+        };
+    }
+
+    @Override
+    public Set<? extends Project> getSubprojects() {
+        Set<Project> prjs = new HashSet<>();
+        for (SuiteSources imp : imported.values()) {
+            prjs.add(imp.prj);
+        }
+        return prjs;
+    }
+
+    @Override
+    public URL[] findUnitTests(FileObject fo) {
+        return new URL[0];
+    }
+
+    @Override
+    public URL[] findSources(FileObject fo) {
+        Group g = findGroup(fo);
+        return g == null ? new URL[0] : new URL[] { g.getRootFolder().toURL() };
+    }
+
+    static interface Dep extends Comparable<Dep> {
+        String getName();
+
+        Collection<String> depNames();
+
+        Collection<Dep> allDeps();
+
+        void setAllDeps(Collection<Dep> set);
+
+        @Override
+        public default int compareTo(Dep o) {
+            return getName().compareTo(o.getName());
+        }
+
+        SuiteSources owner();
+    }
+
+    final class Dist implements Dep, FlaggedClassPathImplementation {
+        final String name;
+        final MxDistribution dist;
+        Collection<Dep> allDeps;
+        private final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        private Boolean exists;
+        private Collection<Group> groups;
+
+        public Dist(String name, MxDistribution dist) {
+            this.name = name;
+            this.dist = dist;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            Set<String> deps = new TreeSet<>();
+            deps.addAll(dist.distDependencies());
+            deps.addAll(dist.exclude());
+            return deps;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return this.allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public String getName() {
+            return this.name;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        private FileObject getJar(boolean ignore) {
+            if (SuiteSources.this.dir == null) {
+                return null;
+            }
+            FileObject dists = SuiteSources.this.dir.getFileObject("mxbuild/dists");
+            if (dists == null) {
+                return null;
+            }
+            List<FileObject> dist = Arrays.stream(dists.getChildren()).filter((fo) -> fo.isFolder() && fo.getName().startsWith("jdk")).collect(Collectors.toList());
+            dist.sort((fo1, fo2) -> fo2.getName().compareTo(fo1.getName()));
+            for (FileObject jdkDir : dist) {
+                FileObject jar = jdkDir.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+                if (jar != null) {
+                    return jar;
+                }
+            }
+            FileObject jar = dists.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+            if (jar != null) {
+                return jar;
+            }
+            return null;
+        }
+
+        @Override
+        public List<? extends PathResourceImplementation> getResources() {
+            computeTransitiveDeps();
+            FileObject jar = getJar(exists == null);
+            final boolean existsNow = jar != null && jar.isData();
+            if (exists == null) {
+                exists = existsNow;
+            } else {
+                if (exists != existsNow) {
+                    exists = existsNow;
+                    support.firePropertyChange(PROP_FLAGS, !exists, (boolean) exists);
+                }
+            }
+            if (jar != null) {
+                PathResourceImplementation res;
+                try {
+                    res = ClassPathSupport.createResource(getJarRoot());
+                    return Collections.singletonList(res);
+                } catch (MalformedURLException ex) {
+                    // OK
+                }
+            }
+            return Collections.emptyList();
+        }
+
+        private URL getJarRoot() throws MalformedURLException {
+            FileObject jar = getJar(true);
+            if (jar != null) {
+                return new URL("jar:" + jar.toURL() + "!/");
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener pl) {
+            support.addPropertyChangeListener(pl);
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener pl) {
+            support.removePropertyChangeListener(pl);
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+
+        private void computeSourceRoots(Map<String, Dep> collectedDeps) {
+            if (groups != null) {
+                return;
+            }
+            Set<Group> contributingGroups = new LinkedHashSet<>();
+            for (String d : this.dist.dependencies()) {
+                Dep dep = collectedDeps.get(d);
+                if (dep == null || dep.allDeps() == null) {
+                    continue;
+                }
+                for (Dep d2 : dep.allDeps()) {
+                    if (d2 instanceof Group) {
+                        contributingGroups.add((Group) d2);
+                    }
+                }
+            }
+            for (String d : this.dist.distDependencies()) {
+                final Dep anyDep = collectedDeps.get(d);
+                if (anyDep instanceof Dist) {
+                    Dist dep = (Dist) anyDep;
+                    dep.computeSourceRoots(collectedDeps);
+                    contributingGroups.removeAll(dep.getContributingGroups());
+                }
+            }
+            groups = contributingGroups;
+        }
+
+        public Collection<Group> getContributingGroups() {
+            return groups;
+        }
+
+        @Override
+        public String toString() {
+            return "Dist[name=" + name + "]";
+        }
+    }
+
+    final class Group implements SourceGroup, Dep, AnnotationProcessingQuery.Result,
+            Compliance.Provider {
+        private final String mxName;
+        private final MxProject mxPrj;
+        private final FileObject srcDir;
+        private final FileObject srcGenDir;
+        private final FileObject binDir;
+        private final String name;
+        private final String displayName;
+        private final Compliance compliance;
+        private ClassPath sourceCP;
+        private ClassPath cp;
+        private ClassPath processorPath;
+        private Collection<Dep> allDeps;
+
+        Group(String mxName, MxProject mxPrj, FileObject srcDir, FileObject srcGenDir, FileObject binDir, String name, String displayName) {
+            this.mxName = mxName;
+            this.mxPrj = mxPrj;
+            this.srcDir = srcDir;
+            this.srcGenDir = srcGenDir;
+            this.binDir = binDir;
+            this.name = name;
+            this.displayName = displayName;
+            this.compliance = Compliance.parse(mxPrj.javaCompliance());
+        }
+
+        @Override
+        public FileObject getRootFolder() {
+            return srcDir;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        @Override
+        public Icon getIcon(boolean opened) {
+            return null;
+        }
+
+        @Override
+        public Compliance getCompliance() {
+            return compliance;
+        }
+
+        @Override
+        public boolean contains(FileObject file) {
+            if (file == srcDir || file == srcGenDir || FileUtil.isParentOf(srcDir, file) || (srcGenDir != null && FileUtil.isParentOf(srcGenDir, file))) {
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public String toString() {
+            return "SuiteSources.Group[name=" + name + ",rootFolder=" + srcDir + "]"; // NOI18N
+        }
+
+        ClassPath getSourceCP() {
+            computeTransitiveDeps();
+            return sourceCP;
+        }
+
+        ClassPath getCP() {
+            computeTransitiveDeps();
+            return cp;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return mxPrj.dependencies();
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            allDeps = set;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        private void computeClassPath(Map<String, Dep> transDeps) {
+            for (Dep d : transDeps.values()) {
+                d.owner().computeTransitiveDeps();
+            }
+
+            List<Group> arr = new ArrayList<>();
+            List<ClassPathImplementation> libs = new ArrayList<>();
+            processTransDep(transDeps.get(mxName), arr, libs);
+            cp = composeClassPath(arr, libs);
+            List<FileObject> roots = new ArrayList<>();
+            if (srcDir != null) {
+                roots.add(srcDir);
+            }
+            if (srcGenDir != null) {
+                roots.add(srcGenDir);
+            }
+            sourceCP = ClassPathSupport.createClassPath(roots.toArray(new FileObject[roots.size()]));
+
+            if (mxPrj.annotationProcessors().isEmpty()) {
+                processorPath = null;
+            } else {
+                List<Group> groups = new ArrayList<>();
+                List<ClassPathImplementation> jars = new ArrayList<>();
+                for (String dep : mxPrj.annotationProcessors()) {
+                    processTransDep(transDeps.get(dep), groups, jars);
+                }
+                processorPath = composeClassPath(groups, jars);
+            }
+        }
+
+        private void processTransDep(Dep dep, List<Group> addGroups, List<ClassPathImplementation> addJars) {
+            if (dep != null) {
+                dep.owner().computeTransitiveDeps();
+                for (Dep d : dep.allDeps()) {
+                    if (d == this) {
+                        continue;
+                    }
+                    d.owner().computeTransitiveDeps();
+                    if (d instanceof Group) {
+                        addGroups.add((Group) d);
+                    } else if (d instanceof ClassPathImplementation) {
+                        addJars.add((ClassPathImplementation) d);
+                    }
+                }
+            }
+        }
+
+        private ClassPath composeClassPath(List<Group> arr, List<ClassPathImplementation> libs) {
+            Set<FileObject> roots = new LinkedHashSet<>();
+            final int depsCount = arr.size();
+            for (int i = 0; i < depsCount; i++) {
+                final Group g = arr.get(i);
+                if (g.binDir != null) {
+                    roots.add(g.binDir);
+                }
+            }
+            ClassPath prjCp = ClassPathSupport.createClassPath(roots.toArray(new FileObject[0]));
+            if (!libs.isEmpty()) {
+                if (libs.size() == 1) {
+                    prjCp = ClassPathSupport.createProxyClassPath(prjCp,
+                                                                  ClassPathFactory.createClassPath(libs.get(0))
+                    );
+                } else {
+                    prjCp = ClassPathSupport.createProxyClassPath(prjCp,
+                                                                  ClassPathFactory.createClassPath(
+                                                                                  ClassPathSupport.createProxyClassPathImplementation(
+                                                                                                  libs.toArray(new ClassPathImplementation[0])
+                                                                                  )
+                                                                  )
+                    );
+                }
+            }
+            return prjCp;
+        }
+
+        ClassPath getProcessorCP() {
+            computeTransitiveDeps();
+            return processorPath;
+        }
+
+        @Override
+        public Set<? extends AnnotationProcessingQuery.Trigger> annotationProcessingEnabled() {
+            return EnumSet.of(AnnotationProcessingQuery.Trigger.ON_SCAN, AnnotationProcessingQuery.Trigger.IN_EDITOR);
+        }
+
+        @Override
+        public Iterable<? extends String> annotationProcessorsToRun() {
+            return null;
+        }
+
+        @Override
+        public URL sourceOutputDirectory() {
+            return srcGenDir == null ? null : srcGenDir.toURL();
+        }
+
+        @Override
+        public Map<? extends String, ? extends String> processorOptions() {
+            return Collections.emptyMap();
+        }
+
+        @Override
+        public void addChangeListener(ChangeListener l) {
+        }
+
+        @Override
+        public void removeChangeListener(ChangeListener l) {
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+    }
+
+    private class Library implements FlaggedClassPathImplementation, Dep {
+        final MxLibrary lib;
+        final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        final String libName;
+        Collection<Dep> allDeps;
+        Boolean exists;
+
+        Library(String libName, MxLibrary lib) {
+            this.libName = libName;
+            this.lib = getOSSLibrary(lib);
+        }
+
+        final MxLibrary getOSSLibrary(MxLibrary lib) {
+            if (lib.sha1() == null && !lib.os_arch().isEmpty()) {
+                Map<String, MxLibrary.Arch> os_dep_libs = lib.os_arch();
+                String os = System.getProperty("os.name").toLowerCase();
+                for (Map.Entry<String, MxLibrary.Arch> entry : os_dep_libs.entrySet()) {
+                    if (os.contains(entry.getKey())) {
+                        return entry.getValue().amd64();

Review comment:
       Uh :) I meant `mx` already supports `aarch64`, maybe even `i386` ?

##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteSources.java
##########
@@ -0,0 +1,1195 @@
+/*
+ * 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.mx.project;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.Icon;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.java.mx.project.suitepy.MxDistribution;
+import org.netbeans.modules.java.mx.project.suitepy.MxImports;
+import org.netbeans.modules.java.mx.project.suitepy.MxLibrary;
+import org.netbeans.modules.java.mx.project.suitepy.MxProject;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.queries.AnnotationProcessingQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.spi.java.classpath.ClassPathFactory;
+import org.netbeans.spi.java.classpath.ClassPathImplementation;
+import org.netbeans.spi.java.classpath.FlaggedClassPathImplementation;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.BinaryForSourceQueryImplementation2;
+import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+import org.openide.util.Exceptions;
+import org.openide.util.Utilities;
+import java.util.stream.Collectors;
+import org.netbeans.api.java.queries.SourceLevelQuery;
+import org.netbeans.spi.java.queries.MultipleRootsUnitTestForSourceQueryImplementation;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
+import org.netbeans.spi.project.SubprojectProvider;
+
+final class SuiteSources implements Sources,
+                BinaryForSourceQueryImplementation2<SuiteSources.Group>, SourceForBinaryQueryImplementation2,
+                SourceLevelQueryImplementation2, SubprojectProvider, MultipleRootsUnitTestForSourceQueryImplementation {
+    private static final Logger LOG = Logger.getLogger(SuiteSources.class.getName());
+    private static final SuiteSources CORE;
+
+    static {
+        MxSuite coreSuite = CoreSuite.CORE_5_279_0;
+        CORE = new SuiteSources(null, null, coreSuite);
+    }
+
+    private final MxSuite suite;
+    private final List<Group> groups;
+    private final List<Library> libraries;
+    private final List<Dist> distributions;
+    private final FileObject dir;
+    /**
+     * non-null if the dependencies haven't yet been properly initialized
+     */
+    private Map<String, Dep> transitiveDeps;
+    /**
+     * avoid GC of imported projects
+     */
+    private final SuiteProject prj;
+    private final Map<String, SuiteSources> imported;
+
+    SuiteSources(SuiteProject owner, FileObject dir, MxSuite suite) {
+        final Map<String, Dep> fillDeps = new HashMap<>();
+        this.prj = owner;
+        this.dir = dir;
+        this.groups = findGroups(fillDeps, suite, dir);
+        this.libraries = findLibraries(fillDeps, suite);
+        this.imported = findImportedSuites(dir, suite, fillDeps);
+        this.distributions = findDistributions(suite, this.libraries, this.groups, fillDeps);
+        this.suite = suite;
+        this.transitiveDeps = fillDeps;
+    }
+
+    @Override
+    public String toString() {
+        return "MxSources[" + (dir == null ? "mx" : dir.toURI()) + "]";
+    }
+
+    private List<Group> findGroups(Map<String, Dep> fillDeps, MxSuite s, FileObject dir) {
+        List<Group> arr = new ArrayList<>();
+        for (Map.Entry<String, MxProject> entry : s.projects().entrySet()) {
+            String name = entry.getKey();
+            MxProject mxPrj = entry.getValue();
+            FileObject prjDir = findPrjDir(dir, name, mxPrj);
+            if (prjDir == null) {
+                fillDeps.put(name, new Group(name, mxPrj, null, null, null, name, name));
+                continue;
+            }
+            String prevName = null;
+            Group firstGroup = null;
+            String binPrefix;
+            if (mxPrj.subDir() == null) {
+                binPrefix = "mxbuild/";
+            } else {
+                binPrefix = "mxbuild/" + mxPrj.subDir() + "/";
+            }
+            for (String rel : mxPrj.sourceDirs()) {
+                FileObject srcDir = prjDir.getFileObject(rel);
+                FileObject binDir = getSubDir(dir, binPrefix + name + "/bin");
+                FileObject srcGenDir = getSubDir(dir, binPrefix + name + "/src_gen");
+                if (srcDir != null && binDir != null) {
+                    String prgName = name + "-" + rel;
+                    String displayName;
+                    if (prevName == null) {
+                        displayName = name;
+                    } else {
+                        displayName = name + "[" + rel + "]";
+                    }
+                    Group g = new Group(name, mxPrj, srcDir, srcGenDir, binDir, prgName, displayName);
+                    arr.add(g);
+                    if (firstGroup == null) {
+                        firstGroup = g;
+                    }
+                    prevName = displayName;
+                }
+            }
+            if (firstGroup != null) {
+                fillDeps.put(name, firstGroup);
+            }
+        }
+        return arr;
+    }
+
+    private static FileObject getSubDir(FileObject dir, String relPath) {
+        FileObject subDir = dir.getFileObject(relPath);
+        if (subDir == null) {
+            try {
+                subDir = FileUtil.createFolder(dir, relPath);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return subDir;
+    }
+
+    private List<Library> findLibraries(Map<String, Dep> fillDeps, MxSuite suite) {
+        final Map<String, MxLibrary> allLibraries = new HashMap<>();
+        registerLibs(allLibraries, null, suite.libraries());
+
+        List<Library> arr = new ArrayList<>();
+        for (Map.Entry<String, MxLibrary> entry : allLibraries.entrySet()) {
+            final Library library = new Library(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        for (Map.Entry<String, MxLibrary> entry : suite.jdklibraries().entrySet()) {
+            final JdkLibrary library = new JdkLibrary(entry.getKey(), entry.getValue());
+            arr.add(library);
+            fillDeps.put(library.getName(), library);
+        }
+        return arr;
+    }
+
+    private static Map<String, SuiteSources> findImportedSuites(FileObject dir, MxSuite s, Map<String, Dep> fillDeps) {
+        if (dir == null) {
+            return Collections.emptyMap();
+        }
+        CORE.registerDeps("mx", fillDeps);
+        final MxImports imports = s.imports();
+        if (imports != null) {
+            Map<String, SuiteSources> imported = new LinkedHashMap<>();
+            for (MxImports.Suite imp : imports.suites()) {
+                SuiteSources impSources = findSuiteSources(dir, imp);
+                final String suiteName = imp.name();
+                if (impSources == null) {
+                    LOG.log(Level.INFO, "cannot find imported suite: {0}", suiteName);
+                    continue;
+                }
+                imported.put(suiteName, impSources);
+                impSources.registerDeps(suiteName, fillDeps);
+            }
+            return imported;
+        }
+        return Collections.emptyMap();
+    }
+
+    private List<Dist> findDistributions(MxSuite s, List<Library> libraries, List<Group> groups, Map<String, Dep> fillDeps) {
+        List<Dist> dists = new ArrayList<>();
+        for (Map.Entry<String, MxDistribution> entry : s.distributions().entrySet()) {
+            Dist d = new Dist(entry.getKey(), entry.getValue());
+            dists.add(d);
+            fillDeps.put(d.getName(), d);
+        }
+        return dists;
+    }
+
+    final synchronized void computeTransitiveDeps() {
+        Map<String, Dep> collectedDeps = this.transitiveDeps;
+        if (collectedDeps == null) {
+            return;
+        }
+        this.transitiveDeps = null;
+        for (Library l : this.libraries) {
+            transitiveDeps(l, collectedDeps);
+        }
+        for (Group g : this.groups) {
+            transitiveDeps(g, collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            transitiveDeps(d, collectedDeps);
+        }
+        for (Group g : groups) {
+            g.computeClassPath(collectedDeps);
+        }
+        for (Dist d : this.distributions) {
+            d.computeSourceRoots(collectedDeps);
+        }
+    }
+
+    private static SuiteSources findSuiteSources(FileObject dir, MxImports.Suite imp) throws IllegalArgumentException {
+        SuiteSources sources = findSuiteSources(dir.getParent(), imp.name());
+        if (sources != null) {
+            return sources;
+        }
+        if (imp.subdir()) {
+            for (FileObject subDir : dir.getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+            for (FileObject subDir : dir.getParent().getParent().getChildren()) {
+                sources = findSuiteSources(subDir, imp.name());
+                if (sources != null) {
+                    return sources;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static SuiteSources findSuiteSources(FileObject root, String name) throws IllegalArgumentException {
+        FileObject impDir = root.getFileObject(name);
+        if (impDir != null) {
+            try {
+                Project impPrj = ProjectManager.getDefault().findProject(impDir);
+                return impPrj == null ? null : impPrj.getLookup().lookup(SuiteSources.class);
+            } catch (IOException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceGroup[] getSourceGroups(String string) {
+        return groups();
+    }
+
+    Group[] groups() {
+        return groups.toArray(new Group[0]);
+    }
+
+    Group findGroup(FileObject fo) {
+        for (Group g : groups) {
+            if (g.contains(fo)) {
+                return g;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void addChangeListener(ChangeListener cl) {
+    }
+
+    @Override
+    public void removeChangeListener(ChangeListener cl) {
+    }
+
+    private static FileObject findPrjDir(FileObject dir, String prjName, MxProject prj) {
+        if (dir == null) {
+            return null;
+        }
+        if (prj.dir() != null) {
+            return dir.getFileObject(prj.dir());
+        }
+        if (prj.subDir() != null) {
+            dir = dir.getFileObject(prj.subDir());
+            if (dir == null) {
+                return null;
+            }
+        }
+        return dir.getFileObject(prjName);
+    }
+
+    private Collection<Dep> transitiveDeps(Dep current, Map<String, Dep> fill) {
+        current.owner().computeTransitiveDeps();
+        final Collection<Dep> currentAllDeps = current.allDeps();
+        if (currentAllDeps == Collections.<Dep>emptySet()) {
+            throw new IllegalStateException("Cyclic dep on " + current.getName());
+        } else if (currentAllDeps != null) {
+            return currentAllDeps;
+        }
+        current.setAllDeps(Collections.emptySet());
+        TreeSet<Dep> computing = new TreeSet<>();
+        computing.add(current);
+        for (String depName : current.depNames()) {
+            Dep dep = fill.get(depName);
+            if (dep == null) {
+                int colon = depName.lastIndexOf(':');
+                dep = fill.get(depName.substring(colon + 1));
+                if (dep == null) {
+                    LOG.log(Level.INFO, "dep not found: {0}", depName);
+                    continue;
+                }
+            }
+            Collection<Dep> allDeps = transitiveDeps(dep, fill);
+            computing.addAll(allDeps);
+        }
+        current.setAllDeps(computing);
+        return computing;
+    }
+
+    private static void registerLibs(Map<String, MxLibrary> collect, String prefix, Map<String, MxLibrary> libraries) {
+        for (Map.Entry<String, MxLibrary> entry : libraries.entrySet()) {
+            String key = entry.getKey();
+            MxLibrary lib = entry.getValue();
+            if (prefix == null) {
+                collect.put(key, lib);
+            } else {
+                collect.put(prefix + ":" + key, lib);
+            }
+        }
+    }
+
+    private void registerDeps(String prefix, Map<String, Dep> fillDeps) {
+        for (Library library : libraries) {
+            fillDeps.put(prefix + ":" + library.getName(), library);
+        }
+        for (Dist d : distributions) {
+            fillDeps.put(prefix + ":" + d.getName(), d);
+        }
+        for (Map.Entry<String, SuiteSources> s : imported.entrySet()) {
+            s.getValue().registerDeps(s.getKey(), fillDeps);
+        }
+    }
+
+    @Override
+    public Group findBinaryRoots2(URL url) {
+        final FileObject srcFo = URLMapper.findFileObject(url);
+        for (Group group : this.groups) {
+            if (group.contains(srcFo)) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public URL[] computeRoots(Group group) {
+        if (group.binDir != null) {
+            return new URL[] { group.binDir.toURL() };
+        } else {
+            return new URL[0];
+        }
+    }
+
+    @Override
+    public boolean computePreferBinaries(Group result) {
+        return true;
+    }
+
+    @Override
+    public void computeChangeListener(Group result, boolean bln, ChangeListener cl) {
+    }
+
+    @Override
+    public SourceForBinaryQueryImplementation2.Result findSourceRoots2(URL url) {
+        this.computeTransitiveDeps();
+        for (Dist dist : this.distributions) {
+            URL jar;
+            try {
+                jar = dist.getJarRoot();
+                if (jar == null) {
+                    continue;
+                }
+            } catch (MalformedURLException ok) {
+                continue;
+            }
+            if (jar.equals(url)) {
+                List<FileObject> roots = new ArrayList<>();
+                for (Group d : dist.getContributingGroups()) {
+                    roots.add(d.srcDir);
+                    roots.add(d.srcGenDir);
+                }
+                return new ImmutableResult(roots.toArray(new FileObject[roots.size()]));
+            }
+        }
+        for (Group group : this.groups) {
+            if (group.binDir != null && group.binDir.toURL().equals(url)) {
+                return new ImmutableResult(group.srcDir, group.srcGenDir);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SourceForBinaryQuery.Result findSourceRoots(URL url) {
+        return findSourceRoots2(url);
+    }
+
+    final Iterable<File> jdks() {
+        Set<File> jdks = new LinkedHashSet<>();
+        String home = System.getProperty("user.home");
+        if (home != null) {
+            File userEnv = new File(new File(new File(home), ".mx"), "env");
+            findJdksInEnv(jdks, userEnv);
+        }
+        FileObject suiteEnv = dir.getFileObject("mx." + dir.getNameExt() + "/env");
+        if (suiteEnv != null) {
+            findJdksInEnv(jdks, FileUtil.toFile(suiteEnv));
+        }
+
+        String javaHomeEnv = System.getenv("JAVA_HOME");
+        if (javaHomeEnv != null) {
+            jdks.add(new File(javaHomeEnv));
+        }
+        String javaHomeProp = System.getProperty("java.home");
+        if (javaHomeProp != null) {
+            jdks.add(new File(javaHomeProp));
+        }
+        return jdks;
+    }
+
+    private void findJdksInEnv(Set<File> jdks, File env) {
+        if (env == null || !env.isFile()) {
+            return;
+        }
+        try (final FileInputStream is = new FileInputStream(env)) {
+            Properties p = new Properties();
+            p.load(is);
+
+            String javaHome = p.getProperty("JAVA_HOME");
+            if (javaHome != null) {
+                jdks.add(new File(javaHome));
+            }
+
+            String extraJavaHomes = p.getProperty("EXTRA_JAVA_HOMES");
+            if (extraJavaHomes != null) {
+                for (String extraHome : extraJavaHomes.split(File.pathSeparator)) {
+                    jdks.add(new File(extraHome));
+                }
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public SourceLevelQueryImplementation2.Result getSourceLevel(FileObject fo) {
+        Group g = findGroup(fo);
+        if (g == null) {
+            return null;
+        }
+        return new SourceLevelQueryImplementation2.Result2() {
+            @Override
+            public SourceLevelQuery.Profile getProfile() {
+                return SourceLevelQuery.Profile.DEFAULT;
+            }
+
+            @Override
+            public String getSourceLevel() {
+                return g.getCompliance().getSourceLevel();
+            }
+
+            @Override
+            public void addChangeListener(ChangeListener cl) {
+            }
+
+            @Override
+            public void removeChangeListener(ChangeListener cl) {
+            }
+        };
+    }
+
+    @Override
+    public Set<? extends Project> getSubprojects() {
+        Set<Project> prjs = new HashSet<>();
+        for (SuiteSources imp : imported.values()) {
+            prjs.add(imp.prj);
+        }
+        return prjs;
+    }
+
+    @Override
+    public URL[] findUnitTests(FileObject fo) {
+        return new URL[0];
+    }
+
+    @Override
+    public URL[] findSources(FileObject fo) {
+        Group g = findGroup(fo);
+        return g == null ? new URL[0] : new URL[] { g.getRootFolder().toURL() };
+    }
+
+    static interface Dep extends Comparable<Dep> {
+        String getName();
+
+        Collection<String> depNames();
+
+        Collection<Dep> allDeps();
+
+        void setAllDeps(Collection<Dep> set);
+
+        @Override
+        public default int compareTo(Dep o) {
+            return getName().compareTo(o.getName());
+        }
+
+        SuiteSources owner();
+    }
+
+    final class Dist implements Dep, FlaggedClassPathImplementation {
+        final String name;
+        final MxDistribution dist;
+        Collection<Dep> allDeps;
+        private final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        private Boolean exists;
+        private Collection<Group> groups;
+
+        public Dist(String name, MxDistribution dist) {
+            this.name = name;
+            this.dist = dist;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            Set<String> deps = new TreeSet<>();
+            deps.addAll(dist.distDependencies());
+            deps.addAll(dist.exclude());
+            return deps;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return this.allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public String getName() {
+            return this.name;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        private FileObject getJar(boolean ignore) {
+            if (SuiteSources.this.dir == null) {
+                return null;
+            }
+            FileObject dists = SuiteSources.this.dir.getFileObject("mxbuild/dists");
+            if (dists == null) {
+                return null;
+            }
+            List<FileObject> dist = Arrays.stream(dists.getChildren()).filter((fo) -> fo.isFolder() && fo.getName().startsWith("jdk")).collect(Collectors.toList());
+            dist.sort((fo1, fo2) -> fo2.getName().compareTo(fo1.getName()));
+            for (FileObject jdkDir : dist) {
+                FileObject jar = jdkDir.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+                if (jar != null) {
+                    return jar;
+                }
+            }
+            FileObject jar = dists.getFileObject(name.toLowerCase().replace("_", "-") + ".jar");
+            if (jar != null) {
+                return jar;
+            }
+            return null;
+        }
+
+        @Override
+        public List<? extends PathResourceImplementation> getResources() {
+            computeTransitiveDeps();
+            FileObject jar = getJar(exists == null);
+            final boolean existsNow = jar != null && jar.isData();
+            if (exists == null) {
+                exists = existsNow;
+            } else {
+                if (exists != existsNow) {
+                    exists = existsNow;
+                    support.firePropertyChange(PROP_FLAGS, !exists, (boolean) exists);
+                }
+            }
+            if (jar != null) {
+                PathResourceImplementation res;
+                try {
+                    res = ClassPathSupport.createResource(getJarRoot());
+                    return Collections.singletonList(res);
+                } catch (MalformedURLException ex) {
+                    // OK
+                }
+            }
+            return Collections.emptyList();
+        }
+
+        private URL getJarRoot() throws MalformedURLException {
+            FileObject jar = getJar(true);
+            if (jar != null) {
+                return new URL("jar:" + jar.toURL() + "!/");
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener pl) {
+            support.addPropertyChangeListener(pl);
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener pl) {
+            support.removePropertyChangeListener(pl);
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+
+        private void computeSourceRoots(Map<String, Dep> collectedDeps) {
+            if (groups != null) {
+                return;
+            }
+            Set<Group> contributingGroups = new LinkedHashSet<>();
+            for (String d : this.dist.dependencies()) {
+                Dep dep = collectedDeps.get(d);
+                if (dep == null || dep.allDeps() == null) {
+                    continue;
+                }
+                for (Dep d2 : dep.allDeps()) {
+                    if (d2 instanceof Group) {
+                        contributingGroups.add((Group) d2);
+                    }
+                }
+            }
+            for (String d : this.dist.distDependencies()) {
+                final Dep anyDep = collectedDeps.get(d);
+                if (anyDep instanceof Dist) {
+                    Dist dep = (Dist) anyDep;
+                    dep.computeSourceRoots(collectedDeps);
+                    contributingGroups.removeAll(dep.getContributingGroups());
+                }
+            }
+            groups = contributingGroups;
+        }
+
+        public Collection<Group> getContributingGroups() {
+            return groups;
+        }
+
+        @Override
+        public String toString() {
+            return "Dist[name=" + name + "]";
+        }
+    }
+
+    final class Group implements SourceGroup, Dep, AnnotationProcessingQuery.Result,
+            Compliance.Provider {
+        private final String mxName;
+        private final MxProject mxPrj;
+        private final FileObject srcDir;
+        private final FileObject srcGenDir;
+        private final FileObject binDir;
+        private final String name;
+        private final String displayName;
+        private final Compliance compliance;
+        private ClassPath sourceCP;
+        private ClassPath cp;
+        private ClassPath processorPath;
+        private Collection<Dep> allDeps;
+
+        Group(String mxName, MxProject mxPrj, FileObject srcDir, FileObject srcGenDir, FileObject binDir, String name, String displayName) {
+            this.mxName = mxName;
+            this.mxPrj = mxPrj;
+            this.srcDir = srcDir;
+            this.srcGenDir = srcGenDir;
+            this.binDir = binDir;
+            this.name = name;
+            this.displayName = displayName;
+            this.compliance = Compliance.parse(mxPrj.javaCompliance());
+        }
+
+        @Override
+        public FileObject getRootFolder() {
+            return srcDir;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        @Override
+        public Icon getIcon(boolean opened) {
+            return null;
+        }
+
+        @Override
+        public Compliance getCompliance() {
+            return compliance;
+        }
+
+        @Override
+        public boolean contains(FileObject file) {
+            if (file == srcDir || file == srcGenDir || FileUtil.isParentOf(srcDir, file) || (srcGenDir != null && FileUtil.isParentOf(srcGenDir, file))) {
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener l) {
+        }
+
+        @Override
+        public String toString() {
+            return "SuiteSources.Group[name=" + name + ",rootFolder=" + srcDir + "]"; // NOI18N
+        }
+
+        ClassPath getSourceCP() {
+            computeTransitiveDeps();
+            return sourceCP;
+        }
+
+        ClassPath getCP() {
+            computeTransitiveDeps();
+            return cp;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return mxPrj.dependencies();
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            allDeps = set;
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        private void computeClassPath(Map<String, Dep> transDeps) {
+            for (Dep d : transDeps.values()) {
+                d.owner().computeTransitiveDeps();
+            }
+
+            List<Group> arr = new ArrayList<>();
+            List<ClassPathImplementation> libs = new ArrayList<>();
+            processTransDep(transDeps.get(mxName), arr, libs);
+            cp = composeClassPath(arr, libs);
+            List<FileObject> roots = new ArrayList<>();
+            if (srcDir != null) {
+                roots.add(srcDir);
+            }
+            if (srcGenDir != null) {
+                roots.add(srcGenDir);
+            }
+            sourceCP = ClassPathSupport.createClassPath(roots.toArray(new FileObject[roots.size()]));
+
+            if (mxPrj.annotationProcessors().isEmpty()) {
+                processorPath = null;
+            } else {
+                List<Group> groups = new ArrayList<>();
+                List<ClassPathImplementation> jars = new ArrayList<>();
+                for (String dep : mxPrj.annotationProcessors()) {
+                    processTransDep(transDeps.get(dep), groups, jars);
+                }
+                processorPath = composeClassPath(groups, jars);
+            }
+        }
+
+        private void processTransDep(Dep dep, List<Group> addGroups, List<ClassPathImplementation> addJars) {
+            if (dep != null) {
+                dep.owner().computeTransitiveDeps();
+                for (Dep d : dep.allDeps()) {
+                    if (d == this) {
+                        continue;
+                    }
+                    d.owner().computeTransitiveDeps();
+                    if (d instanceof Group) {
+                        addGroups.add((Group) d);
+                    } else if (d instanceof ClassPathImplementation) {
+                        addJars.add((ClassPathImplementation) d);
+                    }
+                }
+            }
+        }
+
+        private ClassPath composeClassPath(List<Group> arr, List<ClassPathImplementation> libs) {
+            Set<FileObject> roots = new LinkedHashSet<>();
+            final int depsCount = arr.size();
+            for (int i = 0; i < depsCount; i++) {
+                final Group g = arr.get(i);
+                if (g.binDir != null) {
+                    roots.add(g.binDir);
+                }
+            }
+            ClassPath prjCp = ClassPathSupport.createClassPath(roots.toArray(new FileObject[0]));
+            if (!libs.isEmpty()) {
+                if (libs.size() == 1) {
+                    prjCp = ClassPathSupport.createProxyClassPath(prjCp,
+                                                                  ClassPathFactory.createClassPath(libs.get(0))
+                    );
+                } else {
+                    prjCp = ClassPathSupport.createProxyClassPath(prjCp,
+                                                                  ClassPathFactory.createClassPath(
+                                                                                  ClassPathSupport.createProxyClassPathImplementation(
+                                                                                                  libs.toArray(new ClassPathImplementation[0])
+                                                                                  )
+                                                                  )
+                    );
+                }
+            }
+            return prjCp;
+        }
+
+        ClassPath getProcessorCP() {
+            computeTransitiveDeps();
+            return processorPath;
+        }
+
+        @Override
+        public Set<? extends AnnotationProcessingQuery.Trigger> annotationProcessingEnabled() {
+            return EnumSet.of(AnnotationProcessingQuery.Trigger.ON_SCAN, AnnotationProcessingQuery.Trigger.IN_EDITOR);
+        }
+
+        @Override
+        public Iterable<? extends String> annotationProcessorsToRun() {
+            return null;
+        }
+
+        @Override
+        public URL sourceOutputDirectory() {
+            return srcGenDir == null ? null : srcGenDir.toURL();
+        }
+
+        @Override
+        public Map<? extends String, ? extends String> processorOptions() {
+            return Collections.emptyMap();
+        }
+
+        @Override
+        public void addChangeListener(ChangeListener l) {
+        }
+
+        @Override
+        public void removeChangeListener(ChangeListener l) {
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+    }
+
+    private class Library implements FlaggedClassPathImplementation, Dep {
+        final MxLibrary lib;
+        final PropertyChangeSupport support = new PropertyChangeSupport(this);
+        final String libName;
+        Collection<Dep> allDeps;
+        Boolean exists;
+
+        Library(String libName, MxLibrary lib) {
+            this.libName = libName;
+            this.lib = getOSSLibrary(lib);
+        }
+
+        final MxLibrary getOSSLibrary(MxLibrary lib) {
+            if (lib.sha1() == null && !lib.os_arch().isEmpty()) {
+                Map<String, MxLibrary.Arch> os_dep_libs = lib.os_arch();
+                String os = System.getProperty("os.name").toLowerCase();
+                for (Map.Entry<String, MxLibrary.Arch> entry : os_dep_libs.entrySet()) {
+                    if (os.contains(entry.getKey())) {
+                        return entry.getValue().amd64();
+                    }
+                }
+            }
+            return lib;
+        }
+
+        File getJar(boolean dumpIfMissing) {
+            File mxCache;
+            String cache = System.getenv("MX_CACHE_DIR");
+            if (cache != null) {
+                mxCache = new File(cache);
+            } else {
+                mxCache = new File(new File(new File(System.getProperty("user.home")), ".mx"), "cache");
+            }
+            int prefix = libName.indexOf(':');
+            final String simpleName = libName.substring(prefix + 1);
+
+            File simpleJar = new File(mxCache, simpleName + "_" + lib.sha1() + ".jar");
+            if (simpleJar.exists()) {
+                return simpleJar;
+            }
+            File dir = new File(mxCache, simpleName + "_" + lib.sha1());
+            File jar = new File(dir, simpleName.replace('_', '-').toLowerCase(Locale.ENGLISH) + ".jar");
+
+            if (dumpIfMissing && !jar.exists()) {
+                for (File f = jar;; f = f.getParentFile()) {
+                    if (!f.exists()) {
+                        LOG.log(Level.WARNING, "{0} does not exist", f);
+                    } else {
+                        StringBuilder sb = new StringBuilder();
+                        sb.append(f).append(" exists:\n");
+                        String[] kids = f.list();
+                        if (kids != null) {
+                            for (String n : kids) {
+                                sb.append("  ").append(n).append("\n");
+                            }
+                        }
+                        LOG.log(Level.INFO, sb.toString());
+                        break;
+                    }
+                }
+            }
+            return jar;
+        }
+
+        @Override
+        public String getName() {
+            return libName;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return lib.dependencies();
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        @Override
+        public List<? extends PathResourceImplementation> getResources() {
+            File jar = getJar(exists == null);
+            if (exists == null) {
+                exists = jar.exists();
+            } else {
+                if (exists != jar.exists()) {
+                    exists = jar.exists();
+                    support.firePropertyChange(PROP_FLAGS, !exists, (boolean) exists);
+                }
+            }
+            PathResourceImplementation res;
+            try {
+                res = ClassPathSupport.createResource(new URL("jar:" + Utilities.toURI(jar).toURL() + "!/"));
+                return Collections.singletonList(res);
+            } catch (MalformedURLException ex) {
+                return Collections.emptyList();
+            }
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener pl) {
+            support.addPropertyChangeListener(pl);
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener pl) {
+            support.removePropertyChangeListener(pl);
+        }
+
+        @Override
+        public SuiteSources owner() {
+            return SuiteSources.this;
+        }
+    }
+
+    private class JdkLibrary extends Library {
+        JdkLibrary(String libName, MxLibrary lib) {
+            super(libName, lib);
+        }
+
+        @Override
+        File getJar(boolean dumpIfMissing) {
+            File first = null;
+            for (File jdk : jdks()) {
+                File jre = new File(jdk, "jre");
+                File jrePath = new File(jre, lib.path().replace('/', File.separatorChar));
+                if (jrePath.exists()) {
+                    return jrePath;
+                }
+
+                if (first == null) {
+                    first = jrePath;
+                }
+
+                File jdkPath = new File(jdk, lib.path().replace('/', File.separatorChar));
+
+                if (jdkPath.exists()) {
+                    return jdkPath;
+                }
+
+            }
+
+            if (dumpIfMissing) {
+                for (File jdk : jdks()) {
+                    File libPath = new File(jdk, lib.path().replace('/', File.separatorChar));
+                    if (!libPath.exists()) {
+                        LOG.log(Level.WARNING, "{0} does not exist", libPath);
+                    } else {
+                        StringBuilder sb = new StringBuilder();
+                        sb.append(libPath).append(" exists:\n");
+                        String[] kids = libPath.list();
+                        if (kids != null) {
+                            for (String n : kids) {
+                                sb.append("  ").append(n).append("\n");
+                            }
+                        }
+                        LOG.log(Level.INFO, sb.toString());
+                        break;
+                    }
+                }
+            }
+            return first;
+        }
+
+        @Override
+        public String getName() {
+            return libName;
+        }
+
+        @Override
+        public Collection<String> depNames() {
+            return lib.dependencies();
+        }
+
+        @Override
+        public Collection<Dep> allDeps() {
+            return allDeps;
+        }
+
+        @Override
+        public void setAllDeps(Collection<Dep> set) {
+            this.allDeps = set;
+        }
+
+        @Override
+        public Set<ClassPath.Flag> getFlags() {
+            return exists ? Collections.emptySet() : Collections.singleton(ClassPath.Flag.INCOMPLETE);
+        }
+
+        @Override
+        public List<? extends PathResourceImplementation> getResources() {

Review comment:
       Can be somehow shared with `Library` implementation ?




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544075042



##########
File path: java/java.mx.project/src/org/netbeans/modules/java/mx/project/SuiteActionProvider.java
##########
@@ -0,0 +1,245 @@
+/*
+ * 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.mx.project;
+
+import java.awt.Toolkit;
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.Future;
+import org.netbeans.api.actions.Openable;
+import org.netbeans.api.debugger.*;
+import org.netbeans.api.debugger.jpda.ListeningDICookie;
+import org.netbeans.api.extexecution.ExecutionDescriptor;
+import org.netbeans.api.extexecution.ExecutionService;
+import org.netbeans.api.extexecution.base.ProcessBuilder;
+import org.netbeans.api.extexecution.print.ConvertedLine;
+import org.netbeans.spi.project.ActionProvider;
+import org.netbeans.spi.project.SingleMethod;
+import org.openide.LifecycleManager;
+import org.openide.cookies.LineCookie;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.text.Line;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.util.RequestProcessor;
+import org.openide.windows.OutputEvent;
+import org.openide.windows.OutputListener;
+
+final class SuiteActionProvider implements ActionProvider {
+    private final SuiteProject prj;
+
+    SuiteActionProvider(SuiteProject prj) {
+        this.prj = prj;
+    }
+
+    @Override
+    public String[] getSupportedActions() {
+        return new String[] {
+            ActionProvider.COMMAND_CLEAN,
+            ActionProvider.COMMAND_BUILD,
+            ActionProvider.COMMAND_COMPILE_SINGLE,
+            ActionProvider.COMMAND_REBUILD,
+            ActionProvider.COMMAND_TEST_SINGLE,
+            ActionProvider.COMMAND_RUN_SINGLE,
+            ActionProvider.COMMAND_DEBUG_TEST_SINGLE,
+            ActionProvider.COMMAND_DEBUG_SINGLE,
+            SingleMethod.COMMAND_DEBUG_SINGLE_METHOD,
+            SingleMethod.COMMAND_RUN_SINGLE_METHOD,
+        };
+    }
+
+    @NbBundle.Messages({
+        "MSG_Clean=mx clean {0}",
+        "MSG_Build=mx build {0}",
+        "MSG_BuildOnly=mx build {0} --only {1}",
+        "MSG_Rebuild=mx rebuild {0}",
+        "MSG_Unittest=mx unittest {0}",
+    })
+    @Override
+    public void invokeAction(String action, Lookup context) throws IllegalArgumentException {
+        FileObject fo = context.lookup(FileObject.class);
+        String testSuffix = "";
+        switch (action) {
+            case ActionProvider.COMMAND_CLEAN:
+                runMx(Bundle.MSG_Clean(prj.getName()), "clean"); // NOI18N
+                break;
+            case ActionProvider.COMMAND_BUILD:
+                runMx(Bundle.MSG_Build(prj.getName()), "build"); // NOI18N
+                break;
+            case ActionProvider.COMMAND_REBUILD:
+                runMx(Bundle.MSG_Rebuild(prj.getName()), "build"); // NOI18N
+                break;
+            case ActionProvider.COMMAND_COMPILE_SINGLE: {
+                SuiteSources.Group grp = prj.getSources().findGroup(fo);
+                if (grp == null) {
+                    Toolkit.getDefaultToolkit().beep();
+                    return;
+                }
+                final String name = grp.getDisplayName();
+                runMx(Bundle.MSG_BuildOnly(prj.getName(), name), "build", "--only", name); // NOI18N
+                break;
+            }
+            case SingleMethod.COMMAND_RUN_SINGLE_METHOD: {
+                SingleMethod m = context.lookup(SingleMethod.class);
+                if (m != null && fo == null) {
+                    fo = m.getFile();
+                    testSuffix = "#" + m.getMethodName();
+                }
+                // fallthrough
+            }
+            case ActionProvider.COMMAND_TEST_SINGLE:
+            case ActionProvider.COMMAND_RUN_SINGLE:
+                if (fo == null) {
+                    Toolkit.getDefaultToolkit().beep();
+                    return;
+                }
+                runMx(Bundle.MSG_Unittest(fo.getName()), "unittest", fo.getName() + testSuffix); // NOI18N
+                break;
+            case SingleMethod.COMMAND_DEBUG_SINGLE_METHOD: {
+                SingleMethod m = context.lookup(SingleMethod.class);
+                if (m != null && fo == null) {
+                    fo = m.getFile();
+                    testSuffix = "#" + m.getMethodName();
+                }
+                // fallthrough
+            }
+            case ActionProvider.COMMAND_DEBUG_TEST_SINGLE:
+            case ActionProvider.COMMAND_DEBUG_SINGLE:
+                if (fo == null) {
+                    Toolkit.getDefaultToolkit().beep();
+                    return;
+                }
+                ListeningDICookie ldic = ListeningDICookie.create(-1);
+                Object obj = ldic.getArgs().get("port"); // NOI18N
+                DebuggerInfo di = DebuggerInfo.create(ListeningDICookie.ID, ldic);
+                DebuggerEngine[] engines = { null };
+                RequestProcessor.getDefault().post(() -> {
+                    DebuggerEngine[] engs = DebuggerManager.getDebuggerManager().startDebugging(di);
+                    engines[0] = engs[0];
+                });
+                int port = ldic.getPortNumber();
+                runMx(Bundle.MSG_Unittest(fo.getName()), "--attach", "" + port, "unittest", fo.getName() + testSuffix); // NOI18N
+                break;
+            default:
+                throw new UnsupportedOperationException(action);
+        }
+    }
+
+    private boolean runMx(String taskName, String... args) {
+        final File suiteDir = FileUtil.toFile(prj.getProjectDirectory());
+        if (!suiteDir.isDirectory()) {
+            Toolkit.getDefaultToolkit().beep();
+            return true;
+        }
+        LifecycleManager.getDefault().saveAll();
+        ExecutionDescriptor descriptor = new ExecutionDescriptor()
+                .frontWindow(true).controllable(true)
+                .errConvertorFactory(() -> {
+                    return (String line) -> {
+                        String[] segments = line.split(":");
+                        if (segments.length > 2) {
+                            File src = new File(segments[0]);
+                            if (src.exists()) {
+                                int lineNumber = parseLineNumber(segments) - 1;
+                                return Collections.singletonList(ConvertedLine.forText(line, new OutputListener() {
+                                    @Override
+                                    public void outputLineSelected(OutputEvent ev) {
+                                        openLine(Line.ShowOpenType.NONE, Line.ShowVisibilityType.FRONT);
+                                    }
+
+                                    @Override
+                                    public void outputLineAction(OutputEvent ev) {
+                                        openLine(Line.ShowOpenType.OPEN, Line.ShowVisibilityType.FOCUS);
+                                    }
+
+                                    private boolean openLine(final Line.ShowOpenType openType, final Line.ShowVisibilityType visibilityType) throws IndexOutOfBoundsException {
+                                        FileObject fo = FileUtil.toFileObject(src);
+                                        if (fo != null) {
+                                            Lookup lkp = fo.getLookup();
+                                            final LineCookie lines = lkp.lookup(LineCookie.class);
+                                            if (lines != null) {
+                                                Line open = lines.getLineSet().getOriginal(lineNumber);
+                                                if (open != null) {
+                                                    open.show(openType, visibilityType);
+                                                    return true;
+                                                }
+                                            }
+                                            Openable open = lkp.lookup(Openable.class);
+                                            if (open != null) {
+                                                open.open();
+                                            } else {
+                                                Toolkit.getDefaultToolkit().beep();
+                                            }
+                                        }
+                                        return false;
+                                    }
+
+                                    @Override
+                                    public void outputLineCleared(OutputEvent ev) {
+                                    }
+                                }));
+                            }
+                        }
+                        return null;
+                    };
+                });
+        ProcessBuilder processBuilder = ProcessBuilder.getLocal();
+        processBuilder.setWorkingDirectory(suiteDir.getPath());
+        processBuilder.setExecutable("mx"); // NOI18N

Review comment:
       True, the UI should be improved in the future. Recorded as [NETBEANS-5158](https://issues.apache.org/jira/browse/NETBEANS-5158).




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach merged pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach merged pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575


   


----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] matthiasblaesing commented on pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
matthiasblaesing commented on pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#issuecomment-744677268


   Just for the record: I expressed my concerns, but this change has minimal cross cutting changes and does not bring to much code in. So if mx support gets extracted/removed at some point, it can be done with minmal fuzz and thus the potential negative impact is minimal.


----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #2575: Open GraalVM sources out of the box

Posted by GitBox <gi...@apache.org>.
JaroslavTulach commented on a change in pull request #2575:
URL: https://github.com/apache/netbeans/pull/2575#discussion_r544284372



##########
File path: java/java.mx.project/test/unit/src/org/netbeans/modules/java/mx/project/SuiteCheck.java
##########
@@ -0,0 +1,257 @@
+/*
+ * 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.mx.project;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+import javax.tools.Diagnostic;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertSame;
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
+import org.netbeans.modules.java.mx.project.suitepy.MxSuite;
+import org.junit.Assume;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.classpath.JavaClassPathConstants;
+import org.netbeans.api.java.queries.BinaryForSourceQuery;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.java.source.CompilationController;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.netbeans.api.project.ProjectUtils;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.api.project.ui.OpenProjects;
+import org.netbeans.junit.NbTestCase;
+import org.netbeans.modules.parsing.api.indexing.IndexingManager;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.Utilities;
+
+abstract class SuiteCheck extends NbTestCase {
+    SuiteCheck(String name) {
+        super(name);
+        log(Level.INFO, "Test created by %s classloader", getClass().getClassLoader());
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        log(Level.INFO, "setUp - init");
+        super.setUp();
+        final Logger tooVerboseLogger = Logger.getLogger("org.netbeans.core.startup.InstalledFileLocatorImpl");
+        tooVerboseLogger.setUseParentHandlers(false);
+        try {
+            MxSuite.parse(null);
+        } catch (LinkageError err) {
+            Assume.assumeNoException("Cannot initialize Polyglot API, are you using GraalVM?", err);
+        }
+        log(Level.INFO, "setUp - exit");
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        Enumeration<String> en = LogManager.getLogManager().getLoggerNames();
+        while (en.hasMoreElements()) {
+            String n = en.nextElement();
+            Logger l = LogManager.getLogManager().getLogger(n);
+            boolean first = true;
+            if (l == null || l.getHandlers() == null) {
+                continue;
+            }
+            for (Handler h : l.getHandlers()) {
+                if (first) {
+                    System.err.println("cleaning logger '" + n + "'");
+                    first = false;
+                }
+                System.err.println("  removing handler: " + h);
+                l.removeHandler(h);
+            }
+        }
+    }
+
+    @Override
+    protected int timeOut() {
+        return 1_200_000;
+    }
+
+    protected final void verifyNoErrorsInSuite(final String suiteName, String... onlySourceGroups) throws IllegalArgumentException, IOException, URISyntaxException {
+        long begin = System.currentTimeMillis();
+        File sibling = findSuite(suiteName);
+
+        FileObject fo = FileUtil.toFileObject(sibling);
+        assertNotNull("project directory found", fo);
+
+        log(Level.INFO, "Recognizing project %s", fo);
+        long now = System.currentTimeMillis();
+        Project p = ProjectManager.getDefault().findProject(fo);
+        long took = System.currentTimeMillis() - now;
+        assertNotNull("project found", p);
+        log(Level.INFO, "Project found %s in %d ms", p, took);
+        assertEquals("It is suite project: " + p, "SuiteProject", p.getClass().getSimpleName());
+        OpenProjects.getDefault().open(new Project[]{p}, false);
+
+        StringBuilder errors = new StringBuilder();
+        FileObject[] errornous = { null };
+        Sources src = ProjectUtils.getSources(p);
+        int cnt = 0;
+        for (SourceGroup sourceGroup : src.getSourceGroups("java")) {
+            if (sourceGroup instanceof Compliance.Provider) {
+                Compliance c = ((Compliance.Provider) sourceGroup).getCompliance();
+                if (!c.includes(8)) {
+                    log(Level.INFO, "Skipping check of %s with compliance %s", sourceGroup, c);
+                    continue;
+                }
+            }
+            FOUND: if (onlySourceGroups.length > 0) {
+                for (String gName : onlySourceGroups) {
+                    if (sourceGroup.getDisplayName().equals(gName)) {
+                        cnt++;
+                        break FOUND;
+                    }
+                }
+                // not found
+                continue;
+            }
+            assertSourcesNoError(p, errornous, errors, sourceGroup.getRootFolder(), begin);
+        }
+        assertCompilationErrors(errors, errornous);
+
+        assertEquals("Exactly as many source groups tested as requested", onlySourceGroups.length, cnt);
+    }
+
+    protected final File findSuite(String suite) throws URISyntaxException {
+        File location = getDataDir();
+        while (location != null) {
+            File graal = new File(location, "graal");
+            File suiteDir = new File(graal, suite);
+            if (suiteDir.isDirectory()) {
+                return suiteDir;
+            }
+            location = location.getParentFile();
+        }
+        fail("Cannot find truffle next to " + getDataDir());
+        return null;
+    }
+
+    private void assertSourcesNoError(Project project, FileObject[] errornous, StringBuilder errors, FileObject dir, long begin) throws IOException {
+        long now = System.currentTimeMillis();
+        log(Level.INFO, "assertSourcesNoError for %s", dir);
+        IndexingManager.getDefault().refreshIndexAndWait(dir.toURL(), null, false);
+        log(Level.INFO, "      refresh done       %s", dir);
+        Enumeration<? extends FileObject> en = dir.getChildren(true);
+        int nonJavaCount = 0;
+        int javaCount = 0;
+        while (en.hasMoreElements()) {
+            FileObject fo = en.nextElement();
+            if (fo.isFolder()) {
+                continue;
+            }
+            Project prj = FileOwnerQuery.getOwner(fo);
+            assertSame("FileOwnerQuery returns the right project", project, prj);
+            if (!fo.hasExt("java")) {
+                nonJavaCount++;
+                continue;
+            }
+            JavaSource source = JavaSource.forFileObject(fo);

Review comment:
       Hopefully it fails only if the `.mx/cache` doesn't contain necessary JAR libraries (locally I have them). Let's see whether this gets fixed after 4cbe7aa




----------------------------------------------------------------
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.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists