Skip to content

Commit 19e78d8

Browse files
Moaead Yahyailoveeclipse
authored andcommitted
Fix API tools slow down with multiple source folders sharing output
The fix in commit 215a618 for issue #2096 introduced an O(n^2) time complexity regression when projects have multiple source folders sharing the same output location (like SWT with 26 source roots). The problem was that each source folder caused a new CompositeApiTypeContainer to be created, wrapping all previous containers plus a new one. This resulted in exponential duplication of container visits during API analysis. The fix modifies ProjectTypeContainer to support multiple IPackageFragmentRoots, allowing a single container to discover packages from all source folders that share the same output location. This avoids creating composite containers and maintains O(n) complexity. Fixes #2197 Signed-off-by: moaead <moaead@users.noreply.github.com>
1 parent 324fb67 commit 19e78d8

File tree

3 files changed

+33
-31
lines changed

3 files changed

+33
-31
lines changed

apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/builder/tests/compatibility/ProjectTypeContainerTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,9 @@ public void testMultipleSourceFoldersWithSharedOutput() throws CoreException {
261261
IApiTypeContainer[] containers = component.getApiTypeContainers();
262262
assertEquals("Wrong number of API type containers", 1, containers.length); //$NON-NLS-1$
263263
IApiTypeContainer container = containers[0];
264-
assertEquals("Should be a composite type container", IApiTypeContainer.COMPOSITE, //$NON-NLS-1$
264+
// A single ProjectTypeContainer handles multiple source roots sharing
265+
// the same output location by tracking all package fragment roots
266+
assertEquals("Should be a folder type container", IApiTypeContainer.FOLDER, //$NON-NLS-1$
265267
container.getContainerType());
266268

267269
String[] packageNames = container.getPackageNames();

apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ProjectComponent.java

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
package org.eclipse.pde.api.tools.internal.model;
1515

1616
import java.util.ArrayList;
17-
import java.util.Arrays;
1817
import java.util.Collections;
1918
import java.util.List;
2019
import java.util.Map;
@@ -419,30 +418,11 @@ private static IApiTypeContainer getApiTypeContainer(String location, ProjectCom
419418
}
420419
cfc = new ProjectTypeContainer(component, container, root);
421420
outputLocationToContainer.put(outputLocation, cfc);
422-
} else {
421+
} else if (cfc instanceof ProjectTypeContainer) {
423422
// Multiple source folders share the same output location
424-
// Create a composite container to include packages from all source roots
425-
List<IApiTypeContainer> containers = new ArrayList<>();
426-
if (cfc instanceof CompositeApiTypeContainer) {
427-
// Already a composite, add to it
428-
IApiTypeContainer[] typeContainers = ((CompositeApiTypeContainer) cfc)
429-
.getApiTypeContainers();
430-
containers.addAll(Arrays.asList(typeContainers));
431-
} else {
432-
// Convert single container to composite
433-
containers.add(cfc);
434-
}
435-
// Add new container for this source root
436-
IPath projectFullPath = project.getProject().getFullPath();
437-
IContainer container = null;
438-
if (projectFullPath.equals(outputLocation)) {
439-
container = project.getProject();
440-
} else {
441-
container = project.getProject().getWorkspace().getRoot().getFolder(outputLocation);
442-
}
443-
containers.add(new ProjectTypeContainer(component, container, root));
444-
cfc = new CompositeApiTypeContainer(component, containers);
445-
outputLocationToContainer.put(outputLocation, cfc);
423+
// Add the new package fragment root to the existing container
424+
// so it can discover packages from all source roots
425+
((ProjectTypeContainer) cfc).addPackageFragmentRoot(root);
446426
}
447427
return cfc;
448428
}

apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ProjectTypeContainer.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515

1616
import java.util.ArrayList;
1717
import java.util.List;
18-
import java.util.Objects;
1918
import java.util.Set;
2019
import java.util.SortedSet;
2120
import java.util.TreeSet;
21+
import java.util.concurrent.CopyOnWriteArrayList;
2222

2323
import org.eclipse.core.resources.IContainer;
2424
import org.eclipse.core.resources.IFile;
@@ -50,9 +50,10 @@ public class ProjectTypeContainer extends ApiElement implements IApiTypeContaine
5050
private String[] fPackageNames;
5151

5252
/**
53-
* Optional package fragment root for JDT-based package discovery
53+
* Package fragment roots for JDT-based package discovery. Multiple roots
54+
* may exist when several source folders share the same output location.
5455
*/
55-
private final IPackageFragmentRoot fPackageFragmentRoot;
56+
private final List<IPackageFragmentRoot> fPackageFragmentRoots = new CopyOnWriteArrayList<>();
5657

5758
/**
5859
* Constructs an {@link IApiTypeContainer} rooted at the location with an
@@ -67,7 +68,24 @@ public class ProjectTypeContainer extends ApiElement implements IApiTypeContaine
6768
public ProjectTypeContainer(IApiElement parent, IContainer container, IPackageFragmentRoot packageFragmentRoot) {
6869
super(parent, IApiElement.API_TYPE_CONTAINER, container.getName());
6970
this.fRoot = container;
70-
this.fPackageFragmentRoot = Objects.requireNonNull(packageFragmentRoot);
71+
if (packageFragmentRoot != null) {
72+
this.fPackageFragmentRoots.add(packageFragmentRoot);
73+
}
74+
}
75+
76+
/**
77+
* Adds an additional package fragment root to this container. This is used
78+
* when multiple source folders share the same output location.
79+
*
80+
* @param root the package fragment root to add
81+
* @since 1.3.400
82+
*/
83+
public void addPackageFragmentRoot(IPackageFragmentRoot root) {
84+
if (root != null && !fPackageFragmentRoots.contains(root)) {
85+
fPackageFragmentRoots.add(root);
86+
// Clear cached package names so they will be recomputed
87+
fPackageNames = null;
88+
}
7189
}
7290

7391
@Override
@@ -159,8 +177,10 @@ public IApiTypeRoot findTypeRoot(String qualifiedName) throws CoreException {
159177
public String[] getPackageNames() throws CoreException {
160178
if (fPackageNames == null) {
161179
SortedSet<String> names = new TreeSet<>();
162-
if (fPackageFragmentRoot.exists()) {
163-
collectPackageNames(names, fPackageFragmentRoot);
180+
for (IPackageFragmentRoot root : fPackageFragmentRoots) {
181+
if (root.exists()) {
182+
collectPackageNames(names, root);
183+
}
164184
}
165185
fPackageNames = names.toArray(String[]::new);
166186
}

0 commit comments

Comments
 (0)