1 /*******************************************************************************
2  *  Copyright (c) 2005, 2018 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  *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 487988
14  *     Martin Karpisek <martin.karpisek@gmail.com> - Bug 351356
15  *******************************************************************************/
16 package org.eclipse.pde.internal.ui.editor.plugin;
17 
18 import java.util.*;
19 import org.eclipse.core.resources.IProject;
20 import org.eclipse.core.resources.IResource;
21 import org.eclipse.core.runtime.*;
22 import org.eclipse.jdt.core.*;
23 import org.eclipse.jdt.ui.ISharedImages;
24 import org.eclipse.jdt.ui.JavaUI;
25 import org.eclipse.jdt.ui.actions.FindReferencesInWorkingSetAction;
26 import org.eclipse.jdt.ui.actions.ShowInPackageViewAction;
27 import org.eclipse.jface.action.*;
28 import org.eclipse.jface.viewers.*;
29 import org.eclipse.jface.window.Window;
30 import org.eclipse.osgi.service.resolver.*;
31 import org.eclipse.pde.core.*;
32 import org.eclipse.pde.core.plugin.*;
33 import org.eclipse.pde.core.target.NameVersionDescriptor;
34 import org.eclipse.pde.internal.core.*;
35 import org.eclipse.pde.internal.core.bundle.BundlePluginBase;
36 import org.eclipse.pde.internal.core.ibundle.*;
37 import org.eclipse.pde.internal.core.project.PDEProject;
38 import org.eclipse.pde.internal.core.text.bundle.*;
39 import org.eclipse.pde.internal.core.util.PDEJavaHelper;
40 import org.eclipse.pde.internal.ui.*;
41 import org.eclipse.pde.internal.ui.editor.*;
42 import org.eclipse.pde.internal.ui.editor.context.InputContextManager;
43 import org.eclipse.pde.internal.ui.parts.ConditionalListSelectionDialog;
44 import org.eclipse.pde.internal.ui.parts.TablePart;
45 import org.eclipse.pde.internal.ui.search.dependencies.UnusedDependenciesAction;
46 import org.eclipse.pde.internal.ui.util.SWTUtil;
47 import org.eclipse.pde.internal.ui.util.TextUtil;
48 import org.eclipse.search.ui.NewSearchUI;
49 import org.eclipse.swt.SWT;
50 import org.eclipse.swt.custom.BusyIndicator;
51 import org.eclipse.swt.graphics.Image;
52 import org.eclipse.swt.layout.GridData;
53 import org.eclipse.swt.widgets.*;
54 import org.eclipse.ui.*;
55 import org.eclipse.ui.actions.ActionFactory;
56 import org.eclipse.ui.forms.widgets.FormToolkit;
57 import org.eclipse.ui.forms.widgets.Section;
58 import org.eclipse.ui.progress.UIJob;
59 import org.osgi.framework.Constants;
60 import org.osgi.framework.Version;
61 
62 public class ImportPackageSection extends TableSection {
63 
64 	private static final int ADD_INDEX = 0;
65 	private static final int REMOVE_INDEX = 1;
66 	private static final int PROPERTIES_INDEX = 2;
67 
68 	private ImportPackageHeader fHeader;
69 
70 	class ImportItemWrapper {
71 		Object fUnderlying;
72 
ImportItemWrapper(Object underlying)73 		public ImportItemWrapper(Object underlying) {
74 			fUnderlying = underlying;
75 		}
76 
77 		@Override
toString()78 		public String toString() {
79 			return getName();
80 		}
81 
82 		@Override
equals(Object obj)83 		public boolean equals(Object obj) {
84 			if (obj instanceof ImportItemWrapper) {
85 				ImportItemWrapper item = (ImportItemWrapper) obj;
86 				return getName().equals(item.getName());
87 			}
88 			return false;
89 		}
90 
91 		@Override
hashCode()92 		public int hashCode() {
93 			return getName().hashCode();
94 		}
95 
getName()96 		public String getName() {
97 			if (fUnderlying instanceof ExportPackageDescription)
98 				return ((ExportPackageDescription) fUnderlying).getName();
99 			if (fUnderlying instanceof IPackageFragment)
100 				return ((IPackageFragment) fUnderlying).getElementName();
101 			if (fUnderlying instanceof ExportPackageObject)
102 				return ((ExportPackageObject) fUnderlying).getName();
103 			return null;
104 		}
105 
getVersion()106 		public Version getVersion() {
107 			if (fUnderlying instanceof ExportPackageDescription)
108 				return ((ExportPackageDescription) fUnderlying).getVersion();
109 			if (fUnderlying instanceof ExportPackageObject) {
110 				String version = ((ExportPackageObject) fUnderlying).getVersion();
111 				if (version != null)
112 					return new Version(version);
113 			}
114 			return null;
115 		}
116 
hasVersion()117 		boolean hasVersion() {
118 			return hasEPD() && ((ExportPackageDescription) fUnderlying).getVersion() != null;
119 		}
120 
hasEPD()121 		boolean hasEPD() {
122 			return fUnderlying instanceof ExportPackageDescription;
123 		}
124 	}
125 
126 	class ImportPackageContentProvider implements IStructuredContentProvider {
127 		@Override
getElements(Object parent)128 		public Object[] getElements(Object parent) {
129 			if (fHeader == null) {
130 				Bundle bundle = (Bundle) getBundle();
131 				fHeader = (ImportPackageHeader) bundle.getManifestHeader(Constants.IMPORT_PACKAGE);
132 			}
133 			return fHeader == null ? new Object[0] : fHeader.getPackages();
134 		}
135 	}
136 
137 	class ImportPackageDialogLabelProvider extends LabelProvider {
138 		@Override
getImage(Object element)139 		public Image getImage(Object element) {
140 			return JavaUI.getSharedImages().getImage(ISharedImages.IMG_OBJS_PACKAGE);
141 		}
142 
143 		@Override
getText(Object element)144 		public String getText(Object element) {
145 			StringBuilder buffer = new StringBuilder();
146 			ImportItemWrapper p = (ImportItemWrapper) element;
147 			buffer.append(p.getName());
148 			Version version = p.getVersion();
149 			if (version != null && !Version.emptyVersion.equals(version)) {
150 				// Bug 183417 - Bidi3.3: Elements' labels in the extensions page in the fragment manifest characters order is incorrect
151 				// add RTL zero length character just before the ( and the LTR character just after to ensure:
152 				// 1. The leading parenthesis takes proper orientation when running in bidi configuration
153 				// 2. The bundle's version is always displayed as LTR.  Otherwise if qualifier contains an alpha,
154 				// 		it would be displayed incorrectly when running RTL.
155 				buffer.append(' ');
156 				buffer.append(PDELabelProvider.formatVersion(version.toString()));
157 			}
158 			return buffer.toString();
159 		}
160 	}
161 
162 	private TableViewer fPackageViewer;
163 
164 	private Action fAddAction;
165 	private Action fGoToAction;
166 	private Action fRemoveAction;
167 	private Action fPropertiesAction;
168 
ImportPackageSection(PDEFormPage page, Composite parent)169 	public ImportPackageSection(PDEFormPage page, Composite parent) {
170 		super(page, parent, Section.DESCRIPTION, new String[] {PDEUIMessages.ImportPackageSection_add, PDEUIMessages.ImportPackageSection_remove, PDEUIMessages.ImportPackageSection_properties});
171 	}
172 
isFragment()173 	private boolean isFragment() {
174 		IPluginModelBase model = (IPluginModelBase) getPage().getPDEEditor().getAggregateModel();
175 		return model != null && model.isFragmentModel();
176 	}
177 
178 	@Override
createClient(Section section, FormToolkit toolkit)179 	protected void createClient(Section section, FormToolkit toolkit) {
180 		section.setText(PDEUIMessages.ImportPackageSection_required);
181 		if (isFragment())
182 			section.setDescription(PDEUIMessages.ImportPackageSection_descFragment);
183 		else
184 			section.setDescription(PDEUIMessages.ImportPackageSection_desc);
185 
186 		Composite container = createClientContainer(section, 2, toolkit);
187 		createViewerPartControl(container, SWT.MULTI, 2, toolkit);
188 		TablePart tablePart = getTablePart();
189 		fPackageViewer = tablePart.getTableViewer();
190 		fPackageViewer.setContentProvider(new ImportPackageContentProvider());
191 		fPackageViewer.setLabelProvider(PDEPlugin.getDefault().getLabelProvider());
192 		fPackageViewer.setComparator(new ViewerComparator() {
193 			@Override
194 			public int compare(Viewer viewer, Object e1, Object e2) {
195 				String s1 = e1.toString();
196 				String s2 = e2.toString();
197 				if (s1.contains(" ")) //$NON-NLS-1$
198 					s1 = s1.substring(0, s1.indexOf(' '));
199 				if (s2.contains(" ")) //$NON-NLS-1$
200 					s2 = s2.substring(0, s2.indexOf(' '));
201 				return super.compare(viewer, s1, s2);
202 			}
203 		});
204 		toolkit.paintBordersFor(container);
205 		section.setClient(container);
206 		section.setLayout(FormLayoutFactory.createClearGridLayout(false, 1));
207 		section.setLayoutData(new GridData(GridData.FILL_BOTH));
208 		makeActions();
209 
210 		IBundleModel model = getBundleModel();
211 		fPackageViewer.setInput(model);
212 		model.addModelChangedListener(this);
213 		updateButtons();
214 	}
215 
216 	@Override
doGlobalAction(String actionId)217 	public boolean doGlobalAction(String actionId) {
218 
219 		if (!isEditable()) {
220 			return false;
221 		}
222 
223 		if (actionId.equals(ActionFactory.DELETE.getId())) {
224 			handleRemove();
225 			return true;
226 		}
227 		if (actionId.equals(ActionFactory.CUT.getId())) {
228 			// delete here and let the editor transfer
229 			// the selection to the clipboard
230 			handleRemove();
231 			return false;
232 		}
233 		if (actionId.equals(ActionFactory.PASTE.getId())) {
234 			doPaste();
235 			return true;
236 		}
237 		return super.doGlobalAction(actionId);
238 	}
239 
240 	@Override
canPaste(Object targetObject, Object[] sourceObjects)241 	protected boolean canPaste(Object targetObject, Object[] sourceObjects) {
242 		// Only non-duplicate import packages can be pasted
243 		for (Object sourceObject : sourceObjects) {
244 			// Only import package objects are allowed
245 			if ((sourceObject instanceof ImportPackageObject) == false) {
246 				return false;
247 			}
248 			// Note:  Should check if the package fragment represented by the
249 			// import package object exists
250 			// (like in org.eclipse.pde.internal.ui.editor.plugin.ImportPackageSection.setElements(ConditionalListSelectionDialog))
251 			// However, the operation is too performance intensive as it
252 			// requires searching all workspace and target plug-in
253 
254 			// If the import package header is not defined, no import packages
255 			// have been defined yet
256 			if (fHeader == null) {
257 				continue;
258 			}
259 			// Only import package objects that have not already been
260 			// specified are allowed (no duplicates)
261 			ImportPackageObject importPackageObject = (ImportPackageObject) sourceObject;
262 			if (fHeader.hasPackage(importPackageObject.getName())) {
263 				return false;
264 			}
265 		}
266 		return true;
267 	}
268 
269 	@Override
dispose()270 	public void dispose() {
271 		IBundleModel model = getBundleModel();
272 		if (model != null)
273 			model.removeModelChangedListener(this);
274 		super.dispose();
275 	}
276 
277 	@Override
doPaste(Object targetObject, Object[] sourceObjects)278 	protected void doPaste(Object targetObject, Object[] sourceObjects) {
279 		// Get the model
280 		IBundleModel model = getBundleModel();
281 		// Ensure the model is defined
282 		if (model == null) {
283 			return;
284 		}
285 		// Get the bundle
286 		IBundle bundle = model.getBundle();
287 		// Paste all source objects
288 		for (Object sourceObject : sourceObjects) {
289 			if (sourceObject instanceof ImportPackageObject) {
290 				ImportPackageObject importPackageObject = (ImportPackageObject) sourceObject;
291 				// Import package object
292 				// Adjust all the source object transient field values to
293 				// acceptable values
294 				importPackageObject.reconnect(model, fHeader, getVersionAttribute());
295 				// Add the object to the header
296 				if (fHeader == null) {
297 					// Import package header not defined yet
298 					// Define one
299 					// Value will get inserted into a new import package object
300 					// created by a factory
301 					// Value needs to be empty string so no import package
302 					// object is created as the initial value
303 					bundle.setHeader(getImportedPackageHeader(), ""); //$NON-NLS-1$
304 				}
305 				// Add the import package to the header
306 				fHeader.addPackage(importPackageObject);
307 			}
308 		}
309 	}
310 
getImportedPackageHeader()311 	private String getImportedPackageHeader() {
312 		return Constants.IMPORT_PACKAGE;
313 	}
314 
315 	@Override
selectionChanged(IStructuredSelection sel)316 	protected void selectionChanged(IStructuredSelection sel) {
317 		getPage().getPDEEditor().setSelection(sel);
318 		updateButtons();
319 	}
320 
updateButtons()321 	private void updateButtons() {
322 		Object[] selected = fPackageViewer.getStructuredSelection().toArray();
323 		int size = selected.length;
324 		TablePart tablePart = getTablePart();
325 		tablePart.setButtonEnabled(ADD_INDEX, isEditable());
326 		tablePart.setButtonEnabled(REMOVE_INDEX, isEditable() && size > 0);
327 		tablePart.setButtonEnabled(PROPERTIES_INDEX, shouldEnableProperties(selected));
328 	}
329 
330 	@Override
handleDoubleClick(IStructuredSelection selection)331 	protected void handleDoubleClick(IStructuredSelection selection) {
332 		handleGoToPackage(selection);
333 	}
334 
335 	@Override
buttonSelected(int index)336 	protected void buttonSelected(int index) {
337 		switch (index) {
338 			case ADD_INDEX :
339 				handleAdd();
340 				break;
341 			case REMOVE_INDEX :
342 				handleRemove();
343 				break;
344 			case PROPERTIES_INDEX :
345 				handleOpenProperties();
346 		}
347 	}
348 
getPackageFragment(ISelection sel)349 	private IPackageFragment getPackageFragment(ISelection sel) {
350 		if (sel instanceof IStructuredSelection) {
351 			IStructuredSelection selection = (IStructuredSelection) sel;
352 			if (selection.size() != 1)
353 				return null;
354 
355 			IBaseModel model = getPage().getModel();
356 			if (!(model instanceof IPluginModelBase))
357 				return null;
358 
359 			return PDEJavaHelper.getPackageFragment(((PackageObject) selection.getFirstElement()).getName(), ((IPluginModelBase) model).getPluginBase().getId(), getPage().getPDEEditor().getCommonProject());
360 		}
361 		return null;
362 	}
363 
handleGoToPackage(ISelection selection)364 	private void handleGoToPackage(ISelection selection) {
365 		IPackageFragment frag = getPackageFragment(selection);
366 		if (frag != null)
367 			try {
368 				IViewPart part = PDEPlugin.getActivePage().showView(JavaUI.ID_PACKAGES);
369 				ShowInPackageViewAction action = new ShowInPackageViewAction(part.getSite());
370 				action.run(frag);
371 			} catch (PartInitException e) {
372 			}
373 	}
374 
handleOpenProperties()375 	private void handleOpenProperties() {
376 		Object[] selected = fPackageViewer.getStructuredSelection().toArray();
377 		ImportPackageObject first = (ImportPackageObject) selected[0];
378 		DependencyPropertiesDialog dialog = new DependencyPropertiesDialog(isEditable(), first);
379 		dialog.create();
380 		PlatformUI.getWorkbench().getHelpSystem().setHelp(dialog.getShell(), IHelpContextIds.IMPORTED_PACKAGE_PROPERTIES);
381 		SWTUtil.setDialogSize(dialog, 400, -1);
382 		if (selected.length == 1)
383 			dialog.setTitle(((ImportPackageObject) selected[0]).getName());
384 		else
385 			dialog.setTitle(PDEUIMessages.ExportPackageSection_props);
386 		if (dialog.open() == Window.OK && isEditable()) {
387 			String newVersion = dialog.getVersion();
388 			boolean newOptional = dialog.isOptional();
389 			for (Object selectedObject : selected) {
390 				ImportPackageObject object = (ImportPackageObject) selectedObject;
391 				if (!newVersion.equals(object.getVersion()))
392 					object.setVersion(newVersion);
393 				if (!newOptional == object.isOptional())
394 					object.setOptional(newOptional);
395 			}
396 		}
397 	}
398 
handleRemove()399 	private void handleRemove() {
400 		Object[] removed = fPackageViewer.getStructuredSelection().toArray();
401 		for (Object removedObject : removed) {
402 			fHeader.removePackage((PackageObject) removedObject);
403 		}
404 	}
405 
handleAdd()406 	private void handleAdd() {
407 		final ConditionalListSelectionDialog dialog = new ConditionalListSelectionDialog(PDEPlugin.getActiveWorkbenchShell(), new ImportPackageDialogLabelProvider(), PDEUIMessages.ImportPackageSection_dialogButtonLabel);
408 		Runnable runnable = () -> {
409 			setElements(dialog);
410 			dialog.setMultipleSelection(true);
411 			dialog.setMessage(PDEUIMessages.ImportPackageSection_exported);
412 			dialog.setTitle(PDEUIMessages.ImportPackageSection_selection);
413 			dialog.create();
414 			PlatformUI.getWorkbench().getHelpSystem().setHelp(dialog.getShell(), IHelpContextIds.IMPORT_PACKAGES);
415 			SWTUtil.setDialogSize(dialog, 400, 500);
416 		};
417 
418 		BusyIndicator.showWhile(Display.getCurrent(), runnable);
419 		if (dialog.open() == Window.OK) {
420 			Object[] selected = dialog.getResult();
421 			if (fHeader != null) {
422 				Set<String> names = new HashSet<>(); // set of String names, do not allow the same package to be added twice
423 				for (int i = 0; i < selected.length; i++) {
424 					ImportPackageObject impObject = null;
425 					if (selected[i] instanceof ImportItemWrapper)
426 						selected[i] = ((ImportItemWrapper) selected[i]).fUnderlying;
427 
428 					if (selected[i] instanceof ExportPackageDescription)
429 						impObject = new ImportPackageObject(fHeader, (ExportPackageDescription) selected[i], getVersionAttribute());
430 					else if (selected[i] instanceof IPackageFragment) {
431 						// non exported package
432 						IPackageFragment fragment = ((IPackageFragment) selected[i]);
433 						impObject = new ImportPackageObject(fHeader, fragment.getElementName(), null, getVersionAttribute());
434 					} else if (selected[i] instanceof ExportPackageObject) {
435 						ExportPackageObject epo = (ExportPackageObject) selected[i];
436 						impObject = new ImportPackageObject(fHeader, epo.getName(), epo.getVersion(), getVersionAttribute());
437 					}
438 					if (impObject != null && names.add(impObject.getName()))
439 						fHeader.addPackage(impObject);
440 				}
441 			} else {
442 				getBundle().setHeader(Constants.IMPORT_PACKAGE, getValue(selected));
443 			}
444 		}
445 	}
446 
getValue(Object[] objects)447 	private String getValue(Object[] objects) {
448 		StringBuilder buffer = new StringBuilder();
449 		for (int i = 0; i < objects.length; i++) {
450 			if (!(objects[i] instanceof ImportItemWrapper))
451 				continue;
452 			Version version = ((ImportItemWrapper) objects[i]).getVersion();
453 			if (buffer.length() > 0)
454 				buffer.append("," + getLineDelimiter() + " "); //$NON-NLS-1$ //$NON-NLS-2$
455 			buffer.append(((ImportItemWrapper) objects[i]).getName());
456 			if (version != null && !version.equals(Version.emptyVersion)) {
457 				buffer.append(";"); //$NON-NLS-1$
458 				buffer.append(getVersionAttribute());
459 				buffer.append("=\""); //$NON-NLS-1$
460 				buffer.append(version.toString());
461 				buffer.append("\""); //$NON-NLS-1$
462 			}
463 		}
464 		return buffer.toString();
465 	}
466 
setElements(ConditionalListSelectionDialog dialog)467 	private void setElements(ConditionalListSelectionDialog dialog) {
468 		Set<String> forbidden = getForbiddenIds();
469 		boolean allowJava = "true".equals(getBundle().getHeader(ICoreConstants.ECLIPSE_JREBUNDLE)); //$NON-NLS-1$
470 
471 		ArrayList<ImportItemWrapper> elements = new ArrayList<>();
472 		ArrayList<ImportItemWrapper> conditional = new ArrayList<>();
473 		IPluginModelBase[] models = PluginRegistry.getActiveModels();
474 		Set<NameVersionDescriptor> nameVersions = new HashSet<>(); // Set of NameVersionDescriptors, used to remove duplicate entries
475 
476 		for (IPluginModelBase pluginModel : models) {
477 			BundleDescription desc = pluginModel.getBundleDescription();
478 
479 			// If the current model is a fragment, it can export packages only if its parent has hasExtensibleAPI set
480 			if (isFragmentThatCannotExportPackages(pluginModel))
481 				continue;
482 
483 			String id = desc == null ? null : desc.getSymbolicName();
484 			if (id == null || forbidden.contains(id))
485 				continue;
486 
487 			ExportPackageDescription[] exported = desc.getExportPackages();
488 			for (ExportPackageDescription exportedPackage : exported) {
489 				String name = exportedPackage.getName();
490 				NameVersionDescriptor nameVersion = new NameVersionDescriptor(exportedPackage.getName(), exportedPackage.getVersion().toString(), NameVersionDescriptor.TYPE_PACKAGE);
491 				if (("java".equals(name) || name.startsWith("java.")) && !allowJava) //$NON-NLS-1$ //$NON-NLS-2$
492 					continue;
493 				if (nameVersions.add(nameVersion) && (fHeader == null || !fHeader.hasPackage(name)))
494 					elements.add(new ImportItemWrapper(exportedPackage));
495 			}
496 			IPluginModelBase model = (IPluginModelBase) getPage().getPDEEditor().getAggregateModel();
497 			if (model instanceof IBundlePluginModelBase) {
498 				IBundleModel bmodel = ((IBundlePluginModelBase) model).getBundleModel();
499 				if (bmodel != null) {
500 					ExportPackageHeader header = (ExportPackageHeader) bmodel.getBundle().getManifestHeader(Constants.EXPORT_PACKAGE);
501 					if (header != null) {
502 						ExportPackageObject[] pkgs = header.getPackages();
503 						for (ExportPackageObject pkg : pkgs) {
504 							String name = pkg.getName();
505 							String version = pkg.getVersion();
506 							NameVersionDescriptor nameVersion = new NameVersionDescriptor(name, version, NameVersionDescriptor.TYPE_PACKAGE);
507 							if (nameVersions.add(nameVersion) && (fHeader == null || !fHeader.hasPackage(name)))
508 								elements.add(new ImportItemWrapper(pkg));
509 						}
510 					}
511 				}
512 
513 			}
514 		}
515 		for (IPluginModelBase model : models) {
516 			try {
517 				// add un-exported packages in workspace non-binary plug-ins
518 				IResource resource = model.getUnderlyingResource();
519 				IProject project = resource != null ? resource.getProject() : null;
520 				if (project == null || !project.hasNature(JavaCore.NATURE_ID) || WorkspaceModelManager.isBinaryProject(project) || !PDEProject.getManifest(project).exists())
521 					continue;
522 
523 				// If the current model is a fragment, it can export packages only if its parent has hasExtensibleAPI set
524 				if (isFragmentThatCannotExportPackages(model))
525 					continue;
526 
527 				IJavaProject jp = JavaCore.create(project);
528 				IPackageFragmentRoot[] roots = jp.getPackageFragmentRoots();
529 				for (int j = 0; j < roots.length; j++) {
530 					if (roots[j].getKind() == IPackageFragmentRoot.K_SOURCE || (roots[j].getKind() == IPackageFragmentRoot.K_BINARY && !roots[j].isExternal())) {
531 						IJavaElement[] children = roots[j].getChildren();
532 						for (IJavaElement child : children) {
533 							IPackageFragment f = (IPackageFragment) child;
534 							String name = f.getElementName();
535 							NameVersionDescriptor nameVersion = new NameVersionDescriptor(name, null, NameVersionDescriptor.TYPE_PACKAGE);
536 							if (name.equals("")) //$NON-NLS-1$
537 								name = "."; //$NON-NLS-1$
538 							if (nameVersions.add(nameVersion) && (f.hasChildren() || f.getNonJavaResources().length > 0))
539 								conditional.add(new ImportItemWrapper(f));
540 						}
541 					}
542 				}
543 			} catch (CoreException e) {
544 			}
545 		}
546 		dialog.setElements(elements.toArray());
547 		dialog.setConditionalElements(conditional.toArray());
548 	}
549 
550 	/**
551 	 * Returns whether the provided plug-in model is a fragment that cannot export
552 	 * its packages to other bundles (<code>hasExtensibleAPI</code> is not set).  Will
553 	 * return false if the model does not represent a fragment.
554 	 *
555 	 * @param fragment the model to test
556 	 * @return <code>true</code> if the model is a fragment that cannot export packages
557 	 */
isFragmentThatCannotExportPackages(IPluginModelBase fragment)558 	private boolean isFragmentThatCannotExportPackages(IPluginModelBase fragment) {
559 		if (!fragment.isFragmentModel()) {
560 			// Not a fragment
561 			return false;
562 		}
563 		BundleDescription bundleDescription = fragment.getBundleDescription();
564 		if (bundleDescription == null) {
565 			// Classic plugin, do not change the behavior
566 			return false;
567 		}
568 		HostSpecification hostSpec = bundleDescription.getHost();
569 		if (hostSpec == null) {
570 			// Not a fragment
571 			return false;
572 		}
573 		BundleDescription[] hosts = hostSpec.getHosts();
574 		// At least one of fragment hosts has to have extensible API
575 		for (BundleDescription host : hosts) {
576 			if (ClasspathUtilCore.hasExtensibleAPI(PluginRegistry.findModel(host)))
577 				return false;
578 		}
579 		// Fragment that cannot export
580 		return true;
581 	}
582 
583 	@Override
modelChanged(final IModelChangedEvent event)584 	public void modelChanged(final IModelChangedEvent event) {
585 		if (event.getChangeType() == IModelChangedEvent.WORLD_CHANGED) {
586 			fHeader = null;
587 			markStale();
588 			return;
589 		}
590 
591 		// Model change may have come from a non UI thread such as the auto add dependencies operation. See bug 333533
592 		UIJob job = new UIJob("Update package imports") { //$NON-NLS-1$
593 			@Override
594 			public IStatus runInUIThread(IProgressMonitor monitor) {
595 				if (Constants.IMPORT_PACKAGE.equals(event.getChangedProperty())) {
596 					refresh();
597 					// Bug 171896
598 					// Since the model sends a CHANGE event instead of
599 					// an INSERT event on the very first addition to the empty table
600 					// Selection should fire here to take this first insertion into account
601 					Object lastElement = fPackageViewer.getElementAt(fPackageViewer.getTable().getItemCount() - 1);
602 					if (lastElement != null) {
603 						fPackageViewer.setSelection(new StructuredSelection(lastElement));
604 					}
605 					return Status.OK_STATUS;
606 				}
607 
608 				Object[] objects = event.getChangedObjects();
609 				for (Object changedObject : objects) {
610 					if (changedObject instanceof ImportPackageObject) {
611 						ImportPackageObject object = (ImportPackageObject) changedObject;
612 						switch (event.getChangeType()) {
613 							case IModelChangedEvent.INSERT :
614 								fPackageViewer.remove(object); // If another thread has modified the header, avoid creating a duplicate
615 								fPackageViewer.add(object);
616 								fPackageViewer.setSelection(new StructuredSelection(object));
617 								fPackageViewer.getTable().setFocus();
618 								break;
619 							case IModelChangedEvent.REMOVE :
620 								Table table = fPackageViewer.getTable();
621 								int index = table.getSelectionIndex();
622 								fPackageViewer.remove(object);
623 								table.setSelection(index < table.getItemCount() ? index : table.getItemCount() - 1);
624 								updateButtons();
625 								break;
626 							default :
627 								fPackageViewer.refresh(object);
628 						}
629 					}
630 				}
631 				return Status.OK_STATUS;
632 			}
633 		};
634 		job.setSystem(true);
635 		job.schedule();
636 	}
637 
638 	@Override
refresh()639 	public void refresh() {
640 		fPackageViewer.refresh();
641 		super.refresh();
642 	}
643 
makeActions()644 	private void makeActions() {
645 		fAddAction = new Action(PDEUIMessages.RequiresSection_add) {
646 			@Override
647 			public void run() {
648 				handleAdd();
649 			}
650 		};
651 		fAddAction.setEnabled(isEditable());
652 		fGoToAction = new Action(PDEUIMessages.ImportPackageSection_goToPackage) {
653 			@Override
654 			public void run() {
655 				handleGoToPackage(fPackageViewer.getStructuredSelection());
656 			}
657 		};
658 		fRemoveAction = new Action(PDEUIMessages.RequiresSection_delete) {
659 			@Override
660 			public void run() {
661 				handleRemove();
662 			}
663 		};
664 		fRemoveAction.setEnabled(isEditable());
665 
666 		fPropertiesAction = new Action(PDEUIMessages.ImportPackageSection_propertyAction) {
667 			@Override
668 			public void run() {
669 				handleOpenProperties();
670 			}
671 		};
672 	}
673 
674 	@Override
fillContextMenu(IMenuManager manager)675 	protected void fillContextMenu(IMenuManager manager) {
676 		final IStructuredSelection selection = fPackageViewer.getStructuredSelection();
677 		manager.add(fAddAction);
678 		boolean singleSelection = selection.size() == 1;
679 		if (singleSelection)
680 			manager.add(fGoToAction);
681 		manager.add(new Separator());
682 		if (!selection.isEmpty())
683 			manager.add(fRemoveAction);
684 		getPage().getPDEEditor().getContributor().contextMenuAboutToShow(manager);
685 
686 		if (((IModel) getPage().getModel()).getUnderlyingResource() != null) {
687 			manager.add(new Separator());
688 			if (singleSelection) {
689 				manager.add(new Action(PDEUIMessages.DependencyExtentSearchResultPage_referencesInPlugin) {
690 					@Override
691 					public void run() {
692 						doReferenceSearch(selection);
693 					}
694 				});
695 			}
696 			manager.add(new UnusedDependenciesAction((IPluginModelBase) getPage().getModel(), false));
697 		}
698 
699 		if (shouldEnableProperties(fPackageViewer.getStructuredSelection().toArray())) {
700 			manager.add(new Separator());
701 			manager.add(fPropertiesAction);
702 		}
703 	}
704 
doReferenceSearch(final ISelection sel)705 	private void doReferenceSearch(final ISelection sel) {
706 		IPackageFragmentRoot[] roots = null;
707 		try {
708 			roots = getSourceRoots();
709 		} catch (JavaModelException e) {
710 		}
711 		final IPackageFragment fragment = getPackageFragment(sel);
712 		if (fragment != null && roots != null) {
713 			IWorkingSetManager manager = PlatformUI.getWorkbench().getWorkingSetManager();
714 			IWorkingSet set = manager.createWorkingSet("temp", roots); //$NON-NLS-1$
715 			new FindReferencesInWorkingSetAction(getPage().getEditorSite(), new IWorkingSet[] {set}).run(fragment);
716 			manager.removeWorkingSet(set);
717 		} else if (sel instanceof IStructuredSelection) {
718 			IStructuredSelection selection = (IStructuredSelection) sel;
719 			PackageObject importObject = (PackageObject) selection.getFirstElement();
720 			NewSearchUI.runQueryInBackground(new BlankQuery(importObject));
721 		}
722 	}
723 
getSourceRoots()724 	private IPackageFragmentRoot[] getSourceRoots() throws JavaModelException {
725 		ArrayList<IPackageFragmentRoot> result = new ArrayList<>();
726 		IProject project = getPage().getPDEEditor().getCommonProject();
727 		// would normally return array of size 0, but by returning null can optimize the search to run faster.
728 		if (project == null) {
729 			return null;
730 		}
731 		IPackageFragmentRoot[] roots = JavaCore.create(project).getPackageFragmentRoots();
732 		for (int i = 0; i < roots.length; i++) {
733 			if (roots[i].getKind() == IPackageFragmentRoot.K_SOURCE || (roots[i].isArchive() && !roots[i].isExternal()))
734 				result.add(roots[i]);
735 		}
736 		return result.toArray(new IPackageFragmentRoot[result.size()]);
737 	}
738 
getBundleContext()739 	private BundleInputContext getBundleContext() {
740 		InputContextManager manager = getPage().getPDEEditor().getContextManager();
741 		return (BundleInputContext) manager.findContext(BundleInputContext.CONTEXT_ID);
742 	}
743 
getBundleModel()744 	private IBundleModel getBundleModel() {
745 		BundleInputContext context = getBundleContext();
746 		return (context != null) ? (IBundleModel) context.getModel() : null;
747 
748 	}
749 
getLineDelimiter()750 	private String getLineDelimiter() {
751 		BundleInputContext inputContext = getBundleContext();
752 		if (inputContext != null) {
753 			return inputContext.getLineDelimiter();
754 		}
755 		return TextUtil.getDefaultLineDelimiter();
756 	}
757 
getBundle()758 	private IBundle getBundle() {
759 		IBundleModel model = getBundleModel();
760 		return (model != null) ? model.getBundle() : null;
761 	}
762 
getVersionAttribute()763 	private String getVersionAttribute() {
764 		return getVersionAttribute(getBundle());
765 	}
766 
getVersionAttribute(IBundle bundle)767 	private String getVersionAttribute(IBundle bundle) {
768 		int manifestVersion = BundlePluginBase.getBundleManifestVersion(bundle);
769 		return (manifestVersion < 2) ? ICoreConstants.PACKAGE_SPECIFICATION_VERSION : Constants.VERSION_ATTRIBUTE;
770 	}
771 
getForbiddenIds()772 	private Set<String> getForbiddenIds() {
773 		HashSet<String> set = new HashSet<>();
774 		IPluginModelBase model = (IPluginModelBase) getPage().getPDEEditor().getAggregateModel();
775 		if (model == null) {
776 			return set;
777 		}
778 		String id = model.getPluginBase().getId();
779 		if (id != null)
780 			set.add(id);
781 		IPluginImport[] imports = model.getPluginBase().getImports();
782 		State state = TargetPlatformHelper.getState();
783 		for (IPluginImport pluginImport : imports) {
784 			addDependency(state, pluginImport.getId(), set);
785 		}
786 		return set;
787 	}
788 
addDependency(State state, String bundleID, Set<String> set)789 	private void addDependency(State state, String bundleID, Set<String> set) {
790 		if (bundleID == null || !set.add(bundleID))
791 			return;
792 
793 		BundleDescription desc = state.getBundle(bundleID, null);
794 		if (desc == null)
795 			return;
796 
797 		BundleDescription[] fragments = desc.getFragments();
798 		for (BundleDescription fragment : fragments) {
799 			addDependency(state, fragment.getSymbolicName(), set);
800 		}
801 
802 		BundleSpecification[] specs = desc.getRequiredBundles();
803 		for (BundleSpecification spec : specs) {
804 			if (spec.isResolved() && spec.isExported()) {
805 				addDependency(state, spec.getName(), set);
806 			}
807 		}
808 	}
809 
810 	@Override
createCount()811 	protected boolean createCount() {
812 		return true;
813 	}
814 
shouldEnableProperties(Object[] selected)815 	private boolean shouldEnableProperties(Object[] selected) {
816 		if (selected.length == 0)
817 			return false;
818 		if (selected.length == 1)
819 			return true;
820 
821 		String version = ((ImportPackageObject) selected[0]).getVersion();
822 		boolean optional = ((ImportPackageObject) selected[0]).isOptional();
823 		for (int i = 1; i < selected.length; i++) {
824 			ImportPackageObject object = (ImportPackageObject) selected[i];
825 			if (version == null) {
826 				if (object.getVersion() != null || !(optional == object.isOptional())) {
827 					return false;
828 				}
829 			} else if (!version.equals(object.getVersion()) || !(optional == object.isOptional())) {
830 				return false;
831 			}
832 		}
833 		return true;
834 	}
835 
836 }
837