1 /*******************************************************************************
2  *  Copyright (c) 2005, 2017 IBM Corporation and others.
3  *
4  *  This program and the accompanying materials
5  *  are made available under the terms of the Eclipse Public License 2.0
6  *  which accompanies this distribution, and is available at
7  *  https://www.eclipse.org/legal/epl-2.0/
8  *
9  *  SPDX-License-Identifier: EPL-2.0
10  *
11  *  Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Johannes Ahlers <Johannes.Ahlers@gmx.de> - bug 477677
14  *******************************************************************************/
15 package org.eclipse.pde.internal.ui.search.dependencies;
16 
17 import java.lang.reflect.InvocationTargetException;
18 import java.util.*;
19 import org.eclipse.core.resources.IProject;
20 import org.eclipse.core.runtime.*;
21 import org.eclipse.jdt.core.*;
22 import org.eclipse.jdt.core.search.*;
23 import org.eclipse.jface.operation.IRunnableWithProgress;
24 import org.eclipse.pde.core.plugin.*;
25 import org.eclipse.pde.internal.core.ClasspathUtilCore;
26 import org.eclipse.pde.internal.core.ibundle.*;
27 import org.eclipse.pde.internal.core.search.PluginJavaSearchUtil;
28 import org.eclipse.pde.internal.core.text.bundle.*;
29 import org.eclipse.pde.internal.ui.PDEPlugin;
30 import org.eclipse.pde.internal.ui.PDEUIMessages;
31 import org.eclipse.pde.internal.ui.util.TextUtil;
32 import org.osgi.framework.Constants;
33 
34 public class GatherUnusedDependenciesOperation implements IRunnableWithProgress {
35 
36 	class Requestor extends SearchRequestor {
37 		boolean fFound = false;
38 
39 		@Override
acceptSearchMatch(SearchMatch match)40 		public void acceptSearchMatch(SearchMatch match) throws CoreException {
41 			fFound = true;
42 		}
43 
foundMatches()44 		public boolean foundMatches() {
45 			return fFound;
46 		}
47 	}
48 
49 	private IPluginModelBase fModel;
50 	private ArrayList<Object> fList;
51 
GatherUnusedDependenciesOperation(IPluginModelBase model)52 	public GatherUnusedDependenciesOperation(IPluginModelBase model) {
53 		fModel = model;
54 	}
55 
56 	@Override
run(IProgressMonitor monitor)57 	public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
58 
59 		ImportPackageObject[] packages = null;
60 		Collection<?> exportedPackages = null;
61 		if (ClasspathUtilCore.hasBundleStructure(fModel)) {
62 			IBundle bundle = ((IBundlePluginModelBase) fModel).getBundleModel().getBundle();
63 			IManifestHeader header = bundle.getManifestHeader(Constants.IMPORT_PACKAGE);
64 			if (header instanceof ImportPackageHeader) {
65 				packages = ((ImportPackageHeader) header).getPackages();
66 			} else if (header != null && header.getValue() != null) {
67 				header = new ImportPackageHeader(Constants.IMPORT_PACKAGE, header.getValue(), bundle,
68 						TextUtil.getDefaultLineDelimiter());
69 				packages = ((ImportPackageHeader) header).getPackages();
70 			}
71 
72 			header = bundle.getManifestHeader(Constants.EXPORT_PACKAGE);
73 			if (header instanceof ExportPackageHeader) {
74 				exportedPackages = ((ExportPackageHeader) header).getPackageNames();
75 			} else if (header != null && header.getValue() != null) {
76 				header = new ExportPackageHeader(Constants.EXPORT_PACKAGE, header.getValue(), bundle,
77 						TextUtil.getDefaultLineDelimiter());
78 				exportedPackages = ((ExportPackageHeader) header).getPackageNames();
79 			}
80 		}
81 		IPluginImport[] imports = fModel.getPluginBase().getImports();
82 
83 		int totalWork = imports.length * 3 + (packages != null ? packages.length : 0) + 1;
84 		SubMonitor subMonitor = SubMonitor.convert(monitor, totalWork);
85 
86 		HashMap<String, IPluginImport> usedPlugins = new HashMap<>();
87 		fList = new ArrayList<>();
88 		for (IPluginImport pluginImport : imports) {
89 			if (subMonitor.isCanceled())
90 				break;
91 			if (isUnused(pluginImport, subMonitor.split(3))) {
92 				fList.add(pluginImport);
93 			} else
94 				usedPlugins.put(pluginImport.getId(), pluginImport);
95 			updateMonitor(subMonitor, fList.size());
96 		}
97 
98 		ArrayList<ImportPackageObject> usedPackages = new ArrayList<>();
99 		if (packages != null && !subMonitor.isCanceled()) {
100 			for (ImportPackageObject importPackage : packages) {
101 				if (subMonitor.isCanceled())
102 					break;
103 				if (isUnused(importPackage, exportedPackages, subMonitor.split(1))) {
104 					fList.add(importPackage);
105 					updateMonitor(subMonitor, fList.size());
106 				} else
107 					usedPackages.add(importPackage);
108 			}
109 		}
110 		if (!subMonitor.isCanceled()) {
111 			minimizeDependencies(usedPlugins, usedPackages, subMonitor);
112 		}
113 	}
114 
updateMonitor(IProgressMonitor monitor, int size)115 	private void updateMonitor(IProgressMonitor monitor, int size) {
116 		monitor.setTaskName(PDEUIMessages.UnusedDependencies_analyze + size + " " //$NON-NLS-1$
117 				+ PDEUIMessages.UnusedDependencies_unused + " " //$NON-NLS-1$
118 				+ (size == 1 ? PDEUIMessages.DependencyExtent_singular : PDEUIMessages.DependencyExtent_plural) + " " //$NON-NLS-1$
119 				+ PDEUIMessages.DependencyExtent_found);
120 	}
121 
isUnused(IPluginImport plugin, IProgressMonitor monitor)122 	private boolean isUnused(IPluginImport plugin, IProgressMonitor monitor) {
123 		IPluginModelBase[] models = PluginJavaSearchUtil.getPluginImports(plugin);
124 		return !provideJavaClasses(models, monitor);
125 	}
126 
isUnused(ImportPackageObject pkg, Collection<?> exportedPackages, IProgressMonitor monitor)127 	private boolean isUnused(ImportPackageObject pkg, Collection<?> exportedPackages, IProgressMonitor monitor) {
128 		if (exportedPackages != null && exportedPackages.contains(pkg.getValue())) {
129 			return false;
130 		}
131 		return !provideJavaClasses(pkg, monitor);
132 	}
133 
provideJavaClasses(IPluginModelBase[] models, IProgressMonitor monitor)134 	private boolean provideJavaClasses(IPluginModelBase[] models, IProgressMonitor monitor) {
135 		try {
136 			IProject project = fModel.getUnderlyingResource().getProject();
137 			if (!project.hasNature(JavaCore.NATURE_ID))
138 				return false;
139 
140 			IJavaProject jProject = JavaCore.create(project);
141 			IPackageFragment[] packageFragments = PluginJavaSearchUtil.collectPackageFragments(models, jProject, true);
142 			SearchEngine engine = new SearchEngine();
143 			IJavaSearchScope searchScope = PluginJavaSearchUtil.createSeachScope(jProject);
144 
145 			SubMonitor subMonitor = SubMonitor.convert(monitor, packageFragments.length * 2);
146 			for (IPackageFragment pkgFragment : packageFragments) {
147 				SubMonitor iterationMonitor = subMonitor.split(2);
148 				if (pkgFragment.hasChildren()) {
149 					Requestor requestor = new Requestor();
150 					engine.search(SearchPattern.createPattern(pkgFragment, IJavaSearchConstants.REFERENCES),
151 							new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }, searchScope,
152 							requestor, iterationMonitor.split(1));
153 					if (requestor.foundMatches()) {
154 						if (provideJavaClasses(pkgFragment, engine, searchScope,
155 								iterationMonitor.split(1))) {
156 							return true;
157 						}
158 					}
159 				}
160 			}
161 		} catch (CoreException e) {
162 			PDEPlugin.logException(e);
163 		}
164 		return false;
165 	}
166 
provideJavaClasses(IPackageFragment packageFragment, SearchEngine engine, IJavaSearchScope searchScope, IProgressMonitor monitor)167 	private boolean provideJavaClasses(IPackageFragment packageFragment, SearchEngine engine, IJavaSearchScope searchScope, IProgressMonitor monitor) throws JavaModelException, CoreException {
168 		Requestor requestor;
169 		IJavaElement[] children = packageFragment.getChildren();
170 		SubMonitor subMonitor = SubMonitor.convert(monitor, children.length);
171 
172 		for (IJavaElement child : children) {
173 			IType[] types = null;
174 			if (child instanceof ICompilationUnit) {
175 				types = ((ICompilationUnit) child).getAllTypes();
176 			} else if (child instanceof IOrdinaryClassFile) {
177 				types = new IType[] { ((IOrdinaryClassFile) child).getType() };
178 			}
179 			if (types != null) {
180 				SubMonitor iterationMonitor = subMonitor.split(1).setWorkRemaining(types.length);
181 				for (IType type : types) {
182 					requestor = new Requestor();
183 					engine.search(SearchPattern.createPattern(type, IJavaSearchConstants.REFERENCES),
184 							new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }, searchScope,
185 							requestor, iterationMonitor.split(1));
186 					if (requestor.foundMatches()) {
187 						return true;
188 					}
189 				}
190 			} else {
191 				subMonitor.worked(1);
192 			}
193 		}
194 		return false;
195 	}
196 
provideJavaClasses(ImportPackageObject pkg, IProgressMonitor monitor)197 	private boolean provideJavaClasses(ImportPackageObject pkg, IProgressMonitor monitor) {
198 		try {
199 			IProject project = fModel.getUnderlyingResource().getProject();
200 
201 			if (!project.hasNature(JavaCore.NATURE_ID))
202 				return false;
203 
204 			SubMonitor subMonitor = SubMonitor.convert(monitor, 1);
205 			IJavaProject jProject = JavaCore.create(project);
206 			SearchEngine engine = new SearchEngine();
207 			IJavaSearchScope searchScope = PluginJavaSearchUtil.createSeachScope(jProject);
208 			Requestor requestor = new Requestor();
209 			String packageName = pkg.getName();
210 
211 			engine.search(
212 					SearchPattern.createPattern(packageName, IJavaSearchConstants.PACKAGE,
213 							IJavaSearchConstants.REFERENCES, SearchPattern.R_EXACT_MATCH),
214 					new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }, searchScope, requestor,
215 					subMonitor.split(1));
216 
217 			if (requestor.foundMatches())
218 				return true;
219 		} catch (CoreException e) {
220 			PDEPlugin.logException(e);
221 		}
222 		return false;
223 	}
224 
getList()225 	public ArrayList<Object> getList() {
226 		return fList;
227 	}
228 
removeDependencies(IPluginModelBase model, Object[] elements)229 	public static void removeDependencies(IPluginModelBase model, Object[] elements) {
230 		ImportPackageHeader pkgHeader = null;
231 		for (Object element : elements) {
232 			if (element instanceof IPluginImport)
233 				try {
234 					model.getPluginBase().remove((IPluginImport) element);
235 				} catch (CoreException e) {
236 				}
237 			else if (element instanceof ImportPackageObject) {
238 				if (pkgHeader == null)
239 					pkgHeader = (ImportPackageHeader) ((ImportPackageObject) element).getHeader();
240 				pkgHeader.removePackage((ImportPackageObject) element);
241 			}
242 		}
243 	}
244 
minimizeDependencies(HashMap<String, IPluginImport> usedPlugins, ArrayList<ImportPackageObject> usedPackages, IProgressMonitor monitor)245 	private void minimizeDependencies(HashMap<String, IPluginImport> usedPlugins, ArrayList<ImportPackageObject> usedPackages, IProgressMonitor monitor) {
246 		ListIterator<ImportPackageObject> li = usedPackages.listIterator();
247 		while (li.hasNext()) {
248 			ImportPackageObject ipo = li.next();
249 			String bundle = ipo.getAttribute(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE);
250 			if (usedPlugins.containsKey(bundle))
251 				fList.add(ipo);
252 		}
253 
254 		Iterator<String> it = usedPlugins.keySet().iterator();
255 		ArrayDeque<String> plugins = new ArrayDeque<>();
256 		while (it.hasNext())
257 			plugins.push(it.next().toString());
258 		SubMonitor subMonitor = SubMonitor.convert(monitor);
259 		while (!(plugins.isEmpty())) {
260 			String pluginId = plugins.pop();
261 			IPluginModelBase base = PluginRegistry.findModel(pluginId);
262 			if (base == null)
263 				continue;
264 			IPluginImport[] imports = base.getPluginBase().getImports();
265 			SubMonitor iterationMonitor = subMonitor.setWorkRemaining(Math.max(plugins.size() + 1, 5)).split(1)
266 					.setWorkRemaining(imports.length);
267 			for (IPluginImport imp : imports) {
268 				if (imp.isReexported()) {
269 					String reExportedId = imp.getId();
270 					if (reExportedId != null && reExportedId.equals(pluginId)) {
271 						continue;
272 					}
273 					Object pluginImport = usedPlugins.remove(imp.getId());
274 					if (pluginImport != null) {
275 						fList.add(pluginImport);
276 						updateMonitor(iterationMonitor, fList.size());
277 					}
278 					plugins.push(reExportedId);
279 				}
280 				iterationMonitor.worked(1);
281 			}
282 		}
283 	}
284 }
285