diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/plugin/PluginRegistry.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/plugin/PluginRegistry.java
index 85cda7d26a..0b442f897f 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/plugin/PluginRegistry.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/plugin/PluginRegistry.java
@@ -407,13 +407,15 @@ private static IPluginModelBase[] findModels(String id) {
* @since 3.7
*/
public static IBuildModel createBuildModel(IPluginModelBase model) throws CoreException {
- IProject project = model.getUnderlyingResource().getProject();
- if (project != null) {
- IFile buildFile = PDEProject.getBuildProperties(project);
- if (buildFile.exists()) {
- IBuildModel buildModel = new WorkspaceBuildModel(buildFile);
- buildModel.load();
- return buildModel;
+ if (model != null) {
+ IProject project = model.getUnderlyingResource().getProject();
+ if (project != null) {
+ IFile buildFile = PDEProject.getBuildProperties(project);
+ if (buildFile.exists()) {
+ IBuildModel buildModel = new WorkspaceBuildModel(buildFile);
+ buildModel.load();
+ return buildModel;
+ }
}
}
return null;
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/natures/BndProject.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/natures/BndProject.java
index acba547265..dce3690fe4 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/natures/BndProject.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/natures/BndProject.java
@@ -25,6 +25,8 @@ public class BndProject extends BaseProject {
public static final String INSTRUCTIONS_FILE = "pde" + INSTRUCTIONS_FILE_EXTENSION; //$NON-NLS-1$
+ public static final String BUILDER_ID = "org.eclipse.pde.BndBuilder"; //$NON-NLS-1$
+
@Override
public void configure() throws CoreException {
addToBuildSpec(BndBuilder.BUILDER_ID);
diff --git a/ui/org.eclipse.pde.ui/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.ui/META-INF/MANIFEST.MF
index ed03c0b73a..6a024241ea 100644
--- a/ui/org.eclipse.pde.ui/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.ui/META-INF/MANIFEST.MF
@@ -118,9 +118,11 @@ Require-Bundle:
org.eclipse.help;bundle-version="[3.10.200,4.0.0)",
org.eclipse.equinox.bidi;bundle-version="[1.4.300,2.0.0)",
org.eclipse.equinox.security;bundle-version="[1.4.100,2.0.0)",
- org.eclipse.pde.bnd.ui;bundle-version="1.0.0"
+ org.eclipse.pde.bnd.ui;bundle-version="1.0.0",
+ org.eclipse.pde.ds.core;bundle-version="1.3.300"
Import-Package: aQute.bnd.build;version="4.5.0",
aQute.bnd.build.model;version="[4.2.0,5.0.0)",
+ aQute.bnd.build.model.clauses;version="2.5.0",
aQute.bnd.header;version="[2.5.0,3.0.0)",
aQute.bnd.help;version="2.0.0",
aQute.bnd.osgi;version="[5.6.0,8.0.0)",
diff --git a/ui/org.eclipse.pde.ui/plugin.properties b/ui/org.eclipse.pde.ui/plugin.properties
index f2465f6d2e..10565a463f 100644
--- a/ui/org.eclipse.pde.ui/plugin.properties
+++ b/ui/org.eclipse.pde.ui/plugin.properties
@@ -267,6 +267,8 @@ command.organizeManifests.name = Organize Manifests
command.organizeManifests.label= &Organize Manifests...
command.organizeManifests.description = Cleans up plug-in manifest files
+command.convertAutomaticManifest.label=Convert to &Automatic Manifest Generation...
+
command.externalizeStrings.description = Extract translatable strings from plug-in files
command.externalizeStrings.name = Externalize Strings in Plug-ins
command.externalizeStrings.label = E&xternalize Strings...
diff --git a/ui/org.eclipse.pde.ui/plugin.xml b/ui/org.eclipse.pde.ui/plugin.xml
index cfefb53d71..87b0b9a1df 100644
--- a/ui/org.eclipse.pde.ui/plugin.xml
+++ b/ui/org.eclipse.pde.ui/plugin.xml
@@ -1712,6 +1712,10 @@
class="org.eclipse.pde.internal.ui.wizards.tools.OrganizeManifestsAction"
commandId="org.eclipse.pde.ui.organizeManifest">
+
+
@@ -1952,9 +1956,25 @@
+
+
+
+
+
+
+
+
-
entries
RemoveNodeXMLResolution_label=Remove the {0} element.
RemoveNodeXMLResolution_attrLabel=Remove the {0} attribute.
@@ -2315,8 +2317,10 @@ ConfigureProblemSeverityForPDECompiler_6=Open the Plug-in Development > Compiler
ConfigureTargetPlatformResolution_label=Configure Target-Platform...
ConfigureTargetPlatformResolution_description=Open preference page to configure the Target-Platforms. Target-Platforms describe which external artifacts are visible while developing plugins.
OrganizeManifestJob_taskName=Organizing Manifest Headers...
+ConvertAutomaticManifestJob_taskName=Converting to Automatic Manifest generation...
OrganizeManifestsWizard_title=Organize Manifests Wizard
OrganizeManifestsWizardPage_title=Organize Manifests
+ConvertAutomaticManifestWizardPage_title=Convert to Automatic Manifest Generation
OrganizeManifestsWizardPage_remove=&removing them
OrganizeManifestsOperation_export=organizing export packages... {0}
OrganizeManifestsOperation_unusedDeps=removing unused dependencies... {0}
@@ -2328,6 +2332,16 @@ OrganizeManifestsOperation_nlIconPath=checking icon paths for missing $nl$ segme
OrganizeManifestsOperation_unusedKeys=checking for unused keys... {0}
OrganizeManifestsWizardPage_addMissing=&Ensure that all packages appear in the MANIFEST.MF
OrganizeManifestsProcessor_rootMessage=Organize Manifest for {0}
+ConvertAutomaticManifestsProcessor_rootMessage=Converting {0}...
+ConvertAutomaticManifestsProcessor_changeProject=Convert project {0}
+ConvertAutomaticManifestsWizardSettingsPage_convert_to_annotations=convert to annotations
+ConvertAutomaticManifestsWizardSettingsPage_description=Please choose options below to converts your existing plugin-project into one using automatic manifest generation.
+ConvertAutomaticManifestsWizardSettingsPage_manifest=Generate manifest at
+ConvertAutomaticManifestsWizardSettingsPage_manifest_at_output=output folder
+ConvertAutomaticManifestsWizardSettingsPage_manifest_at_root=project root
+ConvertAutomaticManifestsWizardSettingsPage_discard=discard
+ConvertAutomaticManifestsWizardSettingsPage_keep=keep
+ConvertAutomaticManifestsWizardSettingsPage_to_instructions=to instructions
OrganizeManifestsWizardPage_lazyStart=Remove unnecessary lazy activation headers
OrganizeManifestsWizardPage_uselessPluginFile=Delete unnecessary plugin manifest files
OrganizeManifestsOperation_filterInternal=marking export packages matching filter as internal... {0}
@@ -2672,3 +2686,7 @@ ExtensionAttributeRow_AttrLabelDepr={0}(!):
ExtensionAttributeRow_AttrLabelReq={0}*:
ExtensionAttributeRow_AttrLabelReqDepr={0}(!)*:
AnnotationHover_version_change={0} \n Reason for this version change: \n {1}
+ProjectUpdateChange_configure_nature_and_builder=Update natures and builder
+ProjectUpdateChange_convert_manifest_to_bnd=Convert MANIFEST.MF to bnd instructions
+ProjectUpdateChange_convert_build_to_bnd=Convert build.properties to bnd instructions
+ProjectUpdateChange_set_pde_preference=Set {0} in preferences
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/ConvertAutomaticManifestAction.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/ConvertAutomaticManifestAction.java
new file mode 100644
index 0000000000..7090c9bec5
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/ConvertAutomaticManifestAction.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.wizards.tools;
+
+import java.util.List;
+import java.util.Objects;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
+import org.eclipse.pde.internal.core.natures.BndProject;
+import org.eclipse.pde.internal.core.natures.PDE;
+import org.eclipse.pde.internal.ui.PDEPlugin;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * Command handler to run the convert to automatic manifests operation.
+ *
+ */
+public class ConvertAutomaticManifestAction extends AbstractHandler {
+
+ @Override
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ if (PlatformUI.getWorkbench().saveAllEditors(true)) {
+ // only do our work when all work is committed to files...
+ if (HandlerUtil.getCurrentSelection(event) instanceof IStructuredSelection selection) {
+ List projects = selection.stream().map(ConvertAutomaticManifestAction::toProject)
+ .filter(Objects::nonNull)
+ .filter(proj -> PDE.hasPluginNature(proj))
+ .filter(proj -> !BndProject.isBndProject(proj)).toList();
+ if (projects.isEmpty()) {
+ MessageDialog.openInformation(PDEPlugin.getActiveWorkbenchShell(),
+ PDEUIMessages.ConvertAutomaticManifestWizardPage_title,
+ PDEUIMessages.OrganizeManifestsWizardPage_errorMsg);
+ return null;
+ }
+ RefactoringWizardOpenOperation refactoringOperation = new RefactoringWizardOpenOperation(new ConvertAutomaticManifestsWizard(projects));
+ try {
+ refactoringOperation.run(PDEPlugin.getActiveWorkbenchShell(), PDEUIMessages.ConvertAutomaticManifestWizardPage_title);
+ } catch (final InterruptedException e) {
+ // ignore...
+ }
+ }
+ }
+ return null;
+ }
+
+ private static IProject toProject(Object object) {
+ if (object instanceof IJavaProject java) {
+ return java.getProject();
+ }
+ if (object instanceof IResource resource) {
+ return resource.getProject();
+ }
+ return null;
+ }
+
+}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/ConvertAutomaticManifestProcessor.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/ConvertAutomaticManifestProcessor.java
new file mode 100644
index 0000000000..88a6cf1d9a
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/ConvertAutomaticManifestProcessor.java
@@ -0,0 +1,222 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.wizards.tools;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ProjectScope;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.CompositeChange;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
+import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant;
+import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor;
+import org.eclipse.ltk.core.refactoring.participants.SharableParticipants;
+import org.eclipse.ltk.core.refactoring.resource.DeleteResourceChange;
+import org.eclipse.osgi.service.resolver.ExportPackageDescription;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.pde.core.build.IBuild;
+import org.eclipse.pde.core.build.IBuildEntry;
+import org.eclipse.pde.core.build.IBuildModel;
+import org.eclipse.pde.core.plugin.IPluginModelBase;
+import org.eclipse.pde.core.plugin.PluginRegistry;
+import org.eclipse.pde.internal.core.PDECore;
+import org.eclipse.pde.internal.core.natures.BndProject;
+import org.eclipse.pde.internal.core.project.PDEProject;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
+import org.eclipse.pde.internal.ui.wizards.tools.change.BndProjectUpdateChange;
+import org.eclipse.pde.internal.ui.wizards.tools.change.BuildToBndChange;
+import org.eclipse.pde.internal.ui.wizards.tools.change.CreateJarChange;
+import org.eclipse.pde.internal.ui.wizards.tools.change.CreatePackageInfoChange;
+import org.eclipse.pde.internal.ui.wizards.tools.change.ManifestToBndChange;
+import org.eclipse.pde.internal.ui.wizards.tools.change.PreferenceChange;
+import org.osgi.framework.Version;
+
+public class ConvertAutomaticManifestProcessor extends RefactoringProcessor {
+
+ private List projects;
+ private boolean useProjectRoot;
+ private boolean keepRequireBundle;
+ private boolean keepImportPackage;
+ private boolean keepBREE;
+ private boolean keepExportPackage;
+
+ public ConvertAutomaticManifestProcessor(List projects) {
+ this.projects = projects;
+ }
+
+ @Override
+ public Object[] getElements() {
+ return projects.toArray();
+ }
+
+ @Override
+ public String getIdentifier() {
+ return getClass().getName();
+ }
+
+ @Override
+ public String getProcessorName() {
+ return PDEUIMessages.ConvertAutomaticManifestWizardPage_title;
+ }
+
+ @Override
+ public boolean isApplicable() {
+ return true;
+ }
+
+ @Override
+ public RefactoringStatus checkInitialConditions(IProgressMonitor pm) {
+ return RefactoringStatus.create(Status.OK_STATUS);
+ }
+
+ @Override
+ public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context) {
+ return RefactoringStatus.create(Status.OK_STATUS);
+ }
+
+ @Override
+ public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
+ CompositeChange change = new CompositeChange(PDEUIMessages.ConvertAutomaticManifestWizardPage_title);
+ change.markAsSynthetic();
+ SubMonitor subMonitor = SubMonitor.convert(pm, PDEUIMessages.ConvertAutomaticManifestJob_taskName,
+ projects.size());
+ for (IProject project : projects) {
+ change.add(convertProject(project, subMonitor.split(1)));
+ }
+ return change;
+ }
+
+ private Change convertProject(IProject project, IProgressMonitor monitor) throws CoreException {
+ CompositeChange change = new CompositeChange(
+ NLS.bind(PDEUIMessages.ConvertAutomaticManifestsProcessor_changeProject, project.getName()));
+ SubMonitor.convert(monitor,
+ NLS.bind(PDEUIMessages.ConvertAutomaticManifestsProcessor_rootMessage, project.getName()), 100);
+ IFile manifest = PDEProject.getManifest(project);
+ IPluginModelBase model = PluginRegistry.findModel(project);
+ IFile instructionsFile = project.getFile(BndProject.INSTRUCTIONS_FILE);
+ IBuildModel buildModel = PluginRegistry.createBuildModel(model);
+ change.add(new BndProjectUpdateChange(project));
+ change.add(new ManifestToBndChange(project, manifest, model, instructionsFile, keepRequireBundle,
+ keepImportPackage, keepBREE, keepExportPackage));
+ if (!keepExportPackage) {
+ ExportPackageDescription[] exportPackages = model.getBundleDescription().getExportPackages();
+ if (exportPackages.length > 0) {
+ IJavaProject javaProject = JavaCore.create(project);
+ for (ExportPackageDescription exportPackage : exportPackages) {
+ Change packageInfoChange = getPackageInfoChange(exportPackage.getName(), exportPackage.getVersion(),
+ javaProject);
+ if (packageInfoChange != null) {
+ change.add(packageInfoChange);
+ }
+ }
+ }
+ }
+ IBuild build = buildModel.getBuild();
+ boolean make = false;
+ for (IBuildEntry buildEntry : build.getBuildEntries()) {
+ String name = buildEntry.getName();
+ if (name.startsWith(IBuildEntry.JAR_PREFIX)) {
+ String jarName = name.substring(IBuildEntry.JAR_PREFIX.length());
+ if (jarName.equals(".")) { //$NON-NLS-1$
+ continue;
+ }
+ String outputFolder = Optional.ofNullable(build.getEntry(IBuildEntry.OUTPUT_PREFIX + jarName)).stream()
+ .flatMap(entry -> Arrays.stream(entry.getTokens())).findFirst().orElse(null);
+ change.add(
+ new CreateJarChange(project, instructionsFile, jarName, buildEntry.getTokens(), outputFolder));
+ make = true;
+ }
+ }
+ change.add(new BuildToBndChange(project, buildModel, instructionsFile, make));
+ IFile buildProperties = PDEProject.getBuildProperties(project);
+ if (buildProperties.exists()) {
+ change.add(new DeleteResourceChange(buildProperties.getFullPath(), true));
+ }
+ if (useProjectRoot) {
+ ProjectScope scope = new ProjectScope(project);
+ IEclipsePreferences node = scope.getNode(PDECore.PLUGIN_ID);
+ change.add(new PreferenceChange(node));
+ } else if (manifest.exists()) {
+ change.add(new DeleteResourceChange(manifest.getFullPath(), true));
+ }
+ return change;
+ }
+
+ private Change getPackageInfoChange(String name, Version version, IJavaProject javaProject) throws CoreException {
+ IPackageFragment pkg = findPackage(javaProject, name);
+ if (pkg == null) {
+ return null;
+ }
+ ICompilationUnit cu = pkg.getCompilationUnit(CreatePackageInfoChange.PACKAGE_INFO_JAVA);
+ if (cu.exists()) {
+ // TODO we need to create an update change!
+ return null;
+ }
+ return new CreatePackageInfoChange(pkg, name, version);
+
+ }
+
+ private static IPackageFragment findPackage(IJavaProject javaProject, String name) throws JavaModelException {
+ for (IPackageFragment pkg : javaProject.getPackageFragments()) {
+ if (pkg.getKind() == IPackageFragmentRoot.K_SOURCE && pkg.getElementName().equals(name)) {
+ return pkg;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public RefactoringParticipant[] loadParticipants(RefactoringStatus status, SharableParticipants sharedParticipants)
+ throws CoreException {
+ return new RefactoringParticipant[0];
+ }
+
+ public void setUseProjectRoot(boolean useProjectRoot) {
+ this.useProjectRoot = useProjectRoot;
+ }
+
+ public void setKeepRequireBundle(boolean keepRequireBundle) {
+ this.keepRequireBundle = keepRequireBundle;
+ }
+
+ public void setKeepImportPackage(boolean keepImportPackage) {
+ this.keepImportPackage = keepImportPackage;
+ }
+
+ public void setKeepRequiredExecutionEnvironment(boolean keepBREE) {
+ this.keepBREE = keepBREE;
+ }
+
+ public void setKeepExportPackage(boolean keepExportPackage) {
+ this.keepExportPackage = keepExportPackage;
+ }
+
+}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/ConvertAutomaticManifestsWizard.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/ConvertAutomaticManifestsWizard.java
new file mode 100644
index 0000000000..b0f21f6c78
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/ConvertAutomaticManifestsWizard.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.wizards.tools;
+
+import java.util.List;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
+import org.eclipse.pde.internal.ui.PDEPlugin;
+import org.eclipse.pde.internal.ui.PDEPluginImages;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
+import org.eclipse.pde.internal.ui.refactoring.PDERefactor;
+
+public class ConvertAutomaticManifestsWizard extends RefactoringWizard {
+
+ public ConvertAutomaticManifestsWizard(List projects) {
+ super(new PDERefactor(new ConvertAutomaticManifestProcessor(projects)), WIZARD_BASED_USER_INTERFACE);
+ setNeedsProgressMonitor(true);
+ setWindowTitle(PDEUIMessages.ConvertAutomaticManifestWizardPage_title);
+ setDialogSettings(PDEPlugin.getDefault().getDialogSettings());
+ setDefaultPageImageDescriptor(PDEPluginImages.DESC_ORGANIZE_MANIFESTS);
+ }
+
+ @Override
+ protected void addUserInputPages() {
+ addPage(new ConvertAutomaticManifestsWizardSettingsPage(
+ (ConvertAutomaticManifestProcessor) ((PDERefactor) getRefactoring()).getProcessor()));
+ }
+
+
+}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/ConvertAutomaticManifestsWizardSettingsPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/ConvertAutomaticManifestsWizardSettingsPage.java
new file mode 100644
index 0000000000..de92e9a224
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/ConvertAutomaticManifestsWizardSettingsPage.java
@@ -0,0 +1,213 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.wizards.tools;
+
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.ltk.ui.refactoring.UserInputWizardPage;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.framework.Constants;
+
+public class ConvertAutomaticManifestsWizardSettingsPage extends UserInputWizardPage {
+
+ private static final String SECTION_NAME = "ConvertAutomaticManifestPage"; //$NON-NLS-1$
+ private static final String KEY_USE_PROJECT_ROOT = "use_project_root"; //$NON-NLS-1$
+ private static final String KEY_KEEP_REQUIRE_BUNDLE = "keep_require_bundle"; //$NON-NLS-1$
+ private static final String KEY_KEEP_IMPORT_PACKAGE = "keep_import_package"; //$NON-NLS-1$
+ private static final String KEY_KEEP_EXPORT_PACKAGE = "keep_export_package"; //$NON-NLS-1$
+ private static final String KEY_KEEP_REQUIREDEXECUTIONENVIRONMENT = "keep_requiredexecutionenvironment_package"; //$NON-NLS-1$
+ private ConvertAutomaticManifestProcessor processor;
+
+ public ConvertAutomaticManifestsWizardSettingsPage(ConvertAutomaticManifestProcessor processor) {
+ super(PDEUIMessages.ConvertAutomaticManifestWizardPage_title);
+ this.processor = processor;
+ setTitle(PDEUIMessages.ConvertAutomaticManifestWizardPage_title);
+ setDescription(PDEUIMessages.ConvertAutomaticManifestsWizardSettingsPage_description);
+ }
+
+ @Override
+ public void createControl(Composite parent) {
+ Composite composite = new Composite(parent, SWT.NONE);
+ setControl(composite);
+ composite.setLayout(new GridLayout(2, false));
+ createGenerateOption(composite);
+ createRequireBundleOption(composite);
+ createImportPackageOption(composite);
+ createRequiredExecutionEnvironmentOption(composite);
+ createExportPackageOption(composite);
+ }
+
+ private void createExportPackageOption(Composite parent) {
+ new Label(parent, SWT.NONE).setText(Constants.EXPORT_PACKAGE);
+ Composite composite = new Composite(parent, SWT.NONE);
+ composite.setLayout(new GridLayout(2, false));
+ Button optionAnnotations = new Button(composite, SWT.RADIO);
+ optionAnnotations.setText(PDEUIMessages.ConvertAutomaticManifestsWizardSettingsPage_convert_to_annotations);
+ Button optionKeep = new Button(composite, SWT.RADIO);
+ optionKeep.setText(PDEUIMessages.ConvertAutomaticManifestsWizardSettingsPage_keep);
+ IDialogSettings settings = getSettings();
+ if (settings.getBoolean(KEY_KEEP_EXPORT_PACKAGE)) {
+ optionKeep.setSelection(true);
+ processor.setKeepExportPackage(true);
+ } else {
+ optionAnnotations.setSelection(true);
+ }
+ optionAnnotations.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ if (optionAnnotations.getSelection()) {
+ getSettings().put(KEY_KEEP_EXPORT_PACKAGE, false);
+ processor.setKeepExportPackage(false);
+ }
+ }));
+ optionKeep.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ if (optionKeep.getSelection()) {
+ getSettings().put(KEY_KEEP_EXPORT_PACKAGE, true);
+ processor.setKeepExportPackage(true);
+ }
+ }));
+ }
+
+ @SuppressWarnings("deprecation")
+ private void createRequiredExecutionEnvironmentOption(Composite parent) {
+ new Label(parent, SWT.NONE).setText(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT);
+ Composite composite = new Composite(parent, SWT.NONE);
+ composite.setLayout(new GridLayout(2, false));
+ Button optionInstructions = new Button(composite, SWT.RADIO);
+ optionInstructions.setText(PDEUIMessages.ConvertAutomaticManifestsWizardSettingsPage_to_instructions);
+ Button optionKeep = new Button(composite, SWT.RADIO);
+ optionKeep.setText(PDEUIMessages.ConvertAutomaticManifestsWizardSettingsPage_keep);
+ IDialogSettings settings = getSettings();
+ if (settings.getBoolean(KEY_KEEP_REQUIREDEXECUTIONENVIRONMENT)) {
+ optionKeep.setSelection(true);
+ processor.setKeepRequiredExecutionEnvironment(true);
+ } else {
+ optionInstructions.setSelection(true);
+ }
+ optionInstructions.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ if (optionInstructions.getSelection()) {
+ getSettings().put(KEY_KEEP_REQUIREDEXECUTIONENVIRONMENT, false);
+ processor.setKeepRequiredExecutionEnvironment(false);
+ }
+ }));
+ optionKeep.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ if (optionKeep.getSelection()) {
+ getSettings().put(KEY_KEEP_REQUIREDEXECUTIONENVIRONMENT, true);
+ processor.setKeepRequiredExecutionEnvironment(true);
+ }
+ }));
+ }
+
+ private void createImportPackageOption(Composite parent) {
+ new Label(parent, SWT.NONE).setText(Constants.IMPORT_PACKAGE);
+ Composite composite = new Composite(parent, SWT.NONE);
+ composite.setLayout(new GridLayout(2, false));
+ Button optionDiscard = new Button(composite, SWT.RADIO);
+ optionDiscard.setText(PDEUIMessages.ConvertAutomaticManifestsWizardSettingsPage_discard);
+ Button optionKeep = new Button(composite, SWT.RADIO);
+ optionKeep.setText(PDEUIMessages.ConvertAutomaticManifestsWizardSettingsPage_keep);
+ IDialogSettings settings = getSettings();
+ if (settings.getBoolean(KEY_KEEP_IMPORT_PACKAGE)) {
+ optionKeep.setSelection(true);
+ processor.setKeepImportPackage(true);
+ } else {
+ optionDiscard.setSelection(true);
+ }
+ optionDiscard.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ if (optionDiscard.getSelection()) {
+ getSettings().put(KEY_KEEP_IMPORT_PACKAGE, false);
+ processor.setKeepImportPackage(false);
+ }
+ }));
+ optionKeep.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ if (optionKeep.getSelection()) {
+ getSettings().put(KEY_KEEP_IMPORT_PACKAGE, true);
+ processor.setKeepImportPackage(true);
+ }
+ }));
+ }
+
+ private void createRequireBundleOption(Composite parent) {
+ new Label(parent, SWT.NONE).setText(Constants.REQUIRE_BUNDLE);
+ Composite composite = new Composite(parent, SWT.NONE);
+ composite.setLayout(new GridLayout(2, false));
+ Button optionDiscard = new Button(composite, SWT.RADIO);
+ optionDiscard.setText(PDEUIMessages.ConvertAutomaticManifestsWizardSettingsPage_discard);
+ Button optionKeep = new Button(composite, SWT.RADIO);
+ optionKeep.setText(PDEUIMessages.ConvertAutomaticManifestsWizardSettingsPage_keep);
+ IDialogSettings settings = getSettings();
+ if (settings.getBoolean(KEY_KEEP_REQUIRE_BUNDLE)) {
+ optionKeep.setSelection(true);
+ processor.setKeepRequireBundle(true);
+ } else {
+ optionDiscard.setSelection(true);
+ }
+ optionDiscard.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ if (optionDiscard.getSelection()) {
+ getSettings().put(KEY_KEEP_REQUIRE_BUNDLE, false);
+ processor.setKeepRequireBundle(false);
+ }
+ }));
+ optionKeep.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ if (optionKeep.getSelection()) {
+ getSettings().put(KEY_KEEP_REQUIRE_BUNDLE, true);
+ processor.setKeepRequireBundle(true);
+ }
+ }));
+ }
+
+ private void createGenerateOption(Composite parent) {
+ new Label(parent, SWT.NONE).setText(PDEUIMessages.ConvertAutomaticManifestsWizardSettingsPage_manifest);
+ Composite composite = new Composite(parent, SWT.NONE);
+ composite.setLayout(new GridLayout(2, false));
+ Button optionOutput = new Button(composite, SWT.RADIO);
+ optionOutput.setText(PDEUIMessages.ConvertAutomaticManifestsWizardSettingsPage_manifest_at_output);
+ Button optionRoot = new Button(composite, SWT.RADIO);
+ optionRoot.setText(PDEUIMessages.ConvertAutomaticManifestsWizardSettingsPage_manifest_at_root);
+ IDialogSettings settings = getSettings();
+ if (settings.getBoolean(KEY_USE_PROJECT_ROOT)) {
+ optionRoot.setSelection(true);
+ processor.setUseProjectRoot(true);
+ } else {
+ optionOutput.setSelection(true);
+ processor.setUseProjectRoot(false);
+ }
+ optionOutput.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ if (optionOutput.getSelection()) {
+ getSettings().put(KEY_USE_PROJECT_ROOT, false);
+ processor.setUseProjectRoot(false);
+ }
+ }));
+ optionRoot.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ if (optionRoot.getSelection()) {
+ getSettings().put(KEY_USE_PROJECT_ROOT, true);
+ processor.setUseProjectRoot(true);
+ }
+ }));
+
+ }
+
+ private IDialogSettings getSettings() {
+ IDialogSettings settings = getDialogSettings();
+ IDialogSettings section = settings.getSection(SECTION_NAME);
+ if (section == null) {
+ return settings.addNewSection(SECTION_NAME);
+ }
+ return section;
+ }
+
+}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/BndProjectUpdateChange.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/BndProjectUpdateChange.java
new file mode 100644
index 0000000000..1ba8c0b22d
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/BndProjectUpdateChange.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.wizards.tools.change;
+
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import org.eclipse.core.resources.ICommand;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.pde.core.project.IBundleProjectDescription;
+import org.eclipse.pde.internal.core.natures.BndProject;
+import org.eclipse.pde.internal.core.natures.PDE;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
+
+public final class BndProjectUpdateChange extends Change {
+
+ private IProject project;
+
+ public BndProjectUpdateChange(IProject project) {
+ this.project = project;
+ }
+
+ @Override
+ public String getName() {
+ return PDEUIMessages.ProjectUpdateChange_configure_nature_and_builder;
+ }
+
+ @Override
+ public void initializeValidationData(IProgressMonitor pm) {
+ }
+
+ @Override
+ public RefactoringStatus isValid(IProgressMonitor pm) throws CoreException, OperationCanceledException {
+ return RefactoringStatus.create(Status.OK_STATUS);
+ }
+
+ @Override
+ public Change perform(IProgressMonitor pm) throws CoreException {
+ IProjectDescription description = project.getDescription();
+ String[] newNatures;
+ if (Arrays.stream(description.getNatureIds())
+ .anyMatch(str -> "org.eclipse.pde.api.tools.apiAnalysisNature".equals(str))) { //$NON-NLS-1$
+ // the API nature require the pde nature so we can only add our
+ // nature here...
+ newNatures = Stream
+ .concat(Arrays.stream(description.getNatureIds()), Stream.of(BndProject.NATURE_ID))
+ .toArray(String[]::new);
+ } else {
+ // replace plugin with bnd nature...
+ newNatures = Arrays.stream(description.getNatureIds()).map(nature -> {
+ if (IBundleProjectDescription.PLUGIN_NATURE.equals(nature)) {
+ return BndProject.NATURE_ID;
+ }
+ return nature;
+ }).toArray(String[]::new);
+ }
+ ICommand[] commands = Stream.concat(Arrays.stream(description.getBuildSpec()).filter(command -> {
+ if (PDE.MANIFEST_BUILDER_ID.equals(command.getBuilderName())
+ || "org.eclipse.pde.SchemaBuilder".equals(command.getBuilderName())) { //$NON-NLS-1$
+ return false;
+ }
+ return true;
+ }), Stream.of(newBndBuilder(description))).toArray(ICommand[]::new);
+ description.setBuildSpec(commands);
+ description.setNatureIds(newNatures);
+ project.setDescription(description, pm);
+ return null;
+ }
+
+ private ICommand newBndBuilder(IProjectDescription description) {
+ ICommand bndBuilder = description.newCommand();
+ bndBuilder.setBuilderName(BndProject.BUILDER_ID);
+ return bndBuilder;
+ }
+
+ @Override
+ public Object getModifiedElement() {
+ return project;
+ }
+
+}
\ No newline at end of file
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/BuildToBndChange.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/BuildToBndChange.java
new file mode 100644
index 0000000000..ea2c3097d3
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/BuildToBndChange.java
@@ -0,0 +1,213 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.wizards.tools.change;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.jar.JarFile;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.content.IContentDescription;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.pde.core.build.IBuild;
+import org.eclipse.pde.core.build.IBuildEntry;
+import org.eclipse.pde.core.build.IBuildModel;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
+
+import aQute.bnd.build.model.BndEditModel;
+import aQute.bnd.build.model.clauses.VersionedClause;
+import aQute.bnd.osgi.Constants;
+import aQute.bnd.properties.Document;
+
+public class BuildToBndChange extends Change {
+
+ @SuppressWarnings("restriction")
+ private static final String DS_CONTENT_TYPE_ID = org.eclipse.pde.internal.ds.core.Activator.CONTENT_TYPE_ID;
+ private IBuildModel model;
+ private IProject project;
+ private IFile instructionfile;
+ private boolean make;
+
+ public BuildToBndChange(IProject project, IBuildModel model, IFile instructionfile, boolean make) {
+ this.project = project;
+ this.model = model;
+ this.instructionfile = instructionfile;
+ this.make = make;
+ }
+
+ @Override
+ public String getName() {
+ return PDEUIMessages.ProjectUpdateChange_convert_build_to_bnd;
+ }
+
+ @Override
+ public void initializeValidationData(IProgressMonitor pm) {
+
+ }
+
+ @Override
+ public RefactoringStatus isValid(IProgressMonitor pm) throws CoreException, OperationCanceledException {
+ return RefactoringStatus.create(Status.OK_STATUS);
+ }
+
+ @Override
+ public Change perform(IProgressMonitor pm) throws CoreException {
+ if (model != null) {
+ Document document;
+ if (instructionfile.exists()) {
+ try (InputStream contents = instructionfile.getContents()) {
+ document = new Document(new String(contents.readAllBytes(), StandardCharsets.UTF_8));
+ } catch (IOException e) {
+ throw new CoreException(Status.error("Reading file content failed", e)); //$NON-NLS-1$
+ }
+ } else {
+ document = new Document(""); //$NON-NLS-1$
+ }
+ BndEditModel editModel;
+ try {
+ editModel = new BndEditModel(document);
+ } catch (IOException e) {
+ throw new CoreException(Status.error("Reading document failed", e)); //$NON-NLS-1$
+ }
+ IBuild build = model.getBuild();
+ processBinIncludes(build, editModel);
+ processAdditionalBundles(build, editModel);
+ processExtraClasspath(build, editModel);
+ if (make) {
+ editModel.genericSet(Constants.MAKE, "(*).(jar);type=bnd; recipe=\".jars/$1.bnd\""); //$NON-NLS-1$
+ }
+ editModel.saveChangesTo(document);
+ if (instructionfile.exists()) {
+ instructionfile.setContents(new ByteArrayInputStream(document.get().getBytes(StandardCharsets.UTF_8)),
+ true, true,
+ pm);
+ } else {
+ instructionfile.create(new ByteArrayInputStream(document.get().getBytes(StandardCharsets.UTF_8)), true,
+ pm);
+ }
+ }
+ return null;
+ }
+
+ private void processBinIncludes(IBuild build, BndEditModel editModel) {
+ IBuildEntry entry = build.getEntry(IBuildEntry.BIN_INCLUDES);
+ if (entry == null) {
+ return;
+ }
+ List list = Arrays.stream(entry.getTokens()).filter(str -> isCustomResource(str, build))
+ .map(str -> str.contains("/") ? (String.format("%s=%s", str, str)) : str) //$NON-NLS-1$ //$NON-NLS-2$
+ .toList();
+ // can't use editModel.addIncludeResource because of
+ // https://github.com/bndtools/bnd/pull/5904
+ editModel.genericSet(Constants.INCLUDERESOURCE, list);
+
+ }
+
+ private boolean isCustomResource(String str, IBuild build) {
+ if (".".equals(str)) { //$NON-NLS-1$
+ // this is the default jar inclusion...
+ return false;
+ }
+ if (JarFile.MANIFEST_NAME.equals(str)) {
+ // the manifest we generate!
+ return false;
+ }
+ if ("META-INF/".equals(str) || "META-INF".equals(str)) { //$NON-NLS-1$ //$NON-NLS-2$
+ IFolder folder = project.getFolder(str);
+ try {
+ IResource[] members = folder.members();
+ if (members.length == 0) {
+ // empty folder with manifest previously inside it
+ return false;
+ }
+ if (members.length == 1 && "MANIFEST.MF".equals(members[0].getName())) { //$NON-NLS-1$
+ // a manifest either generated or not yet deleted... but we
+ // don't want to try include it
+ return false;
+ }
+ } catch (CoreException e) {
+ }
+ }
+ if ("OSGI-INF/".equals(str) || "OSGI-INF".equals(str)) { //$NON-NLS-1$ //$NON-NLS-2$
+ IFolder folder = project.getFolder(str);
+ try {
+ IResource[] members = folder.members();
+ if (members.length == 0) {
+ // empty folder then assume it is of no use...
+ }
+ for (IResource member : members) {
+ if (member.getName().startsWith(".")) { //$NON-NLS-1$
+ continue;
+ }
+ if (member instanceof IFile file) {
+ IContentDescription description = file.getContentDescription();
+ if (description != null) {
+ IContentType contentType = description.getContentType();
+ if (contentType != null
+ && DS_CONTENT_TYPE_ID.equals(contentType.getId())) {
+ // a DS component... these will be generated so
+ // we can ignore it
+ continue;
+ }
+ }
+ return true;
+ }
+ if (member instanceof IFolder) {
+ // some subfolder e.g. i10n ... we need to include it
+ return true;
+ }
+ }
+ return false;
+ } catch (CoreException e) {
+ }
+ }
+ return true;
+ }
+
+ private void processAdditionalBundles(IBuild build, BndEditModel editModel) {
+ IBuildEntry entry = build.getEntry(IBuildEntry.SECONDARY_DEPENDENCIES);
+ if (entry == null) {
+ return;
+ }
+ Arrays.stream(entry.getTokens())
+ .forEach(additional -> editModel.addPath(new VersionedClause(additional), Constants.BUILDPATH));
+ }
+
+ private void processExtraClasspath(IBuild build, BndEditModel editModel) {
+ IBuildEntry entry = build.getEntry(IBuildEntry.JARS_EXTRA_CLASSPATH);
+ if (entry == null) {
+ return;
+ }
+ editModel.setClassPath(Arrays.asList(entry.getTokens()));
+ }
+
+ @Override
+ public Object getModifiedElement() {
+ return instructionfile;
+ }
+
+}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/CreateJarChange.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/CreateJarChange.java
new file mode 100644
index 0000000000..f7dc50243c
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/CreateJarChange.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.wizards.tools.change;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
+
+public class CreateJarChange extends Change {
+
+ private IFile jarInstructionFile;
+ private String jarName;
+ private IProject project;
+ private String[] sourceTokens;
+ private String outputFolder;
+
+ public CreateJarChange(IProject project, IFile instructionsFile, String jarName, String[] sourceTokens,
+ String outputFolder) {
+ this.project = project;
+ this.jarName = jarName;
+ this.sourceTokens = sourceTokens;
+ this.outputFolder = outputFolder;
+ this.jarInstructionFile = project.getFolder(".jars") //$NON-NLS-1$
+ .getFile(IPath.fromPortableString(jarName.replace(".jar", ".bnd"))); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ @Override
+ public String getName() {
+ return NLS.bind(PDEUIMessages.CreateJarChange_instruction_jar, jarName);
+ }
+
+ @Override
+ public Change perform(IProgressMonitor pm) throws CoreException {
+ StringBuilder sb = new StringBuilder();
+ sb.append("-includeresource: "); //$NON-NLS-1$
+ sb.append(getOutputFolde());
+ sb.append("\r\n"); //$NON-NLS-1$
+ sb.append("-nomanifest: true\r\n"); //$NON-NLS-1$
+ sb.append("-sources: false\r\n"); //$NON-NLS-1$
+ mkdirs(jarInstructionFile.getParent(), pm);
+ if (!jarInstructionFile.exists()) {
+ jarInstructionFile.create(new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8)), true,
+ pm);
+ }
+ return null;
+ }
+
+ private void mkdirs(IContainer container, IProgressMonitor pm) throws CoreException {
+ if (container instanceof IFolder folder) {
+ mkdirs(folder.getParent(), pm);
+ if (!folder.exists()) {
+ folder.create(true, true, pm);
+ }
+ }
+ }
+
+ private String getOutputFolde() throws CoreException {
+ if (outputFolder == null) {
+ IJavaProject javaProject = JavaCore.create(project);
+ IPath outputLocation = javaProject.getOutputLocation();
+ IClasspathEntry[] classpath = javaProject.getRawClasspath();
+ for (IClasspathEntry entry : classpath) {
+ if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
+ for (String token : sourceTokens) {
+ if (IPath.fromPortableString(token).equals(entry.getPath())) {
+ IPath srcLoc = entry.getOutputLocation();
+ if (srcLoc != null) {
+ outputLocation = srcLoc;
+ }
+ break;
+ }
+ }
+ }
+ }
+ return project.getWorkspace().getRoot().getFolder(outputLocation).getProjectRelativePath()
+ .toPortableString();
+ }
+ return outputFolder;
+ }
+
+ @Override
+ public void initializeValidationData(IProgressMonitor pm) {
+ }
+
+ @Override
+ public RefactoringStatus isValid(IProgressMonitor pm) throws CoreException, OperationCanceledException {
+ return RefactoringStatus.create(Status.OK_STATUS);
+ }
+
+ @Override
+ public Object getModifiedElement() {
+ return jarInstructionFile;
+ }
+}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/CreatePackageInfoChange.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/CreatePackageInfoChange.java
new file mode 100644
index 0000000000..a93671a02c
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/CreatePackageInfoChange.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.wizards.tools.change;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.internal.corext.util.InfoFilesUtil;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.resource.DeleteResourceChange;
+import org.eclipse.ltk.core.refactoring.resource.ResourceChange;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
+import org.osgi.framework.Version;
+
+@SuppressWarnings("restriction")
+public class CreatePackageInfoChange extends ResourceChange {
+
+ public static final String PACKAGE_INFO_JAVA = org.eclipse.jdt.internal.corext.util.JavaModelUtil.PACKAGE_INFO_JAVA;
+
+ private IPackageFragment fragment;
+
+ private String name;
+
+ private Version version;
+
+ public CreatePackageInfoChange(IPackageFragment fragment, String name, Version version) {
+ this.fragment = fragment;
+ this.name = name;
+ this.version = version;
+ }
+
+ @Override
+ protected IResource getModifiedResource() {
+ try {
+ return fragment.getCorrespondingResource();
+ } catch (JavaModelException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public String getName() {
+ return NLS.bind(PDEUIMessages.CreatePackageInfoChange_name, PACKAGE_INFO_JAVA, fragment.getElementName());
+ }
+
+ @Override
+ public Change perform(IProgressMonitor pm) throws CoreException {
+ StringBuilder fileContent = new StringBuilder(
+ "@org.osgi.annotation.bundle.Export(substitution = org.osgi.annotation.bundle.Export.Substitution.NOIMPORT)"); //$NON-NLS-1$
+ if (version != null && !version.equals(Version.emptyVersion)) {
+ fileContent.append("@org.osgi.annotation.versioning.Version(\""); //$NON-NLS-1$
+ fileContent.append(version.getMajor());
+ fileContent.append('.');
+ fileContent.append(version.getMinor());
+ fileContent.append('.');
+ fileContent.append(version.getMicro());
+ fileContent.append("\")"); //$NON-NLS-1$
+ }
+ fileContent.append("package "); //$NON-NLS-1$
+ fileContent.append(name);
+ fileContent.append(";"); //$NON-NLS-1$
+ InfoFilesUtil.createInfoJavaFile(PACKAGE_INFO_JAVA, fileContent.toString(), fragment, false, pm);
+ return new DeleteResourceChange(fragment.getPath(), true);
+ }
+
+}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/ManifestToBndChange.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/ManifestToBndChange.java
new file mode 100644
index 0000000000..6f7ce337ab
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/change/ManifestToBndChange.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.wizards.tools.change;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.osgi.service.resolver.BundleDescription;
+import org.eclipse.pde.core.plugin.IPluginModelBase;
+import org.eclipse.pde.internal.core.DependencyManager;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
+
+import aQute.bnd.build.model.BndEditModel;
+import aQute.bnd.build.model.EE;
+import aQute.bnd.build.model.clauses.VersionedClause;
+import aQute.bnd.osgi.Constants;
+import aQute.bnd.properties.Document;
+
+public class ManifestToBndChange extends Change {
+
+ private IFile manifestFile;
+ private IPluginModelBase model;
+ private boolean keepRequireBundle;
+ private boolean keepImportPackage;
+ private boolean keepBREE;
+ private boolean keepExportPackage;
+ private IFile instructionFile;
+
+ public ManifestToBndChange(IProject project, IFile manifest, IPluginModelBase model, IFile instructionsFile,
+ boolean keepRequireBundle,
+ boolean keepImportPackage,
+ boolean keepBREE,
+ boolean keepExportPackage) {
+ this.keepRequireBundle = keepRequireBundle;
+ this.keepImportPackage = keepImportPackage;
+ this.keepBREE = keepBREE;
+ this.keepExportPackage = keepExportPackage;
+ this.manifestFile = manifest;
+ this.model = model;
+ this.instructionFile = instructionsFile;
+ }
+
+ @Override
+ public String getName() {
+ return PDEUIMessages.ProjectUpdateChange_convert_manifest_to_bnd;
+ }
+
+ @Override
+ public void initializeValidationData(IProgressMonitor pm) {
+ }
+
+ @Override
+ public RefactoringStatus isValid(IProgressMonitor pm) throws CoreException, OperationCanceledException {
+ return RefactoringStatus.create(Status.OK_STATUS);
+ }
+
+ @Override
+ public Change perform(IProgressMonitor pm) throws CoreException {
+ List bundleClasspath = DependencyManager.getDependencies(List.of(model)).stream()
+ .map(BundleDescription::getSymbolicName).map(bsn -> new VersionedClause(bsn)).toList();
+ Document document = new Document(""); //$NON-NLS-1$
+ Manifest manifest;
+ try (InputStream contents = manifestFile.getContents()) {
+ manifest = new Manifest(contents);
+ } catch (IOException e) {
+ throw new CoreException(Status.error("Reading file content failed", e)); //$NON-NLS-1$
+ }
+ BndEditModel editModel;
+ try {
+ editModel = new BndEditModel(document);
+ } catch (IOException e) {
+ throw new CoreException(Status.error("Reading document failed", e)); //$NON-NLS-1$
+ }
+ Attributes mainAttributes = manifest.getMainAttributes();
+ for (Entry