From 737a41f0870595e9c5456cccd5348c3c241ddaa4 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 21 Sep 2026 15:26:26 +0200 Subject: [PATCH 1/9] More prominent Open Folder (as a group) action --- .../modules/project/ui/groups/AdHocGroup.java | 1 - .../ui/groups/DirectoryGroupEditPanel.java | 17 ++--- .../ui/groups/OpenFolderAsGroupAction.java | 69 +++++++++++++++++++ 3 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 ide/projectui/src/org/netbeans/modules/project/ui/groups/OpenFolderAsGroupAction.java diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/AdHocGroup.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/AdHocGroup.java index a58a5d1dfcac..f940fa4fe3c5 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/AdHocGroup.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/AdHocGroup.java @@ -20,7 +20,6 @@ package org.netbeans.modules.project.ui.groups; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroupEditPanel.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroupEditPanel.java index 039d04aa4242..4923449e7b94 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroupEditPanel.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroupEditPanel.java @@ -19,6 +19,7 @@ package org.netbeans.modules.project.ui.groups; +import java.awt.HeadlessException; import java.io.File; import java.util.prefs.Preferences; import javax.swing.JFileChooser; @@ -160,19 +161,9 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { }// //GEN-END:initComponents private void directoryButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_directoryButtonActionPerformed - JFileChooser chooser = new JFileChooser(); - chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - chooser.setMultiSelectionEnabled(false); - File start = ProjectChooser.getProjectsFolder(); - if (folderField.getText() != null && folderField.getText().trim().length() > 0) { - start = new File(folderField.getText().trim()); - } - chooser.setCurrentDirectory(start); - if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { - File f = chooser.getSelectedFile(); - if (f != null) { - folderField.setText(f.getAbsolutePath()); - } + File f = OpenFolderAsGroupAction.showWorkspaceFolderChooser(this, folderField.getText()); + if (f != null) { + folderField.setText(f.getAbsolutePath()); } }//GEN-LAST:event_directoryButtonActionPerformed diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/OpenFolderAsGroupAction.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/OpenFolderAsGroupAction.java new file mode 100644 index 000000000000..c8d94cbfc9df --- /dev/null +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/OpenFolderAsGroupAction.java @@ -0,0 +1,69 @@ +/* + * 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.project.ui.groups; + +import java.awt.HeadlessException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import javax.swing.JFileChooser; +import org.netbeans.spi.project.ui.support.ProjectChooser; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionRegistration; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.NbBundle.Messages; + +@ActionID( + category = "Project", + id = "org.netbeans.modules.project.ui.groups.OpenFolderAsGroupAction" +) +@ActionRegistration( + displayName = "#CTL_OpenFolderAsGroupAction", + lazy = true, asynchronous = true +) +@ActionReference(path = "Menu/File", position = 1200, separatorAfter = 1250) +@Messages("CTL_OpenFolderAsGroupAction=Open Fol&der as Workspace...") +public final class OpenFolderAsGroupAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent ev) { + File dir = showWorkspaceFolderChooser(null, null); + FileObject folder = FileUtil.toFileObject(dir); + if (folder != null) { + DirectoryGroup group = DirectoryGroup.create(folder.getNameExt(), folder); + Group.setActiveGroup(group, true); + } + } + + static File showWorkspaceFolderChooser(java.awt.Component parent, final String hintPath) throws HeadlessException { + JFileChooser chooser = new JFileChooser(); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setMultiSelectionEnabled(false); + File start = ProjectChooser.getProjectsFolder(); + if (hintPath != null && hintPath.trim().length() > 0) { + start = new File(hintPath.trim()); + } + chooser.setCurrentDirectory(start); + final int result = chooser.showOpenDialog(parent); + File f = result == JFileChooser.APPROVE_OPTION ? chooser.getSelectedFile() : null; + return f; + } + +} From 93db08e3a7f19e8e0bcb8a5c01843f72920e2f7c Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 22 Sep 2026 10:54:31 +0200 Subject: [PATCH 2/9] Rebranding Project Groups to Workspace --- ide/projectui/apichanges.xml | 2 +- ide/projectui/arch.xml | 4 +- .../modules/project/ui/Bundle.properties | 2 +- .../project/ui/OpenProjectListSettings.java | 2 +- .../project/ui/groups/Bundle.properties | 44 +++++++++---------- .../modules/project/ui/groups/Group.java | 4 +- .../ui/groups/GroupOptionProcessor.java | 6 +-- .../modules/project/ui/groups/GroupsMenu.java | 14 +++--- .../project/ui/groups/NewGroupPanel.java | 2 +- 9 files changed, 40 insertions(+), 40 deletions(-) diff --git a/ide/projectui/apichanges.xml b/ide/projectui/apichanges.xml index fdacf3f9e48c..aec1c9e24fd6 100644 --- a/ide/projectui/apichanges.xml +++ b/ide/projectui/apichanges.xml @@ -70,7 +70,7 @@

@ProjectCustomizer.CompositeCategoryProvider.Registration annotation now accepts "Groups" projectType parameter value which - adds a panel to the Project Group customizer. Implementations can expect an instance of org.netbeans.api.project.ui.ProjectGroup in the lookup + adds a panel to the workspace customizer. Implementations can expect an instance of org.netbeans.api.project.ui.ProjectGroup in the lookup passed to the provider. NO central storing procedure in place, each panel is expected to set it's own values in Category.setStoreListener

diff --git a/ide/projectui/arch.xml b/ide/projectui/arch.xml index 9215ba685eee..46a9a8c7c67b 100644 --- a/ide/projectui/arch.xml +++ b/ide/projectui/arch.xml @@ -623,7 +623,7 @@
  • - Can use --open-group NAME to open a project group at startup, or --close-group. + Can use --open-group NAME to open a workspace at startup, or --close-group.

  • @@ -1155,7 +1155,7 @@

    - Project groups. See Group. + Workspaces. See Group.

    diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties b/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties index a2b923f28664..84822327978e 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties +++ b/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties @@ -50,7 +50,7 @@ LBL_ProjectMode=Projects #ExportOptions Export_Category_Display_Name=Projects Export_Display_Name=Basic -Export_Groups_Display_Name=Project Groups +Export_Groups_Display_Name=Workspaces #Nodes LBL_OpenProjectsNode_Name=Projects diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java index fa602d15f00f..87a0189679ee 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java @@ -231,7 +231,7 @@ public void setOpenProjectsIcons( List list ) { } public boolean isOpenSubprojects() { - return getPreferences().getBoolean( OPEN_SUBPROJECTS, false); + return getPreferences().getBoolean( OPEN_SUBPROJECTS, true); } public void setOpenSubprojects( boolean openSubprojects ) { diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties index 9d426f8d6511..9b54d855d7cd 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties @@ -36,35 +36,35 @@ SubprojectsGroupEditPanel.masterProjectLabel.text=&Master Project: DirectoryGroupEditPanel.nameLabel.text=&Name\: DirectoryGroupEditPanel.folderLabel.text=&Folder\: -SubprojectsGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the project group +SubprojectsGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the workspace SubprojectsGroupEditPanel.masterProjectLabel.AccessibleContext.accessibleDescription=Group master project name SubprojectsGroupEditPanel.nameField.AccessibleContext.accessibleName=Name -SubprojectsGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Project group name +SubprojectsGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Workspace name SubprojectsGroupEditPanel.masterProjectField.AccessibleContext.accessibleName= SubprojectsGroupEditPanel.masterProjectField.AccessibleContext.accessibleDescription= -SubprojectsGroupEditPanel.AccessibleContext.accessibleName=Project group properties -SubprojectsGroupEditPanel.AccessibleContext.accessibleDescription=Project group properties -DirectoryGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the project group -DirectoryGroupEditPanel.folderLabel.AccessibleContext.accessibleDescription=Project group folder +SubprojectsGroupEditPanel.AccessibleContext.accessibleName=Workspace properties +SubprojectsGroupEditPanel.AccessibleContext.accessibleDescription=Workspace properties +DirectoryGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the workspace +DirectoryGroupEditPanel.folderLabel.AccessibleContext.accessibleDescription=Workspace folder DirectoryGroupEditPanel.nameField.AccessibleContext.accessibleName=Name -DirectoryGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Project group name +DirectoryGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Workspace name DirectoryGroupEditPanel.folderField.AccessibleContext.accessibleName= DirectoryGroupEditPanel.folderField.AccessibleContext.accessibleDescription= -DirectoryGroupEditPanel.AccessibleContext.accessibleName=Project group properties -DirectoryGroupEditPanel.AccessibleContext.accessibleDescription=Project group properties -AdHocGroupEditPanel.AccessibleContext.accessibleName=Project group properties -AdHocGroupEditPanel.AccessibleContext.accessibleDescription=Project group properties -AdHocGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Project group name +DirectoryGroupEditPanel.AccessibleContext.accessibleName=Workspace properties +DirectoryGroupEditPanel.AccessibleContext.accessibleDescription=Workspace properties +AdHocGroupEditPanel.AccessibleContext.accessibleName=Workspace properties +AdHocGroupEditPanel.AccessibleContext.accessibleDescription=Workspace properties +AdHocGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Workspace name AdHocGroupEditPanel.nameField.AccessibleContext.accessibleName=Name -AdHocGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Project group name -AdHocGroupEditPanel.autoSynchCheckbox.AccessibleContext.accessibleDescription=Automatically save project group list +AdHocGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Workspace name +AdHocGroupEditPanel.autoSynchCheckbox.AccessibleContext.accessibleDescription=Automatically save workspace list AdHocGroupEditPanel.synchButton.AccessibleContext.accessibleDescription=Save current project list now -NewGroupPanel.AccessibleContext.accessibleName=New project group -NewGroupPanel.AccessibleContext.accessibleDescription=New project group -NewGroupPanel.nameLabel.AccessibleContext.accessibleDescription=Group name +NewGroupPanel.AccessibleContext.accessibleName=New workspace +NewGroupPanel.AccessibleContext.accessibleDescription=New workspace +NewGroupPanel.nameLabel.AccessibleContext.accessibleDescription=Workspace name NewGroupPanel.nameField.AccessibleContext.accessibleName=Name -NewGroupPanel.nameField.AccessibleContext.accessibleDescription=Group name -NewGroupPanel.adHocKindRadio.AccessibleContext.accessibleDescription=Free group +NewGroupPanel.nameField.AccessibleContext.accessibleDescription=Workspace name +NewGroupPanel.adHocKindRadio.AccessibleContext.accessibleDescription=Free workspace NewGroupPanel.adHocKindLabel.AccessibleContext.accessibleDescription=Contains any projects you like. Can be updated manually or automatically. NewGroupPanel.useOpenCheckbox.AccessibleContext.accessibleDescription=Use Currently Open Projects NewGroupPanel.autoSynchCheckbox.AccessibleContext.accessibleDescription=Automatically Save Project List @@ -80,11 +80,11 @@ NewGroupPanel.directoryLabel.AccessibleContext.accessibleDescription=Folder NewGroupPanel.directoryField.AccessibleContext.accessibleName=Folder NewGroupPanel.directoryField.AccessibleContext.accessibleDescription=Folder of projects NewGroupPanel.directoryButton.AccessibleContext.accessibleDescription=Browse -RemoveGroupPanel.selectionLabel.text=Select Group(s) to remove -RemoveGroupPanel.selectionLabel.text=Select Group(s) to remove +RemoveGroupPanel.selectionLabel.text=Select Workspace(s) to remove +RemoveGroupPanel.selectionLabel.text=Select Workspace(s) to remove ManageGroupsPanel.propertiesButton.text=&Properties ManageGroupsPanel.removeButton.text=&Remove -ManageGroupsPanel.selectionLabel.text=Select Group(s) +ManageGroupsPanel.selectionLabel.text=Select Workspace(s) ManageGroupsPanel.removeAllButton.text=Remove &All DirectoryGroupEditPanel.masterProjectButton.text=&Browse... DirectoryGroupEditPanel.directoryButton.text=Bro&wse... diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java index 42d7b6d10d7b..144acfd3b2ce 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java @@ -74,7 +74,7 @@ import org.openide.windows.WindowSystemListener; /** - * Represents a project group. + * Represents a project group aka workspace. * Static methods represent set of groups and group selection. * @author Jesse Glick */ @@ -149,7 +149,7 @@ public static Group getActiveGroup() { * Set the currently active group (or null). */ @Messages({ - "# {0} - internal group info", "Group.UI.setActiveGroup=Selecting project group: {0}", + "# {0} - internal group info", "Group.UI.setActiveGroup=Selecting workspace: {0}", "#NOI18N", "Group.UI.setActiveGroup_ICON_BASE=org/netbeans/modules/project/ui/resources/openProject.png" }) public static void setActiveGroup(Group nue, boolean isNewGroup) { diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java index 5ee2dfe97250..f7fbe2e4ad8b 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java @@ -36,21 +36,21 @@ public class GroupOptionProcessor implements ArgsProcessor { ) @Messages({ "GroupOptionProcessor.open.name=--open-group NAME", - "GroupOptionProcessor.open.desc=open a project group by shortened or full name (or unique substring)" + "GroupOptionProcessor.open.desc=open a workspace by shortened or full name (or unique substring)" }) public String openOption; @Arg(longName="close-group") @Description( shortDescription="#GroupOptionProcessor.close.desc" ) - @Messages("GroupOptionProcessor.close.desc=close any open project group") + @Messages("GroupOptionProcessor.close.desc=close any open workspace") public boolean closeOption; @Arg(longName="list-groups") @Description( shortDescription="#GroupOptionProcessor.list.desc" ) - @Messages("GroupOptionProcessor.list.desc=list available project groups") + @Messages("GroupOptionProcessor.list.desc=list available workspaces") public boolean listOption; @Messages({ diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java index 4948298132d4..7b4ab517eed4 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java @@ -53,7 +53,7 @@ @ActionReference(path = "Menu/File", position = 1100), @ActionReference(path = ProjectsRootNode.ACTIONS_FOLDER, position = 600, separatorAfter = 700) }) -@Messages("GroupsMenu.label=Project Gro&ups...") +@Messages("GroupsMenu.label=Workspace &Manager...") public class GroupsMenu extends AbstractAction { private static final RequestProcessor RP = new RequestProcessor(GroupsMenu.class.getName()); @@ -72,8 +72,8 @@ public void actionPerformed(ActionEvent e) { * Create (and open) a new group. */ @Messages({ - "GroupsMenu.new_title=Create New Group", - "GroupsMenu.new_create=Create Group", + "GroupsMenu.new_title=Create New Workspace", + "GroupsMenu.new_create=Create Workspace", "GroupsMenu.new_close=Close" }) private static void newGroup() { @@ -116,9 +116,9 @@ private static void newGroup() { * Manage groups. */ @Messages({ - "GroupsMenu.manage_title=Manage Groups", - "GroupsMenu.manage_select_group=&Select Group", - "GroupsMenu.manage_new_group=&New Group...", + "GroupsMenu.manage_title=Manage Workspaces", + "GroupsMenu.manage_select_group=&Select Workspace", + "GroupsMenu.manage_new_group=&New Workspace...", "GroupsMenu.manage_remove=&Remove", "GroupsMenu.manage_close=Close", "GroupsMenu.manage_properties=&Properties", @@ -155,7 +155,7 @@ private static void manageGroups() { /** * Open a properties dialog for the group, according to its type. */ - @Messages("GroupsMenu.properties_title=Project Group Properties") + @Messages("GroupsMenu.properties_title=Workspace Properties") static void openProperties(Group g) { Lookup context = Lookups.fixed(new Object[] { g, BaseUtilities.ACCESSOR.createGroup(g.getName(), g.prefs()) }); Dialog dialog = ProjectCustomizer.createCustomizerDialog("Projects/Groups/Customizer", //NOI18N diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java index 53fff6df79a4..1f803ba94979 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java @@ -43,7 +43,7 @@ import org.openide.util.NbBundle.Messages; /** - * Panel permitting user to create a new project group. + * Panel permitting user to create a new project group aka workspace. * Applicable in advanced mode. * @author Jesse Glick */ From 884b1765c892fdd83da5d6b75a3edacbc2a4255b Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 23 Sep 2026 18:09:20 +0200 Subject: [PATCH 3/9] findProjectOrFallback to make sure each DirectoryGroup contains its root --- .../modules/projectapi/nb/GenericPrj.java | 45 +++++++++++++++++++ .../projectapi/nb/NbProjectManager.java | 36 +++++++++++---- .../netbeans/api/project/ProjectManager.java | 16 +++++++ .../project/ProjectManagerImplementation.java | 6 +++ .../api/project/ProjectManagerTest.java | 23 ++++++++++ .../project/ui/groups/DirectoryGroup.java | 2 +- 6 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/GenericPrj.java diff --git a/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/GenericPrj.java b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/GenericPrj.java new file mode 100644 index 000000000000..f88a814ad5fc --- /dev/null +++ b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/GenericPrj.java @@ -0,0 +1,45 @@ +/* + * 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.projectapi.nb; + +import org.netbeans.api.project.Project; +import org.openide.filesystems.FileObject; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; + +final class GenericPrj implements Project { + private final FileObject dir; + private final Lookup lkp; + + public GenericPrj(FileObject dir) { + this.dir = dir; + this.lkp = Lookups.fixed(this); + } + + @Override + public FileObject getProjectDirectory() { + return dir; + } + + @Override + public Lookup getLookup() { + return lkp; + } + +} 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 372cd5f82ee5..f27e1dcdc6d1 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 @@ -65,7 +65,7 @@ * @author Jesse Glick */ @ServiceProvider(service = ProjectManagerImplementation.class, position = 1000) -public final class NbProjectManager implements ProjectManagerImplementation { +public final class NbProjectManager implements ProjectManagerImplementation.WithFallback { // 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 @@ -90,7 +90,7 @@ public void resultChanged(LookupEvent e) { } }); } - + private static enum LoadStatus { /** * Marker for a directory which is known to not be a project. @@ -212,6 +212,17 @@ public Mutex getMutex( */ @Override public Project findProject(final FileObject projectDirectory) throws IOException, IllegalArgumentException { + return findProjectImpl(projectDirectory, false); + } + + @Override + public Project findProjectOrFallback(FileObject projectDirectory) throws IOException, IllegalArgumentException { + var found = findProjectImpl(projectDirectory, true); + assert found != null; + return found; + } + + private Project findProjectImpl(FileObject projectDirectory, boolean fallback) throws IOException, IllegalArgumentException { Parameters.notNull("projectDirectory", projectDirectory); //NOI18N try { return getMutex().readAccess(new Mutex.ExceptionAction() { @@ -250,11 +261,17 @@ public Project run() throws IOException { assert !LoadStatus.LOADING_PROJECT.is(o); wasSomeSuchProject = LoadStatus.SOME_SUCH_PROJECT.is(o); if (LoadStatus.NO_SUCH_PROJECT.is(o)) { - if (LOG.isLoggable(Level.FINE)) { - LOG.log(Level.FINE, "findProject({0}) in {1}: NO_SUCH_PROJECT", new Object[] {projectDirectory, Thread.currentThread().getName()}); + if (fallback) { + // treat a not checked project yet + o = null; + } else { + if (LOG.isLoggable(Level.FINE)) { + LOG.log(Level.FINE, "findProject({0}) in {1}: NO_SUCH_PROJECT", new Object[]{projectDirectory, Thread.currentThread().getName()}); + } + return null; } - return null; - } else if (o != null && !LoadStatus.SOME_SUCH_PROJECT.is(o)) { + } + if (o != null && !LoadStatus.SOME_SUCH_PROJECT.is(o)) { Project p = o.first().get(); if (p != null) { if (LOG.isLoggable(Level.FINE)) { @@ -285,7 +302,7 @@ public Project run() throws IOException { } boolean resetLP = false; try { - Project p = createProject(projectDirectory); + Project p = createProject(projectDirectory, fallback); //Thread.dumpStack(); synchronized (dir2Proj) { dir2Proj.notifyAll(); @@ -366,13 +383,16 @@ public Project run() throws IOException { * @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 { + private Project createProject(FileObject dir, boolean fallback) 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 && fallback) { + p = new GenericPrj(dir); + } if (p != null) { if (TIMERS.isLoggable(Level.FINE)) { LogRecord rec = new LogRecord(Level.FINE, "Project"); // NOI18N diff --git a/ide/projectapi/src/org/netbeans/api/project/ProjectManager.java b/ide/projectapi/src/org/netbeans/api/project/ProjectManager.java index 81da1b4c7178..9b8a77e2a34b 100644 --- a/ide/projectapi/src/org/netbeans/api/project/ProjectManager.java +++ b/ide/projectapi/src/org/netbeans/api/project/ProjectManager.java @@ -141,6 +141,22 @@ public Project findProject(@NonNull final FileObject projectDirectory) throws IO } return impl.findProject(projectDirectory); } + + /** @since 1.111 */ + @NonNull + public Project findProjectOrFallback(@NonNull FileObject projectDirectory) throws IOException, IllegalArgumentException { + if (projectDirectory == null) { + throw new IllegalArgumentException("Attempted to pass a null directory to findProject"); // NOI18N + } + if (!projectDirectory.isFolder()) { + throw new IllegalArgumentException("Attempted to pass a non-directory to findProject: " + projectDirectory); // NOI18N + } + if (impl instanceof ProjectManagerImplementation.WithFallback implV2) { + return implV2.findProjectOrFallback(projectDirectory); + } else { + throw new IllegalArgumentException("Cannot create fallback project for " + projectDirectory); // NOI18N + } + } /** diff --git a/ide/projectapi/src/org/netbeans/spi/project/ProjectManagerImplementation.java b/ide/projectapi/src/org/netbeans/spi/project/ProjectManagerImplementation.java index c95437bd9204..ab33b296f18b 100644 --- a/ide/projectapi/src/org/netbeans/spi/project/ProjectManagerImplementation.java +++ b/ide/projectapi/src/org/netbeans/spi/project/ProjectManagerImplementation.java @@ -182,6 +182,12 @@ Mutex getMutex( */ void saveAllProjects() throws IOException; + /** @since 1.111 */ + interface WithFallback extends ProjectManagerImplementation { + @NonNull + Project findProjectOrFallback(@NonNull FileObject projectDirectory) throws IOException, IllegalArgumentException; + } + /** * Callback to notify the {@link ProjectManager} about changes. */ diff --git a/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java b/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java index eec0751279b4..dbfe16cc51a8 100644 --- a/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java +++ b/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java @@ -65,6 +65,7 @@ public ProjectManagerTest(String name) { private FileObject goodproject2; private FileObject badproject; private FileObject mysteryproject; + private FileObject justADir; private ProjectManager pm; protected @Override Level logLevel() { @@ -82,6 +83,7 @@ protected void setUp() throws Exception { badproject = scratch.createFolder("bad"); badproject.createFolder("testproject").createData("broken"); mysteryproject = scratch.createFolder("mystery"); + justADir = scratch.createFolder("justADir"); MockLookup.setInstances(TestUtil.testProjectFactory()); pm = ProjectManager.getDefault(); NbProjectManagerAccessor.reset(); @@ -206,6 +208,27 @@ public void testIsProject() throws Exception { assertFalse("Should not have been able to load mysteryproject", pm.isProject(mysteryproject)); } + public void testIsFallbackProject() throws Exception { + var nothing = pm.findProject(justADir); + assertNull("No project is found for just a dir", nothing); + var generic = pm.findProjectOrFallback(justADir); + assertNotNull("But one can ask for a fallback project", generic); + + var then = pm.findProject(justADir); + assertSame("since then findProject works for just a dir", generic, then); + + var ref = new WeakReference<>(generic); + generic = null; + then = null; + // give the references time to disappear + Thread.sleep(TimedWeakReference.TIMEOUT); + + assertGC("The fallback project gets GCed when no longer used", ref); + + var nothingAgain = pm.findProject(justADir); + assertNull("Since then, findProject again returns null", nothingAgain); + } + public void testIsProject2() throws Exception { ProjectManager.Result r = pm.isProject2(goodproject); assertNotNull("Should have recognized goodproject", r); diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroup.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroup.java index adacb9bb6248..c70d46568f35 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroup.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroup.java @@ -77,7 +77,7 @@ protected void findProjects(Set projects, ProgressHandle h, int start, } if (fo != null && fo.isFolder()) { try { - Project p = ProjectManager.getDefault().findProject(fo); + Project p = ProjectManager.getDefault().findProjectOrFallback(fo); if (p != null) { projects.add(p); if (h != null) { From 6bcb9b3da79cf4f99e840e640d34686344ab52af Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 25 Sep 2026 11:37:13 +0200 Subject: [PATCH 4/9] Avoid changing the naming --- ide/projectui/apichanges.xml | 2 +- ide/projectui/arch.xml | 4 +- .../modules/project/ui/Bundle.properties | 2 +- .../project/ui/OpenProjectListSettings.java | 2 +- .../project/ui/groups/Bundle.properties | 44 +++++++++---------- .../modules/project/ui/groups/Group.java | 4 +- .../ui/groups/GroupOptionProcessor.java | 6 +-- .../modules/project/ui/groups/GroupsMenu.java | 14 +++--- .../project/ui/groups/NewGroupPanel.java | 2 +- 9 files changed, 40 insertions(+), 40 deletions(-) diff --git a/ide/projectui/apichanges.xml b/ide/projectui/apichanges.xml index aec1c9e24fd6..fdacf3f9e48c 100644 --- a/ide/projectui/apichanges.xml +++ b/ide/projectui/apichanges.xml @@ -70,7 +70,7 @@

    @ProjectCustomizer.CompositeCategoryProvider.Registration annotation now accepts "Groups" projectType parameter value which - adds a panel to the workspace customizer. Implementations can expect an instance of org.netbeans.api.project.ui.ProjectGroup in the lookup + adds a panel to the Project Group customizer. Implementations can expect an instance of org.netbeans.api.project.ui.ProjectGroup in the lookup passed to the provider. NO central storing procedure in place, each panel is expected to set it's own values in Category.setStoreListener

    diff --git a/ide/projectui/arch.xml b/ide/projectui/arch.xml index 46a9a8c7c67b..9215ba685eee 100644 --- a/ide/projectui/arch.xml +++ b/ide/projectui/arch.xml @@ -623,7 +623,7 @@
  • - Can use --open-group NAME to open a workspace at startup, or --close-group. + Can use --open-group NAME to open a project group at startup, or --close-group.

  • @@ -1155,7 +1155,7 @@

    - Workspaces. See Group. + Project groups. See Group.

    diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties b/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties index 84822327978e..a2b923f28664 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties +++ b/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties @@ -50,7 +50,7 @@ LBL_ProjectMode=Projects #ExportOptions Export_Category_Display_Name=Projects Export_Display_Name=Basic -Export_Groups_Display_Name=Workspaces +Export_Groups_Display_Name=Project Groups #Nodes LBL_OpenProjectsNode_Name=Projects diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java index 87a0189679ee..fa602d15f00f 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java @@ -231,7 +231,7 @@ public void setOpenProjectsIcons( List list ) { } public boolean isOpenSubprojects() { - return getPreferences().getBoolean( OPEN_SUBPROJECTS, true); + return getPreferences().getBoolean( OPEN_SUBPROJECTS, false); } public void setOpenSubprojects( boolean openSubprojects ) { diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties index 9b54d855d7cd..9d426f8d6511 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties @@ -36,35 +36,35 @@ SubprojectsGroupEditPanel.masterProjectLabel.text=&Master Project: DirectoryGroupEditPanel.nameLabel.text=&Name\: DirectoryGroupEditPanel.folderLabel.text=&Folder\: -SubprojectsGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the workspace +SubprojectsGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the project group SubprojectsGroupEditPanel.masterProjectLabel.AccessibleContext.accessibleDescription=Group master project name SubprojectsGroupEditPanel.nameField.AccessibleContext.accessibleName=Name -SubprojectsGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Workspace name +SubprojectsGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Project group name SubprojectsGroupEditPanel.masterProjectField.AccessibleContext.accessibleName= SubprojectsGroupEditPanel.masterProjectField.AccessibleContext.accessibleDescription= -SubprojectsGroupEditPanel.AccessibleContext.accessibleName=Workspace properties -SubprojectsGroupEditPanel.AccessibleContext.accessibleDescription=Workspace properties -DirectoryGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the workspace -DirectoryGroupEditPanel.folderLabel.AccessibleContext.accessibleDescription=Workspace folder +SubprojectsGroupEditPanel.AccessibleContext.accessibleName=Project group properties +SubprojectsGroupEditPanel.AccessibleContext.accessibleDescription=Project group properties +DirectoryGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the project group +DirectoryGroupEditPanel.folderLabel.AccessibleContext.accessibleDescription=Project group folder DirectoryGroupEditPanel.nameField.AccessibleContext.accessibleName=Name -DirectoryGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Workspace name +DirectoryGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Project group name DirectoryGroupEditPanel.folderField.AccessibleContext.accessibleName= DirectoryGroupEditPanel.folderField.AccessibleContext.accessibleDescription= -DirectoryGroupEditPanel.AccessibleContext.accessibleName=Workspace properties -DirectoryGroupEditPanel.AccessibleContext.accessibleDescription=Workspace properties -AdHocGroupEditPanel.AccessibleContext.accessibleName=Workspace properties -AdHocGroupEditPanel.AccessibleContext.accessibleDescription=Workspace properties -AdHocGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Workspace name +DirectoryGroupEditPanel.AccessibleContext.accessibleName=Project group properties +DirectoryGroupEditPanel.AccessibleContext.accessibleDescription=Project group properties +AdHocGroupEditPanel.AccessibleContext.accessibleName=Project group properties +AdHocGroupEditPanel.AccessibleContext.accessibleDescription=Project group properties +AdHocGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Project group name AdHocGroupEditPanel.nameField.AccessibleContext.accessibleName=Name -AdHocGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Workspace name -AdHocGroupEditPanel.autoSynchCheckbox.AccessibleContext.accessibleDescription=Automatically save workspace list +AdHocGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Project group name +AdHocGroupEditPanel.autoSynchCheckbox.AccessibleContext.accessibleDescription=Automatically save project group list AdHocGroupEditPanel.synchButton.AccessibleContext.accessibleDescription=Save current project list now -NewGroupPanel.AccessibleContext.accessibleName=New workspace -NewGroupPanel.AccessibleContext.accessibleDescription=New workspace -NewGroupPanel.nameLabel.AccessibleContext.accessibleDescription=Workspace name +NewGroupPanel.AccessibleContext.accessibleName=New project group +NewGroupPanel.AccessibleContext.accessibleDescription=New project group +NewGroupPanel.nameLabel.AccessibleContext.accessibleDescription=Group name NewGroupPanel.nameField.AccessibleContext.accessibleName=Name -NewGroupPanel.nameField.AccessibleContext.accessibleDescription=Workspace name -NewGroupPanel.adHocKindRadio.AccessibleContext.accessibleDescription=Free workspace +NewGroupPanel.nameField.AccessibleContext.accessibleDescription=Group name +NewGroupPanel.adHocKindRadio.AccessibleContext.accessibleDescription=Free group NewGroupPanel.adHocKindLabel.AccessibleContext.accessibleDescription=Contains any projects you like. Can be updated manually or automatically. NewGroupPanel.useOpenCheckbox.AccessibleContext.accessibleDescription=Use Currently Open Projects NewGroupPanel.autoSynchCheckbox.AccessibleContext.accessibleDescription=Automatically Save Project List @@ -80,11 +80,11 @@ NewGroupPanel.directoryLabel.AccessibleContext.accessibleDescription=Folder NewGroupPanel.directoryField.AccessibleContext.accessibleName=Folder NewGroupPanel.directoryField.AccessibleContext.accessibleDescription=Folder of projects NewGroupPanel.directoryButton.AccessibleContext.accessibleDescription=Browse -RemoveGroupPanel.selectionLabel.text=Select Workspace(s) to remove -RemoveGroupPanel.selectionLabel.text=Select Workspace(s) to remove +RemoveGroupPanel.selectionLabel.text=Select Group(s) to remove +RemoveGroupPanel.selectionLabel.text=Select Group(s) to remove ManageGroupsPanel.propertiesButton.text=&Properties ManageGroupsPanel.removeButton.text=&Remove -ManageGroupsPanel.selectionLabel.text=Select Workspace(s) +ManageGroupsPanel.selectionLabel.text=Select Group(s) ManageGroupsPanel.removeAllButton.text=Remove &All DirectoryGroupEditPanel.masterProjectButton.text=&Browse... DirectoryGroupEditPanel.directoryButton.text=Bro&wse... diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java index 144acfd3b2ce..42d7b6d10d7b 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java @@ -74,7 +74,7 @@ import org.openide.windows.WindowSystemListener; /** - * Represents a project group aka workspace. + * Represents a project group. * Static methods represent set of groups and group selection. * @author Jesse Glick */ @@ -149,7 +149,7 @@ public static Group getActiveGroup() { * Set the currently active group (or null). */ @Messages({ - "# {0} - internal group info", "Group.UI.setActiveGroup=Selecting workspace: {0}", + "# {0} - internal group info", "Group.UI.setActiveGroup=Selecting project group: {0}", "#NOI18N", "Group.UI.setActiveGroup_ICON_BASE=org/netbeans/modules/project/ui/resources/openProject.png" }) public static void setActiveGroup(Group nue, boolean isNewGroup) { diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java index f7fbe2e4ad8b..5ee2dfe97250 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java @@ -36,21 +36,21 @@ public class GroupOptionProcessor implements ArgsProcessor { ) @Messages({ "GroupOptionProcessor.open.name=--open-group NAME", - "GroupOptionProcessor.open.desc=open a workspace by shortened or full name (or unique substring)" + "GroupOptionProcessor.open.desc=open a project group by shortened or full name (or unique substring)" }) public String openOption; @Arg(longName="close-group") @Description( shortDescription="#GroupOptionProcessor.close.desc" ) - @Messages("GroupOptionProcessor.close.desc=close any open workspace") + @Messages("GroupOptionProcessor.close.desc=close any open project group") public boolean closeOption; @Arg(longName="list-groups") @Description( shortDescription="#GroupOptionProcessor.list.desc" ) - @Messages("GroupOptionProcessor.list.desc=list available workspaces") + @Messages("GroupOptionProcessor.list.desc=list available project groups") public boolean listOption; @Messages({ diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java index 7b4ab517eed4..4948298132d4 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java @@ -53,7 +53,7 @@ @ActionReference(path = "Menu/File", position = 1100), @ActionReference(path = ProjectsRootNode.ACTIONS_FOLDER, position = 600, separatorAfter = 700) }) -@Messages("GroupsMenu.label=Workspace &Manager...") +@Messages("GroupsMenu.label=Project Gro&ups...") public class GroupsMenu extends AbstractAction { private static final RequestProcessor RP = new RequestProcessor(GroupsMenu.class.getName()); @@ -72,8 +72,8 @@ public void actionPerformed(ActionEvent e) { * Create (and open) a new group. */ @Messages({ - "GroupsMenu.new_title=Create New Workspace", - "GroupsMenu.new_create=Create Workspace", + "GroupsMenu.new_title=Create New Group", + "GroupsMenu.new_create=Create Group", "GroupsMenu.new_close=Close" }) private static void newGroup() { @@ -116,9 +116,9 @@ private static void newGroup() { * Manage groups. */ @Messages({ - "GroupsMenu.manage_title=Manage Workspaces", - "GroupsMenu.manage_select_group=&Select Workspace", - "GroupsMenu.manage_new_group=&New Workspace...", + "GroupsMenu.manage_title=Manage Groups", + "GroupsMenu.manage_select_group=&Select Group", + "GroupsMenu.manage_new_group=&New Group...", "GroupsMenu.manage_remove=&Remove", "GroupsMenu.manage_close=Close", "GroupsMenu.manage_properties=&Properties", @@ -155,7 +155,7 @@ private static void manageGroups() { /** * Open a properties dialog for the group, according to its type. */ - @Messages("GroupsMenu.properties_title=Workspace Properties") + @Messages("GroupsMenu.properties_title=Project Group Properties") static void openProperties(Group g) { Lookup context = Lookups.fixed(new Object[] { g, BaseUtilities.ACCESSOR.createGroup(g.getName(), g.prefs()) }); Dialog dialog = ProjectCustomizer.createCustomizerDialog("Projects/Groups/Customizer", //NOI18N diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java index 1f803ba94979..53fff6df79a4 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java @@ -43,7 +43,7 @@ import org.openide.util.NbBundle.Messages; /** - * Panel permitting user to create a new project group aka workspace. + * Panel permitting user to create a new project group. * Applicable in advanced mode. * @author Jesse Glick */ From 374fce5e73585cc8088066509424577a3dd7e825 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 30 Sep 2026 13:50:01 +0200 Subject: [PATCH 5/9] Let's expand the FallbackProject.getLookup() --- .../{GenericPrj.java => FallbackProject.java} | 12 ++++-- .../projectapi/nb/NbProjectManager.java | 2 +- .../api/project/ProjectManagerTest.java | 42 +++++++++++++++++++ 3 files changed, 52 insertions(+), 4 deletions(-) rename ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/{GenericPrj.java => FallbackProject.java} (70%) diff --git a/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/GenericPrj.java b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/FallbackProject.java similarity index 70% rename from ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/GenericPrj.java rename to ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/FallbackProject.java index f88a814ad5fc..7ba4563d93c5 100644 --- a/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/GenericPrj.java +++ b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/FallbackProject.java @@ -19,17 +19,23 @@ package org.netbeans.modules.projectapi.nb; import org.netbeans.api.project.Project; +import org.netbeans.spi.project.support.GenericSources; +import org.netbeans.spi.project.support.LookupProviderSupport; import org.openide.filesystems.FileObject; import org.openide.util.Lookup; import org.openide.util.lookup.Lookups; -final class GenericPrj implements Project { +final class FallbackProject implements Project { + private static final String DASHNAME = "org-netbeans-modules-project-fallback"; // NOI18N + private final FileObject dir; private final Lookup lkp; - public GenericPrj(FileObject dir) { + public FallbackProject(FileObject dir) { this.dir = dir; - this.lkp = Lookups.fixed(this); + var sources = GenericSources.genericOnly(this); + Lookup basicLookup = Lookups.fixed(this, dir, sources); + this.lkp = LookupProviderSupport.createCompositeLookup(basicLookup, "Projects/" + DASHNAME + "/Lookup"); } @Override 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 f27e1dcdc6d1..c8f9de6852db 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 @@ -391,7 +391,7 @@ private Project createProject(FileObject dir, boolean fallback) throws IOExcepti for (ProjectFactory factory : factories.allInstances()) { Project p = factory.loadProject(dir, state); if (p == null && fallback) { - p = new GenericPrj(dir); + p = new FallbackProject(dir); } if (p != null) { if (TIMERS.isLoggable(Level.FINE)) { diff --git a/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java b/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java index dbfe16cc51a8..d0e008f8fc27 100644 --- a/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java +++ b/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java @@ -33,6 +33,9 @@ import org.netbeans.modules.projectapi.nb.TimedWeakReference; import org.openide.filesystems.FileLock; import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; import org.openide.util.Mutex; import org.openide.util.test.MockLookup; @@ -229,6 +232,45 @@ public void testIsFallbackProject() throws Exception { assertNull("Since then, findProject again returns null", nothingAgain); } + public void testFallbackProjectLookup() throws Exception { + FileObject lookupDir = FileUtil.createFolder(FileUtil.getConfigRoot(), "Projects/org-netbeans-modules-project-fallback/Lookup"); + + var nothing = pm.findProject(justADir); + assertNull("No project is found for just a dir", nothing); + var generic = pm.findProjectOrFallback(justADir); + assertNotNull("But one can ask for a fallback project", generic); + + var then = pm.findProject(justADir); + assertSame("since then findProject works for just a dir", generic, then); + + Sources src = then.getLookup().lookup(Sources.class); + assertNotNull("sources are provided", src); + SourceGroup[] genSrc = src.getSourceGroups(Sources.TYPE_GENERIC); + assertNotNull("They support the generic sources", genSrc); + assertEquals("They support the generic sources", 1, genSrc.length); + assertEquals("root is project root", then.getProjectDirectory(), genSrc[0].getRootFolder()); + + MockLookuptype notFoundInLookup = then.getLookup().lookup(MockLookuptype.class); + assertNull("No MockLookuptype found in project's lookup", notFoundInLookup); + + { + // create a registration in the lookup + FileObject mockLookupType = FileUtil.createData(lookupDir, MockLookuptype.class.getName().replace(".", "-") + ".instance"); + MockLookuptype found = FileUtil.getConfigObject(mockLookupType.getPath(), MockLookuptype.class); + assertNotNull("MockLookuptype is registered in the lookup directory", found); + } + + MockLookuptype foundInLookup = then.getLookup().lookup(MockLookuptype.class); + assertNotNull("MockLookuptype also found in project's lookup", foundInLookup); + } + + public static final class MockLookuptype implements org.netbeans.spi.project.LookupProvider { + @Override + public Lookup createAdditionalLookup(Lookup baseContext) { + return Lookups.singleton(this); + } + } + public void testIsProject2() throws Exception { ProjectManager.Result r = pm.isProject2(goodproject); assertNotNull("Should have recognized goodproject", r); From a9261c47fe59021e3fa7238637e1583b58ec0cc0 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 30 Sep 2026 18:40:51 +0200 Subject: [PATCH 6/9] Try hard to recover previously existing project --- .../src/org/netbeans/modules/project/ui/OpenProjectList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java index e6d4f0297078..04efb8e3fe7c 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java @@ -1304,7 +1304,7 @@ private static Set URLs2Projects(Collection urls) { FileObject dir = URLMapper.findFileObject(url); if (dir != null && dir.isFolder()) { try { - Project p = ProjectManager.getDefault().findProject(dir); + Project p = ProjectManager.getDefault().findProjectOrFallback(dir); if (p != null && !result.contains(p)) { //#238093, #238811 if multiple entries point to the same project we end up with the same instance multiple times in the linked list. That's wrong. result.add(p); } From 9548a45599b529487f1dbdb945b9b4ff64a323ca Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 30 Sep 2026 18:41:35 +0200 Subject: [PATCH 7/9] Give the fallback project an icon and display it in the logical view --- .../projectapi/nb/FallbackProject.java | 75 ++++++++++++++- .../modules/projectapi/nb/fallback.svg | 59 ++++++++++++ ide/projectapi/nbproject/project.xml | 5 + .../api/project/ProjectManagerTest.java | 95 ++++++++++++++++--- .../modules/project/ui/PhysicalView.java | 14 +++ 5 files changed, 230 insertions(+), 18 deletions(-) create mode 100644 ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/fallback.svg diff --git a/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/FallbackProject.java b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/FallbackProject.java index 7ba4563d93c5..5f0726aa6f3b 100644 --- a/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/FallbackProject.java +++ b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/FallbackProject.java @@ -18,23 +18,41 @@ */ package org.netbeans.modules.projectapi.nb; +import java.beans.PropertyChangeListener; +import javax.swing.Icon; +import javax.swing.event.ChangeListener; import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectInformation; +import org.netbeans.api.project.SourceGroup; +import org.netbeans.api.project.Sources; import org.netbeans.spi.project.support.GenericSources; import org.netbeans.spi.project.support.LookupProviderSupport; import org.openide.filesystems.FileObject; +import org.openide.util.ImageUtilities; import org.openide.util.Lookup; +import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; -final class FallbackProject implements Project { +final class FallbackProject implements Project, ProjectInformation, Sources { private static final String DASHNAME = "org-netbeans-modules-project-fallback"; // NOI18N + private static final String ICON = "org/netbeans/modules/projectapi/nb/fallback.svg"; // NOI18N private final FileObject dir; private final Lookup lkp; + private final SourceGroup genericGroup; public FallbackProject(FileObject dir) { this.dir = dir; - var sources = GenericSources.genericOnly(this); - Lookup basicLookup = Lookups.fixed(this, dir, sources); + this.genericGroup = GenericSources.group( + this, dir, + Sources.TYPE_GENERIC, getDisplayName(), + getIcon(), getIcon() + ); + Lookup basicLookup = Lookups.fixed( + this, + dir, + LookupProviderSupport.createActionProviderMerger() + ); this.lkp = LookupProviderSupport.createCompositeLookup(basicLookup, "Projects/" + DASHNAME + "/Lookup"); } @@ -48,4 +66,55 @@ public Lookup getLookup() { return lkp; } + @Override + public String getName() { + return dir.getNameExt(); + } + + @Override + @NbBundle.Messages({ + "# {0} - name of the folder", + "CTL_FallbackProjectDisplayName=Folder {0}" + }) + public String getDisplayName() { + return Bundle.CTL_FallbackProjectDisplayName(getName()); + } + + @Override + public Icon getIcon() { + Icon icon = ImageUtilities.loadIcon(ICON); + assert icon != null; + return icon; + } + + @Override + public Project getProject() { + return this; + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener listener) { + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener listener) { + } + + @Override + public SourceGroup[] getSourceGroups(String type) { + if (Sources.TYPE_GENERIC.equals(type)) { + return new SourceGroup[] { genericGroup }; + } else { + return new SourceGroup[0]; + } + } + + @Override + public void addChangeListener(ChangeListener listener) { + } + + @Override + public void removeChangeListener(ChangeListener listener) { + } + } diff --git a/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/fallback.svg b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/fallback.svg new file mode 100644 index 000000000000..5f7295022b67 --- /dev/null +++ b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/fallback.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ide/projectapi/nbproject/project.xml b/ide/projectapi/nbproject/project.xml index 8bdd3709ed39..ac1cb160a238 100644 --- a/ide/projectapi/nbproject/project.xml +++ b/ide/projectapi/nbproject/project.xml @@ -113,6 +113,11 @@ + + org.openide.util.ui.svg + + + org.openide.util.lookup diff --git a/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java b/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java index d0e008f8fc27..dc87b6570349 100644 --- a/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java +++ b/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java @@ -31,12 +31,14 @@ import org.netbeans.junit.NbTestCase; import org.netbeans.modules.projectapi.nb.NbProjectManagerAccessor; import org.netbeans.modules.projectapi.nb.TimedWeakReference; +import org.netbeans.spi.project.ActionProvider; import org.openide.filesystems.FileLock; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.Lookup; -import org.openide.util.lookup.Lookups; import org.openide.util.Mutex; +import org.openide.util.lookup.AbstractLookup; +import org.openide.util.lookup.InstanceContent; import org.openide.util.test.MockLookup; /* XXX tests needed: @@ -237,37 +239,100 @@ public void testFallbackProjectLookup() throws Exception { var nothing = pm.findProject(justADir); assertNull("No project is found for just a dir", nothing); - var generic = pm.findProjectOrFallback(justADir); - assertNotNull("But one can ask for a fallback project", generic); + var fallbackProject = pm.findProjectOrFallback(justADir); + assertNotNull("But one can ask for a fallback project", fallbackProject); - var then = pm.findProject(justADir); - assertSame("since then findProject works for just a dir", generic, then); + var theProject = pm.findProject(justADir); + assertSame("since then findProject works for just a dir", fallbackProject, theProject); - Sources src = then.getLookup().lookup(Sources.class); + Sources src = theProject.getLookup().lookup(Sources.class); assertNotNull("sources are provided", src); SourceGroup[] genSrc = src.getSourceGroups(Sources.TYPE_GENERIC); assertNotNull("They support the generic sources", genSrc); assertEquals("They support the generic sources", 1, genSrc.length); - assertEquals("root is project root", then.getProjectDirectory(), genSrc[0].getRootFolder()); + assertEquals("root is project root", theProject.getProjectDirectory(), genSrc[0].getRootFolder()); - MockLookuptype notFoundInLookup = then.getLookup().lookup(MockLookuptype.class); + MockLookupForProject notFoundInLookup = theProject.getLookup().lookup(MockLookupForProject.class); assertNull("No MockLookuptype found in project's lookup", notFoundInLookup); { // create a registration in the lookup - FileObject mockLookupType = FileUtil.createData(lookupDir, MockLookuptype.class.getName().replace(".", "-") + ".instance"); - MockLookuptype found = FileUtil.getConfigObject(mockLookupType.getPath(), MockLookuptype.class); - assertNotNull("MockLookuptype is registered in the lookup directory", found); + FileObject mockLookupType = FileUtil.createData(lookupDir, MockLookupForProject.class.getName().replace(".", "-") + ".instance"); + MockLookupForProject found = FileUtil.getConfigObject(mockLookupType.getPath(), MockLookupForProject.class); + assertNotNull("MockLookupForProject is registered in the lookup directory", found); } - MockLookuptype foundInLookup = then.getLookup().lookup(MockLookuptype.class); - assertNotNull("MockLookuptype also found in project's lookup", foundInLookup); + MockLookupForProject mockLookup = theProject.getLookup().lookup(MockLookupForProject.class); + assertNotNull("MockLookupForProject also found in project's lookup", mockLookup); + + ActionProvider ap = theProject.getLookup().lookup(ActionProvider.class); + assertNotNull("There are always actions for a project being merged together", ap); + { + var ap1 = new ActionProvider() { + @Override + public String[] getSupportedActions() { + return new String[] { "jedna", "dva" }; + } + + @Override + public void invokeAction(String command, Lookup context) throws IllegalArgumentException { + throw new UnsupportedOperationException(command); + } + + @Override + public boolean isActionEnabled(String command, Lookup context) throws IllegalArgumentException { + return false; + } + }; + + var ap2 = new ActionProvider() { + @Override + public String[] getSupportedActions() { + return new String[] { "odin", "dva" }; + } + + @Override + public void invokeAction(String command, Lookup context) throws IllegalArgumentException { + throw new UnsupportedOperationException(command); + } + + @Override + public boolean isActionEnabled(String command, Lookup context) throws IllegalArgumentException { + return false; + } + }; + + mockLookup.ic.add(ap1); + + String[] ap1Actions = theProject.getLookup().lookup(ActionProvider.class).getSupportedActions(); + assertEquals("Found two actions", 2, ap1Actions.length); + + mockLookup.ic.add(ap2); + String[] bothActions = theProject.getLookup().lookup(ActionProvider.class).getSupportedActions(); + assertEquals("Found three actions", 3, bothActions.length); + + assertSame("Action provider instance stays the same", ap, theProject.getLookup().lookup(ActionProvider.class)); + + ProjectInformation info = ProjectUtils.getInformation(theProject); + assertNotNull("Cannot be null", info); + assertEquals(theProject.getProjectDirectory().getNameExt(), info.getName()); + assertEquals("Folder " + justADir.getNameExt(), info.getDisplayName()); + assertNotNull("Icon is provided", info.getIcon()); + assertSame("Same icon as the group icon", info.getIcon(), genSrc[0].getIcon(true)); + } } - public static final class MockLookuptype implements org.netbeans.spi.project.LookupProvider { + public static final class MockLookupForProject implements org.netbeans.spi.project.LookupProvider { + final InstanceContent ic = new InstanceContent(); + private final Lookup lkp = new AbstractLookup(ic); + { + ic.add(this); + } + + @Override public Lookup createAdditionalLookup(Lookup baseContext) { - return Lookups.singleton(this); + return lkp; } } diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/PhysicalView.java b/ide/projectui/src/org/netbeans/modules/project/ui/PhysicalView.java index c9532c52512e..6e629e2de76a 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/PhysicalView.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/PhysicalView.java @@ -252,6 +252,20 @@ private boolean initialized() { return isProjectDir ? HINT_project(dir) : HINT_group(dir); } + @Override + public Image getIcon(int type) { + Icon icon = group.getIcon(false); + return icon == null ? super.getIcon(type) : ImageUtilities.icon2Image(icon); + } + + @Override + public Image getOpenedIcon(int type) { + Icon icon = group.getIcon(true); + return icon == null ? super.getOpenedIcon(type) : ImageUtilities.icon2Image(icon); + } + + + public @Override boolean canRename() { return false; } From e3c1619640072b60286b266dd0116802179ffe86 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 30 Sep 2026 19:13:07 +0200 Subject: [PATCH 8/9] Re-check whether FallbackProject is still needed --- .../projectapi/nb/NbProjectManager.java | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) 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 c8f9de6852db..2c4cba3f1a8f 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 @@ -390,9 +390,6 @@ private Project createProject(FileObject dir, boolean fallback) throws IOExcepti ProjectStateImpl state = new ProjectStateImpl(); for (ProjectFactory factory : factories.allInstances()) { Project p = factory.loadProject(dir, state); - if (p == null && fallback) { - p = new FallbackProject(dir); - } if (p != null) { if (TIMERS.isLoggable(Level.FINE)) { LogRecord rec = new LogRecord(Level.FINE, "Project"); // NOI18N @@ -404,7 +401,14 @@ private Project createProject(FileObject dir, boolean fallback) throws IOExcepti return p; } } - return null; + if (fallback) { + FallbackProject p = new FallbackProject(dir); + proj2Factory.put(p, null); + state.attach(p); + return p; + } else { + return null; + } } @@ -509,11 +513,28 @@ public void clearNonProjectCache() { LoadStatus.NO_SUCH_PROJECT.wrap(), LoadStatus.SOME_SUCH_PROJECT.wrap(), })); - // XXX remove everything too? but then e.g. AntProjectFactorySingleton - // will stay while its delegates are changed, which does no good - // XXX should there be any way to signal that a particular - // folder should be "reloaded" by a new factory? } + MUTEX.postReadRequest(() -> { + synchronized (dir2Proj) { + // check if FallbackProject is still needed + + Iterator, LoadStatus>>> it = dir2Proj.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry, LoadStatus>> entry = it.next(); + if (entry.getValue().hasFirst() && entry.getValue().first().get() instanceof FallbackProject fallback) { + Result result = checkForProject(fallback.getProjectDirectory()); + if (result != null) { + it.remove(); + } + } + } + } + }); + + // XXX remove everything too? but then e.g. AntProjectFactorySingleton + // will stay while its delegates are changed, which does no good + // XXX should there be any way to signal that a particular + // folder should be "reloaded" by a new factory? } private final class ProjectStateImpl implements ProjectState { From 3fbfbc9a228bb09a5614ba1f7d70643a22d13ef2 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 1 Oct 2026 09:31:21 +0200 Subject: [PATCH 9/9] Documenting ProjectManager.findProjectOrFallback --- ide/projectapi/apichanges.xml | 47 +++++--- ide/projectapi/arch.xml | 102 ++++++++++-------- ide/projectapi/manifest.mf | 2 +- ide/projectapi/nbproject/project.xml | 2 +- .../api/project/ProjectActionContext.java | 2 +- .../netbeans/api/project/ProjectManager.java | 47 +++++++- .../project/ProjectManagerImplementation.java | 22 +++- 7 files changed, 153 insertions(+), 71 deletions(-) diff --git a/ide/projectapi/apichanges.xml b/ide/projectapi/apichanges.xml index 1086f14ffaa5..01b63e3821db 100644 --- a/ide/projectapi/apichanges.xml +++ b/ide/projectapi/apichanges.xml @@ -20,7 +20,7 @@ --> - + + + + findProjectOrFallback + + + + + +

    + + ProjectManager.findProjectOrFallack + + was introduced in order to allow any folder to be treated as a project, when needed. +

    +
    + +
    Added action for running tests in parallel @@ -177,7 +194,7 @@ is the proper place.

    In presence of multiple source roots, e.g. several source folders, or test folders, some of them may be more - related to the + related to the

    @@ -242,7 +259,7 @@ is the proper place.

    Newly introduced interfaces DependencyProjectProvider and ProjectContainerProvider are meant as less general replacement for SubprojectProvider - as they explicitly declaring their contract + as they explicitly declaring their contract (DependencyProjectProvider the project's dependency projects and ProjectContainerProvider projects that are build as part of this project). Code using SubprojectProvider currently should evaluate if DependencyProjectProvider or ProjectContainerProvider contracts suit their usage better. Please note that the new interfaces are not mandatory for project types to implement and if it's missing, SubprojectProvider should be used. @@ -253,7 +270,7 @@ is the proper place. - +

    New method createCompositeLookup(Lookup, Lookup) in LookupProviderSupport @@ -263,7 +280,7 @@ is the proper place.

    - Introducing method LookupProviderSupport.createCompositeLookup(Lookup, Lookup) which is a more freeform variant of + Introducing method LookupProviderSupport.createCompositeLookup(Lookup, Lookup) which is a more freeform variant of already existing method. The first parameter is expected to be the base lookup containing instances to be found in project lookup, the second parameter is a lookup containing just LookupProviders.

    @@ -271,7 +288,7 @@ is the proper place.
    - + New constant UNOWNED in FileOwnerQuery to mark a folder as definitely not owned by any project. @@ -282,10 +299,10 @@ is the proper place.

    Introducing constant FileOwnerQuery.UNOWNED, a pseudoproject indicating just that a directory is definitely unowned. May - be returned by either + be returned by either FileOwnerQueryImplementation.getOwner, in which case null is returned from FileOwnerQuery.getOwner. May also be passed to either - FileOwnerQuery.markExternalOwner overload, in which case the standard directory search will be pruned at this point with no result. + FileOwnerQuery.markExternalOwner overload, in which case the standard directory search will be pruned at this point with no result.

    @@ -301,9 +318,9 @@ is the proper place.

    In order to be able to fully integrate the profiler with the project infrastructure - it is necessary to add three new constants to + it is necessary to add three new constants to ActionProvider SPI for profiling a project, profiling a single file and - profiling a test for single file. + profiling a test for single file.

    @@ -569,7 +586,7 @@ is the proper place.
    - + Added support for composing project's lookup from multiple sources. @@ -581,7 +598,7 @@ is the proper place.

    Added interfaces and support classes that can be used to compose the project's lookup - from multiple 3rd party sources. Framework for creating merged instances included. + from multiple 3rd party sources. Framework for creating merged instances included. LookupMerger implementation for Sources added.

    @@ -590,7 +607,7 @@ is the proper place.
    - + Added support for project configurations @@ -629,7 +646,7 @@ is the proper place. - + The FileOwnerQuery.markExternalOwner allows registration of individal files and URIs @@ -653,7 +670,7 @@ is the proper place. - + Switched to major release version 1 diff --git a/ide/projectapi/arch.xml b/ide/projectapi/arch.xml index 81349ae3f7d0..bb226526d336 100644 --- a/ide/projectapi/arch.xml +++ b/ide/projectapi/arch.xml @@ -19,8 +19,8 @@ under the License. --> - + ]> What will be API for - clients and what support API? + clients and what support API? What parts will be pluggable? How will plug-ins be registered? Please use <api type="export"/> to describe your general APIs. - If possible please provide - simple diagrams. + If possible please provide + simple diagrams. --> @@ -59,7 +59,7 @@ into the project lookup (rather than global lookup). The implementations will be consulted only in the case the relevant file belongs to that project (according to org.netbeans.api.project.FileOwnerQuery). - This helps avoid the need to consult irrelevant query implementations. + This helps avoid the need to consult irrelevant query implementations. @@ -68,12 +68,12 @@ @@ -91,7 +91,7 @@ Please express your estimates of how long the design, implementation, stabilization are likely to last. How many people will be needed to - implement this and what is the expected milestone the work should be + implement this and what is the expected milestone the work should be ready. @@ -127,8 +127,8 @@ What is this project good for? - Please provide here a few lines describing the project, - what problem it should solve, provide links to documentation, + Please provide here a few lines describing the project, + what problem it should solve, provide links to documentation, specifications, etc. @@ -147,7 +147,7 @@ Is your module correctly internationalized? - Correct internationalization means that it obeys instructions + Correct internationalization means that it obeys instructions at NetBeans I18N pages. @@ -163,7 +163,7 @@ @@ -181,7 +181,7 @@ versions of itself? Can you correctly read all old settings? Will future versions be able to read your current settings? Can you read or politely ignore settings stored by a future version? - + Very helpful for reading settings is to store version number there, so future versions can decide whether how to read/convert @@ -201,7 +201,7 @@ Which version of JRE do you need (1.2, 1.3, 1.4, etc.)? - It is expected that if your module runs on 1.x that it will run + It is expected that if your module runs on 1.x that it will run on 1.x+1 if no, state that please. Also describe here cases where you run different code on different versions of JRE and why. @@ -269,7 +269,7 @@ @@ -838,9 +838,9 @@ Nothing.

    - No. + + The ProjectManager.findProjectOrFallack + method provides access to fallback generic project representing + directories that would otherwise have no project associated. This project + exposes its configuration via layer, at Projects/org-netbeans-modules-project-fallback + folder. More details are provided in the Javadoc of + findProjectOrFallack + method. +

    @@ -883,7 +891,7 @@ Nothing. Does your module mask/hide/override any resources provided by other modules in their layers? - + If you mask a file provided by another module, you probably depend on that and do not want the other module to (for example) change @@ -903,10 +911,10 @@ Nothing.