Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import org.netbeans.api.project.Sources;
import org.netbeans.spi.project.ActionProvider;
import org.netbeans.spi.project.FileOwnerQueryImplementation;
import org.netbeans.spi.project.SubprojectProvider;
import org.netbeans.spi.project.ui.LogicalViewProvider;
import org.netbeans.spi.project.ui.support.ProjectConvertors;
import org.openide.filesystems.FileChangeAdapter;
Expand Down Expand Up @@ -461,7 +462,24 @@ final void refresh(Project p) {
// Own methods ---------------------------------------------------------

public Collection<Pair> getKeys() {
List<Project> projects = Arrays.asList( OpenProjectList.getDefault().getOpenProjects() );
var arr = OpenProjectList.getDefault().getOpenProjects();
var projects = new ArrayList<>(Arrays.asList(arr));
if (type == LOGICAL_VIEW) {
var hideNestedProjects = new HashSet<Project>();
for (var p : projects) {
var lkp = p.getLookup();
var nestedProjects = lkp.lookup(LogicalViewProvider.WithNestedProjects.class);
if (nestedProjects == null) {
continue;
}
var subProjects = lkp.lookup(SubprojectProvider.class);
if (subProjects == null) {
continue;
}
hideNestedProjects.addAll(subProjects.getSubprojects());
}
projects.removeAll(hideNestedProjects);
}
projects.sort(OpenProjectList.projectByDisplayName());

final List<Pair> dirs = new ArrayList<>(projects.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,15 @@
import org.netbeans.junit.NbTestCase;
import org.netbeans.modules.project.ui.actions.TestSupport;
import org.netbeans.spi.project.SubprojectProvider;
import org.netbeans.spi.project.ui.LogicalViewProvider;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataFolder;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
import org.openide.util.lookup.Lookups;
import org.openide.util.test.MockLookup;

Expand All @@ -55,25 +62,34 @@ protected void setUp() throws Exception {
OpenProjectList.waitProjectsFullyOpen();
}

public void testOpenNestedProjects() throws Exception {
doOpenProjects(true, () -> {
public void testNestedSubProjects() throws Exception {
doOpenProjects(true, true, () -> {
var em = OpenProjects.getDefault().createLogicalView();
var all = em.getRootContext().getChildren().getNodes(true);
assertEquals("Only one project is visible, the other is nested", 1, all.length);
return null;
});
}

public void testNoNestedSubProjects() throws Exception {
doOpenProjects(true, false, () -> {
var em = OpenProjects.getDefault().createLogicalView();
var all = em.getRootContext().getChildren().getNodes(true);
assertEquals("Two projects are visible", 2, all.length);
return null;
});
}

public void testOpenNonNestedProjects() throws Exception {
doOpenProjects(false, () -> {
public void testNestedNoSubProjects() throws Exception {
doOpenProjects(false, true, () -> {
var em = OpenProjects.getDefault().createLogicalView();
var all = em.getRootContext().getChildren().getNodes(true);
assertEquals("One project is visible", 1, all.length);
assertEquals("Both projects are visible", 2, all.length);
return null;
});
}

private void doOpenProjects(boolean withSubprojects, Callable<Void> inner) throws Exception {
private void doOpenProjects(boolean withSubprojects, boolean withNested, Callable<Void> inner) throws Exception {
MockLookup.setInstances(new TestSupport.TestProjectFactory());
clearWorkDir();
FileObject workDir = FileUtil.toFileObject(getWorkDir());
Expand All @@ -83,42 +99,61 @@ private void doOpenProjects(boolean withSubprojects, Callable<Void> inner) throw
final TestSupport.TestProject mainPrj = (TestSupport.TestProject) ProjectManager.getDefault().findProject(prjFo);
final TestSupport.TestProject nestedPrj = (TestSupport.TestProject) ProjectManager.getDefault().findProject(nestedFo);
assertNotNull("Project found", mainPrj);
var subProvider = new SubprojectProvider() {
@Override
public Set<? extends Project> getSubprojects() {
return Set.of(nestedPrj);
}

@Override
public void addChangeListener(ChangeListener listener) {
}

@Override
public void removeChangeListener(ChangeListener listener) {
}
};
var content = new InstanceContent();
if (withSubprojects) {
mainPrj.setLookup(Lookups.singleton(subProvider));
var subProvider = new SubprojectProvider() {
@Override
public Set<? extends Project> getSubprojects() {
return Set.of(nestedPrj);
}

@Override
public void addChangeListener(ChangeListener listener) {
}

@Override
public void removeChangeListener(ChangeListener listener) {
}
};
content.add(subProvider);
}
if (withNested) {
var logical = new LogicalViewProvider.WithNestedProjects() {
@Override
public Node createLogicalView() {
var ch = new Children.Array();
var mainNode = new AbstractNode(ch);
var nestedNode = DataFolder.findFolder(nestedPrj.getProjectDirectory()).getNodeDelegate().cloneNode();
ch.add(nestedNode);
return mainNode;
}

@Override
public Node findPath(Node root, Object target) {
if (target == mainPrj || target.equals(mainPrj.getProjectDirectory())) {
return root;
}
if (target == nestedPrj || target.equals(nestedPrj.getProjectDirectory())) {
return root.getChildren().getNodeAt(0);
}
return null;
}
};
content.add(logical);
}
mainPrj.setLookup(new AbstractLookup(content));

OpenProjectList.waitProjectsFullyOpen();
assertEquals("Initially empty", 0, OpenProjects.getDefault().openProjects().get().length);

OpenProjects.getDefault().open(new Project[] { mainPrj }, true);
OpenProjects.getDefault().open(new Project[] { mainPrj, nestedPrj }, false);

List<Project> arr = Arrays.asList(OpenProjects.getDefault().openProjects().get());
if (withSubprojects) {
assertEquals("Both projects open", 2, arr.size());
assertTrue("Prj1 is there", arr.contains(mainPrj));
assertTrue("Nested1 is there", arr.contains(nestedPrj));
inner.call();
OpenProjects.getDefault().close (new Project[] { nestedPrj, mainPrj });
} else {
assertEquals("However one project instance is there", 1, arr.size());
assertEquals("arr[0] is equal to p", arr.get(0), mainPrj);
inner.call();
OpenProjects.getDefault().close (new Project[] { mainPrj });
}
assertEquals("Both projects open", 2, arr.size());
assertTrue("Prj1 is there", arr.contains(mainPrj));
assertTrue("Nested1 is there", arr.contains(nestedPrj));
inner.call();
OpenProjects.getDefault().close (new Project[] { nestedPrj, mainPrj });

if (OpenProjects.getDefault().getOpenProjects().length != 0) {
fail("All projects shall be closed: " + Arrays.asList(OpenProjects.getDefault().getOpenProjects()));
Expand Down
17 changes: 16 additions & 1 deletion ide/projectuiapi/apichanges.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

-->
<?xml-stylesheet type="text/xml" href="../../nbbuild/javadoctools/apichanges.xsl"?>
<!DOCTYPE apichanges PUBLIC "-//NetBeans//DTD API changes list 1.0//EN" "../../nbbuild/javadoctools/apichanges.dtd">
<!DOCTYPE apichanges PUBLIC "-//NetBeans//DTD API changes list 1.0//EN" "../../nbbuild/javadoctools/apichanges-1.1.dtd">

<!--

Expand Down Expand Up @@ -83,6 +83,21 @@ is the proper place.
<!-- ACTUAL CHANGES BEGIN HERE: -->

<changes>
<change id="nested-projects">
<api name="general"/>
<summary>Logical view supports nested projects.</summary>
<version major="1" minor="124"/>
<date day="9" month="9" year="2026"/>
<author login="jtulach"/>
<compatibility addition="yes"/>
<description>
<p>
There is a new interface
<a href="@TOP@/org/netbeans/spi/project/ui/LogicalViewProvider.WithNestedProjects.html">LogicalViewProvider.WithNestedProjects</a>
that allows a <em>parent</em> project to nest its children projects.
</p>
</description>
</change>
<change id="project-problems-implementation">
<api name="general"/>
<summary>Allow pluggable implementation for ProjectProblems API.</summary>
Expand Down
2 changes: 1 addition & 1 deletion ide/projectuiapi/nbproject/project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

javac.compilerargs=-Xlint -Xlint:-serial
javac.release=17
spec.version.base=1.123.0
spec.version.base=1.124.0
is.autoload=true
javadoc.arch=${basedir}/arch.xml
javadoc.apichanges=${basedir}/apichanges.xml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ public interface LogicalViewProvider extends PathFinder {
* @return a node displaying the contents of the project in an intuitive way
* @see CommonProjectActions#forType
*/
Node createLogicalView();

Node createLogicalView();

/** An extension of {@link LogicalViewProvider} handling also <em>nested projects</em>.
*
* @since 1.124
*/
public interface WithNestedProjects extends LogicalViewProvider {

}
}
54 changes: 14 additions & 40 deletions platform/openide.nodes/src/org/openide/nodes/Children.java
Original file line number Diff line number Diff line change
Expand Up @@ -357,15 +357,15 @@ protected Object clone() throws CloneNotSupportedException {
* @param nodes set of nodes to add to the list
* @return <code>true</code> if successfully added
*/
public abstract boolean add(final Node[] nodes);
public abstract boolean add(final Node... nodes);

/** Remove nodes from the list. Only nodes that are present are
* removed.
*
* @param nodes nodes to be removed
* @return <code>true</code> if the nodes could be removed
*/
public abstract boolean remove(final Node[] nodes);
public abstract boolean remove(final Node... nodes);

/** Get the nodes as an enumeration.
* @return enumeration of nodes
Expand Down Expand Up @@ -437,8 +437,6 @@ public final Node getNodeAt(int index) {
* this point, see {@link #getNodes(boolean)}
* @return array of nodes
*/

// private static String off = ""; // NOI18N
public final Node[] getNodes() {
checkSupport();
return entrySupport().getNodes(false);
Expand Down Expand Up @@ -605,12 +603,14 @@ private static final class Empty extends Children {
}

/** @return false, does no action */
public boolean add(Node[] nodes) {
@Override
public boolean add(Node... nodes) {
return false;
}

/** @return false, does no action */
public boolean remove(Node[] nodes) {
@Override
public boolean remove(Node... nodes) {
return false;
}
}
Expand Down Expand Up @@ -789,7 +789,7 @@ final Collection<Node> getCollection() {
* @return true if changed false if not
*/
@Override
public boolean add(final Node[] arr) {
public boolean add(final Node... arr) {
synchronized (COLLECTION_LOCK) {
if (!getCollection().addAll(Arrays.asList(arr))) {
// no change to the collection
Expand All @@ -805,7 +805,7 @@ public boolean add(final Node[] arr) {
* @return true if changed false if not
*/
@Override
public boolean remove(final Node[] arr) {
public boolean remove(final Node... arr) {
synchronized (COLLECTION_LOCK) {
final Collection<Node> collection = getCollection();
// fast check
Expand Down Expand Up @@ -1055,7 +1055,7 @@ protected java.util.Map<T,Node> initMap() {
* @param arr nodes to add
* @return <code>false</code> in the default implementation
*/
public boolean add(Node[] arr) {
public boolean add(Node... arr) {
return false;
}

Expand All @@ -1064,7 +1064,7 @@ public boolean add(Node[] arr) {
* @param arr nodes to remove
* @return <code>false</code> in the default implementation
*/
public boolean remove(Node[] arr) {
public boolean remove(Node... arr) {
return false;
}

Expand Down Expand Up @@ -1403,7 +1403,7 @@ void switchSupport(boolean toLazy) {
*/
@Deprecated
@Override
public boolean add(Node[] arr) {
public boolean add(Node... arr) {
if (lazySupport) {
fallbackToDefaultSupport();
}
Expand All @@ -1415,7 +1415,7 @@ public boolean add(Node[] arr) {
*/
@Deprecated
@Override
public boolean remove(final Node[] arr) {
public boolean remove(final Node... arr) {
if (lazySupport) {
return false;
}
Expand Down Expand Up @@ -1840,12 +1840,12 @@ Children getOriginal() {
}

@Override
public boolean add(Node[] nodes) {
public boolean add(Node... nodes) {
return getOriginal().add(nodes);
}

@Override
public boolean remove(Node[] nodes) {
public boolean remove(Node... nodes) {
return getOriginal().remove(nodes);
}

Expand All @@ -1871,32 +1871,6 @@ public Node findChild(String name) {

}

/*
static void printNodes (Node[] n) {
for (int i = 0; i < n.length; i++) {
System.out.println (" " + i + ". " + n[i].getName () + " number: " + System.identityHashCode (n[i]));
}
}
*/
/* JST: Useful test routine ;-) *
static {
final TopComponent.Registry r = TopComponent.getRegistry ();
r.addPropertyChangeListener (new PropertyChangeListener () {
Node last = new AbstractNode (LEAF);

public void propertyChange (PropertyChangeEvent ev) {
Node[] arr = r.getCurrentNodes ();
if (arr != null && arr.length == 1) {
last = arr[0];
}
System.out.println (
"Activated node: " + last + " \nparent: " + last.getParentNode ()
);
}
});
}
*/

private static final class ProjectManagerDeadlockDetector implements Executor {
private final Mutex FALLBACK = new Mutex();
private final AtomicReference<WeakReference<Mutex>> pmMutexRef = new AtomicReference<WeakReference<Mutex>>();
Expand Down
Loading