You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by lk...@apache.org on 2019/07/28 20:48:18 UTC

[netbeans] branch NETBEANS-1802_OpenProjectAs created (now f1bd0b1)

This is an automated email from the ASF dual-hosted git repository.

lkishalmi pushed a change to branch NETBEANS-1802_OpenProjectAs
in repository https://gitbox.apache.org/repos/asf/netbeans.git.


      at f1bd0b1  [NETBEANS-1802] Basic infrastructure to load a project with a Project Type.

This branch includes the following new commits:

     new f1bd0b1  [NETBEANS-1802] Basic infrastructure to load a project with a Project Type.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



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

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


[netbeans] 01/01: [NETBEANS-1802] Basic infrastructure to load a project with a Project Type.

Posted by lk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lkishalmi pushed a commit to branch NETBEANS-1802_OpenProjectAs
in repository https://gitbox.apache.org/repos/asf/netbeans.git

commit f1bd0b190dc37ade7a1adca5a663cc5dcad2f7e3
Author: Laszlo Kishalmi <la...@gmail.com>
AuthorDate: Sun Jul 28 13:47:47 2019 -0700

    [NETBEANS-1802] Basic infrastructure to load a project with a Project Type.
---
 ide/projectapi.nb/nbproject/project.properties     |   2 +-
 .../modules/projectapi/nb/NbProjectManager.java    | 409 ++++++++++-----------
 .../org/netbeans/api/project/ProjectManager.java   |  38 +-
 .../spi/project/ProjectManagerImplementation2.java |  62 ++++
 .../ui/actions/OpenProjectFolderAction.java        |   8 +-
 5 files changed, 305 insertions(+), 214 deletions(-)

diff --git a/ide/projectapi.nb/nbproject/project.properties b/ide/projectapi.nb/nbproject/project.properties
index 7f641b7..3b054ca 100644
--- a/ide/projectapi.nb/nbproject/project.properties
+++ b/ide/projectapi.nb/nbproject/project.properties
@@ -15,5 +15,5 @@
 # specific language governing permissions and limitations
 # under the License.
 is.eager=true
-javac.source=1.6
+javac.source=1.8
 javac.compilerargs=-Xlint -Xlint:-serial
diff --git a/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/NbProjectManager.java b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/NbProjectManager.java
index 5997014..26e0fd2 100644
--- a/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/NbProjectManager.java
+++ b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/NbProjectManager.java
@@ -23,10 +23,12 @@ import java.awt.EventQueue;
 import java.io.IOException;
 import java.lang.ref.Reference;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
@@ -42,6 +44,7 @@ import org.netbeans.api.project.ProjectManager.Result;
 import org.netbeans.spi.project.ProjectFactory;
 import org.netbeans.spi.project.ProjectFactory2;
 import org.netbeans.spi.project.ProjectManagerImplementation;
+import org.netbeans.spi.project.ProjectManagerImplementation2;
 import org.netbeans.spi.project.ProjectState;
 import org.openide.filesystems.FileChangeAdapter;
 import org.openide.filesystems.FileChangeListener;
@@ -51,7 +54,6 @@ import org.openide.filesystems.FileRenameEvent;
 import org.openide.filesystems.FileUtil;
 import org.openide.util.Lookup;
 import org.openide.util.LookupEvent;
-import org.openide.util.LookupListener;
 import org.openide.util.Mutex;
 import org.openide.util.Mutex.ExceptionAction;
 import org.openide.util.MutexException;
@@ -66,7 +68,7 @@ import org.openide.util.spi.MutexImplementation;
  * @author Jesse Glick
  */
 @ServiceProvider(service = ProjectManagerImplementation.class, position = 1000)
-public final class NbProjectManager implements ProjectManagerImplementation {
+public final class NbProjectManager implements ProjectManagerImplementation2 {
     
     // XXX need to figure out how to convince the system that a Project object is modified
     // so that Save All and the exit dialog work... could temporarily use a DataLoader
@@ -84,14 +86,11 @@ public final class NbProjectManager implements ProjectManagerImplementation {
         Lookup.getDefault().lookupResult(ProjectFactory.class);
     
     public NbProjectManager() {
-        factories.addLookupListener(new LookupListener() {
-            @Override
-            public void resultChanged(LookupEvent e) {
-                clearNonProjectCache();
-            }
+        factories.addLookupListener((LookupEvent e) -> {
+            clearNonProjectCache();
         });
     }
-    
+
     private static enum LoadStatus {
         /**
          * Marker for a directory which is known to not be a project.
@@ -122,19 +121,19 @@ public final class NbProjectManager implements ProjectManagerImplementation {
      * Cache of loaded projects (modified or not).
      * Also caches a dir which is <em>not</em> a project.
      */
-    private final Map<FileObject,Union2<Reference<Project>,LoadStatus>> dir2Proj = new WeakHashMap<FileObject,Union2<Reference<Project>,LoadStatus>>();
+    private final Map<FileObject,Union2<Reference<Project>,LoadStatus>> dir2Proj = new WeakHashMap<>();
     
     /**
      * Set of modified projects (subset of loaded projects).
      */
-    private final Set<Project> modifiedProjects = new HashSet<Project>();
+    private final Set<Project> modifiedProjects = new HashSet<>();
     
-    private final Set<Project> removedProjects = Collections.synchronizedSet(new WeakSet<Project>());
+    private final Set<Project> removedProjects = Collections.synchronizedSet(new WeakSet<>());
     
     /**
      * Mapping from projects to the factories that created them.
      */
-    private final Map<Project,ProjectFactory> proj2Factory = Collections.synchronizedMap(new WeakHashMap<Project,ProjectFactory>());
+    private final Map<Project,ProjectFactory> proj2Factory = Collections.synchronizedMap(new WeakHashMap<>());
     
     /**
      * Checks for deleted projects.
@@ -144,7 +143,7 @@ public final class NbProjectManager implements ProjectManagerImplementation {
     /**
      * Whether this thread is currently loading a project.
      */
-    private ThreadLocal<Set<FileObject>> loadingThread = new ThreadLocal<Set<FileObject>>();
+    private final ThreadLocal<Set<FileObject>> loadingThread = new ThreadLocal<>();
 
     /**
      * Callback to ProjectManager.
@@ -213,15 +212,66 @@ public final class NbProjectManager implements ProjectManagerImplementation {
      */
     @Override
     public Project findProject(final FileObject projectDirectory) throws IOException, IllegalArgumentException {
+        return findProject(projectDirectory, null);
+    }
+    
+    /**
+     * Create a project from a given directory.
+     * @param dir the project dir
+     * @return a project made from it, or null if it is not recognized
+     * @throws IOException if there was a problem loading the project
+     */
+    private Project createProject(FileObject dir, String projectType) throws IOException {
+        assert dir != null;
+        assert dir.isFolder();
+        assert getMutex().isReadAccess();
+        ProjectStateImpl state = new ProjectStateImpl();
+        for (ProjectFactory factory : factories.allInstances()) {
+            Project p = null;
+            if (factory instanceof ProjectFactory2) {
+                Result res = ((ProjectFactory2) factory).isProject2(dir);
+                if (res != null) {
+                    if (projectType != null) {
+                        if (projectType.equals(res.getProjectType())) {
+                            p = factory.loadProject(dir, state);
+                        }
+                    } else {
+                        p = factory.loadProject(dir, state);
+                    }
+                }
+            } else if (projectType == null) {
+                p = factory.loadProject(dir, state);
+            }
+            if (p != null) {
+                if (TIMERS.isLoggable(Level.FINE)) {
+                    LogRecord rec = new LogRecord(Level.FINE, "Project"); // NOI18N
+                    rec.setParameters(new Object[] { p });
+                    TIMERS.log(rec);
+                }
+                proj2Factory.put(p, factory);
+                state.attach(p);
+                return p;
+            }
+        }
+        return null;
+    }
+    
+
+    @Override
+    public Result isProject(final FileObject projectDirectory) throws IllegalArgumentException {
+        Result[] res = checkProject(projectDirectory);
+        return res.length > 0 ? res[0] : null;
+    }
+
+    @Override
+    public Project findProject(FileObject projectDirectory, String projectType) throws IOException, IllegalArgumentException {
         Parameters.notNull("projectDirectory", projectDirectory);   //NOI18N
         try {
-            return getMutex().readAccess(new Mutex.ExceptionAction<Project>() {
-                @Override
-                public Project run() throws IOException {
-                    // Read access, but still needs to synch on the cache since there
-                    // may be >1 reader.
-                    try {
-                        boolean wasSomeSuchProject;
+            return getMutex().readAccess((Mutex.ExceptionAction<Project>) () -> {
+                // Read access, but still needs to synch on the cache since there
+                // may be >1 reader.
+                try {
+                    boolean wasSomeSuchProject;
                     synchronized (dir2Proj) {
                         Union2<Reference<Project>,LoadStatus> o;
                         do {
@@ -286,7 +336,7 @@ public final class NbProjectManager implements ProjectManagerImplementation {
                     }
                     boolean resetLP = false;
                     try {
-                        Project p = createProject(projectDirectory);
+                        Project p = createProject(projectDirectory, projectType);
                         //Thread.dumpStack();
                         synchronized (dir2Proj) {
                             dir2Proj.notifyAll();
@@ -332,118 +382,80 @@ public final class NbProjectManager implements ProjectManagerImplementation {
                             }
                         }
                     }
-    // Workaround for issue #51911:
-    // Log project creation exception here otherwise it can get lost
-    // in following scenario:
-    // If project creation calls ProjectManager.postWriteRequest() (what for 
-    // example FreeformSources.initSources does) and then it throws an 
-    // exception then this exception can get lost because leaving read mutex
-    // will immediately execute the runnable posted by 
-    // ProjectManager.postWriteRequest() and if this runnable fails (what
-    // for FreeformSources.initSources will happen because
-    // AntBasedProjectFactorySingleton.getProjectFor() will not find project in
-    // its helperRef cache) then only this second fail is logged, but the cause - 
-    // the failure to create project - is never logged. So, better log it here:
-                    } catch (Error e) {
-                        LOG.log(Level.FINE, null, e);
-                        throw e;
-                    } catch (RuntimeException e) {
-                        LOG.log(Level.FINE, null, e);
-                        throw e;
-                    } catch (IOException e) {
-                        LOG.log(Level.FINE, null, e);
-                        throw e;
-                    }
+                    // Workaround for issue #51911:
+                    // Log project creation exception here otherwise it can get lost
+                    // in following scenario:
+                    // If project creation calls ProjectManager.postWriteRequest() (what for
+                    // example FreeformSources.initSources does) and then it throws an
+                    // exception then this exception can get lost because leaving read mutex
+                    // will immediately execute the runnable posted by
+                    // ProjectManager.postWriteRequest() and if this runnable fails (what
+                    // for FreeformSources.initSources will happen because
+                    // AntBasedProjectFactorySingleton.getProjectFor() will not find project in
+                    // its helperRef cache) then only this second fail is logged, but the cause -
+                    // the failure to create project - is never logged. So, better log it here:
+                } catch (Error | RuntimeException | IOException e) {
+                    LOG.log(Level.FINE, null, e);
+                    throw e;
                 }
             });
         } catch (MutexException e) {
             throw (IOException)e.getException();
         }
     }
-    
-    /**
-     * Create a project from a given directory.
-     * @param dir the project dir
-     * @return a project made from it, or null if it is not recognized
-     * @throws IOException if there was a problem loading the project
-     */
-    private Project createProject(FileObject dir) throws IOException {
-        assert dir != null;
-        assert dir.isFolder();
-        assert getMutex().isReadAccess();
-        ProjectStateImpl state = new ProjectStateImpl();
-        for (ProjectFactory factory : factories.allInstances()) {
-            Project p = factory.loadProject(dir, state);
-            if (p != null) {
-                if (TIMERS.isLoggable(Level.FINE)) {
-                    LogRecord rec = new LogRecord(Level.FINE, "Project"); // NOI18N
-                    rec.setParameters(new Object[] { p });
-                    TIMERS.log(rec);
-                }
-                proj2Factory.put(p, factory);
-                state.attach(p);
-                return p;
-            }
-        }
-        return null;
-    }
-    
 
     @Override
-    public Result isProject(final FileObject projectDirectory) throws IllegalArgumentException {
+    public Result[] checkProject(FileObject projectDirectory) throws IllegalArgumentException {
+        final Result[] EMPTY_RESULT = new Result[0];
         Parameters.notNull("projectDirectory", projectDirectory);
-        return getMutex().readAccess(new Mutex.Action<Result>() {
-            @Override
-            public Result run() {
-                synchronized (dir2Proj) {
-                    Union2<Reference<Project>,LoadStatus> o;
-                    do {
-                        o = dir2Proj.get(projectDirectory);
-                        if (LoadStatus.LOADING_PROJECT.is(o)) {
-                            if (EventQueue.isDispatchThread()) {
-                                // #183192: permitted false positive; better than blocking EQ
-                                return new Result(null);
-                            }
-                            try {
-                                dir2Proj.wait();
-                            } catch (InterruptedException e) {
-                                LOG.log(Level.INFO, null, e);
-                                return null;
-                            }
+        return getMutex().readAccess((Mutex.Action<Result[]>) () -> {
+            synchronized (dir2Proj) {
+                Union2<Reference<Project>,LoadStatus> o;
+                do {
+                    o = dir2Proj.get(projectDirectory);
+                    if (LoadStatus.LOADING_PROJECT.is(o)) {
+                        if (EventQueue.isDispatchThread()) {
+                            // #183192: permitted false positive; better than blocking EQ
+                            return new Result[] {new Result(null)};
+                        }
+                        try {
+                            dir2Proj.wait();
+                        } catch (InterruptedException e) {
+                            LOG.log(Level.INFO, null, e);
+                            return EMPTY_RESULT;
                         }
-                    } while (LoadStatus.LOADING_PROJECT.is(o));
-                    assert !LoadStatus.LOADING_PROJECT.is(o);
-                    if (LoadStatus.NO_SUCH_PROJECT.is(o)) {
-                        return null;
-                    } else if (o != null) {
-                        // Reference<Project> or SOME_SUCH_PROJECT
-                        // rather check for result than load project and lookup projectInformation for icon.
-                        return checkForProject(projectDirectory);
                     }
-                    // Not in cache.
-                    dir2Proj.put(projectDirectory, LoadStatus.LOADING_PROJECT.wrap());
+                } while (LoadStatus.LOADING_PROJECT.is(o));
+                assert !LoadStatus.LOADING_PROJECT.is(o);
+                if (LoadStatus.NO_SUCH_PROJECT.is(o)) {
+                    return EMPTY_RESULT;
+                } else if (o != null) {
+                    // Reference<Project> or SOME_SUCH_PROJECT
+                    // rather check for result than load project and lookup projectInformation for icon.
+                    return checkForProject(projectDirectory);
                 }
-                boolean resetLP = false;
-                try {
-                    Result p = checkForProject(projectDirectory);
-                    synchronized (dir2Proj) {
-                        resetLP = true;
-                        dir2Proj.notifyAll();
-                        if (p != null) {
-                            dir2Proj.put(projectDirectory, LoadStatus.SOME_SUCH_PROJECT.wrap());
-                            return p;
-                        } else {
-                            dir2Proj.put(projectDirectory, LoadStatus.NO_SUCH_PROJECT.wrap());
-                            return null;
-                        }
+                // Not in cache.
+                dir2Proj.put(projectDirectory, LoadStatus.LOADING_PROJECT.wrap());
+            }
+            boolean resetLP = false;
+            try {
+                Result[] ps = checkForProject(projectDirectory);
+                synchronized (dir2Proj) {
+                    resetLP = true;
+                    dir2Proj.notifyAll();
+                    if (ps.length > 0) {
+                        dir2Proj.put(projectDirectory, LoadStatus.SOME_SUCH_PROJECT.wrap());
+                    } else {
+                        dir2Proj.put(projectDirectory, LoadStatus.NO_SUCH_PROJECT.wrap());
                     }
-                } finally {
-                    if (!resetLP) {
-                        // some runtime exception interrupted.
-                        synchronized (dir2Proj) {
-                            assert LoadStatus.LOADING_PROJECT.is(dir2Proj.get(projectDirectory));
-                            dir2Proj.remove(projectDirectory);
-                        }
+                    return ps;
+                }
+            } finally {
+                if (!resetLP) {
+                    // some runtime exception interrupted.
+                    synchronized (dir2Proj) {
+                        assert LoadStatus.LOADING_PROJECT.is(dir2Proj.get(projectDirectory));
+                        dir2Proj.remove(projectDirectory);
                     }
                 }
             }
@@ -453,29 +465,28 @@ public final class NbProjectManager implements ProjectManagerImplementation {
     /**
      *
      * @param dir
-     * @param preferResult, if false will not actually call the factory methods with populated Results, but
-     *                      create dummy ones and use the Result as boolean flag only.
      * @return
      */
-    private Result checkForProject(FileObject dir) {
+    private Result[] checkForProject(FileObject dir) {
         assert dir != null;
         assert dir.isFolder() : dir;
         assert getMutex().isReadAccess();
-        Iterator<? extends ProjectFactory> it = factories.allInstances().iterator();
-        while (it.hasNext()) {
-            ProjectFactory factory = it.next();
+        List<Result> detectedProjectTypes = new ArrayList<>();
+        for (ProjectFactory factory : factories.allInstances()) {
             if (factory instanceof ProjectFactory2) {
                 Result res = ((ProjectFactory2)factory).isProject2(dir);
                 if (res != null) {
-                    return res;
+                    String type = res.getProjectType() != null ? res.getProjectType() : factory.getClass().getSimpleName();
+                    String name = res.getDisplayName() != null ? res.getDisplayName() : dir.getName();
+                    detectedProjectTypes.add(new Result(name, type, res.getIcon()));
                 }
             } else {
                 if (factory.isProject(dir)) {
-                    return new Result((Icon)null);
+                    detectedProjectTypes.add(new Result((Icon)null));
                 }
             }
         }
-        return null;
+        return detectedProjectTypes.toArray(new Result[detectedProjectTypes.size()]);
     }
     
     /**
@@ -511,16 +522,13 @@ public final class NbProjectManager implements ProjectManagerImplementation {
         public void markModified() {
             assert p != null;
             LOG.log(Level.FINE, "markModified({0})", p.getProjectDirectory());
-            getMutex().writeAccess(new Mutex.Action<Void>() {
-                @Override
-                public Void run() {
-                    if (proj2Factory.containsKey(p)) {
-                        modifiedProjects.add(p);
-                    } else {
-                        LOG.log(Level.WARNING, "An attempt to call ProjectState.markModified on an unknown project: {0}", p.getProjectDirectory());
-                    }
-                    return null;
+            getMutex().writeAccess((Mutex.Action<Void>) () -> {
+                if (proj2Factory.containsKey(p)) {
+                    modifiedProjects.add(p);
+                } else {
+                    LOG.log(Level.WARNING, "An attempt to call ProjectState.markModified on an unknown project: {0}", p.getProjectDirectory());
                 }
+                return null;
             });
         }
 
@@ -529,26 +537,23 @@ public final class NbProjectManager implements ProjectManagerImplementation {
             assert p != null;
             final FileObject dir = p.getProjectDirectory();
             LOG.log(Level.FINE, "notifyDeleted: {0}", dir);
-            getMutex().writeAccess(new Mutex.Action<Void>() {
-                @Override
-                public Void run() {
-                    synchronized (dir2Proj) {
-                        Union2<Reference<Project>,LoadStatus> o = dir2Proj.get(dir);
-                        if (o != null && o.hasFirst() && o.first().get() == p) {
-                            dir2Proj.remove(dir);
-                        } else {
-                            // #194046: project folder was moved, so now points to new project
-                            LOG.log(Level.FINE, "notifyDeleted skipping dir2Proj update since {0} @{1} != {2}", new Object[] {p, p.hashCode(), o});
-                        }
-                    }
-                    proj2Factory.remove(p);
-                    modifiedProjects.remove(p);
-                    if (!removedProjects.add(p)) {
-                        LOG.log(Level.WARNING, "An attempt to call notifyDeleted more than once. Project: {0}", dir);
+            getMutex().writeAccess((Mutex.Action<Void>) () -> {
+                synchronized (dir2Proj) {
+                    Union2<Reference<Project>,LoadStatus> o = dir2Proj.get(dir);
+                    if (o != null && o.hasFirst() && o.first().get() == p) {
+                        dir2Proj.remove(dir);
+                    } else {
+                        // #194046: project folder was moved, so now points to new project
+                        LOG.log(Level.FINE, "notifyDeleted skipping dir2Proj update since {0} @{1} != {2}", new Object[] {p, p.hashCode(), o});
                     }
-                    callBack.notifyDeleted(p);
-                    return null;
                 }
+                proj2Factory.remove(p);
+                modifiedProjects.remove(p);
+                if (!removedProjects.add(p)) {
+                    LOG.log(Level.WARNING, "An attempt to call notifyDeleted more than once. Project: {0}", dir);
+                }
+                callBack.notifyDeleted(p);
+                return null;
             });
         }
 
@@ -558,13 +563,9 @@ public final class NbProjectManager implements ProjectManagerImplementation {
      * <p>Acquires read access.
      * @return an immutable set of projects
      */
+    @Override
     public Set<Project> getModifiedProjects() {
-        return getMutex().readAccess(new Mutex.Action<Set<Project>>() {
-            @Override
-            public Set<Project> run() {
-                return new HashSet<Project>(modifiedProjects);
-            }
-        });
+        return getMutex().readAccess((Mutex.Action<Set<Project>>) () -> new HashSet<Project>(modifiedProjects));
     }
     
     /**
@@ -573,17 +574,15 @@ public final class NbProjectManager implements ProjectManagerImplementation {
      * @param p a project loaded by this manager
      * @return true if it is modified, false if has been saved since the last modification
      */
+    @Override
     public boolean isModified(final Project p) {
-        return getMutex().readAccess(new Mutex.Action<Boolean>() {
-            @Override
-            public Boolean run() {
-                synchronized (dir2Proj) {
-                    if (!proj2Factory.containsKey(p)) {
-                        LOG.log(Level.WARNING, "Project {0} was already deleted", p);
-                    }
+        return getMutex().readAccess((Mutex.Action<Boolean>) () -> {
+            synchronized (dir2Proj) {
+                if (!proj2Factory.containsKey(p)) {
+                    LOG.log(Level.WARNING, "Project {0} was already deleted", p);
                 }
-                return modifiedProjects.contains(p);
             }
+            return modifiedProjects.contains(p);
         });
     }
     
@@ -603,27 +602,25 @@ public final class NbProjectManager implements ProjectManagerImplementation {
      * @throws IOException if it cannot be saved
      * @see ProjectFactory#saveProject
      */
+    @Override
     public void saveProject(final Project p) throws IOException {
         try {
-            getMutex().writeAccess(new Mutex.ExceptionAction<Void>() {
-                @Override
-                public Void run() throws IOException {
-                    //removed projects are the ones that cannot be mapped to an existing project type anymore.
-                    if (removedProjects.contains(p)) {
-                        return null;
-                    }
-                    if (modifiedProjects.contains(p)) {
-                        ProjectFactory f = proj2Factory.get(p);
-                        if (f != null) {
-                            f.saveProject(p);
-                            LOG.log(Level.FINE, "saveProject({0})", p.getProjectDirectory());
-                        } else {
-                            LOG.log(Level.WARNING, "Project {0} was already deleted", p);
-                        }
-                        modifiedProjects.remove(p);
-                    }
+            getMutex().writeAccess((Mutex.ExceptionAction<Void>) () -> {
+                //removed projects are the ones that cannot be mapped to an existing project type anymore.
+                if (removedProjects.contains(p)) {
                     return null;
                 }
+                if (modifiedProjects.contains(p)) {
+                    ProjectFactory f = proj2Factory.get(p);
+                    if (f != null) {
+                        f.saveProject(p);
+                        LOG.log(Level.FINE, "saveProject({0})", p.getProjectDirectory());
+                    } else {
+                        LOG.log(Level.WARNING, "Project {0} was already deleted", p);
+                    }
+                    modifiedProjects.remove(p);
+                }
+                return null;
             });
         } catch (MutexException e) {
             //##91398 have a more descriptive error message, in case of RO folders.
@@ -641,25 +638,23 @@ public final class NbProjectManager implements ProjectManagerImplementation {
      * @throws IOException if any of them cannot be saved
      * @see ProjectFactory#saveProject
      */
+    @Override
     public void saveAllProjects() throws IOException {
         try {
-            getMutex().writeAccess(new Mutex.ExceptionAction<Void>() {
-                @Override
-                public Void run() throws IOException {
-                    Iterator<Project> it = modifiedProjects.iterator();
-                    while (it.hasNext()) {
-                        Project p = it.next();
-                        ProjectFactory f = proj2Factory.get(p);
-                        if (f != null) {
-                            f.saveProject(p);
-                            LOG.log(Level.FINE, "saveProject({0})", p.getProjectDirectory());
-                        } else {
-                            LOG.log(Level.WARNING, "Project {0} was already deleted", p);
-                        }
-                        it.remove();
+            getMutex().writeAccess((Mutex.ExceptionAction<Void>) () -> {
+                Iterator<Project> it = modifiedProjects.iterator();
+                while (it.hasNext()) {
+                    Project p = it.next();
+                    ProjectFactory f = proj2Factory.get(p);
+                    if (f != null) {
+                        f.saveProject(p);
+                        LOG.log(Level.FINE, "saveProject({0})", p.getProjectDirectory());
+                    } else {
+                        LOG.log(Level.WARNING, "Project {0} was already deleted", p);
                     }
-                    return null;
+                    it.remove();
                 }
+                return null;
             });
         } catch (MutexException e) {
             throw (IOException)e.getException();
@@ -675,13 +670,11 @@ public final class NbProjectManager implements ProjectManagerImplementation {
      * @param p a project
      * @return true if the project is still valid, false if it has been deleted
      */
+    @Override
     public boolean isValid(final Project p) {
-        return getMutex().readAccess(new Mutex.Action<Boolean>() {
-            @Override
-            public Boolean run() {
-                synchronized (dir2Proj) {
-                    return proj2Factory.containsKey(p);
-                }
+        return getMutex().readAccess((Mutex.Action<Boolean>) () -> {
+            synchronized (dir2Proj) {
+                return proj2Factory.containsKey(p);
             }
         });
     }
diff --git a/ide/projectapi/src/org/netbeans/api/project/ProjectManager.java b/ide/projectapi/src/org/netbeans/api/project/ProjectManager.java
index 81da1b4..998834f 100644
--- a/ide/projectapi/src/org/netbeans/api/project/ProjectManager.java
+++ b/ide/projectapi/src/org/netbeans/api/project/ProjectManager.java
@@ -32,6 +32,7 @@ import org.netbeans.modules.projectapi.SimpleFileOwnerQueryImplementation;
 import org.netbeans.spi.project.FileOwnerQueryImplementation;
 import org.netbeans.spi.project.ProjectFactory;
 import org.netbeans.spi.project.ProjectManagerImplementation;
+import org.netbeans.spi.project.ProjectManagerImplementation2;
 import org.openide.filesystems.FileObject;
 import org.openide.util.Lookup;
 import org.openide.util.Mutex;
@@ -207,7 +208,42 @@ public final class ProjectManager {
         }
         return impl.isProject(projectDirectory);
     }
-        
+
+    /**
+     * Returns all {@link  ProjectManager.Result} that can be associated with
+     * the given folder. If the actual implementation does not support
+     * {@link ProjectManagerImplementation2} this method just fallback to
+     * {@link ProjectManagerImplementation#isProject(org.openide.filesystems.FileObject)} method.
+     *
+     * @since 1.73
+     * @param projectDirectory the folder for inspection
+     * @return {@link  ProjectManager.Result} that can be associated with the
+     *         folder or an empty array if none has found.
+     * @throws IllegalArgumentException
+     */
+    public Result[] checkProject(@NonNull final FileObject projectDirectory) throws IllegalArgumentException {
+        if (projectDirectory == null) {
+            throw new IllegalArgumentException("Attempted to pass a null directory to isProject"); // NOI18N
+        }
+        if (!projectDirectory.isFolder() ) {
+            //#78215 it can happen that a no longer existing folder is queried. throw
+            // exception only for real wrong usage..
+            if (projectDirectory.isValid()) {
+                throw new IllegalArgumentException("Attempted to pass a non-directory to isProject: " + projectDirectory); // NOI18N
+            } else {
+                return null;
+            }
+        }
+        Result[] ret = new Result[0];
+        if (impl instanceof ProjectManagerImplementation2) {
+            ret = ((ProjectManagerImplementation2) impl).checkProject(projectDirectory);
+        } else {
+            Result p = impl.isProject(projectDirectory);
+            ret = p != null ? new Result[] {p} : ret;
+        }
+        return ret;
+    }
+
     /**
      * Clear the cached list of folders thought <em>not</em> to be projects.
      * This may be useful after creating project metadata in a folder, etc.
diff --git a/ide/projectapi/src/org/netbeans/spi/project/ProjectManagerImplementation2.java b/ide/projectapi/src/org/netbeans/spi/project/ProjectManagerImplementation2.java
new file mode 100644
index 0000000..663fdfc
--- /dev/null
+++ b/ide/projectapi/src/org/netbeans/spi/project/ProjectManagerImplementation2.java
@@ -0,0 +1,62 @@
+/*
+ * 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.spi.project;
+
+import java.io.IOException;
+import org.netbeans.api.annotations.common.CheckForNull;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectManager;
+import org.openide.filesystems.FileObject;
+
+/**
+ * Trivial extension of {@link ProjectManagerImplementation} to support access
+ * to multiple project types per project directories.
+ *
+ * @since 1.73
+ * @author lkishalmi
+ */
+public interface ProjectManagerImplementation2 extends ProjectManagerImplementation {
+    /**
+     * Find a NetBeans {@link Project} for the given directory and project type.
+     *
+     * @param projectDirectory the project folder
+     * @param projectType the project type to load. This can be {@code null}, that
+     *        case the first identified project shall be used and the call
+     *        is equivalent with {@link #findProject(org.openide.filesystems.FileObject)}.
+     * @see ProjectManager.Result#getProjectType()
+     * @return a project with the requested type or {@code null}.
+     * @throws IOException
+     * @throws IllegalArgumentException
+     */
+    @CheckForNull
+    Project findProject(@NonNull FileObject projectDirectory, String projectType) throws IOException, IllegalArgumentException;
+
+    /**
+     * Returns all {@link  ProjectManager.Result} that can be associated with
+     * the given folder.
+     *
+     * @since 1.73
+     * @param projectDirectory the folder for inspection
+     * @return {@link  ProjectManager.Result} that can be associated with the
+     *         folder or an empty array if none has found.
+     * @throws IllegalArgumentException
+     */
+   ProjectManager.Result[] checkProject(@NonNull FileObject projectDirectory) throws IllegalArgumentException;
+}
diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/actions/OpenProjectFolderAction.java b/ide/projectui/src/org/netbeans/modules/project/ui/actions/OpenProjectFolderAction.java
index a5c862f..75dad87 100644
--- a/ide/projectui/src/org/netbeans/modules/project/ui/actions/OpenProjectFolderAction.java
+++ b/ide/projectui/src/org/netbeans/modules/project/ui/actions/OpenProjectFolderAction.java
@@ -76,14 +76,14 @@ public final class OpenProjectFolderAction extends AbstractAction implements Con
         public ContextAction(Lookup context) {
             super(OpenProjectFolderAction_LBL_action());
             this.context = context;
-            boolean foundProject = false;
+            ProjectManager.Result[] projects = new ProjectManager.Result[0];
             for (DataFolder d : context.lookupAll(DataFolder.class)) {
-                if (ProjectManager.getDefault().isProject(d.getPrimaryFile())) {
-                    foundProject = true;
+                projects = ProjectManager.getDefault().checkProject(d.getPrimaryFile());
+                if (projects.length > 0) {
                     break;
                 }
             }
-            if (!foundProject) {
+            if (projects.length != 1) {
                 putValue(DynamicMenuContent.HIDE_WHEN_DISABLED, true);
                 setEnabled(false);
             }


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

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