11/*******************************************************************************
2- * Copyright (c) 2005, 2021 IBM Corporation and others.
2+ * Copyright (c) 2005, 2025 IBM Corporation and others.
33 *
44 * This program and the accompanying materials
55 * are made available under the terms of the Eclipse Public License 2.0
1111 * Contributors:
1212 * IBM Corporation - initial API and implementation
1313 * Johannes Ahlers <Johannes.Ahlers@gmx.de> - bug 477677
14+ * Christoph Läubrich - Use bnd analyzer to compute required packages
1415 *******************************************************************************/
1516package org .eclipse .pde .internal .ui .search .dependencies ;
1617
1718import java .lang .reflect .InvocationTargetException ;
1819import java .util .ArrayDeque ;
1920import java .util .ArrayList ;
21+ import java .util .Arrays ;
2022import java .util .Collection ;
2123import java .util .HashMap ;
2224import java .util .Iterator ;
2325import java .util .List ;
2426import java .util .ListIterator ;
27+ import java .util .Map ;
28+ import java .util .Set ;
29+ import java .util .stream .Collectors ;
2530
26- import org .eclipse .core .resources .IProject ;
2731import org .eclipse .core .runtime .CoreException ;
2832import org .eclipse .core .runtime .IProgressMonitor ;
2933import org .eclipse .core .runtime .SubMonitor ;
30- import org .eclipse .jdt .core .ICompilationUnit ;
31- import org .eclipse .jdt .core .IJavaElement ;
32- import org .eclipse .jdt .core .IJavaProject ;
33- import org .eclipse .jdt .core .IOrdinaryClassFile ;
34- import org .eclipse .jdt .core .IPackageFragment ;
35- import org .eclipse .jdt .core .IType ;
36- import org .eclipse .jdt .core .JavaCore ;
37- import org .eclipse .jdt .core .JavaModelException ;
38- import org .eclipse .jdt .core .search .IJavaSearchConstants ;
39- import org .eclipse .jdt .core .search .IJavaSearchScope ;
40- import org .eclipse .jdt .core .search .SearchEngine ;
4134import org .eclipse .jdt .core .search .SearchMatch ;
42- import org .eclipse .jdt .core .search .SearchParticipant ;
43- import org .eclipse .jdt .core .search .SearchPattern ;
4435import org .eclipse .jdt .core .search .SearchRequestor ;
4536import org .eclipse .jface .operation .IRunnableWithProgress ;
4637import org .eclipse .osgi .container .namespaces .EquinoxModuleDataNamespace ;
38+ import org .eclipse .osgi .service .resolver .BundleDescription ;
39+ import org .eclipse .osgi .service .resolver .ExportPackageDescription ;
4740import org .eclipse .pde .core .plugin .IPluginImport ;
4841import org .eclipse .pde .core .plugin .IPluginModelBase ;
4942import org .eclipse .pde .core .plugin .PluginRegistry ;
5043import org .eclipse .pde .internal .core .ClasspathUtilCore ;
44+ import org .eclipse .pde .internal .core .bnd .PdeProjectAnalyzer ;
5145import org .eclipse .pde .internal .core .ibundle .IBundle ;
46+ import org .eclipse .pde .internal .core .ibundle .IBundleModel ;
5247import org .eclipse .pde .internal .core .ibundle .IBundlePluginModelBase ;
5348import org .eclipse .pde .internal .core .ibundle .IManifestHeader ;
5449import org .eclipse .pde .internal .core .plugin .PluginImport ;
5550import org .eclipse .pde .internal .core .search .PluginJavaSearchUtil ;
5651import org .eclipse .pde .internal .core .text .bundle .ExportPackageHeader ;
5752import org .eclipse .pde .internal .core .text .bundle .ImportPackageHeader ;
5853import org .eclipse .pde .internal .core .text .bundle .ImportPackageObject ;
59- import org .eclipse .pde .internal .ui .PDEPlugin ;
6054import org .eclipse .pde .internal .ui .PDEUIMessages ;
6155import org .eclipse .pde .internal .ui .util .TextUtil ;
6256import org .osgi .framework .Constants ;
6357
58+ import aQute .bnd .osgi .Descriptors .PackageRef ;
59+ import aQute .bnd .osgi .Packages ;
60+
6461public class GatherUnusedDependenciesOperation implements IRunnableWithProgress {
6562
6663 static class Requestor extends SearchRequestor {
@@ -77,37 +74,43 @@ public boolean foundMatches() {
7774 }
7875
7976 private final IPluginModelBase fModel ;
80- private ArrayList <Object > fList ;
77+ private List <Object > fList ;
8178
8279 public GatherUnusedDependenciesOperation (IPluginModelBase model ) {
8380 fModel = model ;
8481 }
8582
8683 @ Override
8784 public void run (IProgressMonitor monitor ) throws InvocationTargetException , InterruptedException {
88-
89- ImportPackageObject [] packages = null ;
90- Collection <String > exportedPackages = null ;
91- if (ClasspathUtilCore .hasBundleStructure (fModel )) {
92- IBundle bundle = ((IBundlePluginModelBase ) fModel ).getBundleModel ().getBundle ();
93- IManifestHeader header = bundle .getManifestHeader (Constants .IMPORT_PACKAGE );
94- if (header instanceof ImportPackageHeader ) {
95- packages = ((ImportPackageHeader ) header ).getPackages ();
96- } else if (header != null && header .getValue () != null ) {
97- header = new ImportPackageHeader (Constants .IMPORT_PACKAGE , header .getValue (), bundle ,
98- TextUtil .getDefaultLineDelimiter ());
99- packages = ((ImportPackageHeader ) header ).getPackages ();
100- }
101-
102- header = bundle .getManifestHeader (Constants .EXPORT_PACKAGE );
103- if (header instanceof ExportPackageHeader ) {
104- exportedPackages = ((ExportPackageHeader ) header ).getPackageNames ();
105- } else if (header != null && header .getValue () != null ) {
106- header = new ExportPackageHeader (Constants .EXPORT_PACKAGE , header .getValue (), bundle ,
107- TextUtil .getDefaultLineDelimiter ());
108- exportedPackages = ((ExportPackageHeader ) header ).getPackageNames ();
85+ if (!ClasspathUtilCore .hasBundleStructure (fModel )) {
86+ return ;
87+ }
88+ Set <String > computedPackages ;
89+ try (PdeProjectAnalyzer analyzer = new PdeProjectAnalyzer (fModel .getUnderlyingResource ().getProject (), false )) {
90+ analyzer .setImportPackage ("*" ); //$NON-NLS-1$
91+ analyzer .calcManifest ();
92+ Packages imports = analyzer .getImports ();
93+ if (imports == null ) {
94+ computedPackages = Set .of ();
95+ } else {
96+ computedPackages = imports .keySet ().stream ().map (PackageRef ::getFQN ).collect (Collectors .toSet ());
10997 }
98+ } catch (InterruptedException e ) {
99+ throw e ;
100+ } catch (Exception e ) {
101+ throw new InvocationTargetException (e );
110102 }
103+ ImportPackageObject [] packages = null ;
104+ IBundle bundle = ((IBundlePluginModelBase ) fModel ).getBundleModel ().getBundle ();
105+ IManifestHeader header = bundle .getManifestHeader (Constants .IMPORT_PACKAGE );
106+ if (header instanceof ImportPackageHeader ) {
107+ packages = ((ImportPackageHeader ) header ).getPackages ();
108+ } else if (header != null && header .getValue () != null ) {
109+ header = new ImportPackageHeader (Constants .IMPORT_PACKAGE , header .getValue (), bundle ,
110+ TextUtil .getDefaultLineDelimiter ());
111+ packages = ((ImportPackageHeader ) header ).getPackages ();
112+ }
113+ Collection <String > exportedPackages = getExportedPackages (fModel );
111114 IPluginImport [] imports = fModel .getPluginBase ().getImports ();
112115
113116 int totalWork = imports .length * 3 + (packages != null ? packages .length : 0 ) + 1 ;
@@ -117,58 +120,83 @@ public void run(IProgressMonitor monitor) throws InvocationTargetException, Inte
117120 fList = new ArrayList <>();
118121 for (IPluginImport pluginImport : imports ) {
119122 if (subMonitor .isCanceled ()) {
120- break ;
123+ return ;
121124 }
122- if (isUnused (pluginImport , subMonitor .split (3 ))) {
125+ if (isUnused (pluginImport , computedPackages , subMonitor .split (3 ))) {
123126 fList .add (pluginImport );
124127 } else {
125128 usedPlugins .put (pluginImport .getId (), pluginImport );
126129 }
127130 updateMonitor (subMonitor , fList .size ());
128131 }
129132
130- ArrayList <ImportPackageObject > usedPackages = new ArrayList <>();
133+ List <ImportPackageObject > usedPackages = new ArrayList <>();
131134 if (packages != null && !subMonitor .isCanceled ()) {
132135 for (ImportPackageObject importPackage : packages ) {
133136 if (subMonitor .isCanceled ()) {
134- break ;
137+ return ;
135138 }
136- if (isUnused (importPackage , exportedPackages , subMonitor .split (1 ))) {
139+ if (isUnused (importPackage , exportedPackages , computedPackages , subMonitor .split (1 ))) {
137140 fList .add (importPackage );
138141 updateMonitor (subMonitor , fList .size ());
139142 } else {
140143 usedPackages .add (importPackage );
141144 }
142145 }
143146 }
144- if (!subMonitor .isCanceled ()) {
145- minimizeDependencies (usedPlugins , usedPackages , subMonitor );
147+ if (subMonitor .isCanceled ()) {
148+ return ;
149+ }
150+ minimizeDependencies (usedPlugins , usedPackages , subMonitor );
151+ removeBuddies ();
152+ removeReexported ();
153+ }
146154
155+ private static Collection <String > getExportedPackages (IPluginModelBase model ) {
156+ if (model instanceof IBundlePluginModelBase plugin ) {
157+ IBundleModel bundleModel = plugin .getBundleModel ();
158+ if (bundleModel != null ) {
159+ IBundle bundle = bundleModel .getBundle ();
160+ IManifestHeader header = bundle .getManifestHeader (Constants .EXPORT_PACKAGE );
161+ if (header instanceof ExportPackageHeader pkg ) {
162+ return pkg .getPackageNames ();
163+ } else if (header != null && header .getValue () != null ) {
164+ ExportPackageHeader pkg = new ExportPackageHeader (Constants .EXPORT_PACKAGE , header .getValue (),
165+ bundle , TextUtil .getDefaultLineDelimiter ());
166+ return pkg .getPackageNames ();
167+ }
168+ }
147169 }
148- if ( ClasspathUtilCore . hasBundleStructure ( fModel )) {
149- removeBuddies ();
150- removeReexported ();
170+ BundleDescription bundleDescription = model . getBundleDescription ();
171+ if ( bundleDescription != null ) {
172+ return Arrays . stream ( bundleDescription . getExportPackages ()). map ( ExportPackageDescription :: getName ). toList ();
151173 }
174+ return List .of ();
152175 }
153176
154177 protected void removeBuddies () {
155- IBundle bundle = ((IBundlePluginModelBase ) fModel ).getBundleModel ().getBundle ();
156- IManifestHeader header = bundle .getManifestHeader (EquinoxModuleDataNamespace .REGISTERED_BUDDY_HEADER );
157- if (header != null ) {
158- String values = header .getValue ();
159- String [] registerBud = values .split ("\\ s*,\\ s*" ); //$NON-NLS-1$
160- List <Object > found = new ArrayList <>();
161- for (String string : registerBud ) {
162- for (Object obj : fList ) {
163- if (obj instanceof PluginImport ) {
164- String id = ((PluginImport ) obj ).getId ();
165- if (string .equals (id )) {
166- found .add (obj );
178+ if (fModel instanceof IBundlePluginModelBase plugin ) {
179+ IBundleModel bundleModel = plugin .getBundleModel ();
180+ if (bundleModel != null ) {
181+ IManifestHeader header = bundleModel .getBundle ()
182+ .getManifestHeader (EquinoxModuleDataNamespace .REGISTERED_BUDDY_HEADER );
183+ if (header != null ) {
184+ String values = header .getValue ();
185+ String [] registerBud = values .split ("\\ s*,\\ s*" ); //$NON-NLS-1$
186+ List <Object > found = new ArrayList <>();
187+ for (String string : registerBud ) {
188+ for (Object obj : fList ) {
189+ if (obj instanceof PluginImport ) {
190+ String id = ((PluginImport ) obj ).getId ();
191+ if (string .equals (id )) {
192+ found .add (obj );
193+ }
194+ }
167195 }
168196 }
197+ fList .removeAll (found );
169198 }
170199 }
171- fList .removeAll (found );
172200 }
173201 }
174202
@@ -191,125 +219,29 @@ private void updateMonitor(IProgressMonitor monitor, int size) {
191219 + PDEUIMessages .DependencyExtent_found );
192220 }
193221
194- private boolean isUnused (IPluginImport plugin , IProgressMonitor monitor ) {
222+ private boolean isUnused (IPluginImport plugin , Set < String > computedPackages , IProgressMonitor monitor ) {
195223 IPluginModelBase [] models = PluginJavaSearchUtil .getPluginImports (plugin );
196- return !provideJavaClasses (models , monitor );
197- }
198-
199- private boolean isUnused (ImportPackageObject pkg , Collection <String > exportedPackages , IProgressMonitor monitor ) {
200- if (exportedPackages != null && exportedPackages .contains (pkg .getValue ())) {
201- return false ;
202- }
203- return !provideJavaClasses (pkg , monitor );
204- }
205-
206- private boolean provideJavaClasses (IPluginModelBase [] models , IProgressMonitor monitor ) {
207- try {
208- IProject project = fModel .getUnderlyingResource ().getProject ();
209- if (!project .hasNature (JavaCore .NATURE_ID )) {
210- return false ;
211- }
212-
213- IJavaProject jProject = JavaCore .create (project );
214- IPackageFragment [] packageFragments = PluginJavaSearchUtil .collectPackageFragments (models , jProject , true );
215- SearchEngine engine = new SearchEngine ();
216- IJavaSearchScope searchScope = PluginJavaSearchUtil .createSeachScope (jProject );
217-
218- SubMonitor subMonitor = SubMonitor .convert (monitor , packageFragments .length * 2 );
219- for (IPackageFragment pkgFragment : packageFragments ) {
220- SubMonitor iterationMonitor = subMonitor .split (2 );
221- if (pkgFragment .hasChildren ()) {
222- Requestor requestor = new Requestor ();
223- SearchPattern pattern = SearchPattern .createPattern (pkgFragment , IJavaSearchConstants .REFERENCES );
224- if (pattern == null ) {
225- continue ;
226- }
227- engine .search (pattern ,
228- new SearchParticipant [] { SearchEngine .getDefaultSearchParticipant () }, searchScope ,
229- requestor , iterationMonitor .split (1 ));
230- if (requestor .foundMatches ()) {
231- if (provideJavaClasses (pkgFragment , engine , searchScope ,
232- iterationMonitor .split (1 ))) {
233- return true ;
234- }
235- }
236- }
237- }
238- } catch (CoreException e ) {
239- PDEPlugin .logException (e );
240- }
241- return false ;
242- }
243-
244- private boolean provideJavaClasses (IPackageFragment packageFragment , SearchEngine engine , IJavaSearchScope searchScope , IProgressMonitor monitor ) throws JavaModelException , CoreException {
245- Requestor requestor ;
246- IJavaElement [] children = packageFragment .getChildren ();
247- SubMonitor subMonitor = SubMonitor .convert (monitor , children .length );
248-
249- for (IJavaElement child : children ) {
250- IType [] types = null ;
251- if (child instanceof ICompilationUnit ) {
252- types = ((ICompilationUnit ) child ).getAllTypes ();
253- } else if (child instanceof IOrdinaryClassFile ) {
254- types = new IType [] { ((IOrdinaryClassFile ) child ).getType () };
255- }
256- if (types != null ) {
257- SubMonitor iterationMonitor = subMonitor .split (1 ).setWorkRemaining (types .length );
258- for (IType type : types ) {
259- requestor = new Requestor ();
260- SearchPattern pattern = SearchPattern .createPattern (type , IJavaSearchConstants .REFERENCES );
261- if (pattern == null ) {
262- continue ;
263- }
264- engine .search (pattern ,
265- new SearchParticipant [] { SearchEngine .getDefaultSearchParticipant () }, searchScope ,
266- requestor , iterationMonitor .split (1 ));
267- if (requestor .foundMatches ()) {
268- return true ;
269- }
224+ for (IPluginModelBase model : models ) {
225+ Collection <String > exportedPackages = getExportedPackages (model );
226+ for (String exportedPackage : exportedPackages ) {
227+ if (computedPackages .contains (exportedPackage )) {
228+ return false ;
270229 }
271- } else {
272- subMonitor .worked (1 );
273230 }
274231 }
275- return false ;
232+ return true ;
276233 }
277234
278- private boolean provideJavaClasses (ImportPackageObject pkg , IProgressMonitor monitor ) {
279- try {
280- IProject project = fModel .getUnderlyingResource ().getProject ();
281-
282- if (!project .hasNature (JavaCore .NATURE_ID )) {
283- return false ;
284- }
285-
286- SubMonitor subMonitor = SubMonitor .convert (monitor , 1 );
287- IJavaProject jProject = JavaCore .create (project );
288- SearchEngine engine = new SearchEngine ();
289- IJavaSearchScope searchScope = PluginJavaSearchUtil .createSeachScope (jProject );
290- Requestor requestor = new Requestor ();
291- String packageName = pkg .getName ();
292-
293- SearchPattern pattern = SearchPattern .createPattern (packageName , IJavaSearchConstants .PACKAGE ,
294- IJavaSearchConstants .REFERENCES , SearchPattern .R_EXACT_MATCH );
295- if (pattern == null ) {
296- return false ;
297- }
298- engine .search (
299- pattern ,
300- new SearchParticipant [] { SearchEngine .getDefaultSearchParticipant () }, searchScope , requestor ,
301- subMonitor .split (1 ));
302-
303- if (requestor .foundMatches ()) {
304- return true ;
305- }
306- } catch (CoreException e ) {
307- PDEPlugin .logException (e );
235+ private boolean isUnused (ImportPackageObject pkg , Collection <String > exportedPackages , Set <String > computedPackages ,
236+ IProgressMonitor monitor ) {
237+ if (exportedPackages != null && exportedPackages .contains (pkg .getValue ())) {
238+ return false ;
308239 }
309- return false ;
240+ String name = pkg .getName ();
241+ return !computedPackages .contains (name );
310242 }
311243
312- public ArrayList <Object > getList () {
244+ public List <Object > getList () {
313245 return fList ;
314246 }
315247
@@ -330,7 +262,8 @@ public static void removeDependencies(IPluginModelBase model, Object[] elements)
330262 }
331263 }
332264
333- private void minimizeDependencies (HashMap <String , IPluginImport > usedPlugins , ArrayList <ImportPackageObject > usedPackages , IProgressMonitor monitor ) {
265+ private void minimizeDependencies (Map <String , IPluginImport > usedPlugins , List <ImportPackageObject > usedPackages ,
266+ IProgressMonitor monitor ) {
334267 ListIterator <ImportPackageObject > li = usedPackages .listIterator ();
335268 while (li .hasNext ()) {
336269 ImportPackageObject ipo = li .next ();
0 commit comments