Conversation
| if (dir != null && dir.isFolder()) { | ||
| try { | ||
| Project p = ProjectManager.getDefault().findProject(dir); | ||
| Project p = ProjectManager.getDefault().findProjectOrFallback(dir); |
There was a problem hiding this comment.
One obvious usage of this new "fallback project" functionality is opening of projects on startup.
The files on disk may change since the NetBeans shutdown. Sometimes it happens that original projects are no longer recognized even the original directory path exists.
In such case it is still better to open the "fallback project" rather than opening nothing.
| if (fo != null && fo.isFolder()) { | ||
| try { | ||
| Project p = ProjectManager.getDefault().findProject(fo); | ||
| Project p = ProjectManager.getDefault().findProjectOrFallback(fo); |
There was a problem hiding this comment.
When using File/Project Groups... menu:
Selecting the menu item opens a dialog
and allows one to click the New group... button to open another dialog. After choosing Folder of Projects and browsing for it:
... one can click "Create Group" button. Which scans for all the projects in the given folder and opens them in Projects view (after closing all previous ones).
No Projects Found
It may happen that there are no projects found during the scan. Leaving the Projects View empty. That's very weird.
With this one line change, the root of the folder is always opened: either as a real project or as a "fallback project".
Works great with #9602 and its nesting capabilities. The root folder is opened on the top and all other projects are nested beneath it.
3392f76 to
a9a36fd
Compare
| if (impl instanceof ProjectManagerImplementation.WithFallback implV2) { | ||
| return implV2.findProjectOrFallback(projectDirectory); | ||
| } else { | ||
| throw new IllegalArgumentException("Cannot create fallback project for " + projectDirectory); // NOI18N |
There was a problem hiding this comment.
This exception message seems off? Doesn't this throw even if the fallback isn't needed?
Personally I'd prefer a default method over the additional type here, but I know your feelings there.
There was a problem hiding this comment.
Done in c01de98 - together with introduction of a new argument type, it is binary compatible change as analyzed here.
There was a problem hiding this comment.
Thanks! Almost what I was going to suggest with the opening flags. I don't agree with all of your post on default methods (JSONList), but I do understand the need for considering compatibility.
In light of which, I am still concerned about the exception in ProjectManager, and the potential for certain places to start throwing exceptions that didn't before. I'd still allow findProjectOrFallback to return null if we're going to use it as a replacement.
There was a problem hiding this comment.
concerned about the exception in
ProjectManager
Which exception do you have in mind? The NPE that could be throw here? Well, that's never going to be thrown as with FindOptions.WITH_FALLBACK argument the implementation never returns null. Just (as a result of introducing a default method for all the FindOptions variants) the type system (nor @NonNull & co. annotations) really can express that easily.
There was a problem hiding this comment.
Which exception do you have in mind? The NPE that could be throw here? Well, that's never going to be thrown as with
FindOptions.WITH_FALLBACKargument the implementation never returnsnull.
Exactly that NPE. Sure, your implementation never returns null, but the public SPI interface default method does. Now, if we're treating that as an SPI that can only have one implementation, that's fine, but then the arguments about maintaining compatibility don't really hold.
So, we could have ProjectManager::findProjectOrFallback throw an exception like this in cases where the implementation doesn't override the default method. But it is not then a simple drop-in replacement for ProjectManager::findProject. Your changes in DirectoryGroup and OpenProjectList retain the null check that is no longer needed, but will hit a different code path if an SPI implementation causes an exception - the latter will catch it, but the former will blow up I think?
So, if we want to treat this as a simple drop-in replacement across our internal code and treat it as a public SPI, I think it's easier to allow a null return as with ProjectManager::findProject when fallback creation fails. If not, then the usages in this PR might need a rethink.
There was a problem hiding this comment.
Exactly that NPE.
OK. I'll think about our alternatives.
if we're treating that as an SPI that can only have one implementation
- I believe we do
- e.g. for all practical purposes there is just a single implementation
NbProjectManager - the assumption there was just a single implementation was the major reason why usage of
defaultmethods seemed justifiable in this case to me
|
(Whatever we do in this area, @eppleton is right we need a more proper check in the source file launcher check; that can be done separately.) I assume the end goal here is to achieve the "Open Folder"/"Add to Workspace" actions, as mentioned in the recent e-mail. I do wholeheartedly agree with adding (some) support for "workspaces" (i.e. ability to work without projects, or without specifying projects one-by-one). What I am not quite sure if whether starting with an API change is the best move. Whether mapping this to project groups matches the requirements also remains to be seen. I think I would first try to implement the actions, and from that it would lead what API changes are needed (I suspect some will be needed). #9631 implemented the Open Folder as Workspace action, but not the Add to Workspace action; and also the resulting UI is I think not ideal: when I do "Open Folder as Workspace", would I really expect to see all the nested projects in the Projects tab? (I realize this is a consequence of reusing the Project Groups directly, but a novice user is likely to be confused by this, I think.) OTOH, I do agree it would be good to open the projects under the workspace folder. I guess if I would be doing this, I would:
|
| return null; | ||
| } else if (o != null && !LoadStatus.SOME_SUCH_PROJECT.is(o)) { | ||
| } | ||
| if (o != null && !LoadStatus.SOME_SUCH_PROJECT.is(o)) { |
There was a problem hiding this comment.
Fallbacks break existing project recognition consumers
Once cached, a fallback is returned by ordinary findProject(). Consequently, FileOwnerQuery assigns previously unowned Java files to it, disabling single-file Run/Debug and classpath support, which require a null owner. It also makes ProjectGenerator.createProject() reject the folder as already containing a project. Preserve these existing consumers’ behavior before exposing fallbacks through the ordinary cache.
Cache clearing returns before replacing fallbacks under write access — NbProjectManager.java (line 517)
postReadRequest() defers execution until the current write lock is released. Creating project metadata, calling clearNonProjectCache(), then calling findProject() within that lock therefore still returns the fallback. Replacement needs to take effect before the subsequent lookup.
Replaced fallbacks remain valid and can retain file ownership — NbProjectManager.java (line 525)
Removal updates only dir2Proj. The fallback remains in proj2Factory, so isValid(fallback) stays true. When replacement is triggered by a factory lookup change, the file-owner cache also remains stale: findProject() returns the real project while FileOwnerQuery returns the fallback. Retire the old instance and invalidate ownership caches together.
There was a problem hiding this comment.
Thanks Martine. Some of these are similar to Toni's findings. Some are new. All fixed in e348182
|
|
||
| public FallbackProject(FileObject dir) { | ||
| this.dir = dir; | ||
| this.genericGroup = GenericSources.group( |
There was a problem hiding this comment.
A nested fallback’s source group can exclude its own root
Query ownership of a child folder inside an existing project, then create a fallback for that child. Ownership remains cached against the parent, so GenericSources.group(...).contains(root) returns false, violating the SourceGroup contract. Fallback registration must reconcile cached ownership with its source-group implementation.
There was a problem hiding this comment.
This one was tricky! A test based on your example and a fix is in as e348182. Thank you, Martine!
|
Thank you Jan.
|
Co-authored-by: Martin Balín <Martin.Balin@oracle.com>
Ok, if the end goal is to introduce the API (and the two uses of the API), and nothing more, OK. Not sure if the API pulls its own weight in that case, but I'll leave that to you. Given this API (should) pull(s) its own weight without referring to the "workspace UI" stuff, I assume that under the "workspace UI" approach, we will feel free to use or not use this API, as we see fit. I.e. we won't feel under pressure to use this API just because it exists. If it will be useful - great. If not, we simply won't use it. |
…isable single Java file actions.
|
| if (MultiSourceRootProvider.DISABLE_MULTI_SOURCE_ROOT) { | ||
| return false; | ||
| } | ||
| Project owner = FileOwnerQuery.getOwner(file); |
There was a problem hiding this comment.
This enables single-file support under a fallback, but MultiSourceRootProvider (around line 510, RootPathResourceImplementation.includes) still filters the source path with FileOwnerQuery.getOwner(fo) == null. So a loose file under a fallback now gets a source path, but that path excludes the file itself and its siblings.
I checked it in MultiSourceRootProviderTest on this branch: a folder with pack/A.java and pack/B.java, opened via findProjectOrFallback. findClassPath(A, SOURCE) returns a path rooted at the folder, but contains(A) and contains(B) are both false. Without the fallback, both are true.
Run/Debug is enabled now, but A can't use B and the files are probably not indexed. Maybe includes() should apply the same rule as here (no owner, or an owner without Java source groups), ideally through a shared helper so the two can't drift apart again.
There was a problem hiding this comment.
- Shared utility introduced in c22ea60
- but I don't understand the test case
- I tried
pack.Areferencingpack.B
- however that doesn't work in any previous version of NetBeans either
- maybe the whole
java/java.file.launchermodule is a bit fragile?
There was a problem hiding this comment.
- These options aren't even visible in non-Java projects
- need 86be29e to make them appear
- then the support work on JDK 25 in empty directories as well as non-Java projects
Sorry, having given this some more thought, I agree with @lahodaj here. There is more to discuss. I think the requirement for this new API is not currently proven, and handling this probably can, and should, be via an ordinary project factory. As we're effectively in our ramp down phase to the next release, the end user benefits for this are currently minimal, there may still be some fallout from the implementation, and the API may lock us in to something that is the wrong approach, I don't think we should fast track this in. Let's hold fire for the next cycle and consider along with the wider impact usage that goes with it. So, that's a -1 from me to merging this prior to branching NB 32. Unless one of our active maintainers wants to make a strong argument in the other direction. I would really like to see the ability to opt in to opening any folder as a project. Don't get me wrong, I'm not negative on solving the problem. It should be possible for the user to open any folder as a project. There is potential there to address a whole bunch of user requests, including but not just making the folder group function better. eg. I'd like to see the inverse of our current folder action
In terms of marker, I would suggest the presence of the default |

ProjectManager.findProjectOrFallbackto allow viewing every folder as a generic fallback project